am 69a07917: am 6a22fb9b: am f7ebcfec: am e17b4e71: Merge "Adding the backward-compatible filtering heuristics for the new device features definitions added in FroYo." into froyo
diff --git a/Android.mk b/Android.mk
index 8f968d8..c6036c0 100644
--- a/Android.mk
+++ b/Android.mk
@@ -197,6 +197,9 @@
 LOCAL_MODULE := framework
 LOCAL_MODULE_CLASS := JAVA_LIBRARIES
 
+LOCAL_NO_EMMA_INSTRUMENT := true
+LOCAL_NO_EMMA_COMPILE := true
+
 # List of classes and interfaces which should be loaded by the Zygote.
 LOCAL_JAVA_RESOURCE_FILES += $(LOCAL_PATH)/preloaded-classes
 
@@ -411,6 +414,10 @@
 		            resources/samples/Snake "Snake" \
 		-samplecode $(sample_dir)/SoftKeyboard \
 		            resources/samples/SoftKeyboard "Soft Keyboard" \
+		-samplecode $(sample_dir)/Spinner  \
+		            resources/samples/Spinner "Spinner" \
+		-samplecode $(sample_dir)/SpinnerTest \
+		            resources/samples/SpinnerTest "SpinnerTest" \
 		-samplecode $(sample_dir)/Wiktionary \
 		            resources/samples/Wiktionary "Wiktionary" \
 		-samplecode $(sample_dir)/WiktionarySimple \
@@ -583,6 +590,9 @@
 
 LOCAL_MODULE := ext
 
+LOCAL_NO_EMMA_INSTRUMENT := true
+LOCAL_NO_EMMA_COMPILE := true
+
 include $(BUILD_JAVA_LIBRARY)
 
 
diff --git a/api/current.xml b/api/current.xml
index 1a7fad8..d338b31 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -2132,6 +2132,17 @@
  visibility="public"
 >
 </field>
+<field name="adapter"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843521"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="addStatesFromChildren"
  type="int"
  transient="false"
@@ -2165,6 +2176,17 @@
  visibility="public"
 >
 </field>
+<field name="allContactsName"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843533"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="allowBackup"
  type="int"
  transient="false"
@@ -2341,6 +2363,17 @@
  visibility="public"
 >
 </field>
+<field name="as"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843527"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="author"
  type="int"
  transient="false"
@@ -3001,6 +3034,17 @@
  visibility="public"
 >
 </field>
+<field name="column"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843530"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="columnDelay"
  type="int"
  transient="false"
@@ -4167,6 +4211,17 @@
  visibility="public"
 >
 </field>
+<field name="from"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843525"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="fromAlpha"
  type="int"
  transient="false"
@@ -4189,6 +4244,17 @@
  visibility="public"
 >
 </field>
+<field name="fromValue"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843528"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="fromXDelta"
  type="int"
  transient="false"
@@ -8303,6 +8369,17 @@
  visibility="public"
 >
 </field>
+<field name="selection"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843522"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="settingsActivity"
  type="int"
  transient="false"
@@ -8490,6 +8567,17 @@
  visibility="public"
 >
 </field>
+<field name="sortOrder"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843523"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="soundEffectsEnabled"
  type="int"
  transient="false"
@@ -9678,6 +9766,17 @@
  visibility="public"
 >
 </field>
+<field name="to"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843526"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="toAlpha"
  type="int"
  transient="false"
@@ -9700,6 +9799,17 @@
  visibility="public"
 >
 </field>
+<field name="toValue"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843529"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="toXDelta"
  type="int"
  transient="false"
@@ -9876,6 +9986,17 @@
  visibility="public"
 >
 </field>
+<field name="uri"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843524"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="useLevel"
  type="int"
  transient="false"
@@ -10426,6 +10547,28 @@
  visibility="public"
 >
 </field>
+<field name="withClass"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843532"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="withExpression"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843531"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="writePermission"
  type="int"
  transient="false"
@@ -20304,6 +20447,17 @@
 <parameter name="view" type="android.view.View">
 </parameter>
 </method>
+<method name="openFragmentTransaction"
+ return="android.app.FragmentTransaction"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
 <method name="openOptionsMenu"
  return="void"
  abstract="false"
@@ -24658,6 +24812,300 @@
 </parameter>
 </method>
 </class>
+<class name="Fragment"
+ extends="java.lang.Object"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<implements name="android.content.ComponentCallbacks">
+</implements>
+<constructor name="Fragment"
+ type="android.app.Fragment"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</constructor>
+<constructor name="Fragment"
+ type="android.app.Fragment"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="name" type="java.lang.String">
+</parameter>
+</constructor>
+<method name="getActivity"
+ return="android.app.Activity"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getName"
+ return="java.lang.String"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="onAttach"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="activity" type="android.app.Activity">
+</parameter>
+</method>
+<method name="onConfigurationChanged"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="newConfig" type="android.content.res.Configuration">
+</parameter>
+</method>
+<method name="onCreate"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="savedInstanceState" type="android.os.Bundle">
+</parameter>
+</method>
+<method name="onCreateView"
+ return="android.view.View"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="inflater" type="android.view.LayoutInflater">
+</parameter>
+<parameter name="container" type="android.view.ViewGroup">
+</parameter>
+</method>
+<method name="onDestroy"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="onDetach"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="onLowMemory"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="onPause"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="onRestart"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="onRestoreInstanceState"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="savedInstanceState" type="android.os.Bundle">
+</parameter>
+</method>
+<method name="onResume"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="onRetainNonConfigurationInstance"
+ return="java.lang.Object"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="onSaveInstanceState"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="outState" type="android.os.Bundle">
+</parameter>
+</method>
+<method name="onStart"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="onStop"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+</class>
+<interface name="FragmentTransaction"
+ abstract="true"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<method name="add"
+ return="android.app.FragmentTransaction"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="fragment" type="android.app.Fragment">
+</parameter>
+<parameter name="containerViewId" type="int">
+</parameter>
+</method>
+<method name="add"
+ return="android.app.FragmentTransaction"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="fragment" type="android.app.Fragment">
+</parameter>
+<parameter name="name" type="java.lang.String">
+</parameter>
+<parameter name="containerViewId" type="int">
+</parameter>
+</method>
+<method name="commit"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="remove"
+ return="android.app.FragmentTransaction"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="fragment" type="android.app.Fragment">
+</parameter>
+</method>
+</interface>
 <class name="Instrumentation"
  extends="java.lang.Object"
  abstract="false"
@@ -38074,6 +38522,17 @@
  visibility="public"
 >
 </field>
+<field name="STORAGE_SERVICE"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;storage&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="TELEPHONY_SERVICE"
  type="java.lang.String"
  transient="false"
@@ -44699,6 +45158,21 @@
 <parameter name="defValue" type="java.lang.String">
 </parameter>
 </method>
+<method name="getStringSet"
+ return="java.util.Set&lt;java.lang.String&gt;"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="key" type="java.lang.String">
+</parameter>
+<parameter name="defValues" type="java.util.Set&lt;java.lang.String&gt;">
+</parameter>
+</method>
 <method name="registerOnSharedPreferenceChangeListener"
  return="void"
  abstract="true"
@@ -44830,6 +45304,21 @@
 <parameter name="value" type="java.lang.String">
 </parameter>
 </method>
+<method name="putStringSet"
+ return="android.content.SharedPreferences.Editor"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="key" type="java.lang.String">
+</parameter>
+<parameter name="values" type="java.util.Set&lt;java.lang.String&gt;">
+</parameter>
+</method>
 <method name="remove"
  return="android.content.SharedPreferences.Editor"
  abstract="true"
@@ -55653,6 +56142,27 @@
 >
 </method>
 </class>
+<interface name="DatabaseErrorHandler"
+ abstract="true"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<method name="onCorruption"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="dbObj" type="android.database.sqlite.SQLiteDatabase">
+</parameter>
+</method>
+</interface>
 <class name="DatabaseUtils"
  extends="java.lang.Object"
  abstract="false"
@@ -56537,6 +57047,38 @@
 >
 </field>
 </class>
+<class name="DefaultDatabaseErrorHandler"
+ extends="java.lang.Object"
+ abstract="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<implements name="android.database.DatabaseErrorHandler">
+</implements>
+<constructor name="DefaultDatabaseErrorHandler"
+ type="android.database.DefaultDatabaseErrorHandler"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</constructor>
+<method name="onCorruption"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="dbObj" type="android.database.sqlite.SQLiteDatabase">
+</parameter>
+</method>
+</class>
 <class name="MatrixCursor"
  extends="android.database.AbstractCursor"
  abstract="false"
@@ -57405,6 +57947,17 @@
 <parameter name="tables" type="java.lang.String">
 </parameter>
 </method>
+<method name="getAttachedDbs"
+ return="java.util.ArrayList&lt;android.util.Pair&lt;java.lang.String, java.lang.String&gt;&gt;"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
 <method name="getMaximumSize"
  return="long"
  abstract="false"
@@ -57526,6 +58079,17 @@
 <parameter name="conflictAlgorithm" type="int">
 </parameter>
 </method>
+<method name="isDatabaseIntegrityOk"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
 <method name="isDbLockedByCurrentThread"
  return="boolean"
  abstract="false"
@@ -57643,6 +58207,25 @@
 <parameter name="flags" type="int">
 </parameter>
 </method>
+<method name="openDatabase"
+ return="android.database.sqlite.SQLiteDatabase"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="path" type="java.lang.String">
+</parameter>
+<parameter name="factory" type="android.database.sqlite.SQLiteDatabase.CursorFactory">
+</parameter>
+<parameter name="flags" type="int">
+</parameter>
+<parameter name="errorHandler" type="android.database.DatabaseErrorHandler">
+</parameter>
+</method>
 <method name="openOrCreateDatabase"
  return="android.database.sqlite.SQLiteDatabase"
  abstract="false"
@@ -57673,6 +58256,23 @@
 <parameter name="factory" type="android.database.sqlite.SQLiteDatabase.CursorFactory">
 </parameter>
 </method>
+<method name="openOrCreateDatabase"
+ return="android.database.sqlite.SQLiteDatabase"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="path" type="java.lang.String">
+</parameter>
+<parameter name="factory" type="android.database.sqlite.SQLiteDatabase.CursorFactory">
+</parameter>
+<parameter name="errorHandler" type="android.database.DatabaseErrorHandler">
+</parameter>
+</method>
 <method name="query"
  return="android.database.Cursor"
  abstract="false"
@@ -57892,6 +58492,19 @@
 <parameter name="lockingEnabled" type="boolean">
 </parameter>
 </method>
+<method name="setMaxSqlCacheSize"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="true"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="cacheSize" type="int">
+</parameter>
+</method>
 <method name="setMaximumSize"
  return="long"
  abstract="false"
@@ -58094,6 +58707,17 @@
  visibility="public"
 >
 </field>
+<field name="MAX_SQL_CACHE_SIZE"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="100"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="NO_LOCALIZED_COLLATORS"
  type="int"
  transient="false"
@@ -72952,6 +73576,17 @@
  visibility="public"
 >
 </method>
+<method name="computeConstantSize"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="protected"
+>
+</method>
 <method name="getChangingConfigurations"
  return="int"
  abstract="false"
@@ -73846,6 +74481,36 @@
 </parameter>
 </method>
 </class>
+<class name="MipmapDrawable"
+ extends="android.graphics.drawable.DrawableContainer"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="MipmapDrawable"
+ type="android.graphics.drawable.MipmapDrawable"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</constructor>
+<method name="addDrawable"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="drawable" type="android.graphics.drawable.Drawable">
+</parameter>
+</method>
+</class>
 <class name="NinePatchDrawable"
  extends="android.graphics.drawable.Drawable"
  abstract="false"
@@ -124203,6 +124868,240 @@
 </method>
 </class>
 </package>
+<package name="android.os.storage"
+>
+<class name="StorageEventListener"
+ extends="java.lang.Object"
+ abstract="true"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="StorageEventListener"
+ type="android.os.storage.StorageEventListener"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</constructor>
+<method name="onStorageStateChanged"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="path" type="java.lang.String">
+</parameter>
+<parameter name="oldState" type="java.lang.String">
+</parameter>
+<parameter name="newState" type="java.lang.String">
+</parameter>
+</method>
+<method name="onUsbMassStorageConnectionChanged"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="connected" type="boolean">
+</parameter>
+</method>
+</class>
+<class name="StorageManager"
+ extends="java.lang.Object"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<method name="disableUsbMassStorage"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="enableUsbMassStorage"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="isUsbMassStorageConnected"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="isUsbMassStorageEnabled"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="registerListener"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="listener" type="android.os.storage.StorageEventListener">
+</parameter>
+</method>
+<method name="unregisterListener"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="listener" type="android.os.storage.StorageEventListener">
+</parameter>
+</method>
+</class>
+<class name="StorageResultCode"
+ extends="java.lang.Object"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="StorageResultCode"
+ type="android.os.storage.StorageResultCode"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</constructor>
+<field name="OperationFailedInternalError"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="-1"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="OperationFailedMediaBlank"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="-3"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="OperationFailedMediaCorrupt"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="-4"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="OperationFailedNoMedia"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="-2"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="OperationFailedStorageBusy"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="-7"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="OperationFailedStorageMounted"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="-6"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="OperationFailedStorageNotMounted"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="-5"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="OperationSucceeded"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="0"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+</class>
+</package>
 <package name="android.preference"
 >
 <class name="CheckBoxPreference"
@@ -124996,6 +125895,148 @@
 </parameter>
 </method>
 </class>
+<class name="MultiSelectListPreference"
+ extends="android.preference.DialogPreference"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="MultiSelectListPreference"
+ type="android.preference.MultiSelectListPreference"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="context" type="android.content.Context">
+</parameter>
+<parameter name="attrs" type="android.util.AttributeSet">
+</parameter>
+</constructor>
+<constructor name="MultiSelectListPreference"
+ type="android.preference.MultiSelectListPreference"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="context" type="android.content.Context">
+</parameter>
+</constructor>
+<method name="findIndexOfValue"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="value" type="java.lang.String">
+</parameter>
+</method>
+<method name="getEntries"
+ return="java.lang.CharSequence[]"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getEntryValues"
+ return="java.lang.CharSequence[]"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getValues"
+ return="java.util.Set&lt;java.lang.String&gt;"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="setEntries"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="entries" type="java.lang.CharSequence[]">
+</parameter>
+</method>
+<method name="setEntries"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="entriesResId" type="int">
+</parameter>
+</method>
+<method name="setEntryValues"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="entryValues" type="java.lang.CharSequence[]">
+</parameter>
+</method>
+<method name="setEntryValues"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="entryValuesResId" type="int">
+</parameter>
+</method>
+<method name="setValues"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="values" type="java.util.Set&lt;java.lang.String&gt;">
+</parameter>
+</method>
+</class>
 <class name="Preference"
  extends="java.lang.Object"
  abstract="false"
@@ -133366,6 +134407,17 @@
  deprecated="not deprecated"
  visibility="protected"
 >
+<field name="AUTO_ADD"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;auto_add&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="DELETED"
  type="java.lang.String"
  transient="false"
@@ -133377,6 +134429,17 @@
  visibility="public"
 >
 </field>
+<field name="FAVORITES"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;favorites&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="GROUP_VISIBLE"
  type="java.lang.String"
  transient="false"
@@ -158147,7 +159210,7 @@
 >
 <parameter name="text" type="java.lang.CharSequence">
 </parameter>
-<parameter name="p" type="android.text.TextPaint">
+<parameter name="paint" type="android.text.TextPaint">
 </parameter>
 <parameter name="avail" type="float">
 </parameter>
@@ -201262,6 +202325,195 @@
 </parameter>
 </method>
 </interface>
+<class name="Adapters"
+ extends="java.lang.Object"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="Adapters"
+ type="android.widget.Adapters"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</constructor>
+<method name="loadAdapter"
+ return="android.widget.BaseAdapter"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="context" type="android.content.Context">
+</parameter>
+<parameter name="id" type="int">
+</parameter>
+<parameter name="parameters" type="java.lang.Object...">
+</parameter>
+</method>
+<method name="loadCursorAdapter"
+ return="android.widget.CursorAdapter"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="context" type="android.content.Context">
+</parameter>
+<parameter name="id" type="int">
+</parameter>
+<parameter name="uri" type="java.lang.String">
+</parameter>
+<parameter name="parameters" type="java.lang.Object...">
+</parameter>
+</method>
+<method name="loadCursorAdapter"
+ return="android.widget.CursorAdapter"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="context" type="android.content.Context">
+</parameter>
+<parameter name="id" type="int">
+</parameter>
+<parameter name="cursor" type="android.database.Cursor">
+</parameter>
+<parameter name="parameters" type="java.lang.Object...">
+</parameter>
+</method>
+</class>
+<class name="Adapters.CursorBinder"
+ extends="java.lang.Object"
+ abstract="true"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="Adapters.CursorBinder"
+ type="android.widget.Adapters.CursorBinder"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="context" type="android.content.Context">
+</parameter>
+<parameter name="transformation" type="android.widget.Adapters.CursorTransformation">
+</parameter>
+</constructor>
+<method name="bind"
+ return="boolean"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="view" type="android.view.View">
+</parameter>
+<parameter name="cursor" type="android.database.Cursor">
+</parameter>
+<parameter name="columnIndex" type="int">
+</parameter>
+</method>
+<field name="mContext"
+ type="android.content.Context"
+ transient="false"
+ volatile="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="protected"
+>
+</field>
+<field name="mTransformation"
+ type="android.widget.Adapters.CursorTransformation"
+ transient="false"
+ volatile="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="protected"
+>
+</field>
+</class>
+<class name="Adapters.CursorTransformation"
+ extends="java.lang.Object"
+ abstract="true"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="Adapters.CursorTransformation"
+ type="android.widget.Adapters.CursorTransformation"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="context" type="android.content.Context">
+</parameter>
+</constructor>
+<method name="transform"
+ return="java.lang.String"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="cursor" type="android.database.Cursor">
+</parameter>
+<parameter name="columnIndex" type="int">
+</parameter>
+</method>
+<method name="transformToResource"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="cursor" type="android.database.Cursor">
+</parameter>
+<parameter name="columnIndex" type="int">
+</parameter>
+</method>
+<field name="mContext"
+ type="android.content.Context"
+ transient="false"
+ volatile="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="protected"
+>
+</field>
+</class>
 <class name="AlphabetIndexer"
  extends="android.database.DataSetObserver"
  abstract="false"
@@ -203223,6 +204475,20 @@
 <parameter name="autoRequery" type="boolean">
 </parameter>
 </constructor>
+<constructor name="CursorAdapter"
+ type="android.widget.CursorAdapter"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="context" type="android.content.Context">
+</parameter>
+<parameter name="c" type="android.database.Cursor">
+</parameter>
+<parameter name="flags" type="int">
+</parameter>
+</constructor>
 <method name="bindView"
  return="void"
  abstract="true"
@@ -203370,6 +204636,23 @@
 <parameter name="autoRequery" type="boolean">
 </parameter>
 </method>
+<method name="init"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="protected"
+>
+<parameter name="context" type="android.content.Context">
+</parameter>
+<parameter name="c" type="android.database.Cursor">
+</parameter>
+<parameter name="flags" type="int">
+</parameter>
+</method>
 <method name="newDropDownView"
  return="android.view.View"
  abstract="false"
@@ -203441,6 +204724,28 @@
 <parameter name="filterQueryProvider" type="android.widget.FilterQueryProvider">
 </parameter>
 </method>
+<field name="FLAG_AUTO_REQUERY"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="1"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="FLAG_REGISTER_CONTENT_OBSERVER"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="2"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 </class>
 <class name="CursorTreeAdapter"
  extends="android.widget.BaseExpandableListAdapter"
@@ -209448,6 +210753,17 @@
 <parameter name="excludeMimes" type="java.lang.String[]">
 </parameter>
 </method>
+<method name="setImageToDefault"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
 <method name="setMode"
  return="void"
  abstract="false"
diff --git a/cmds/input/input b/cmds/input/input
old mode 100644
new mode 100755
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index a962391..15bf242 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -650,6 +650,65 @@
     private CharSequence mTitle;
     private int mTitleColor = 0;
 
+    final FragmentManager mFragments = new FragmentManager();
+    
+    private final class FragmentTransactionImpl implements FragmentTransaction {
+        ArrayList<Fragment> mAdded;
+        ArrayList<Fragment> mRemoved;
+        
+        public FragmentTransaction add(Fragment fragment, int containerViewId) {
+            return add(fragment, null, containerViewId);
+        }
+
+        public FragmentTransaction add(Fragment fragment, String name, int containerViewId) {
+            if (fragment.mActivity != null) {
+                throw new IllegalStateException("Fragment already added: " + fragment);
+            }
+            if (name != null) {
+                fragment.mName = name;
+            }
+            if (mRemoved != null) {
+                mRemoved.remove(fragment);
+            }
+            if (mAdded == null) {
+                mAdded = new ArrayList<Fragment>();
+            }
+            fragment.mContainerId = containerViewId;
+            mAdded.add(fragment);
+            return this;
+        }
+
+        public FragmentTransaction remove(Fragment fragment) {
+            if (fragment.mActivity == null) {
+                throw new IllegalStateException("Fragment not added: " + fragment);
+            }
+            if (mAdded != null) {
+                mAdded.remove(fragment);
+            }
+            if (mRemoved == null) {
+                mRemoved = new ArrayList<Fragment>();
+            }
+            mRemoved.add(fragment);
+            return this;
+        }
+
+        public void commit() {
+            if (mRemoved != null) {
+                for (int i=mRemoved.size()-1; i>=0; i--) {
+                    mFragments.removeFragment(mRemoved.get(i));
+                }
+            }
+            if (mAdded != null) {
+                for (int i=mAdded.size()-1; i>=0; i--) {
+                    mFragments.addFragment(mAdded.get(i));
+                }
+            }
+            if (mFragments != null) {
+                mFragments.moveToState(mFragments.mCurState);
+            }
+        }
+    }
+    
     private static final class ManagedCursor {
         ManagedCursor(Cursor cursor) {
             mCursor = cursor;
@@ -1464,6 +1523,14 @@
     }
     
     /**
+     * Start a series of edit operations on the Fragments associated with
+     * this activity.
+     */
+    public FragmentTransaction openFragmentTransaction() {
+        return new FragmentTransactionImpl();
+    }
+    
+    /**
      * Wrapper around
      * {@link ContentResolver#query(android.net.Uri , String[], String, String[], String)}
      * that gives the resulting {@link Cursor} to call
@@ -3743,6 +3810,8 @@
             Configuration config) {
         attachBaseContext(context);
 
+        mFragments.attachActivity(this);
+        
         mWindow = PolicyManager.makeNewWindow(this);
         mWindow.setCallback(this);
         if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
@@ -3776,6 +3845,11 @@
         return mParent != null ? mParent.getActivityToken() : mToken;
     }
 
+    final void performCreate(Bundle icicle) {
+        onCreate(icicle);
+        mFragments.dispatchCreate(icicle);
+    }
+    
     final void performStart() {
         mCalled = false;
         mInstrumentation.callActivityOnStart(this);
@@ -3784,6 +3858,7 @@
                 "Activity " + mComponent.toShortString() +
                 " did not call through to super.onStart()");
         }
+        mFragments.dispatchStart();
     }
     
     final void performRestart() {
@@ -3830,6 +3905,9 @@
         // Now really resume, and install the current status bar and menu.
         mResumed = true;
         mCalled = false;
+        
+        mFragments.dispatchResume();
+        
         onPostResume();
         if (!mCalled) {
             throw new SuperNotCalledException(
@@ -3839,6 +3917,7 @@
     }
 
     final void performPause() {
+        mFragments.dispatchPause();
         onPause();
     }
     
@@ -3853,6 +3932,8 @@
                 mWindow.closeAllPanels();
             }
 
+            mFragments.dispatchStop();
+            
             mCalled = false;
             mInstrumentation.callActivityOnStop(this);
             if (!mCalled) {
@@ -3877,6 +3958,11 @@
         mResumed = false;
     }
 
+    final void performDestroy() {
+        mFragments.dispatchDestroy();
+        onDestroy();
+    }
+    
     final boolean isResumed() {
         return mResumed;
     }
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index fd84859..87b77d65 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -1465,7 +1465,7 @@
         private static final String HEAP_COLUMN = "%17s %8s %8s %8s %8s";
         private static final String ONE_COUNT_COLUMN = "%17s %8d";
         private static final String TWO_COUNT_COLUMNS = "%17s %8d %17s %8d";
-        private static final String DB_INFO_FORMAT = "  %8d %8d %10d  %s";
+        private static final String DB_INFO_FORMAT = "  %4d %6d %8d %14s  %s";
 
         // Formatting for checkin service - update version if row format changes
         private static final int ACTIVITY_THREAD_CHECKIN_VERSION = 1;
@@ -1865,7 +1865,7 @@
                 for (int i = 0; i < stats.dbStats.size(); i++) {
                     DbStats dbStats = stats.dbStats.get(i);
                     printRow(pw, DB_INFO_FORMAT, dbStats.pageSize, dbStats.dbSize,
-                            dbStats.lookaside, dbStats.dbName);
+                            dbStats.lookaside, dbStats.cache, dbStats.dbName);
                     pw.print(',');
                 }
 
@@ -1916,11 +1916,12 @@
             int N = stats.dbStats.size();
             if (N > 0) {
                 pw.println(" DATABASES");
-                printRow(pw, "  %8s %8s %10s  %s", "Pagesize", "Dbsize", "Lookaside", "Dbname");
+                printRow(pw, "  %4s %6s %8s %14s  %s", "pgsz", "dbsz", "lkaside", "cache",
+                    "Dbname");
                 for (int i = 0; i < N; i++) {
                     DbStats dbStats = stats.dbStats.get(i);
                     printRow(pw, DB_INFO_FORMAT, dbStats.pageSize, dbStats.dbSize,
-                            dbStats.lookaside, dbStats.dbName);
+                            dbStats.lookaside, dbStats.cache, dbStats.dbName);
                 }
             }
 
@@ -3642,7 +3643,7 @@
             }
             try {
                 r.activity.mCalled = false;
-                r.activity.onDestroy();
+                mInstrumentation.callActivityOnDestroy(r.activity);
                 if (!r.activity.mCalled) {
                     throw new SuperNotCalledException(
                         "Activity " + safeToComponentShortString(r.intent) +
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 15ce2b2..e646827 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -2776,6 +2776,13 @@
                 return v != null ? v : defValue;
             }
         }
+        
+        public Set<String> getStringSet(String key, Set<String> defValues) {
+            synchronized (this) {
+                Set<String> v = (Set<String>) mMap.get(key);
+                return v != null ? v : defValues;
+            }
+        }
 
         public int getInt(String key, int defValue) {
             synchronized (this) {
@@ -2818,6 +2825,12 @@
                     return this;
                 }
             }
+            public Editor putStringSet(String key, Set<String> values) {
+                synchronized (this) {
+                    mModified.put(key, values);
+                    return this;
+                }
+            }
             public Editor putInt(String key, int value) {
                 synchronized (this) {
                     mModified.put(key, value);
diff --git a/core/java/android/app/Fragment.java b/core/java/android/app/Fragment.java
new file mode 100644
index 0000000..c0dc869
--- /dev/null
+++ b/core/java/android/app/Fragment.java
@@ -0,0 +1,119 @@
+/*
+ * 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.app;
+
+import android.content.ComponentCallbacks;
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * A Fragment is a piece of an application's user interface or behavior
+ * that can be placed in an {@link Activity}.
+ */
+public class Fragment implements ComponentCallbacks {
+    static final int INITIALIZING = 0;  // Not yet created.
+    static final int CREATED = 1;       // Created.
+    static final int STARTED = 2;       // Created and started, not resumed.
+    static final int RESUMED = 3;       // Created started and resumed.
+    
+    String mName;
+    
+    int mState = INITIALIZING;
+    Activity mActivity;
+    
+    boolean mCalled;
+    int mContainerId;
+    
+    ViewGroup mContainer;
+    View mView;
+    
+    public Fragment() {
+    }
+    
+    public Fragment(String name) {
+        mName = name;
+    }
+    
+    public String getName() {
+        return mName;
+    }
+    
+    public Activity getActivity() {
+        return mActivity;
+    }
+    
+    public void onAttach(Activity activity) {
+        mCalled = true;
+    }
+    
+    public void onCreate(Bundle savedInstanceState) {
+        mCalled = true;
+    }
+    
+    public View onCreateView(LayoutInflater inflater, ViewGroup container) {
+        return null;
+    }
+    
+    public void onRestoreInstanceState(Bundle savedInstanceState) {
+    }
+    
+    public void onStart() {
+        mCalled = true;
+    }
+    
+    public void onRestart() {
+        mCalled = true;
+    }
+    
+    public void onResume() {
+        mCalled = true;
+    }
+    
+    public void onSaveInstanceState(Bundle outState) {
+    }
+    
+    public void onConfigurationChanged(Configuration newConfig) {
+        mCalled = true;
+    }
+    
+    public Object onRetainNonConfigurationInstance() {
+        return null;
+    }
+    
+    public void onPause() {
+        mCalled = true;
+    }
+    
+    public void onStop() {
+        mCalled = true;
+    }
+    
+    public void onLowMemory() {
+        mCalled = true;
+    }
+    
+    public void onDestroy() {
+        mCalled = true;
+    }
+    
+    public void onDetach() {
+        mCalled = true;
+    }
+}
diff --git a/core/java/android/app/FragmentManager.java b/core/java/android/app/FragmentManager.java
new file mode 100644
index 0000000..d5e49cf
--- /dev/null
+++ b/core/java/android/app/FragmentManager.java
@@ -0,0 +1,183 @@
+/*
+ * 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.app;
+
+import android.os.Bundle;
+import android.view.ViewGroup;
+
+import java.util.ArrayList;
+
+/**
+ * Container for fragments associated with an activity.
+ */
+class FragmentManager {
+    final ArrayList<Fragment> mFragments = new ArrayList<Fragment>();
+    
+    int mCurState = Fragment.INITIALIZING;
+    Activity mActivity;
+    
+    void moveToState(Fragment f, int newState) {
+        if (f.mState < newState) {
+            switch (f.mState) {
+                case Fragment.INITIALIZING:
+                    f.mActivity = mActivity;
+                    f.mCalled = false;
+                    f.onAttach(mActivity);
+                    if (!f.mCalled) {
+                        throw new SuperNotCalledException("Fragment " + f
+                                + " did not call through to super.onAttach()");
+                    }
+                    f.mCalled = false;
+                    f.onCreate(null);
+                    if (!f.mCalled) {
+                        throw new SuperNotCalledException("Fragment " + f
+                                + " did not call through to super.onCreate()");
+                    }
+                    
+                    ViewGroup container = null;
+                    if (f.mContainerId != 0) {
+                        container = (ViewGroup)mActivity.findViewById(f.mContainerId);
+                        if (container == null) {
+                            throw new IllegalArgumentException("New view found for id 0x"
+                                    + Integer.toHexString(f.mContainerId)
+                                    + " for fragment " + f);
+                        }
+                    }
+                    f.mContainer = container;
+                    f.mView = f.onCreateView(mActivity.getLayoutInflater(), container);
+                    if (container != null && f.mView != null) {
+                        container.addView(f.mView);
+                    }
+                    
+                case Fragment.CREATED:
+                    if (newState > Fragment.CREATED) {
+                        f.mCalled = false;
+                        f.onStart();
+                        if (!f.mCalled) {
+                            throw new SuperNotCalledException("Fragment " + f
+                                    + " did not call through to super.onStart()");
+                        }
+                    }
+                case Fragment.STARTED:
+                    if (newState > Fragment.STARTED) {
+                        f.mCalled = false;
+                        f.onResume();
+                        if (!f.mCalled) {
+                            throw new SuperNotCalledException("Fragment " + f
+                                    + " did not call through to super.onResume()");
+                        }
+                    }
+            }
+        } else if (f.mState > newState) {
+            switch (f.mState) {
+                case Fragment.RESUMED:
+                    if (newState < Fragment.RESUMED) {
+                        f.mCalled = false;
+                        f.onPause();
+                        if (!f.mCalled) {
+                            throw new SuperNotCalledException("Fragment " + f
+                                    + " did not call through to super.onPause()");
+                        }
+                    }
+                case Fragment.STARTED:
+                    if (newState < Fragment.STARTED) {
+                        f.mCalled = false;
+                        f.onStop();
+                        if (!f.mCalled) {
+                            throw new SuperNotCalledException("Fragment " + f
+                                    + " did not call through to super.onStop()");
+                        }
+                    }
+                case Fragment.CREATED:
+                    if (newState < Fragment.CREATED) {
+                        if (f.mContainer != null && f.mView != null) {
+                            f.mContainer.removeView(f.mView);
+                        }
+                        f.mContainer = null;
+                        f.mView = null;
+                        
+                        f.mCalled = false;
+                        f.onDestroy();
+                        if (!f.mCalled) {
+                            throw new SuperNotCalledException("Fragment " + f
+                                    + " did not call through to super.onDestroy()");
+                        }
+                        f.mCalled = false;
+                        f.onDetach();
+                        if (!f.mCalled) {
+                            throw new SuperNotCalledException("Fragment " + f
+                                    + " did not call through to super.onDetach()");
+                        }
+                        f.mActivity = null;
+                    }
+            }
+        }
+        
+        f.mState = newState;
+    }
+    
+    void moveToState(int newState) {
+        if (mActivity == null && newState != Fragment.INITIALIZING) {
+            throw new IllegalStateException("No activity");
+        }
+        
+        mCurState = newState;
+        for (int i=0; i<mFragments.size(); i++) {
+            Fragment f = mFragments.get(i);
+            moveToState(f, newState);
+        }
+    }
+    
+    public void addFragment(Fragment fragment) {
+        mFragments.add(fragment);
+    }
+    
+    public void removeFragment(Fragment fragment) {
+        mFragments.remove(fragment);
+        moveToState(fragment, Fragment.INITIALIZING);
+    }
+    
+    public void attachActivity(Activity activity) {
+        if (mActivity != null) throw new IllegalStateException();
+        mActivity = activity;
+    }
+    
+    public void dispatchCreate(Bundle state) {
+        moveToState(Fragment.CREATED);
+    }
+    
+    public void dispatchStart() {
+        moveToState(Fragment.STARTED);
+    }
+    
+    public void dispatchResume() {
+        moveToState(Fragment.RESUMED);
+    }
+    
+    public void dispatchPause() {
+        moveToState(Fragment.STARTED);
+    }
+    
+    public void dispatchStop() {
+        moveToState(Fragment.CREATED);
+    }
+    
+    public void dispatchDestroy() {
+        moveToState(Fragment.INITIALIZING);
+        mActivity = null;
+    }
+}
diff --git a/core/java/android/app/FragmentTransaction.java b/core/java/android/app/FragmentTransaction.java
new file mode 100644
index 0000000..f97e510
--- /dev/null
+++ b/core/java/android/app/FragmentTransaction.java
@@ -0,0 +1,11 @@
+package android.app;
+
+/**
+ * API for performing a set of Fragment operations.
+ */
+public interface FragmentTransaction {
+    public FragmentTransaction add(Fragment fragment, int containerViewId);
+    public FragmentTransaction add(Fragment fragment, String name, int containerViewId);
+    public FragmentTransaction remove(Fragment fragment);
+    public void commit();
+}
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index b8c3aa3..7ed7c49 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -1072,7 +1072,7 @@
           }
       }
       
-      activity.onDestroy();
+      activity.performDestroy();
       
       if (mActivityMonitors != null) {
           synchronized (mSync) {
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 30822d4..0afd6d2 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -1372,7 +1372,6 @@
     public static final String SENSOR_SERVICE = "sensor";
     
     /**
-     * @hide
      * Use with {@link #getSystemService} to retrieve a {@link
      * android.os.storage.StorageManager} for accesssing system storage
      * functions.
diff --git a/core/java/android/content/SharedPreferences.java b/core/java/android/content/SharedPreferences.java
index a15e29e..5847216 100644
--- a/core/java/android/content/SharedPreferences.java
+++ b/core/java/android/content/SharedPreferences.java
@@ -17,6 +17,7 @@
 package android.content;
 
 import java.util.Map;
+import java.util.Set;
 
 /**
  * Interface for accessing and modifying preference data returned by {@link
@@ -69,6 +70,17 @@
         Editor putString(String key, String value);
         
         /**
+         * Set a set of String values in the preferences editor, to be written
+         * back once {@link #commit} is called.
+         * 
+         * @param key The name of the preference to modify.
+         * @param values The new values for the preference.
+         * @return Returns a reference to the same Editor object, so you can
+         * chain put calls together.
+         */
+        Editor putStringSet(String key, Set<String> values);
+        
+        /**
          * Set an int value in the preferences editor, to be written back once
          * {@link #commit} is called.
          * 
@@ -186,6 +198,20 @@
     String getString(String key, String defValue);
     
     /**
+     * Retrieve a set of String values from the preferences.
+     * 
+     * @param key The name of the preference to retrieve.
+     * @param defValues Values to return if this preference does not exist.
+     * 
+     * @return Returns the preference values if they exist, or defValues.
+     * Throws ClassCastException if there is a preference with this name
+     * that is not a Set.
+     * 
+     * @throws ClassCastException
+     */
+    Set<String> getStringSet(String key, Set<String> defValues);
+    
+    /**
      * Retrieve an int value from the preferences.
      * 
      * @param key The name of the preference to retrieve.
diff --git a/core/java/android/content/SyncManager.java b/core/java/android/content/SyncManager.java
index d0b67cc..7f749bb 100644
--- a/core/java/android/content/SyncManager.java
+++ b/core/java/android/content/SyncManager.java
@@ -16,6 +16,8 @@
 
 package android.content;
 
+import com.google.android.collect.Maps;
+
 import com.android.internal.R;
 import com.android.internal.util.ArrayUtils;
 
@@ -55,6 +57,7 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Random;
@@ -126,14 +129,13 @@
 
     private static final int INITIALIZATION_UNBIND_DELAY_MS = 5000;
 
-    private static final String SYNC_WAKE_LOCK = "SyncManagerSyncWakeLock";
+    private static final String SYNC_WAKE_LOCK_PREFIX = "SyncWakeLock";
     private static final String HANDLE_SYNC_ALARM_WAKE_LOCK = "SyncManagerHandleSyncAlarmWakeLock";
 
     private Context mContext;
 
     private volatile Account[] mAccounts = INITIAL_ACCOUNTS_ARRAY;
 
-    volatile private PowerManager.WakeLock mSyncWakeLock;
     volatile private PowerManager.WakeLock mHandleAlarmWakeLock;
     volatile private boolean mDataConnectionIsConnected = false;
     volatile private boolean mStorageIsLow = false;
@@ -195,6 +197,8 @@
 
     private static final Account[] INITIAL_ACCOUNTS_ARRAY = new Account[0];
 
+    private final PowerManager mPowerManager;
+
     public void onAccountsUpdated(Account[] accounts) {
         // remember if this was the first time this was called after an update
         final boolean justBootedUp = mAccounts == INITIAL_ACCOUNTS_ARRAY;
@@ -356,15 +360,13 @@
         } else {
             mNotificationMgr = null;
         }
-        PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
-        mSyncWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, SYNC_WAKE_LOCK);
-        mSyncWakeLock.setReferenceCounted(false);
+        mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
 
         // This WakeLock is used to ensure that we stay awake between the time that we receive
         // a sync alarm notification and when we finish processing it. We need to do this
         // because we don't do the work in the alarm handler, rather we do it in a message
         // handler.
-        mHandleAlarmWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
+        mHandleAlarmWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
                 HANDLE_SYNC_ALARM_WAKE_LOCK);
         mHandleAlarmWakeLock.setReferenceCounted(false);
 
@@ -1302,6 +1304,9 @@
         public final SyncNotificationInfo mSyncNotificationInfo = new SyncNotificationInfo();
         private Long mAlarmScheduleTime = null;
         public final SyncTimeTracker mSyncTimeTracker = new SyncTimeTracker();
+        private PowerManager.WakeLock mSyncWakeLock;
+        private final HashMap<Pair<String, String>,  PowerManager.WakeLock> mWakeLocks =
+                Maps.newHashMap();
 
         // used to track if we have installed the error notification so that we don't reinstall
         // it if sync is still failing
@@ -1315,6 +1320,18 @@
             }
         }
 
+        private PowerManager.WakeLock getSyncWakeLock(String accountType, String authority) {
+            final Pair<String, String> wakeLockKey = Pair.create(accountType, authority);
+            PowerManager.WakeLock wakeLock = mWakeLocks.get(wakeLockKey);
+            if (wakeLock == null) {
+                final String name = SYNC_WAKE_LOCK_PREFIX + "_" + authority + "_" + accountType;
+                wakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, name);
+                wakeLock.setReferenceCounted(false);
+                mWakeLocks.put(wakeLockKey, wakeLock);
+            }
+            return wakeLock;
+        }
+
         private void waitUntilReadyToRun() {
             CountDownLatch latch = mReadyToRunLatch;
             if (latch != null) {
@@ -1477,8 +1494,9 @@
                 }
             } finally {
                 final boolean isSyncInProgress = mActiveSyncContext != null;
-                if (!isSyncInProgress) {
+                if (!isSyncInProgress && mSyncWakeLock != null) {
                     mSyncWakeLock.release();
+                    mSyncWakeLock = null;
                 }
                 manageSyncNotification();
                 manageErrorNotification();
@@ -1704,7 +1722,26 @@
                 return;
             }
 
-            mSyncWakeLock.acquire();
+            // Find the wakelock for this account and authority and store it in mSyncWakeLock.
+            // Be sure to release the previous wakelock so that we don't end up with it being
+            // held until it is used again.
+            // There are a couple tricky things about this code:
+            // - make sure that we acquire the new wakelock before releasing the old one,
+            //   otherwise the device might go to sleep as soon as we release it.
+            // - since we use non-reference counted wakelocks we have to be sure not to do
+            //   the release if the wakelock didn't change. Othewise we would do an
+            //   acquire followed by a release on the same lock, resulting in no lock
+            //   being held.
+            PowerManager.WakeLock oldWakeLock = mSyncWakeLock;
+            try {
+                mSyncWakeLock = getSyncWakeLock(op.account.type, op.authority);
+                mSyncWakeLock.acquire();
+            } finally {
+                if (oldWakeLock != null && oldWakeLock != mSyncWakeLock) {
+                    oldWakeLock.release();
+                }
+            }
+
             // no need to schedule an alarm, as that will be done by our caller.
 
             // the next step will occur when we get either a timeout or a
diff --git a/core/java/android/database/AbstractCursor.java b/core/java/android/database/AbstractCursor.java
index 038eedf..541f91a2 100644
--- a/core/java/android/database/AbstractCursor.java
+++ b/core/java/android/database/AbstractCursor.java
@@ -18,16 +18,11 @@
 
 import android.content.ContentResolver;
 import android.net.Uri;
+import android.os.Bundle;
 import android.util.Config;
 import android.util.Log;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Looper;
-import android.os.Message;
 
 import java.lang.ref.WeakReference;
-import java.lang.UnsupportedOperationException;
 import java.util.HashMap;
 import java.util.Map;
 
@@ -88,7 +83,7 @@
         }
         mDataSetObservable.notifyInvalidated();
     }
-    
+
     public boolean requery() {
         if (mSelfObserver != null && mSelfObserverRegistered == false) {
             mContentResolver.registerContentObserver(mNotifyUri, true, mSelfObserver);
diff --git a/core/java/android/database/Cursor.java b/core/java/android/database/Cursor.java
index 79178f4..dc4471e 100644
--- a/core/java/android/database/Cursor.java
+++ b/core/java/android/database/Cursor.java
@@ -493,6 +493,10 @@
      * contents. This may be done at any time, including after a call to {@link
      * #deactivate}.
      *
+     * Since this method could execute a query on the database and potentially take
+     * a while, it could cause ANR if it is called on Main (UI) thread.
+     * A warning is printed if this method is being executed on Main thread.
+     *
      * @return true if the requery succeeded, false if not, in which case the
      *         cursor becomes invalid.
      */
diff --git a/core/java/android/database/DataSetObservable.java b/core/java/android/database/DataSetObservable.java
index 9200e81..51c72c1 100644
--- a/core/java/android/database/DataSetObservable.java
+++ b/core/java/android/database/DataSetObservable.java
@@ -27,8 +27,12 @@
      */
     public void notifyChanged() {
         synchronized(mObservers) {
-            for (DataSetObserver observer : mObservers) {
-                observer.onChanged();
+            // since onChanged() is implemented by the app, it could do anything, including
+            // removing itself from {@link mObservers} - and that could cause problems if
+            // an iterator is used on the ArrayList {@link mObservers}.
+            // to avoid such problems, just march thru the list in the reverse order.
+            for (int i = mObservers.size() - 1; i >= 0; i--) {
+                mObservers.get(i).onChanged();
             }
         }
     }
@@ -39,8 +43,8 @@
      */
     public void notifyInvalidated() {
         synchronized (mObservers) {
-            for (DataSetObserver observer : mObservers) {
-                observer.onInvalidated();
+            for (int i = mObservers.size() - 1; i >= 0; i--) {
+                mObservers.get(i).onInvalidated();
             }
         }
     }
diff --git a/core/java/android/database/DatabaseErrorHandler.java b/core/java/android/database/DatabaseErrorHandler.java
new file mode 100644
index 0000000..f0c5452
--- /dev/null
+++ b/core/java/android/database/DatabaseErrorHandler.java
@@ -0,0 +1,33 @@
+/*
+ * 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.database;
+
+import android.database.sqlite.SQLiteDatabase;
+
+/**
+ * An interface to let the apps define the actions to take when the following errors are detected
+ *   database corruption
+ */
+public interface DatabaseErrorHandler {
+
+    /**
+     * defines the method to be invoked when database corruption is detected.
+     * @param dbObj the {@link SQLiteDatabase} object representing the database on which corruption
+     * is detected.
+     */
+    void onCorruption(SQLiteDatabase dbObj);
+}
diff --git a/core/java/android/database/DefaultDatabaseErrorHandler.java b/core/java/android/database/DefaultDatabaseErrorHandler.java
new file mode 100644
index 0000000..98aa54a
--- /dev/null
+++ b/core/java/android/database/DefaultDatabaseErrorHandler.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.database;
+
+import java.io.File;
+
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteException;
+import android.util.Log;
+import android.util.Pair;
+
+/**
+ * Default class used defining the actions to take when the following errors are detected
+ *   database corruption
+ */
+public final class DefaultDatabaseErrorHandler implements DatabaseErrorHandler {
+
+    private static final String TAG = "DefaultDatabaseErrorHandler";
+
+    /**
+     * defines the default method to be invoked when database corruption is detected.
+     * @param dbObj the {@link SQLiteDatabase} object representing the database on which corruption
+     * is detected.
+     */
+    public void onCorruption(SQLiteDatabase dbObj) {
+        Log.e(TAG, "Corruption reported by sqlite on database: " + dbObj.getPath());
+
+        // is the corruption detected even before database could be 'opened'?
+        if (!dbObj.isOpen()) {
+            // database files are not even openable. delete this database file.
+            // NOTE if the database has attached databases, then any of them could be corrupt.
+            // and not deleting all of them could cause corrupted database file to remain and 
+            // make the application crash on database open operation. To avoid this problem,
+            // the application should provide its own {@link DatabaseErrorHandler} impl class
+            // to delete ALL files of the database (including the attached databases).
+            if (!dbObj.getPath().equalsIgnoreCase(":memory")) {
+                // not memory database.
+                try {
+                    new File(dbObj.getPath()).delete();
+                } catch (Exception e) {
+                    /* ignore */
+                }
+            }
+            return;
+        }
+
+        try {
+            // Close the database, which will cause subsequent operations to fail.
+            try {
+                dbObj.close();
+            } catch (SQLiteException e) {
+                /* ignore */
+            }
+        } finally {
+            // Delete all files of this corrupt database and/or attached databases
+            for (Pair<String, String> p : dbObj.getAttachedDbs()) {
+                Log.e(TAG, "deleting the database file: " + p.second);
+                if (!p.second.equalsIgnoreCase(":memory:")) {
+                    // delete file if it is a non-memory database file
+                    try {
+                        new File(p.second).delete();
+                    } catch (Exception e) {
+                        /* ignore */
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/core/java/android/database/RequeryOnUiThreadException.java b/core/java/android/database/RequeryOnUiThreadException.java
new file mode 100644
index 0000000..97a50d8
--- /dev/null
+++ b/core/java/android/database/RequeryOnUiThreadException.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2006 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.database;
+
+/**
+ * An exception that indicates invoking {@link Cursor#requery()} on Main thread could cause ANR.
+ * This exception should encourage apps to invoke {@link Cursor#requery()} in a background thread. 
+ * @hide
+ */
+public class RequeryOnUiThreadException extends RuntimeException {
+    public RequeryOnUiThreadException(String packageName) {
+        super("In " + packageName + " Requery is executing on main (UI) thread. could cause ANR. " +
+                "do it in background thread.");
+    }
+}
diff --git a/core/java/android/database/sqlite/DatabaseObjectNotClosedException.java b/core/java/android/database/sqlite/DatabaseObjectNotClosedException.java
index 8ac4c0f..f28c70f 100644
--- a/core/java/android/database/sqlite/DatabaseObjectNotClosedException.java
+++ b/core/java/android/database/sqlite/DatabaseObjectNotClosedException.java
@@ -21,13 +21,11 @@
  * that is not explicitly closed
  * @hide
  */
-public class DatabaseObjectNotClosedException extends RuntimeException
-{
+public class DatabaseObjectNotClosedException extends RuntimeException {
     private static final String s = "Application did not close the cursor or database object " +
             "that was opened here";
 
-    public DatabaseObjectNotClosedException()
-    {
+    public DatabaseObjectNotClosedException() {
         super(s);
     }
 }
diff --git a/core/java/android/database/sqlite/SQLiteCompiledSql.java b/core/java/android/database/sqlite/SQLiteCompiledSql.java
index 25aa9b3..16ff2ab 100644
--- a/core/java/android/database/sqlite/SQLiteCompiledSql.java
+++ b/core/java/android/database/sqlite/SQLiteCompiledSql.java
@@ -78,20 +78,13 @@
      *  existing compiled SQL program already around
      */
     private void compile(String sql, boolean forceCompilation) {
-        if (!mDatabase.isOpen()) {
-            throw new IllegalStateException("database " + mDatabase.getPath() + " already closed");
-        }
+        mDatabase.verifyLockOwner();
         // Only compile if we don't have a valid statement already or the caller has
         // explicitly requested a recompile.
         if (forceCompilation) {
-            mDatabase.lock();
-            try {
-                // Note that the native_compile() takes care of destroying any previously
-                // existing programs before it compiles.
-                native_compile(sql);
-            } finally {
-                mDatabase.unlock();
-            }
+            // Note that the native_compile() takes care of destroying any previously
+            // existing programs before it compiles.
+            native_compile(sql);
         }
     }
 
@@ -134,6 +127,10 @@
         mInUse = false;
     }
 
+    /* package */ synchronized boolean isInUse() {
+        return mInUse;
+    }
+
     /**
      * Make sure that the native resource is cleaned up.
      */
diff --git a/core/java/android/database/sqlite/SQLiteCursor.java b/core/java/android/database/sqlite/SQLiteCursor.java
index 6e5b3e1..fea3438 100644
--- a/core/java/android/database/sqlite/SQLiteCursor.java
+++ b/core/java/android/database/sqlite/SQLiteCursor.java
@@ -16,12 +16,14 @@
 
 package android.database.sqlite;
 
+import android.app.ActivityThread;
 import android.database.AbstractWindowedCursor;
 import android.database.CursorWindow;
 import android.database.DataSetObserver;
+import android.database.RequeryOnUiThreadException;
 import android.database.SQLException;
-
 import android.os.Handler;
+import android.os.Looper;
 import android.os.Message;
 import android.os.Process;
 import android.text.TextUtils;
@@ -74,6 +76,11 @@
     private int mCursorState = 0;
     private ReentrantLock mLock = null;
     private boolean mPendingData = false;
+
+    /**
+     * Used by {@link #requery()} to remember for which database we've already shown the warning.
+     */
+    private static final HashMap<String, Boolean> sAlreadyWarned = new HashMap<String, Boolean>();
     
     /**
      *  support for a cursor variant that doesn't always read all results
@@ -503,11 +510,30 @@
         mDriver.cursorClosed();
     }
 
+    /**
+     * Show a warning against the use of requery() if called on the main thread.
+     * This warning is shown per database per process.
+     */
+    private void warnIfUiThread() {
+        if (Looper.getMainLooper() == Looper.myLooper()) {
+            String databasePath = mDatabase.getPath();
+            // We show the warning once per database in order not to spam logcat.
+            if (!sAlreadyWarned.containsKey(databasePath)) {
+                sAlreadyWarned.put(databasePath, true);
+                String packageName = ActivityThread.currentPackageName();
+                Log.w(TAG, "should not attempt requery on main (UI) thread: app = " +
+                        packageName == null ? "'unknown'" : packageName,
+                        new RequeryOnUiThreadException(packageName));
+            }
+        }
+    }
+
     @Override
     public boolean requery() {
         if (isClosed()) {
             return false;
         }
+        warnIfUiThread();
         long timeStart = 0;
         if (Config.LOGV) {
             timeStart = System.currentTimeMillis();
diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index fb5507d..e64e2c3 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -16,12 +16,12 @@
 
 package android.database.sqlite;
 
-import com.google.android.collect.Maps;
-
 import android.app.ActivityThread;
 import android.content.ContentValues;
 import android.database.Cursor;
+import android.database.DatabaseErrorHandler;
 import android.database.DatabaseUtils;
+import android.database.DefaultDatabaseErrorHandler;
 import android.database.SQLException;
 import android.database.sqlite.SQLiteDebug.DbStats;
 import android.os.Debug;
@@ -35,11 +35,11 @@
 
 import java.io.File;
 import java.lang.ref.WeakReference;
-import java.text.SimpleDateFormat;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
+import java.util.LinkedHashMap;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Random;
@@ -251,7 +251,7 @@
     private WeakHashMap<SQLiteClosable, Object> mPrograms;
 
     /**
-     * for each instance of this class, a cache is maintained to store
+     * for each instance of this class, a LRU cache is maintained to store
      * the compiled query statement ids returned by sqlite database.
      *     key = sql statement with "?" for bind args
      *     value = {@link SQLiteCompiledSql}
@@ -263,15 +263,42 @@
      * invoked.
      *
      * this cache has an upper limit of mMaxSqlCacheSize (settable by calling the method
-     * (@link setMaxCacheSize(int)}). its default is 0 - i.e., no caching by default because
-     * most of the apps don't use "?" syntax in their sql, caching is not useful for them.
+     * (@link setMaxSqlCacheSize(int)}).
      */
-    /* package */ Map<String, SQLiteCompiledSql> mCompiledQueries = Maps.newHashMap();
+    // default statement-cache size per database connection ( = instance of this class)
+    private int mMaxSqlCacheSize = 25;
+    /* package */ Map<String, SQLiteCompiledSql> mCompiledQueries =
+        new LinkedHashMap<String, SQLiteCompiledSql>(mMaxSqlCacheSize + 1, 0.75f, true) {
+            @Override
+            public boolean removeEldestEntry(Map.Entry<String, SQLiteCompiledSql> eldest) {
+                // eldest = least-recently used entry
+                // if it needs to be removed to accommodate a new entry,
+                //     close {@link SQLiteCompiledSql} represented by this entry, if not in use
+                //     and then let it be removed from the Map.
+                // when this is called, the caller must be trying to add a just-compiled stmt
+                // to cache; i.e., caller should already have acquired database lock AND
+                // the lock on mCompiledQueries. do as assert of these two 2 facts.
+                verifyLockOwner();
+                if (this.size() <= mMaxSqlCacheSize) {
+                    // cache is not full. nothing needs to be removed
+                    return false;
+                }
+                // cache is full. eldest will be removed.
+                SQLiteCompiledSql entry = eldest.getValue();
+                if (!entry.isInUse()) {
+                    // this {@link SQLiteCompiledSql} is not in use. release it.
+                    entry.releaseSqlStatement();
+                }
+                // return true, so that this entry is removed automatically by the caller.
+                return true;
+            }
+        };
     /**
-     * @hide
+     * absolute max value that can be set by {@link #setMaxSqlCacheSize(int)}
+     * size of each prepared-statement is between 1K - 6K, depending on the complexity of the
+     * sql statement & schema.
      */
-    public static final int MAX_SQL_CACHE_SIZE = 250;
-    private int mMaxSqlCacheSize = MAX_SQL_CACHE_SIZE; // max cache size per Database instance
+    public static final int MAX_SQL_CACHE_SIZE = 100;
     private int mCacheFullWarnings;
     private static final int MAX_WARNINGS_ON_CACHESIZE_CONDITION = 1;
 
@@ -279,10 +306,6 @@
     private int mNumCacheHits;
     private int mNumCacheMisses;
 
-    /** the following 2 members maintain the time when a database is opened and closed */
-    private String mTimeOpened = null;
-    private String mTimeClosed = null;
-
     /** Used to find out where this object was created in case it never got closed. */
     private Throwable mStackTrace = null;
 
@@ -290,6 +313,11 @@
     private static final String LOG_SLOW_QUERIES_PROPERTY = "db.log.slow_query_threshold";
     private final int mSlowQueryThreshold;
 
+    /** {@link DatabaseErrorHandler} to be used when SQLite returns any of the following errors
+     *    Corruption
+     * */
+    private DatabaseErrorHandler errorHandler;
+
     /**
      * @param closable
      */
@@ -314,9 +342,6 @@
     @Override
     protected void onAllReferencesReleased() {
         if (isOpen()) {
-            if (SQLiteDebug.DEBUG_SQL_CACHE) {
-                mTimeClosed = getTime();
-            }
             dbclose();
         }
     }
@@ -347,19 +372,8 @@
     private boolean mLockingEnabled = true;
 
     /* package */ void onCorruption() {
-        Log.e(TAG, "Removing corrupt database: " + mPath);
         EventLog.writeEvent(EVENT_DB_CORRUPT, mPath);
-        try {
-            // Close the database (if we can), which will cause subsequent operations to fail.
-            close();
-        } finally {
-            // Delete the corrupt file.  Don't re-create it now -- that would just confuse people
-            // -- but the next time someone tries to open it, they can set it up from scratch.
-            if (!mPath.equalsIgnoreCase(":memory")) {
-                // delete is only for non-memory database files
-                new File(mPath).delete();
-            }
-        }
+        errorHandler.onCorruption(this);
     }
 
     /**
@@ -811,10 +825,25 @@
      * @throws SQLiteException if the database cannot be opened
      */
     public static SQLiteDatabase openDatabase(String path, CursorFactory factory, int flags) {
-        SQLiteDatabase sqliteDatabase = null;
+        return openDatabase(path, factory, flags, new DefaultDatabaseErrorHandler());
+    }
+
+    /**
+     * same as {@link #openDatabase(String, CursorFactory, int)} except for an additional param
+     * errorHandler.
+     * @param errorHandler the {@link DatabaseErrorHandler} obj to be used when database
+     * corruption is detected on the database.
+     */
+    public static SQLiteDatabase openDatabase(String path, CursorFactory factory, int flags,
+            DatabaseErrorHandler errorHandler) {
+        SQLiteDatabase sqliteDatabase = new SQLiteDatabase(path, factory, flags);
+
+        // set the ErrorHandler to be used when SQLite reports exceptions
+        sqliteDatabase.errorHandler = errorHandler;
+
         try {
             // Open the database.
-            sqliteDatabase = new SQLiteDatabase(path, factory, flags);
+            sqliteDatabase.openDatabase(path, flags);
             if (SQLiteDebug.DEBUG_SQL_STATEMENTS) {
                 sqliteDatabase.enableSqlTracing(path);
             }
@@ -822,14 +851,8 @@
                 sqliteDatabase.enableSqlProfiling(path);
             }
         } catch (SQLiteDatabaseCorruptException e) {
-            // Try to recover from this, if we can.
-            // TODO: should we do this for other open failures?
-            Log.e(TAG, "Deleting and re-creating corrupt database " + path, e);
-            EventLog.writeEvent(EVENT_DB_CORRUPT, path);
-            if (!path.equalsIgnoreCase(":memory")) {
-                // delete is only for non-memory database files
-                new File(path).delete();
-            }
+            // Database is not even openable.
+            errorHandler.onCorruption(sqliteDatabase);
             sqliteDatabase = new SQLiteDatabase(path, factory, flags);
         }
         ActiveDatabases.getInstance().mActiveDatabases.add(
@@ -837,6 +860,18 @@
         return sqliteDatabase;
     }
 
+    private void openDatabase(String path, int flags) {
+        // Open the database.
+        dbopen(path, flags);
+        try {
+            setLocale(Locale.getDefault());
+        } catch (RuntimeException e) {
+            Log.e(TAG, "Failed to setLocale(). closing the database", e);
+            dbclose();
+            throw e;
+        }
+    }
+
     /**
      * Equivalent to openDatabase(file.getPath(), factory, CREATE_IF_NECESSARY).
      */
@@ -852,6 +887,17 @@
     }
 
     /**
+     * same as {@link #openOrCreateDatabase(String, CursorFactory)} except for an additional param
+     * errorHandler.
+     * @param errorHandler the {@link DatabaseErrorHandler} obj to be used when database
+     * corruption is detected on the database.
+     */
+    public static SQLiteDatabase openOrCreateDatabase(String path, CursorFactory factory,
+            DatabaseErrorHandler errorHandler) {
+        return openDatabase(path, factory, CREATE_IF_NECESSARY, errorHandler);
+    }
+
+    /**
      * Create a memory backed SQLite database.  Its contents will be destroyed
      * when the database is closed.
      *
@@ -1809,25 +1855,7 @@
         mSlowQueryThreshold = SystemProperties.getInt(LOG_SLOW_QUERIES_PROPERTY, -1);
         mStackTrace = new DatabaseObjectNotClosedException().fillInStackTrace();
         mFactory = factory;
-        dbopen(mPath, mFlags);
-        if (SQLiteDebug.DEBUG_SQL_CACHE) {
-            mTimeOpened = getTime();
-        }
         mPrograms = new WeakHashMap<SQLiteClosable,Object>();
-        try {
-            setLocale(Locale.getDefault());
-        } catch (RuntimeException e) {
-            Log.e(TAG, "Failed to setLocale() when constructing, closing the database", e);
-            dbclose();
-            if (SQLiteDebug.DEBUG_SQL_CACHE) {
-                mTimeClosed = getTime();
-            }
-            throw e;
-        }
-    }
-
-    private String getTime() {
-        return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS ").format(System.currentTimeMillis());
     }
 
     /**
@@ -1953,6 +1981,15 @@
         }
     }
 
+    /* package */ void verifyLockOwner() {
+        if (!isOpen()) {
+            throw new IllegalStateException("database " + getPath() + " already closed");
+        }
+        if (!isDbLockedByCurrentThread() && mLockingEnabled) {
+            throw new IllegalStateException("Don't have database lock!");
+        }
+    }
+
     /*
      * ============================================================================
      *
@@ -1968,14 +2005,6 @@
      * mapping is NOT replaced with the new mapping).
      */
     /* package */ void addToCompiledQueries(String sql, SQLiteCompiledSql compiledStatement) {
-        if (mMaxSqlCacheSize == 0) {
-            // for this database, there is no cache of compiled sql.
-            if (SQLiteDebug.DEBUG_SQL_CACHE) {
-                Log.v(TAG, "|NOT adding_sql_to_cache|" + getPath() + "|" + sql);
-            }
-            return;
-        }
-
         SQLiteCompiledSql compiledSql = null;
         synchronized(mCompiledQueries) {
             // don't insert the new mapping if a mapping already exists
@@ -1983,35 +2012,30 @@
             if (compiledSql != null) {
                 return;
             }
-            // add this <sql, compiledStatement> to the cache
+
             if (mCompiledQueries.size() == mMaxSqlCacheSize) {
                 /*
                  * cache size of {@link #mMaxSqlCacheSize} is not enough for this app.
-                 * log a warning MAX_WARNINGS_ON_CACHESIZE_CONDITION times
-                 * chances are it is NOT using ? for bindargs - so caching is useless.
-                 * TODO: either let the callers set max cchesize for their app, or intelligently
-                 * figure out what should be cached for a given app.
+                 * log a warning.
+                 * chances are it is NOT using ? for bindargs - or cachesize is too small.
                  */
                 if (++mCacheFullWarnings == MAX_WARNINGS_ON_CACHESIZE_CONDITION) {
                     Log.w(TAG, "Reached MAX size for compiled-sql statement cache for database " +
-                            getPath() + "; i.e., NO space for this sql statement in cache: " +
-                            sql + ". Please change your sql statements to use '?' for " +
-                            "bindargs, instead of using actual values");
+                            getPath() + ". Consider increasing cachesize.");
                 }
-                // don't add this entry to cache
-            } else {
-                // cache is NOT full. add this to cache.
-                mCompiledQueries.put(sql, compiledStatement);
-                if (SQLiteDebug.DEBUG_SQL_CACHE) {
-                    Log.v(TAG, "|adding_sql_to_cache|" + getPath() + "|" +
-                            mCompiledQueries.size() + "|" + sql);
-                }
+            } 
+            /* add the given SQLiteCompiledSql compiledStatement to cache.
+             * no need to worry about the cache size - because {@link #mCompiledQueries}
+             * self-limits its size to {@link #mMaxSqlCacheSize}.
+             */
+            mCompiledQueries.put(sql, compiledStatement);
+            if (SQLiteDebug.DEBUG_SQL_CACHE) {
+                Log.v(TAG, "|adding_sql_to_cache|" + getPath() + "|" +
+                        mCompiledQueries.size() + "|" + sql);
             }
         }
-        return;
     }
 
-
     private void deallocCachedSqlStatements() {
         synchronized (mCompiledQueries) {
             for (SQLiteCompiledSql compiledSql : mCompiledQueries.values()) {
@@ -2029,13 +2053,6 @@
         SQLiteCompiledSql compiledStatement = null;
         boolean cacheHit;
         synchronized(mCompiledQueries) {
-            if (mMaxSqlCacheSize == 0) {
-                // for this database, there is no cache of compiled sql.
-                if (SQLiteDebug.DEBUG_SQL_CACHE) {
-                    Log.v(TAG, "|cache NOT found|" + getPath());
-                }
-                return null;
-            }
             cacheHit = (compiledStatement = mCompiledQueries.get(sql)) != null;
         }
         if (cacheHit) {
@@ -2048,63 +2065,23 @@
             Log.v(TAG, "|cache_stats|" +
                     getPath() + "|" + mCompiledQueries.size() +
                     "|" + mNumCacheHits + "|" + mNumCacheMisses +
-                    "|" + cacheHit + "|" + mTimeOpened + "|" + mTimeClosed + "|" + sql);
+                    "|" + cacheHit + "|" + sql);
         }
         return compiledStatement;
     }
 
     /**
-     * returns true if the given sql is cached in compiled-sql cache.
-     * @hide
-     */
-    public boolean isInCompiledSqlCache(String sql) {
-        synchronized(mCompiledQueries) {
-            return mCompiledQueries.containsKey(sql);
-        }
-    }
-
-    /**
-     * purges the given sql from the compiled-sql cache.
-     * @hide
-     */
-    public void purgeFromCompiledSqlCache(String sql) {
-        synchronized(mCompiledQueries) {
-            mCompiledQueries.remove(sql);
-        }
-    }
-
-    /**
-     * remove everything from the compiled sql cache
-     * @hide
-     */
-    public void resetCompiledSqlCache() {
-        synchronized(mCompiledQueries) {
-            mCompiledQueries.clear();
-        }
-    }
-
-    /**
-     * return the current maxCacheSqlCacheSize
-     * @hide
-     */
-    public synchronized int getMaxSqlCacheSize() {
-        return mMaxSqlCacheSize;
-    }
-
-    /**
-     * set the max size of the compiled sql cache for this database after purging the cache.
+     * set the max size of the prepared-statement cache for this database.
      * (size of the cache = number of compiled-sql-statements stored in the cache).
      *
-     * max cache size can ONLY be increased from its current size (default = 0).
+     * max cache size can ONLY be increased from its current size (default = 10).
      * if this method is called with smaller size than the current value of mMaxSqlCacheSize,
      * then IllegalStateException is thrown
      *
      * synchronized because we don't want t threads to change cache size at the same time.
-     * @param cacheSize the size of the cache. can be (0 to MAX_SQL_CACHE_SIZE)
-     * @throws IllegalStateException if input cacheSize > MAX_SQL_CACHE_SIZE or < 0 or
-     * < the value set with previous setMaxSqlCacheSize() call.
-     *
-     * @hide
+     * @param cacheSize the size of the cache. can be (0 to {@link #MAX_SQL_CACHE_SIZE})
+     * @throws IllegalStateException if input cacheSize > {@link #MAX_SQL_CACHE_SIZE} or
+     * > the value set with previous setMaxSqlCacheSize() call.
      */
     public synchronized void setMaxSqlCacheSize(int cacheSize) {
         if (cacheSize > MAX_SQL_CACHE_SIZE || cacheSize < 0) {
@@ -2144,7 +2121,7 @@
             String lastnode = path.substring((indx != -1) ? ++indx : 0);
 
             // get list of attached dbs and for each db, get its size and pagesize
-            ArrayList<Pair<String, String>> attachedDbs = getAttachedDbs(db);
+            ArrayList<Pair<String, String>> attachedDbs = db.getAttachedDbs();
             if (attachedDbs == null) {
                 continue;
             }
@@ -2169,7 +2146,8 @@
                 }
                 if (pageCount > 0) {
                     dbStatsList.add(new DbStats(dbName, pageCount, db.getPageSize(),
-                            lookasideUsed));
+                            lookasideUsed, db.mNumCacheHits, db.mNumCacheMisses,
+                            db.mCompiledQueries.size()));
                 }
             }
         }
@@ -2197,24 +2175,74 @@
     }
 
     /**
-     * returns list of full pathnames of all attached databases
-     * including the main database
-     * TODO: move this to {@link DatabaseUtils}
+     * returns list of full pathnames of all attached databases including the main database
+     * @return ArrayList of pairs of (database name, database file path) or null if the database
+     * is not open.
      */
-    private static ArrayList<Pair<String, String>> getAttachedDbs(SQLiteDatabase dbObj) {
-        if (!dbObj.isOpen()) {
+    public ArrayList<Pair<String, String>> getAttachedDbs() {
+        if (!isOpen()) {
             return null;
         }
         ArrayList<Pair<String, String>> attachedDbs = new ArrayList<Pair<String, String>>();
-        Cursor c = dbObj.rawQuery("pragma database_list;", null);
-        while (c.moveToNext()) {
-             attachedDbs.add(new Pair<String, String>(c.getString(1), c.getString(2)));
+        Cursor c = null;
+        try {
+            c = rawQuery("pragma database_list;", null);
+            while (c.moveToNext()) {
+                // sqlite returns a row for each database in the returned list of databases.
+                //   in each row,
+                //       1st column is the database name such as main, or the database
+                //                              name specified on the "ATTACH" command
+                //       2nd column is the database file path.
+                attachedDbs.add(new Pair<String, String>(c.getString(1), c.getString(2)));
+            }
+        } finally {
+            if (c != null) {
+                c.close();
+            }
         }
-        c.close();
         return attachedDbs;
     }
 
     /**
+     * run pragma integrity_check on the given database (and all the attached databases)
+     * and return true if the given database (and all its attached databases) pass integrity_check,
+     * false otherwise.
+     *
+     * if the result is false, then this method logs the errors reported by the integrity_check
+     * command execution.
+     *
+     * @return true if the given database (and all its attached databases) pass integrity_check,
+     * false otherwise
+     */
+    public boolean isDatabaseIntegrityOk() {
+        if (!isOpen()) {
+            throw new IllegalStateException("database: " + getPath() + " is NOT open");
+        }
+        ArrayList<Pair<String, String>> attachedDbs = getAttachedDbs();
+        if (attachedDbs == null) {
+            throw new IllegalStateException("databaselist for: " + getPath() + " couldn't " +
+                    "be retrieved. probably because the database is closed");
+        }
+        boolean isDatabaseCorrupt = false;
+        for (int i = 0; i < attachedDbs.size(); i++) {
+            Pair<String, String> p = attachedDbs.get(i);
+            SQLiteStatement prog = null;
+            try {
+                prog = compileStatement("PRAGMA " + p.first + ".integrity_check(1);");
+                String rslt = prog.simpleQueryForString();
+                if (!rslt.equalsIgnoreCase("ok")) {
+                    // integrity_checker failed on main or attached databases
+                    isDatabaseCorrupt = true;
+                    Log.e(TAG, "PRAGMA integrity_check on " + p.second + " returned: " + rslt);
+                }
+            } finally {
+                if (prog != null) prog.close();
+            }
+        }
+        return isDatabaseCorrupt;
+    }
+
+    /**
      * Native call to open the database.
      *
      * @param path The full path to the database
diff --git a/core/java/android/database/sqlite/SQLiteDebug.java b/core/java/android/database/sqlite/SQLiteDebug.java
index 89c3f96..94960791 100644
--- a/core/java/android/database/sqlite/SQLiteDebug.java
+++ b/core/java/android/database/sqlite/SQLiteDebug.java
@@ -132,11 +132,16 @@
         /** documented here http://www.sqlite.org/c3ref/c_dbstatus_lookaside_used.html */
         public int lookaside;
 
-        public DbStats(String dbName, long pageCount, long pageSize, int lookaside) {
+        /** statement cache stats: hits/misses/cachesize */
+        public String cache;
+
+        public DbStats(String dbName, long pageCount, long pageSize, int lookaside,
+            int hits, int misses, int cachesize) {
             this.dbName = dbName;
-            this.pageSize = pageSize;
+            this.pageSize = pageSize / 1024;
             dbSize = (pageCount * pageSize) / 1024;
             this.lookaside = lookaside;
+            this.cache = hits + "/" + misses + "/" + cachesize;
         }
     }
 
diff --git a/core/java/android/database/sqlite/SQLiteDirectCursorDriver.java b/core/java/android/database/sqlite/SQLiteDirectCursorDriver.java
index 2144fc3..ac60b27 100644
--- a/core/java/android/database/sqlite/SQLiteDirectCursorDriver.java
+++ b/core/java/android/database/sqlite/SQLiteDirectCursorDriver.java
@@ -39,9 +39,11 @@
 
     public Cursor query(CursorFactory factory, String[] selectionArgs) {
         // Compile the query
-        SQLiteQuery query = new SQLiteQuery(mDatabase, mSql, 0, selectionArgs);
+        SQLiteQuery query = null;
 
         try {
+            mDatabase.lock();
+            query = new SQLiteQuery(mDatabase, mSql, 0, selectionArgs);
             // Arg binding
             int numArgs = selectionArgs == null ? 0 : selectionArgs.length;
             for (int i = 0; i < numArgs; i++) {
@@ -61,6 +63,7 @@
         } finally {
             // Make sure this object is cleaned up if something happens
             if (query != null) query.close();
+            mDatabase.unlock();
         }
     }
 
diff --git a/core/java/android/database/sqlite/SQLiteOpenHelper.java b/core/java/android/database/sqlite/SQLiteOpenHelper.java
index 52aac3a..d4907d9 100644
--- a/core/java/android/database/sqlite/SQLiteOpenHelper.java
+++ b/core/java/android/database/sqlite/SQLiteOpenHelper.java
@@ -99,6 +99,10 @@
             }
 
             int version = db.getVersion();
+            if (version > mNewVersion) {
+                throw new IllegalStateException("Database " + mName +
+                        " cannot be downgraded. instead, please uninstall new version first.");
+            }
             if (version != mNewVersion) {
                 db.beginTransaction();
                 try {
diff --git a/core/java/android/database/sqlite/SQLiteProgram.java b/core/java/android/database/sqlite/SQLiteProgram.java
index 89a5f0d1..a9c1ac6 100644
--- a/core/java/android/database/sqlite/SQLiteProgram.java
+++ b/core/java/android/database/sqlite/SQLiteProgram.java
@@ -147,7 +147,7 @@
      * @return a unique identifier for this program
      */
     public final int getUniqueId() {
-        return nStatement;
+        return (mCompiledSql != null) ? mCompiledSql.nStatement : 0;
     }
 
     /* package */ String getSqlString() {
diff --git a/core/java/android/net/Downloads.java b/core/java/android/net/Downloads.java
index fd33781..ddde5c1 100644
--- a/core/java/android/net/Downloads.java
+++ b/core/java/android/net/Downloads.java
@@ -430,11 +430,10 @@
 
             ContentResolver cr = context.getContentResolver();
 
-            Cursor c = cr.query(
-                    downloadUri, DOWNLOADS_PROJECTION, null /* selection */, null /* selection args */,
-                    null /* sort order */);
+            Cursor c = cr.query(downloadUri, DOWNLOADS_PROJECTION, null /* selection */,
+                    null /* selection args */, null /* sort order */);
             try {
-                if (!c.moveToNext()) {
+                if (c == null || !c.moveToNext()) {
                     return result;
                 }
 
diff --git a/core/java/android/os/AsyncTask.java b/core/java/android/os/AsyncTask.java
index d28148c..e74697a 100644
--- a/core/java/android/os/AsyncTask.java
+++ b/core/java/android/os/AsyncTask.java
@@ -16,16 +16,16 @@
 
 package android.os;
 
-import java.util.concurrent.ThreadPoolExecutor;
-import java.util.concurrent.TimeUnit;
 import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.FutureTask;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.ThreadFactory;
-import java.util.concurrent.Callable;
-import java.util.concurrent.FutureTask;
-import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
-import java.util.concurrent.CancellationException;
 import java.util.concurrent.atomic.AtomicInteger;
 
 /**
@@ -36,8 +36,8 @@
  * <p>An asynchronous task is defined by a computation that runs on a background thread and
  * whose result is published on the UI thread. An asynchronous task is defined by 3 generic
  * types, called <code>Params</code>, <code>Progress</code> and <code>Result</code>,
- * and 4 steps, called <code>begin</code>, <code>doInBackground</code>,
- * <code>processProgress</code> and <code>end</code>.</p>
+ * and 4 steps, called <code>onPreExecute</code>, <code>doInBackground</code>,
+ * <code>onProgressUpdate</code> and <code>onPostExecute</code>.</p>
  *
  * <h2>Usage</h2>
  * <p>AsyncTask must be subclassed to be used. The subclass will override at least
diff --git a/core/java/android/os/storage/StorageEventListener.java b/core/java/android/os/storage/StorageEventListener.java
index 7b883a7..d3d39d6 100644
--- a/core/java/android/os/storage/StorageEventListener.java
+++ b/core/java/android/os/storage/StorageEventListener.java
@@ -18,7 +18,6 @@
 
 /**
  * Used for receiving notifications from the StorageManager
- * @hide
  */
 public abstract class StorageEventListener {
     /**
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index a12603c..b49979c 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -45,8 +45,6 @@
  * {@link android.content.Context#getSystemService(java.lang.String)} with an argument
  * of {@link android.content.Context#STORAGE_SERVICE}.
  *
- * @hide
- *
  */
 
 public class StorageManager
diff --git a/core/java/android/os/storage/StorageResultCode.java b/core/java/android/os/storage/StorageResultCode.java
index 075f47f..07d95df 100644
--- a/core/java/android/os/storage/StorageResultCode.java
+++ b/core/java/android/os/storage/StorageResultCode.java
@@ -19,8 +19,6 @@
 /**
  * Class that provides access to constants returned from StorageManager
  * and lower level MountService APIs.
- *
- * @hide
  */
 public class StorageResultCode
 {
diff --git a/core/java/android/pim/RecurrenceSet.java b/core/java/android/pim/RecurrenceSet.java
index 635323e..282417d 100644
--- a/core/java/android/pim/RecurrenceSet.java
+++ b/core/java/android/pim/RecurrenceSet.java
@@ -181,7 +181,9 @@
         boolean inUtc = start.parse(dtstart);
         boolean allDay = start.allDay;
 
-        if (inUtc) {
+        // We force TimeZone to UTC for "all day recurring events" as the server is sending no
+        // TimeZone in DTSTART for them
+        if (inUtc || allDay) {
             tzid = Time.TIMEZONE_UTC;
         }
                 
@@ -204,10 +206,7 @@
         }
         
         if (allDay) {
-        	// TODO: also change tzid to be UTC?  that would be consistent, but
-        	// that would not reflect the original timezone value back to the
-        	// server.
-        	start.timezone = Time.TIMEZONE_UTC;
+            start.timezone = Time.TIMEZONE_UTC;
         }
         long millis = start.toMillis(false /* use isDst */);
         values.put(Calendar.Events.DTSTART, millis);
diff --git a/core/java/android/pim/vcard/JapaneseUtils.java b/core/java/android/pim/vcard/JapaneseUtils.java
index 875c29e..dcfe980 100644
--- a/core/java/android/pim/vcard/JapaneseUtils.java
+++ b/core/java/android/pim/vcard/JapaneseUtils.java
@@ -27,7 +27,6 @@
         new HashMap<Character, String>();
 
     static {
-        // There's no logical mapping rule in Unicode. Sigh.
         sHalfWidthMap.put('\u3001', "\uFF64");
         sHalfWidthMap.put('\u3002', "\uFF61");
         sHalfWidthMap.put('\u300C', "\uFF62");
@@ -366,11 +365,11 @@
     }
 
     /**
-     * Return half-width version of that character if possible. Return null if not possible
+     * Returns half-width version of that character if possible. Returns null if not possible
      * @param ch input character
      * @return CharSequence object if the mapping for ch exists. Return null otherwise.
      */
-    public static String tryGetHalfWidthText(char ch) {
+    public static String tryGetHalfWidthText(final char ch) {
         if (sHalfWidthMap.containsKey(ch)) {
             return sHalfWidthMap.get(ch);
         } else {
diff --git a/core/java/android/pim/vcard/VCardBuilder.java b/core/java/android/pim/vcard/VCardBuilder.java
index 0a6415d..789b5f8 100644
--- a/core/java/android/pim/vcard/VCardBuilder.java
+++ b/core/java/android/pim/vcard/VCardBuilder.java
@@ -47,7 +47,23 @@
 import java.util.Set;
 
 /**
- * The class which lets users create their own vCard String.
+ * <p>
+ * The class which lets users create their own vCard String. Typical usage is as follows:
+ * </p>
+ * <pre class="prettyprint">final VCardBuilder builder = new VCardBuilder(vcardType);
+ * builder.appendNameProperties(contentValuesListMap.get(StructuredName.CONTENT_ITEM_TYPE))
+ *     .appendNickNames(contentValuesListMap.get(Nickname.CONTENT_ITEM_TYPE))
+ *     .appendPhones(contentValuesListMap.get(Phone.CONTENT_ITEM_TYPE))
+ *     .appendEmails(contentValuesListMap.get(Email.CONTENT_ITEM_TYPE))
+ *     .appendPostals(contentValuesListMap.get(StructuredPostal.CONTENT_ITEM_TYPE))
+ *     .appendOrganizations(contentValuesListMap.get(Organization.CONTENT_ITEM_TYPE))
+ *     .appendWebsites(contentValuesListMap.get(Website.CONTENT_ITEM_TYPE))
+ *     .appendPhotos(contentValuesListMap.get(Photo.CONTENT_ITEM_TYPE))
+ *     .appendNotes(contentValuesListMap.get(Note.CONTENT_ITEM_TYPE))
+ *     .appendEvents(contentValuesListMap.get(Event.CONTENT_ITEM_TYPE))
+ *     .appendIms(contentValuesListMap.get(Im.CONTENT_ITEM_TYPE))
+ *     .appendRelation(contentValuesListMap.get(Relation.CONTENT_ITEM_TYPE));
+ * return builder.toString();</pre>
  */
 public class VCardBuilder {
     private static final String LOG_TAG = "VCardBuilder";
@@ -75,13 +91,14 @@
     private static final String VCARD_WS = " ";
     private static final String VCARD_PARAM_EQUAL = "=";
 
-    private static final String VCARD_PARAM_ENCODING_QP = "ENCODING=QUOTED-PRINTABLE";
-
-    private static final String VCARD_PARAM_ENCODING_BASE64_V21 = "ENCODING=BASE64";
-    private static final String VCARD_PARAM_ENCODING_BASE64_V30 = "ENCODING=b";
+    private static final String VCARD_PARAM_ENCODING_QP =
+            "ENCODING=" + VCardConstants.PARAM_ENCODING_QP;
+    private static final String VCARD_PARAM_ENCODING_BASE64_V21 =
+            "ENCODING=" + VCardConstants.PARAM_ENCODING_BASE64;
+    private static final String VCARD_PARAM_ENCODING_BASE64_V30 =
+            "ENCODING=" + VCardConstants.PARAM_ENCODING_B;
 
     private static final String SHIFT_JIS = "SHIFT_JIS";
-    private static final String UTF_8 = "UTF-8";
 
     private final int mVCardType;
 
@@ -92,21 +109,28 @@
     private final boolean mShouldUseQuotedPrintable;
     private final boolean mUsesAndroidProperty;
     private final boolean mUsesDefactProperty;
-    private final boolean mUsesUtf8;
-    private final boolean mUsesShiftJis;
     private final boolean mAppendTypeParamName;
     private final boolean mRefrainsQPToNameProperties;
     private final boolean mNeedsToConvertPhoneticString;
 
     private final boolean mShouldAppendCharsetParam;
 
-    private final String mCharsetString;
+    private final String mCharset;
     private final String mVCardCharsetParameter;
 
     private StringBuilder mBuilder;
     private boolean mEndAppended;
 
     public VCardBuilder(final int vcardType) {
+        // Default charset should be used
+        this(vcardType, null);
+    }
+
+    /**
+     * @param vcardType
+     * @param charset If null, we use default charset for export.
+     */
+    public VCardBuilder(final int vcardType, String charset) {
         mVCardType = vcardType;
 
         mIsV30 = VCardConfig.isV30(vcardType);
@@ -116,40 +140,74 @@
         mOnlyOneNoteFieldIsAvailable = VCardConfig.onlyOneNoteFieldIsAvailable(vcardType);
         mUsesAndroidProperty = VCardConfig.usesAndroidSpecificProperty(vcardType);
         mUsesDefactProperty = VCardConfig.usesDefactProperty(vcardType);
-        mUsesUtf8 = VCardConfig.usesUtf8(vcardType);
-        mUsesShiftJis = VCardConfig.usesShiftJis(vcardType);
         mRefrainsQPToNameProperties = VCardConfig.shouldRefrainQPToNameProperties(vcardType);
         mAppendTypeParamName = VCardConfig.appendTypeParamName(vcardType);
         mNeedsToConvertPhoneticString = VCardConfig.needsToConvertPhoneticString(vcardType);
 
-        mShouldAppendCharsetParam = !(mIsV30 && mUsesUtf8);
+        // vCard 2.1 requires charset.
+        // vCard 3.0 does not allow it but we found some devices use it to determine
+        // the exact charset.
+        // We currently append it only when charset other than UTF_8 is used.
+        mShouldAppendCharsetParam = !(mIsV30 && "UTF-8".equalsIgnoreCase(charset));
 
-        if (mIsDoCoMo) {
-            String charset;
-            try {
-                charset = CharsetUtils.charsetForVendor(SHIFT_JIS, "docomo").name();
-            } catch (UnsupportedCharsetException e) {
-                Log.e(LOG_TAG, "DoCoMo-specific SHIFT_JIS was not found. Use SHIFT_JIS as is.");
-                charset = SHIFT_JIS;
+        if (VCardConfig.isDoCoMo(vcardType)) {
+            if (!SHIFT_JIS.equalsIgnoreCase(charset)) {
+                Log.w(LOG_TAG,
+                        "The charset \"" + charset + "\" is used while "
+                        + SHIFT_JIS + " is needed to be used.");
+                if (TextUtils.isEmpty(charset)) {
+                    mCharset = SHIFT_JIS;
+                } else {
+                    try {
+                        charset = CharsetUtils.charsetForVendor(charset).name();
+                    } catch (UnsupportedCharsetException e) {
+                        Log.i(LOG_TAG,
+                                "Career-specific \"" + charset + "\" was not found (as usual). "
+                                + "Use it as is.");
+                    }
+                    mCharset = charset;
+                }
+            } else {
+                if (mIsDoCoMo) {
+                    try {
+                        charset = CharsetUtils.charsetForVendor(SHIFT_JIS, "docomo").name();
+                    } catch (UnsupportedCharsetException e) {
+                        Log.e(LOG_TAG,
+                                "DoCoMo-specific SHIFT_JIS was not found. "
+                                + "Use SHIFT_JIS as is.");
+                        charset = SHIFT_JIS;
+                    }
+                } else {
+                    try {
+                        charset = CharsetUtils.charsetForVendor(SHIFT_JIS).name();
+                    } catch (UnsupportedCharsetException e) {
+                        Log.e(LOG_TAG,
+                                "Career-specific SHIFT_JIS was not found. "
+                                + "Use SHIFT_JIS as is.");
+                        charset = SHIFT_JIS;
+                    }
+                }
+                mCharset = charset;
             }
-            mCharsetString = charset;
-            // Do not use mCharsetString bellow since it is different from "SHIFT_JIS" but
-            // may be "DOCOMO_SHIFT_JIS" or something like that (internal expression used in
-            // Android, not shown to the public).
-            mVCardCharsetParameter = "CHARSET=" + SHIFT_JIS;
-        } else if (mUsesShiftJis) {
-            String charset;
-            try {
-                charset = CharsetUtils.charsetForVendor(SHIFT_JIS).name();
-            } catch (UnsupportedCharsetException e) {
-                Log.e(LOG_TAG, "Vendor-specific SHIFT_JIS was not found. Use SHIFT_JIS as is.");
-                charset = SHIFT_JIS;
-            }
-            mCharsetString = charset;
             mVCardCharsetParameter = "CHARSET=" + SHIFT_JIS;
         } else {
-            mCharsetString = UTF_8;
-            mVCardCharsetParameter = "CHARSET=" + UTF_8;
+            if (TextUtils.isEmpty(charset)) {
+                Log.i(LOG_TAG,
+                        "Use the charset \"" + VCardConfig.DEFAULT_EXPORT_CHARSET
+                        + "\" for export.");
+                mCharset = VCardConfig.DEFAULT_EXPORT_CHARSET;
+                mVCardCharsetParameter = "CHARSET=" + VCardConfig.DEFAULT_EXPORT_CHARSET;
+            } else {
+                try {
+                    charset = CharsetUtils.charsetForVendor(charset).name();
+                } catch (UnsupportedCharsetException e) {
+                    Log.i(LOG_TAG,
+                            "Career-specific \"" + charset + "\" was not found (as usual). "
+                            + "Use it as is.");
+                }
+                mCharset = charset;
+                mVCardCharsetParameter = "CHARSET=" + charset;
+            }
         }
         clear();
     }
@@ -379,8 +437,8 @@
             mBuilder.append(VCardConstants.PROPERTY_FN);
 
             // Note: "CHARSET" param is not allowed in vCard 3.0, but we may add it
-            //       when it would be useful for external importers, assuming no external
-            //       importer allows this vioration.
+            //       when it would be useful or necessary for external importers,
+            //       assuming the external importer allows this vioration of the spec.
             if (shouldAppendCharsetParam(displayName)) {
                 mBuilder.append(VCARD_PARAM_SEPARATOR);
                 mBuilder.append(mVCardCharsetParameter);
@@ -454,18 +512,18 @@
             mBuilder.append(VCARD_END_OF_LINE);
         } else if (mIsJapaneseMobilePhone) {
             // Note: There is no appropriate property for expressing
-            //       phonetic name in vCard 2.1, while there is in
+            //       phonetic name (Yomigana in Japanese) in vCard 2.1, while there is in
             //       vCard 3.0 (SORT-STRING).
-            //       We chose to use DoCoMo's way when the device is Japanese one
-            //       since it is supported by
-            //       a lot of Japanese mobile phones. This is "X-" property, so
-            //       any parser hopefully would not get confused with this.
+            //       We use DoCoMo's way when the device is Japanese one since it is already
+            //       supported by a lot of Japanese mobile phones.
+            //       This is "X-" property, so any parser hopefully would not get
+            //       confused with this.
             //
             //       Also, DoCoMo's specification requires vCard composer to use just the first
             //       column.
             //       i.e.
-            //       o  SOUND;X-IRMC-N:Miyakawa Daisuke;;;;
-            //       x  SOUND;X-IRMC-N:Miyakawa;Daisuke;;;
+            //       good:  SOUND;X-IRMC-N:Miyakawa Daisuke;;;;
+            //       bad :  SOUND;X-IRMC-N:Miyakawa;Daisuke;;;
             mBuilder.append(VCardConstants.PROPERTY_SOUND);
             mBuilder.append(VCARD_PARAM_SEPARATOR);
             mBuilder.append(VCardConstants.PARAM_TYPE_X_IRMC_N);
@@ -519,10 +577,10 @@
                     mBuilder.append(encodedPhoneticGivenName);
                 }
             }
-            mBuilder.append(VCARD_ITEM_SEPARATOR);
-            mBuilder.append(VCARD_ITEM_SEPARATOR);
-            mBuilder.append(VCARD_ITEM_SEPARATOR);
-            mBuilder.append(VCARD_ITEM_SEPARATOR);
+            mBuilder.append(VCARD_ITEM_SEPARATOR);  // family;given
+            mBuilder.append(VCARD_ITEM_SEPARATOR);  // given;middle
+            mBuilder.append(VCARD_ITEM_SEPARATOR);  // middle;prefix
+            mBuilder.append(VCARD_ITEM_SEPARATOR);  // prefix;suffix
             mBuilder.append(VCARD_END_OF_LINE);
         }
 
@@ -549,7 +607,7 @@
                 mBuilder.append(VCARD_DATA_SEPARATOR);
                 mBuilder.append(encodedPhoneticGivenName);
                 mBuilder.append(VCARD_END_OF_LINE);
-            }
+            }  // if (!TextUtils.isEmpty(phoneticGivenName))
             if (!TextUtils.isEmpty(phoneticMiddleName)) {
                 final boolean reallyUseQuotedPrintable =
                     (mShouldUseQuotedPrintable &&
@@ -572,7 +630,7 @@
                 mBuilder.append(VCARD_DATA_SEPARATOR);
                 mBuilder.append(encodedPhoneticMiddleName);
                 mBuilder.append(VCARD_END_OF_LINE);
-            }
+            }  // if (!TextUtils.isEmpty(phoneticGivenName))
             if (!TextUtils.isEmpty(phoneticFamilyName)) {
                 final boolean reallyUseQuotedPrintable =
                     (mShouldUseQuotedPrintable &&
@@ -595,7 +653,7 @@
                 mBuilder.append(VCARD_DATA_SEPARATOR);
                 mBuilder.append(encodedPhoneticFamilyName);
                 mBuilder.append(VCARD_END_OF_LINE);
-            }
+            }  // if (!TextUtils.isEmpty(phoneticFamilyName))
         }
     }
 
@@ -642,22 +700,18 @@
                 if (TextUtils.isEmpty(phoneNumber)) {
                     continue;
                 }
-                int type = (typeAsObject != null ? typeAsObject : DEFAULT_PHONE_TYPE);
-                if (type == Phone.TYPE_PAGER) {
+
+                // PAGER number needs unformatted "phone number".
+                final int type = (typeAsObject != null ? typeAsObject : DEFAULT_PHONE_TYPE);
+                if (type == Phone.TYPE_PAGER ||
+                        VCardConfig.refrainPhoneNumberFormatting(mVCardType)) {
                     phoneLineExists = true;
                     if (!phoneSet.contains(phoneNumber)) {
                         phoneSet.add(phoneNumber);
                         appendTelLine(type, label, phoneNumber, isPrimary);
                     }
                 } else {
-                    // The entry "may" have several phone numbers when the contact entry is
-                    // corrupted because of its original source.
-                    //
-                    // e.g. I encountered the entry like the following.
-                    // "111-222-3333 (Miami)\n444-555-6666 (Broward; 305-653-6796 (Miami); ..."
-                    // This kind of entry is not able to be inserted via Android devices, but
-                    // possible if the source of the data is already corrupted.
-                    List<String> phoneNumberList = splitIfSeveralPhoneNumbersExist(phoneNumber);
+                    final List<String> phoneNumberList = splitAndTrimPhoneNumbers(phoneNumber);
                     if (phoneNumberList.isEmpty()) {
                         continue;
                     }
@@ -670,7 +724,7 @@
                             phoneSet.add(actualPhoneNumber);
                             appendTelLine(type, label, formattedPhoneNumber, isPrimary);
                         }
-                    }
+                    }  // for (String actualPhoneNumber : phoneNumberList) {
                 }
             }
         }
@@ -682,15 +736,38 @@
         return this;
     }
 
-    private List<String> splitIfSeveralPhoneNumbersExist(final String phoneNumber) {
-        List<String> phoneList = new ArrayList<String>();
+    /**
+     * <p>
+     * Splits a given string expressing phone numbers into several strings, and remove
+     * unnecessary characters inside them. The size of a returned list becomes 1 when
+     * no split is needed.
+     * </p>
+     * <p>
+     * The given number "may" have several phone numbers when the contact entry is corrupted
+     * because of its original source.
+     * e.g. "111-222-3333 (Miami)\n444-555-6666 (Broward; 305-653-6796 (Miami)"
+     * </p>
+     * <p>
+     * This kind of "phone numbers" will not be created with Android vCard implementation,
+     * but we may encounter them if the source of the input data has already corrupted
+     * implementation.
+     * </p>
+     * <p>
+     * To handle this case, this method first splits its input into multiple parts
+     * (e.g. "111-222-3333 (Miami)", "444-555-6666 (Broward", and 305653-6796 (Miami)") and
+     * removes unnecessary strings like "(Miami)".
+     * </p>
+     * <p>
+     * Do not call this method when trimming is inappropriate for its receivers.
+     * </p>
+     */
+    private List<String> splitAndTrimPhoneNumbers(final String phoneNumber) {
+        final List<String> phoneList = new ArrayList<String>();
 
         StringBuilder builder = new StringBuilder();
         final int length = phoneNumber.length();
         for (int i = 0; i < length; i++) {
             final char ch = phoneNumber.charAt(i);
-            // TODO: add a test case for string with '+', and care the other possible issues
-            // which may happen by ignoring non-digits other than '+'.
             if (Character.isDigit(ch) || ch == '+') {
                 builder.append(ch);
             } else if ((ch == ';' || ch == '\n') && builder.length() > 0) {
@@ -903,21 +980,21 @@
                 encodedCountry = escapeCharacters(rawCountry);
                 encodedNeighborhood = escapeCharacters(rawNeighborhood);
             }
-            final StringBuffer addressBuffer = new StringBuffer();
-            addressBuffer.append(encodedPoBox);
-            addressBuffer.append(VCARD_ITEM_SEPARATOR);
-            addressBuffer.append(VCARD_ITEM_SEPARATOR);
-            addressBuffer.append(encodedStreet);
-            addressBuffer.append(VCARD_ITEM_SEPARATOR);
-            addressBuffer.append(encodedLocality);
-            addressBuffer.append(VCARD_ITEM_SEPARATOR);
-            addressBuffer.append(encodedRegion);
-            addressBuffer.append(VCARD_ITEM_SEPARATOR);
-            addressBuffer.append(encodedPostalCode);
-            addressBuffer.append(VCARD_ITEM_SEPARATOR);
-            addressBuffer.append(encodedCountry);
+            final StringBuilder addressBuilder = new StringBuilder();
+            addressBuilder.append(encodedPoBox);
+            addressBuilder.append(VCARD_ITEM_SEPARATOR);  // PO BOX ; Extended Address
+            addressBuilder.append(VCARD_ITEM_SEPARATOR);  // Extended Address : Street
+            addressBuilder.append(encodedStreet);
+            addressBuilder.append(VCARD_ITEM_SEPARATOR);  // Street : Locality
+            addressBuilder.append(encodedLocality);
+            addressBuilder.append(VCARD_ITEM_SEPARATOR);  // Locality : Region
+            addressBuilder.append(encodedRegion);
+            addressBuilder.append(VCARD_ITEM_SEPARATOR);  // Region : Postal Code
+            addressBuilder.append(encodedPostalCode);
+            addressBuilder.append(VCARD_ITEM_SEPARATOR);  // Postal Code : Country
+            addressBuilder.append(encodedCountry);
             return new PostalStruct(
-                    reallyUseQuotedPrintable, appendCharset, addressBuffer.toString());
+                    reallyUseQuotedPrintable, appendCharset, addressBuilder.toString());
         } else {  // VCardUtils.areAllEmpty(rawAddressArray) == true
             // Try to use FORMATTED_ADDRESS instead.
             final String rawFormattedAddress =
@@ -940,16 +1017,16 @@
             // We use the second value ("Extended Address") just because Japanese mobile phones
             // do so. If the other importer expects the value be in the other field, some flag may
             // be needed.
-            final StringBuffer addressBuffer = new StringBuffer();
-            addressBuffer.append(VCARD_ITEM_SEPARATOR);
-            addressBuffer.append(encodedFormattedAddress);
-            addressBuffer.append(VCARD_ITEM_SEPARATOR);
-            addressBuffer.append(VCARD_ITEM_SEPARATOR);
-            addressBuffer.append(VCARD_ITEM_SEPARATOR);
-            addressBuffer.append(VCARD_ITEM_SEPARATOR);
-            addressBuffer.append(VCARD_ITEM_SEPARATOR);
+            final StringBuilder addressBuilder = new StringBuilder();
+            addressBuilder.append(VCARD_ITEM_SEPARATOR);  // PO BOX ; Extended Address
+            addressBuilder.append(encodedFormattedAddress);
+            addressBuilder.append(VCARD_ITEM_SEPARATOR);  // Extended Address : Street
+            addressBuilder.append(VCARD_ITEM_SEPARATOR);  // Street : Locality
+            addressBuilder.append(VCARD_ITEM_SEPARATOR);  // Locality : Region
+            addressBuilder.append(VCARD_ITEM_SEPARATOR);  // Region : Postal Code
+            addressBuilder.append(VCARD_ITEM_SEPARATOR);  // Postal Code : Country
             return new PostalStruct(
-                    reallyUseQuotedPrintable, appendCharset, addressBuffer.toString());
+                    reallyUseQuotedPrintable, appendCharset, addressBuilder.toString());
         }
     }
 
@@ -1146,6 +1223,8 @@
     }
 
     public VCardBuilder appendEvents(final List<ContentValues> contentValuesList) {
+        // There's possibility where a given object may have more than one birthday, which
+        // is inappropriate. We just build one birthday.
         if (contentValuesList != null) {
             String primaryBirthday = null;
             String secondaryBirthday = null;
@@ -1213,16 +1292,19 @@
         return this;
     }
 
+    /**
+     * @param emitEveryTime If true, builder builds the line even when there's no entry.
+     */
     public void appendPostalLine(final int type, final String label,
             final ContentValues contentValues,
-            final boolean isPrimary, final boolean emitLineEveryTime) {
+            final boolean isPrimary, final boolean emitEveryTime) {
         final boolean reallyUseQuotedPrintable;
         final boolean appendCharset;
         final String addressValue;
         {
             PostalStruct postalStruct = tryConstructPostalStruct(contentValues);
             if (postalStruct == null) {
-                if (emitLineEveryTime) {
+                if (emitEveryTime) {
                     reallyUseQuotedPrintable = false;
                     appendCharset = false;
                     addressValue = "";
@@ -1537,7 +1619,8 @@
         mBuilder.append(VCARD_END_OF_LINE);
     }
 
-    public void appendAndroidSpecificProperty(final String mimeType, ContentValues contentValues) {
+    public void appendAndroidSpecificProperty(
+            final String mimeType, ContentValues contentValues) {
         if (!sAllowedAndroidPropertySet.contains(mimeType)) {
             return;
         }
@@ -1659,7 +1742,7 @@
             encodedValue = encodeQuotedPrintable(rawValue);
         } else {
             // TODO: one line may be too huge, which may be invalid in vCard spec, though
-            //       several (even well-known) applications do not care this.
+            //       several (even well-known) applications do not care that violation.
             encodedValue = escapeCharacters(rawValue);
         }
 
@@ -1794,9 +1877,9 @@
         byte[] strArray = null;
 
         try {
-            strArray = str.getBytes(mCharsetString);
+            strArray = str.getBytes(mCharset);
         } catch (UnsupportedEncodingException e) {
-            Log.e(LOG_TAG, "Charset " + mCharsetString + " cannot be used. "
+            Log.e(LOG_TAG, "Charset " + mCharset + " cannot be used. "
                     + "Try default charset");
             strArray = str.getBytes();
         }
diff --git a/core/java/android/pim/vcard/VCardComposer.java b/core/java/android/pim/vcard/VCardComposer.java
index 0e8b665..170d6fa 100644
--- a/core/java/android/pim/vcard/VCardComposer.java
+++ b/core/java/android/pim/vcard/VCardComposer.java
@@ -41,6 +41,7 @@
 import android.provider.ContactsContract.CommonDataKinds.StructuredName;
 import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
 import android.provider.ContactsContract.CommonDataKinds.Website;
+import android.text.TextUtils;
 import android.util.CharsetUtils;
 import android.util.Log;
 
@@ -61,15 +62,11 @@
 
 /**
  * <p>
- * The class for composing VCard from Contacts information. Note that this is
- * completely differnt implementation from
- * android.syncml.pim.vcard.VCardComposer, which is not maintained anymore.
+ * The class for composing vCard from Contacts information.
  * </p>
- *
  * <p>
  * Usually, this class should be used like this.
  * </p>
- *
  * <pre class="prettyprint">VCardComposer composer = null;
  * try {
  *     composer = new VCardComposer(context);
@@ -93,15 +90,18 @@
  *     if (composer != null) {
  *         composer.terminate();
  *     }
- * } </pre>
+ * }</pre>
+ * <p>
+ * Users have to manually take care of memory efficiency. Even one vCard may contain
+ * image of non-trivial size for mobile devices.
+ * </p>
+ * <p>
+ * {@link VCardBuilder} is used to build each vCard.
+ * </p>
  */
 public class VCardComposer {
     private static final String LOG_TAG = "VCardComposer";
 
-    public static final int DEFAULT_PHONE_TYPE = Phone.TYPE_HOME;
-    public static final int DEFAULT_POSTAL_TYPE = StructuredPostal.TYPE_HOME;
-    public static final int DEFAULT_EMAIL_TYPE = Email.TYPE_OTHER;
-
     public static final String FAILURE_REASON_FAILED_TO_GET_DATABASE_INFO =
         "Failed to get database information";
 
@@ -119,6 +119,8 @@
 
     public static final String VCARD_TYPE_STRING_DOCOMO = "docomo";
 
+    // Strictly speaking, "Shift_JIS" is the most appropriate, but we use upper version here,
+    // since usual vCard devices for Japanese devices already use it.
     private static final String SHIFT_JIS = "SHIFT_JIS";
     private static final String UTF_8 = "UTF-8";
 
@@ -141,7 +143,7 @@
         sImMap.put(Im.PROTOCOL_ICQ, VCardConstants.PROPERTY_X_ICQ);
         sImMap.put(Im.PROTOCOL_JABBER, VCardConstants.PROPERTY_X_JABBER);
         sImMap.put(Im.PROTOCOL_SKYPE, VCardConstants.PROPERTY_X_SKYPE_USERNAME);
-        // Google talk is a special case.
+        // We don't add Google talk here since it has to be handled separately.
     }
 
     public static interface OneEntryHandler {
@@ -152,37 +154,37 @@
 
     /**
      * <p>
-     * An useful example handler, which emits VCard String to outputstream one by one.
+     * An useful handler for emitting vCard String to an OutputStream object one by one.
      * </p>
      * <p>
      * The input OutputStream object is closed() on {@link #onTerminate()}.
-     * Must not close the stream outside.
+     * Must not close the stream outside this class.
      * </p>
      */
-    public class HandlerForOutputStream implements OneEntryHandler {
+    public final class HandlerForOutputStream implements OneEntryHandler {
         @SuppressWarnings("hiding")
-        private static final String LOG_TAG = "vcard.VCardComposer.HandlerForOutputStream";
-
-        final private OutputStream mOutputStream; // mWriter will close this.
-        private Writer mWriter;
+        private static final String LOG_TAG = "VCardComposer.HandlerForOutputStream";
 
         private boolean mOnTerminateIsCalled = false;
 
+        private final OutputStream mOutputStream; // mWriter will close this.
+        private Writer mWriter;
+
         /**
          * Input stream will be closed on the detruction of this object.
          */
-        public HandlerForOutputStream(OutputStream outputStream) {
+        public HandlerForOutputStream(final OutputStream outputStream) {
             mOutputStream = outputStream;
         }
 
-        public boolean onInit(Context context) {
+        public boolean onInit(final Context context) {
             try {
                 mWriter = new BufferedWriter(new OutputStreamWriter(
-                        mOutputStream, mCharsetString));
+                        mOutputStream, mCharset));
             } catch (UnsupportedEncodingException e1) {
-                Log.e(LOG_TAG, "Unsupported charset: " + mCharsetString);
+                Log.e(LOG_TAG, "Unsupported charset: " + mCharset);
                 mErrorReason = "Encoding is not supported (usually this does not happen!): "
-                        + mCharsetString;
+                        + mCharset;
                 return false;
             }
 
@@ -235,14 +237,19 @@
                             "IOException during closing the output stream: "
                                     + e.getMessage());
                 } finally {
-                    try {
-                        mWriter.close();
-                    } catch (IOException e) {
-                    }
+                    closeOutputStream();
                 }
             }
         }
 
+        public void closeOutputStream() {
+            try {
+                mWriter.close();
+            } catch (IOException e) {
+                Log.w(LOG_TAG, "IOException is thrown during close(). Ignoring.");
+            }
+        }
+
         @Override
         public void finalize() {
             if (!mOnTerminateIsCalled) {
@@ -257,11 +264,10 @@
     private final ContentResolver mContentResolver;
 
     private final boolean mIsDoCoMo;
-    private final boolean mUsesShiftJis;
     private Cursor mCursor;
     private int mIdColumn;
 
-    private final String mCharsetString;
+    private final String mCharset;
     private boolean mTerminateIsCalled;
     private final List<OneEntryHandler> mHandlerList;
 
@@ -272,21 +278,39 @@
     };
 
     public VCardComposer(Context context) {
-        this(context, VCardConfig.VCARD_TYPE_DEFAULT, true);
+        this(context, VCardConfig.VCARD_TYPE_DEFAULT, null, true);
     }
 
+    /**
+     * The variant which sets charset to null and sets careHandlerErrors to true.
+     */
     public VCardComposer(Context context, int vcardType) {
-        this(context, vcardType, true);
+        this(context, vcardType, null, true);
     }
 
-    public VCardComposer(Context context, String vcardTypeStr, boolean careHandlerErrors) {
-        this(context, VCardConfig.getVCardTypeFromString(vcardTypeStr), careHandlerErrors);
+    public VCardComposer(Context context, int vcardType, String charset) {
+        this(context, vcardType, charset, true);
+    }
+
+    /**
+     * The variant which sets charset to null.
+     */
+    public VCardComposer(final Context context, final int vcardType,
+            final boolean careHandlerErrors) {
+        this(context, vcardType, null, careHandlerErrors);
     }
 
     /**
      * Construct for supporting call log entry vCard composing.
+     *
+     * @param context Context to be used during the composition.
+     * @param vcardType The type of vCard, typically available via {@link VCardConfig}.
+     * @param charset The charset to be used. Use null when you don't need the charset.
+     * @param careHandlerErrors If true, This object returns false everytime
+     * a Handler object given via {{@link #addHandler(OneEntryHandler)} returns false.
+     * If false, this ignores those errors.
      */
-    public VCardComposer(final Context context, final int vcardType,
+    public VCardComposer(final Context context, final int vcardType, String charset,
             final boolean careHandlerErrors) {
         mContext = context;
         mVCardType = vcardType;
@@ -294,30 +318,67 @@
         mContentResolver = context.getContentResolver();
 
         mIsDoCoMo = VCardConfig.isDoCoMo(vcardType);
-        mUsesShiftJis = VCardConfig.usesShiftJis(vcardType);
         mHandlerList = new ArrayList<OneEntryHandler>();
 
-        if (mIsDoCoMo) {
-            String charset;
-            try {
-                charset = CharsetUtils.charsetForVendor(SHIFT_JIS, "docomo").name();
-            } catch (UnsupportedCharsetException e) {
-                Log.e(LOG_TAG, "DoCoMo-specific SHIFT_JIS was not found. Use SHIFT_JIS as is.");
-                charset = SHIFT_JIS;
+        charset = (TextUtils.isEmpty(charset) ? VCardConfig.DEFAULT_EXPORT_CHARSET : charset);
+        final boolean shouldAppendCharsetParam = !(
+                VCardConfig.isV30(vcardType) && UTF_8.equalsIgnoreCase(charset));
+
+        if (mIsDoCoMo || shouldAppendCharsetParam) {
+            if (SHIFT_JIS.equalsIgnoreCase(charset)) {
+                if (mIsDoCoMo) {
+                    try {
+                        charset = CharsetUtils.charsetForVendor(SHIFT_JIS, "docomo").name();
+                    } catch (UnsupportedCharsetException e) {
+                        Log.e(LOG_TAG,
+                                "DoCoMo-specific SHIFT_JIS was not found. "
+                                + "Use SHIFT_JIS as is.");
+                        charset = SHIFT_JIS;
+                    }
+                } else {
+                    try {
+                        charset = CharsetUtils.charsetForVendor(SHIFT_JIS).name();
+                    } catch (UnsupportedCharsetException e) {
+                        Log.e(LOG_TAG,
+                                "Career-specific SHIFT_JIS was not found. "
+                                + "Use SHIFT_JIS as is.");
+                        charset = SHIFT_JIS;
+                    }
+                }
+                mCharset = charset;
+            } else {
+                Log.w(LOG_TAG,
+                        "The charset \"" + charset + "\" is used while "
+                        + SHIFT_JIS + " is needed to be used.");
+                if (TextUtils.isEmpty(charset)) {
+                    mCharset = SHIFT_JIS;
+                } else {
+                    try {
+                        charset = CharsetUtils.charsetForVendor(charset).name();
+                    } catch (UnsupportedCharsetException e) {
+                        Log.i(LOG_TAG,
+                                "Career-specific \"" + charset + "\" was not found (as usual). "
+                                + "Use it as is.");
+                    }
+                    mCharset = charset;
+                }
             }
-            mCharsetString = charset;
-        } else if (mUsesShiftJis) {
-            String charset;
-            try {
-                charset = CharsetUtils.charsetForVendor(SHIFT_JIS).name();
-            } catch (UnsupportedCharsetException e) {
-                Log.e(LOG_TAG, "Vendor-specific SHIFT_JIS was not found. Use SHIFT_JIS as is.");
-                charset = SHIFT_JIS;
-            }
-            mCharsetString = charset;
         } else {
-            mCharsetString = UTF_8;
+            if (TextUtils.isEmpty(charset)) {
+                mCharset = UTF_8;
+            } else {
+                try {
+                    charset = CharsetUtils.charsetForVendor(charset).name();
+                } catch (UnsupportedCharsetException e) {
+                    Log.i(LOG_TAG,
+                            "Career-specific \"" + charset + "\" was not found (as usual). "
+                            + "Use it as is.");
+                }
+                mCharset = charset;
+            }
         }
+
+        Log.d(LOG_TAG, "Use the charset \"" + mCharset + "\"");
     }
 
     /**
@@ -351,7 +412,7 @@
         }
 
         if (mCareHandlerErrors) {
-            List<OneEntryHandler> finishedList = new ArrayList<OneEntryHandler>(
+            final List<OneEntryHandler> finishedList = new ArrayList<OneEntryHandler>(
                     mHandlerList.size());
             for (OneEntryHandler handler : mHandlerList) {
                 if (!handler.onInit(mContext)) {
@@ -414,7 +475,7 @@
             mErrorReason = FAILURE_REASON_NOT_INITIALIZED;
             return false;
         }
-        String vcard;
+        final String vcard;
         try {
             if (mIdColumn >= 0) {
                 vcard = createOneEntryInternal(mCursor.getString(mIdColumn),
@@ -437,8 +498,7 @@
             mCursor.moveToNext();
         }
 
-        // This function does not care the OutOfMemoryError on the handler side
-        // :-P
+        // This function does not care the OutOfMemoryError on the handler side :-P
         if (mCareHandlerErrors) {
             List<OneEntryHandler> finishedList = new ArrayList<OneEntryHandler>(
                     mHandlerList.size());
@@ -457,7 +517,7 @@
     }
 
     private String createOneEntryInternal(final String contactId,
-            Method getEntityIteratorMethod) throws VCardException {
+            final Method getEntityIteratorMethod) throws VCardException {
         final Map<String, List<ContentValues>> contentValuesListMap =
                 new HashMap<String, List<ContentValues>>();
         // The resolver may return the entity iterator with no data. It is possible.
@@ -471,7 +531,7 @@
             final String selection = Data.CONTACT_ID + "=?";
             final String[] selectionArgs = new String[] {contactId};
             if (getEntityIteratorMethod != null) {
-                // Please note that this branch is executed by some tests only
+                // Please note that this branch is executed by unit tests only
                 try {
                     entityIterator = (EntityIterator)getEntityIteratorMethod.invoke(null,
                             mContentResolver, uri, selection, selectionArgs, null);
@@ -527,22 +587,33 @@
             }
         }
 
-        final VCardBuilder builder = new VCardBuilder(mVCardType);
-        builder.appendNameProperties(contentValuesListMap.get(StructuredName.CONTENT_ITEM_TYPE))
-                .appendNickNames(contentValuesListMap.get(Nickname.CONTENT_ITEM_TYPE))
-                .appendPhones(contentValuesListMap.get(Phone.CONTENT_ITEM_TYPE))
-                .appendEmails(contentValuesListMap.get(Email.CONTENT_ITEM_TYPE))
-                .appendPostals(contentValuesListMap.get(StructuredPostal.CONTENT_ITEM_TYPE))
-                .appendOrganizations(contentValuesListMap.get(Organization.CONTENT_ITEM_TYPE))
-                .appendWebsites(contentValuesListMap.get(Website.CONTENT_ITEM_TYPE));
-        if ((mVCardType & VCardConfig.FLAG_REFRAIN_IMAGE_EXPORT) == 0) {
-            builder.appendPhotos(contentValuesListMap.get(Photo.CONTENT_ITEM_TYPE));
+        return buildVCard(contentValuesListMap);
+    }
+
+    /**
+     * Builds and returns vCard using given map, whose key is CONTENT_ITEM_TYPE defined in
+     * {ContactsContract}. Developers can override this method to customize the output.
+     */
+    public String buildVCard(final Map<String, List<ContentValues>> contentValuesListMap) {
+        if (contentValuesListMap == null) {
+            Log.e(LOG_TAG, "The given map is null. Ignore and return empty String");
+            return "";
+        } else {
+            final VCardBuilder builder = new VCardBuilder(mVCardType, mCharset);
+            builder.appendNameProperties(contentValuesListMap.get(StructuredName.CONTENT_ITEM_TYPE))
+                    .appendNickNames(contentValuesListMap.get(Nickname.CONTENT_ITEM_TYPE))
+                    .appendPhones(contentValuesListMap.get(Phone.CONTENT_ITEM_TYPE))
+                    .appendEmails(contentValuesListMap.get(Email.CONTENT_ITEM_TYPE))
+                    .appendPostals(contentValuesListMap.get(StructuredPostal.CONTENT_ITEM_TYPE))
+                    .appendOrganizations(contentValuesListMap.get(Organization.CONTENT_ITEM_TYPE))
+                    .appendWebsites(contentValuesListMap.get(Website.CONTENT_ITEM_TYPE))
+                    .appendPhotos(contentValuesListMap.get(Photo.CONTENT_ITEM_TYPE))
+                    .appendNotes(contentValuesListMap.get(Note.CONTENT_ITEM_TYPE))
+                    .appendEvents(contentValuesListMap.get(Event.CONTENT_ITEM_TYPE))
+                    .appendIms(contentValuesListMap.get(Im.CONTENT_ITEM_TYPE))
+                    .appendRelation(contentValuesListMap.get(Relation.CONTENT_ITEM_TYPE));
+            return builder.toString();
         }
-        builder.appendNotes(contentValuesListMap.get(Note.CONTENT_ITEM_TYPE))
-                .appendEvents(contentValuesListMap.get(Event.CONTENT_ITEM_TYPE))
-                .appendIms(contentValuesListMap.get(Im.CONTENT_ITEM_TYPE))
-                .appendRelation(contentValuesListMap.get(Relation.CONTENT_ITEM_TYPE));
-        return builder.toString();
     }
 
     public void terminate() {
@@ -565,26 +636,38 @@
     @Override
     public void finalize() {
         if (!mTerminateIsCalled) {
+            Log.w(LOG_TAG, "terminate() is not called yet. We call it in finalize() step.");
             terminate();
         }
     }
 
+    /**
+     * @return returns the number of available entities. The return value is undefined
+     * when this object is not ready yet (typically when {{@link #init()} is not called
+     * or when {@link #terminate()} is already called).
+     */
     public int getCount() {
         if (mCursor == null) {
+            Log.w(LOG_TAG, "This object is not ready yet.");
             return 0;
         }
         return mCursor.getCount();
     }
 
+    /**
+     * @return true when there's no entity to be built. The return value is undefined
+     * when this object is not ready yet.
+     */
     public boolean isAfterLast() {
         if (mCursor == null) {
+            Log.w(LOG_TAG, "This object is not ready yet.");
             return false;
         }
         return mCursor.isAfterLast();
     }
 
     /**
-     * @return Return the error reason if possible.
+     * @return Returns the error reason.
      */
     public String getErrorReason() {
         return mErrorReason;
diff --git a/core/java/android/pim/vcard/VCardConfig.java b/core/java/android/pim/vcard/VCardConfig.java
index 3409be6..6c25216 100644
--- a/core/java/android/pim/vcard/VCardConfig.java
+++ b/core/java/android/pim/vcard/VCardConfig.java
@@ -15,6 +15,7 @@
  */
 package android.pim.vcard;
 
+import android.telephony.PhoneNumberUtils;
 import android.util.Log;
 
 import java.util.HashMap;
@@ -37,16 +38,32 @@
 
     /* package */ static final int LOG_LEVEL = LOG_LEVEL_NONE;
 
-    /* package */ static final int PARSE_TYPE_UNKNOWN = 0;
-    /* package */ static final int PARSE_TYPE_APPLE = 1;
-    /* package */ static final int PARSE_TYPE_MOBILE_PHONE_JP = 2;  // For Japanese mobile phones.
-    /* package */ static final int PARSE_TYPE_FOMA = 3;  // For Japanese FOMA mobile phones.
-    /* package */ static final int PARSE_TYPE_WINDOWS_MOBILE_JP = 4;
+    /**
+     * <p>
+     * The charset used during import.
+     * </p>
+     * <p>
+     * We cannot determine which charset should be used to interpret a given vCard file
+     * at first, while we have to decode sime encoded data (e.g. BASE64) to binary.
+     * In order to avoid "misinterpretation" of charset as much as possible,
+     * "ISO-8859-1" (a.k.a Latin-1) is first used for reading a stream.
+     * When charset is specified in a property (with "CHARSET=..." parameter),
+     * the string is decoded to raw bytes and encoded into the specific charset,
+     * assuming "ISO-8859-1" is able to map "all" 8bit characters to some unicode,
+     * and it has 1 to 1 mapping in all 8bit characters.
+     * If the assumption is not correct, this setting will cause some bug.
+     * </p>
+     * @hide made public just for unit test
+     */
+    public static final String DEFAULT_INTERMEDIATE_CHARSET = "ISO-8859-1";
 
-    // Assumes that "iso-8859-1" is able to map "all" 8bit characters to some unicode and
-    // decode the unicode to the original charset. If not, this setting will cause some bug. 
-    public static final String DEFAULT_CHARSET = "iso-8859-1";
-    
+    /**
+     * The charset used when there's no information affbout what charset should be used to
+     * encode the binary given from vCard.
+     */
+    public static final String DEFAULT_IMPORT_CHARSET = "UTF-8";
+    public static final String DEFAULT_EXPORT_CHARSET = "UTF-8";
+
     public static final int FLAG_V21 = 0;
     public static final int FLAG_V30 = 1;
 
@@ -58,311 +75,329 @@
     private static final int NAME_ORDER_MASK = 0xC;
 
     // 0x10 is reserved for safety
-    
-    private static final int FLAG_CHARSET_UTF8 = 0;
-    private static final int FLAG_CHARSET_SHIFT_JIS = 0x100;
-    private static final int FLAG_CHARSET_MASK = 0xF00;
 
     /**
+     * <p>
      * The flag indicating the vCard composer will add some "X-" properties used only in Android
      * when the formal vCard specification does not have appropriate fields for that data.
-     * 
+     * </p>
+     * <p>
      * For example, Android accepts nickname information while vCard 2.1 does not.
      * When this flag is on, vCard composer emits alternative "X-" property (like "X-NICKNAME")
      * instead of just dropping it.
-     * 
+     * </p>
+     * <p>
      * vCard parser code automatically parses the field emitted even when this flag is off.
-     * 
-     * Note that this flag does not assure all the information must be hold in the emitted vCard.
+     * </p>
      */
     private static final int FLAG_USE_ANDROID_PROPERTY = 0x80000000;
     
     /**
+     * <p>
      * The flag indicating the vCard composer will add some "X-" properties seen in the
      * vCard data emitted by the other softwares/devices when the formal vCard specification
-     * does not have appropriate field(s) for that data. 
-     * 
+     * does not have appropriate field(s) for that data.
+     * </p> 
+     * <p>
      * One example is X-PHONETIC-FIRST-NAME/X-PHONETIC-MIDDLE-NAME/X-PHONETIC-LAST-NAME, which are
      * for phonetic name (how the name is pronounced), seen in the vCard emitted by some other
      * non-Android devices/softwares. We chose to enable the vCard composer to use those
      * defact properties since they are also useful for Android devices.
-     * 
+     * </p>
+     * <p>
      * Note for developers: only "X-" properties should be added with this flag. vCard 2.1/3.0
      * allows any kind of "X-" properties but does not allow non-"X-" properties (except IANA tokens
      * in vCard 3.0). Some external parsers may get confused with non-valid, non-"X-" properties.
+     * </p>
      */
     private static final int FLAG_USE_DEFACT_PROPERTY = 0x40000000;
 
     /**
-     * The flag indicating some specific dialect seen in vcard of DoCoMo (one of Japanese
+     * <p>
+     * The flag indicating some specific dialect seen in vCard of DoCoMo (one of Japanese
      * mobile careers) should be used. This flag does not include any other information like
      * that "the vCard is for Japanese". So it is "possible" that "the vCard should have DoCoMo's
      * dialect but the name order should be European", but it is not recommended.
+     * </p>
      */
     private static final int FLAG_DOCOMO = 0x20000000;
 
     /**
-     * <P>
+     * <p>
      * The flag indicating the vCard composer does "NOT" use Quoted-Printable toward "primary"
      * properties even though it is required by vCard 2.1 (QP is prohibited in vCard 3.0).
-     * </P>
-     * <P>
+     * </p>
+     * <p>
      * We actually cannot define what is the "primary" property. Note that this is NOT defined
      * in vCard specification either. Also be aware that it is NOT related to "primary" notion
      * used in {@link android.provider.ContactsContract}.
      * This notion is just for vCard composition in Android.
-     * </P>
-     * <P>
+     * </p>
+     * <p>
      * We added this Android-specific notion since some (incomplete) vCard exporters for vCard 2.1
      * do NOT use Quoted-Printable encoding toward some properties related names like "N", "FN", etc.
      * even when their values contain non-ascii or/and CR/LF, while they use the encoding in the
      * other properties like "ADR", "ORG", etc.
-     * <P>
+     * <p>
      * We are afraid of the case where some vCard importer also forget handling QP presuming QP is
      * not used in such fields.
-     * </P>
-     * <P>
+     * </p>
+     * <p>
      * This flag is useful when some target importer you are going to focus on does not accept
      * such properties with Quoted-Printable encoding.
-     * </P>
-     * <P>
+     * </p>
+     * <p>
      * Again, we should not use this flag at all for complying vCard 2.1 spec.
-     * </P>
-     * <P>
+     * </p>
+     * <p>
      * In vCard 3.0, Quoted-Printable is explicitly "prohibitted", so we don't need to care this
      * kind of problem (hopefully).
-     * </P>
+     * </p>
+     * @hide
      */
     public static final int FLAG_REFRAIN_QP_TO_NAME_PROPERTIES = 0x10000000;
 
     /**
-     * <P>
+     * <p>
      * The flag indicating that phonetic name related fields must be converted to
      * appropriate form. Note that "appropriate" is not defined in any vCard specification.
      * This is Android-specific.
-     * </P>
-     * <P>
+     * </p>
+     * <p>
      * One typical (and currently sole) example where we need this flag is the time when
      * we need to emit Japanese phonetic names into vCard entries. The property values
      * should be encoded into half-width katakana when the target importer is Japanese mobile
      * phones', which are probably not able to parse full-width hiragana/katakana for
      * historical reasons, while the vCard importers embedded to softwares for PC should be
      * able to parse them as we expect.
-     * </P>
+     * </p>
      */
-    public static final int FLAG_CONVERT_PHONETIC_NAME_STRINGS = 0x0800000;
+    public static final int FLAG_CONVERT_PHONETIC_NAME_STRINGS = 0x08000000;
 
     /**
-     * <P>
+     * <p>
      * The flag indicating the vCard composer "for 2.1" emits "TYPE=" string toward TYPE params
      * every time possible. The default behavior does not emit it and is valid in the spec.
      * In vCrad 3.0, this flag is unnecessary, since "TYPE=" is MUST in vCard 3.0 specification.
-     * </P>
-     * <P>
+     * </p>
+     * <p>
      * Detail:
      * How more than one TYPE fields are expressed is different between vCard 2.1 and vCard 3.0.
      * </p>
-     * <P>
-     * e.g.<BR />
-     * 1) Probably valid in both vCard 2.1 and vCard 3.0: "ADR;TYPE=DOM;TYPE=HOME:..."<BR />
-     * 2) Valid in vCard 2.1 but not in vCard 3.0: "ADR;DOM;HOME:..."<BR />
-     * 3) Valid in vCard 3.0 but not in vCard 2.1: "ADR;TYPE=DOM,HOME:..."<BR />
-     * </P>
-     * <P>
-     * 2) had been the default of VCard exporter/importer in Android, but it is found that
-     * some external exporter is not able to parse the type format like 2) but only 3).
-     * </P>
-     * <P>
+     * <p>
+     * e.g.
+     * </p>
+     * <ol>
+     * <li>Probably valid in both vCard 2.1 and vCard 3.0: "ADR;TYPE=DOM;TYPE=HOME:..."</li>
+     * <li>Valid in vCard 2.1 but not in vCard 3.0: "ADR;DOM;HOME:..."</li>
+     * <li>Valid in vCard 3.0 but not in vCard 2.1: "ADR;TYPE=DOM,HOME:..."</li>
+     * </ol>
+     * <p>
      * If you are targeting to the importer which cannot accept TYPE params without "TYPE="
      * strings (which should be rare though), please use this flag.
-     * </P>
-     * <P>
-     * Example usage: int vcardType = (VCARD_TYPE_V21_GENERIC | FLAG_APPEND_TYPE_PARAM);
-     * </P>
+     * </p>
+     * <p>
+     * Example usage:
+     * <pre class="prettyprint">int type = (VCARD_TYPE_V21_GENERIC | FLAG_APPEND_TYPE_PARAM);</pre>
+     * </p>
      */
     public static final int FLAG_APPEND_TYPE_PARAM = 0x04000000;
 
     /**
-     * <P>
-     * The flag asking exporter to refrain image export.
-     * </P>
-     * @hide will be deleted in the near future.
+     * <p>
+     * The flag indicating the vCard composer does touch nothing toward phone number Strings
+     * but leave it as is.
+     * </p>
+     * <p>
+     * The vCard specifications mention nothing toward phone numbers, while some devices
+     * do (wrongly, but with innevitable reasons).
+     * For example, there's a possibility Japanese mobile phones are expected to have
+     * just numbers, hypens, plus, etc. but not usual alphabets, while US mobile phones
+     * should get such characters. To make exported vCard simple for external parsers,
+     * we have used {@link PhoneNumberUtils#formatNumber(String)} during export, and
+     * removed unnecessary characters inside the number (e.g. "111-222-3333 (Miami)"
+     * becomes "111-222-3333").
+     * Unfortunate side effect of that use was some control characters used in the other
+     * areas may be badly affected by the formatting.
+     * </p>
+     * <p>
+     * This flag disables that formatting, affecting both importer and exporter.
+     * If the user is aware of some side effects due to the implicit formatting, use this flag.
+     * </p>
      */
-    public static final int FLAG_REFRAIN_IMAGE_EXPORT = 0x02000000;
+    public static final int FLAG_REFRAIN_PHONE_NUMBER_FORMATTING = 0x02000000;
+
+    /**
+     * <p>
+     * For importer only. Ignored in exporter.
+     * </p>
+     * <p>
+     * The flag indicating the parser should handle a nested vCard, in which vCard clause starts
+     * in another vCard clause. Here's a typical example.
+     * </p>
+     * <pre class="prettyprint">BEGIN:VCARD
+     * BEGIN:VCARD
+     * VERSION:2.1
+     * ...
+     * END:VCARD
+     * END:VCARD</pre>
+     * <p>
+     * The vCard 2.1 specification allows the nest, but also let parsers ignore nested entries,
+     * while some mobile devices emit nested ones as primary data to be imported.
+     * </p>
+     * <p>
+     * This flag forces a vCard parser to torelate such a nest and understand its content.
+     * </p>
+     */
+    public static final int FLAG_TORELATE_NEST = 0x01000000;
 
     //// The followings are VCard types available from importer/exporter. ////
 
     /**
-     * <P>
-     * Generic vCard format with the vCard 2.1. Uses UTF-8 for the charset.
-     * When composing a vCard entry, the US convension will be used toward formatting
-     * some values.
-     * </P>
-     * <P>
+     * <p>
+     * The type indicating nothing. Used by {@link VCardSourceDetector} when it
+     * was not able to guess the exact vCard type.
+     * </p>
+     */
+    public static final int VCARD_TYPE_UNKNOWN = 0;
+
+    /**
+     * <p>
+     * Generic vCard format with the vCard 2.1. When composing a vCard entry,
+     * the US convension will be used toward formatting some values.
+     * </p>
+     * <p>
      * e.g. The order of the display name would be "Prefix Given Middle Family Suffix",
      * while it should be "Prefix Family Middle Given Suffix" in Japan for example.
-     * </P>
+     * </p>
+     * <p>
+     * Uses UTF-8 for the charset as a charset for exporting. Note that old vCard importer
+     * outside Android cannot accept it since vCard 2.1 specifically does not allow
+     * that charset, while we need to use it to support various languages around the world.
+     * </p>
+     * <p>
+     * If you want to use alternative charset, you should notify the charset to the other
+     * compontent to be used.
+     * </p>
      */
-    public static final int VCARD_TYPE_V21_GENERIC_UTF8 =
-        (FLAG_V21 | NAME_ORDER_DEFAULT | FLAG_CHARSET_UTF8 |
-                FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
+    public static final int VCARD_TYPE_V21_GENERIC =
+        (FLAG_V21 | NAME_ORDER_DEFAULT | FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
 
-    /* package */ static String VCARD_TYPE_V21_GENERIC_UTF8_STR = "v21_generic";
+    /* package */ static String VCARD_TYPE_V21_GENERIC_STR = "v21_generic";
     
     /**
-     * <P>
+     * <p>
      * General vCard format with the version 3.0. Uses UTF-8 for the charset.
-     * </P>
-     * <P>
+     * </p>
+     * <p>
      * Not fully ready yet. Use with caution when you use this.
-     * </P>
+     * </p>
      */
-    public static final int VCARD_TYPE_V30_GENERIC_UTF8 =
-        (FLAG_V30 | NAME_ORDER_DEFAULT | FLAG_CHARSET_UTF8 |
-                FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
+    public static final int VCARD_TYPE_V30_GENERIC =
+        (FLAG_V30 | NAME_ORDER_DEFAULT | FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
 
-    /* package */ static final String VCARD_TYPE_V30_GENERIC_UTF8_STR = "v30_generic";
+    /* package */ static final String VCARD_TYPE_V30_GENERIC_STR = "v30_generic";
     
     /**
-     * <P>
+     * <p>
      * General vCard format for the vCard 2.1 with some Europe convension. Uses Utf-8.
      * Currently, only name order is considered ("Prefix Middle Given Family Suffix")
-     * </P>
+     * </p>
      */
-    public static final int VCARD_TYPE_V21_EUROPE_UTF8 =
-        (FLAG_V21 | NAME_ORDER_EUROPE | FLAG_CHARSET_UTF8 |
-                FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
-    
-    /* package */ static final String VCARD_TYPE_V21_EUROPE_UTF8_STR = "v21_europe";
+    public static final int VCARD_TYPE_V21_EUROPE =
+        (FLAG_V21 | NAME_ORDER_EUROPE | FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
+
+    /* package */ static final String VCARD_TYPE_V21_EUROPE_STR = "v21_europe";
     
     /**
-     * <P>
+     * <p>
      * General vCard format with the version 3.0 with some Europe convension. Uses UTF-8.
-     * </P>
-     * <P>
+     * </p>
+     * <p>
      * Not ready yet. Use with caution when you use this.
-     * </P>
+     * </p>
      */
-    public static final int VCARD_TYPE_V30_EUROPE_UTF8 =
-        (FLAG_V30 | NAME_ORDER_EUROPE | FLAG_CHARSET_UTF8 |
-                FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
+    public static final int VCARD_TYPE_V30_EUROPE =
+        (FLAG_V30 | NAME_ORDER_EUROPE | FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
     
     /* package */ static final String VCARD_TYPE_V30_EUROPE_STR = "v30_europe";
 
     /**
-     * <P>
+     * <p>
      * The vCard 2.1 format for miscellaneous Japanese devices, using UTF-8 as default charset.
-     * </P>
-     * <P>
+     * </p>
+     * <p>
      * Not ready yet. Use with caution when you use this.
-     * </P>
+     * </p>
      */
-    public static final int VCARD_TYPE_V21_JAPANESE_UTF8 =
-        (FLAG_V21 | NAME_ORDER_JAPANESE | FLAG_CHARSET_UTF8 |
-                FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
+    public static final int VCARD_TYPE_V21_JAPANESE =
+        (FLAG_V21 | NAME_ORDER_JAPANESE | FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
 
-    /* package */ static final String VCARD_TYPE_V21_JAPANESE_UTF8_STR = "v21_japanese_utf8";
+    /* package */ static final String VCARD_TYPE_V21_JAPANESE_STR = "v21_japanese_utf8";
 
     /**
-     * <P>
-     * vCard 2.1 format for miscellaneous Japanese devices. Shift_Jis is used for
-     * parsing/composing the vCard data.
-     * </P>
-     * <P>
-     * Not ready yet. Use with caution when you use this.
-     * </P>
-     */
-    public static final int VCARD_TYPE_V21_JAPANESE_SJIS =
-        (FLAG_V21 | NAME_ORDER_JAPANESE | FLAG_CHARSET_SHIFT_JIS |
-                FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
-
-    /* package */ static final String VCARD_TYPE_V21_JAPANESE_SJIS_STR = "v21_japanese_sjis";
-    
-    /**
-     * <P>
-     * vCard format for miscellaneous Japanese devices, using Shift_Jis for
-     * parsing/composing the vCard data.
-     * </P>
-     * <P>
-     * Not ready yet. Use with caution when you use this.
-     * </P>
-     */
-    public static final int VCARD_TYPE_V30_JAPANESE_SJIS =
-        (FLAG_V30 | NAME_ORDER_JAPANESE | FLAG_CHARSET_SHIFT_JIS |
-                FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
-        
-    /* package */ static final String VCARD_TYPE_V30_JAPANESE_SJIS_STR = "v30_japanese_sjis";
-    
-    /**
-     * <P>
+     * <p>
      * The vCard 3.0 format for miscellaneous Japanese devices, using UTF-8 as default charset.
-     * </P>
-     * <P>
+     * </p>
+     * <p>
      * Not ready yet. Use with caution when you use this.
-     * </P>
+     * </p>
      */
-    public static final int VCARD_TYPE_V30_JAPANESE_UTF8 =
-        (FLAG_V30 | NAME_ORDER_JAPANESE | FLAG_CHARSET_UTF8 |
-                FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
+    public static final int VCARD_TYPE_V30_JAPANESE =
+        (FLAG_V30 | NAME_ORDER_JAPANESE | FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
 
-    /* package */ static final String VCARD_TYPE_V30_JAPANESE_UTF8_STR = "v30_japanese_utf8";
+    /* package */ static final String VCARD_TYPE_V30_JAPANESE_STR = "v30_japanese_utf8";
 
     /**
-     * <P>
+     * <p>
      * The vCard 2.1 based format which (partially) considers the convention in Japanese
      * mobile phones, where phonetic names are translated to half-width katakana if
-     * possible, etc.
-     * </P>
-     * <P>
-     * Not ready yet. Use with caution when you use this.
-     * </P>
+     * possible, etc. It would be better to use Shift_JIS as a charset for maximum
+     * compatibility.
+     * </p>
+     * @hide Should not be available world wide.
      */
     public static final int VCARD_TYPE_V21_JAPANESE_MOBILE =
-        (FLAG_V21 | NAME_ORDER_JAPANESE | FLAG_CHARSET_SHIFT_JIS |
-                FLAG_CONVERT_PHONETIC_NAME_STRINGS |
-                FLAG_REFRAIN_QP_TO_NAME_PROPERTIES);
+        (FLAG_V21 | NAME_ORDER_JAPANESE |
+                FLAG_CONVERT_PHONETIC_NAME_STRINGS | FLAG_REFRAIN_QP_TO_NAME_PROPERTIES);
 
     /* package */ static final String VCARD_TYPE_V21_JAPANESE_MOBILE_STR = "v21_japanese_mobile";
 
     /**
-     * <P>
-     * VCard format used in DoCoMo, which is one of Japanese mobile phone careers.
+     * <p>
+     * The vCard format used in DoCoMo, which is one of Japanese mobile phone careers.
      * </p>
-     * <P>
+     * <p>
      * Base version is vCard 2.1, but the data has several DoCoMo-specific convensions.
      * No Android-specific property nor defact property is included. The "Primary" properties
      * are NOT encoded to Quoted-Printable.
-     * </P>
+     * </p>
+     * @hide Should not be available world wide.
      */
     public static final int VCARD_TYPE_DOCOMO =
         (VCARD_TYPE_V21_JAPANESE_MOBILE | FLAG_DOCOMO);
 
     /* package */ static final String VCARD_TYPE_DOCOMO_STR = "docomo";
 
-    public static int VCARD_TYPE_DEFAULT = VCARD_TYPE_V21_GENERIC_UTF8;
+    public static int VCARD_TYPE_DEFAULT = VCARD_TYPE_V21_GENERIC;
 
     private static final Map<String, Integer> sVCardTypeMap;
     private static final Set<Integer> sJapaneseMobileTypeSet;
     
     static {
         sVCardTypeMap = new HashMap<String, Integer>();
-        sVCardTypeMap.put(VCARD_TYPE_V21_GENERIC_UTF8_STR, VCARD_TYPE_V21_GENERIC_UTF8);
-        sVCardTypeMap.put(VCARD_TYPE_V30_GENERIC_UTF8_STR, VCARD_TYPE_V30_GENERIC_UTF8);
-        sVCardTypeMap.put(VCARD_TYPE_V21_EUROPE_UTF8_STR, VCARD_TYPE_V21_EUROPE_UTF8);
-        sVCardTypeMap.put(VCARD_TYPE_V30_EUROPE_STR, VCARD_TYPE_V30_EUROPE_UTF8);
-        sVCardTypeMap.put(VCARD_TYPE_V21_JAPANESE_SJIS_STR, VCARD_TYPE_V21_JAPANESE_SJIS);
-        sVCardTypeMap.put(VCARD_TYPE_V21_JAPANESE_UTF8_STR, VCARD_TYPE_V21_JAPANESE_UTF8);
-        sVCardTypeMap.put(VCARD_TYPE_V30_JAPANESE_SJIS_STR, VCARD_TYPE_V30_JAPANESE_SJIS);
-        sVCardTypeMap.put(VCARD_TYPE_V30_JAPANESE_UTF8_STR, VCARD_TYPE_V30_JAPANESE_UTF8);
+        sVCardTypeMap.put(VCARD_TYPE_V21_GENERIC_STR, VCARD_TYPE_V21_GENERIC);
+        sVCardTypeMap.put(VCARD_TYPE_V30_GENERIC_STR, VCARD_TYPE_V30_GENERIC);
+        sVCardTypeMap.put(VCARD_TYPE_V21_EUROPE_STR, VCARD_TYPE_V21_EUROPE);
+        sVCardTypeMap.put(VCARD_TYPE_V30_EUROPE_STR, VCARD_TYPE_V30_EUROPE);
+        sVCardTypeMap.put(VCARD_TYPE_V21_JAPANESE_STR, VCARD_TYPE_V21_JAPANESE);
+        sVCardTypeMap.put(VCARD_TYPE_V30_JAPANESE_STR, VCARD_TYPE_V30_JAPANESE);
         sVCardTypeMap.put(VCARD_TYPE_V21_JAPANESE_MOBILE_STR, VCARD_TYPE_V21_JAPANESE_MOBILE);
         sVCardTypeMap.put(VCARD_TYPE_DOCOMO_STR, VCARD_TYPE_DOCOMO);
 
         sJapaneseMobileTypeSet = new HashSet<Integer>();
-        sJapaneseMobileTypeSet.add(VCARD_TYPE_V21_JAPANESE_SJIS);
-        sJapaneseMobileTypeSet.add(VCARD_TYPE_V21_JAPANESE_UTF8);
-        sJapaneseMobileTypeSet.add(VCARD_TYPE_V21_JAPANESE_SJIS);
-        sJapaneseMobileTypeSet.add(VCARD_TYPE_V30_JAPANESE_SJIS);
-        sJapaneseMobileTypeSet.add(VCARD_TYPE_V30_JAPANESE_UTF8);
+        sJapaneseMobileTypeSet.add(VCARD_TYPE_V21_JAPANESE);
+        sJapaneseMobileTypeSet.add(VCARD_TYPE_V30_JAPANESE);
         sJapaneseMobileTypeSet.add(VCARD_TYPE_V21_JAPANESE_MOBILE);
         sJapaneseMobileTypeSet.add(VCARD_TYPE_DOCOMO);
     }
@@ -387,14 +422,6 @@
         return !isV30(vcardType);
     }
 
-    public static boolean usesUtf8(final int vcardType) {
-        return ((vcardType & FLAG_CHARSET_MASK) == FLAG_CHARSET_UTF8);
-    }
-
-    public static boolean usesShiftJis(final int vcardType) {
-        return ((vcardType & FLAG_CHARSET_MASK) == FLAG_CHARSET_SHIFT_JIS);
-    }
-
     public static int getNameOrderType(final int vcardType) {
         return vcardType & NAME_ORDER_MASK;
     }
@@ -431,6 +458,10 @@
         return sJapaneseMobileTypeSet.contains(vcardType);
     }
 
+    /* package */ static boolean refrainPhoneNumberFormatting(final int vcardType) {
+        return ((vcardType & FLAG_REFRAIN_PHONE_NUMBER_FORMATTING) != 0);
+    }
+
     public static boolean needsToConvertPhoneticString(final int vcardType) {
         return ((vcardType & FLAG_CONVERT_PHONETIC_NAME_STRINGS) != 0);
     }
diff --git a/core/java/android/pim/vcard/VCardConstants.java b/core/java/android/pim/vcard/VCardConstants.java
index 8c07126..e11b1fd 100644
--- a/core/java/android/pim/vcard/VCardConstants.java
+++ b/core/java/android/pim/vcard/VCardConstants.java
@@ -109,6 +109,12 @@
     public static final String PARAM_TYPE_BBS = "BBS";
     public static final String PARAM_TYPE_VIDEO = "VIDEO";
 
+    public static final String PARAM_ENCODING_7BIT = "7BIT";
+    public static final String PARAM_ENCODING_8BIT = "8BIT";
+    public static final String PARAM_ENCODING_QP = "QUOTED-PRINTABLE";
+    public static final String PARAM_ENCODING_BASE64 = "BASE64";  // Available in vCard 2.1
+    public static final String PARAM_ENCODING_B = "B";  // Available in vCard 3.0
+
     // TYPE parameters for Phones, which are not formally valid in vCard (at least 2.1).
     // These types are basically encoded to "X-" parameters when composing vCard.
     // Parser passes these when "X-" is added to the parameter or not.
@@ -130,10 +136,6 @@
     // Do not use in composer side.
     public static final String PARAM_EXTRA_TYPE_COMPANY = "COMPANY";
 
-    // DoCoMo specific type parameter. Used with "SOUND" property, which is alternate of SORT-STRING in
-    // vCard 3.0.
-    public static final String PARAM_TYPE_X_IRMC_N = "X-IRMC-N";
-
     public interface ImportOnly {
         public static final String PROPERTY_X_NICKNAME = "X-NICKNAME";
         // Some device emits this "X-" parameter for expressing Google Talk,
@@ -142,6 +144,12 @@
         public static final String PROPERTY_X_GOOGLE_TALK_WITH_SPACE = "X-GOOGLE TALK";
     }
 
+    //// Mainly for package constants.
+
+    // DoCoMo specific type parameter. Used with "SOUND" property, which is alternate of
+    // SORT-STRING invCard 3.0.
+    /* package */ static final String PARAM_TYPE_X_IRMC_N = "X-IRMC-N";
+
     /* package */ static final int MAX_DATA_COLUMN = 15;
 
     /* package */ static final int MAX_CHARACTER_NUMS_QP = 76;
diff --git a/core/java/android/pim/vcard/VCardEntry.java b/core/java/android/pim/vcard/VCardEntry.java
index 1327770..5b9cf17 100644
--- a/core/java/android/pim/vcard/VCardEntry.java
+++ b/core/java/android/pim/vcard/VCardEntry.java
@@ -61,9 +61,6 @@
 
     private final static int DEFAULT_ORGANIZATION_TYPE = Organization.TYPE_WORK;
 
-    private static final String ACCOUNT_TYPE_GOOGLE = "com.google";
-    private static final String GOOGLE_MY_CONTACTS_GROUP = "System Group: My Contacts";
-
     private static final Map<String, Integer> sImMap = new HashMap<String, Integer>();
 
     static {
@@ -78,12 +75,12 @@
                 Im.PROTOCOL_GOOGLE_TALK);
     }
 
-    static public class PhoneData {
+    public static class PhoneData {
         public final int type;
         public final String data;
         public final String label;
-        // isPrimary is changable only when there's no appropriate one existing in
-        // the original VCard.
+        // isPrimary is (not final but) changable, only when there's no appropriate one existing
+        // in the original VCard.
         public boolean isPrimary;
         public PhoneData(int type, String data, String label, boolean isPrimary) {
             this.type = type;
@@ -109,13 +106,11 @@
         }
     }
 
-    static public class EmailData {
+    public static class EmailData {
         public final int type;
         public final String data;
         // Used only when TYPE is TYPE_CUSTOM.
         public final String label;
-        // isPrimary is changable only when there's no appropriate one existing in
-        // the original VCard.
         public boolean isPrimary;
         public EmailData(int type, String data, String label, boolean isPrimary) {
             this.type = type;
@@ -141,9 +136,9 @@
         }
     }
 
-    static public class PostalData {
-        // Determined by vCard spec.
-        // PO Box, Extended Addr, Street, Locality, Region, Postal Code, Country Name
+    public static class PostalData {
+        // Determined by vCard specification.
+        // - PO Box, Extended Addr, Street, Locality, Region, Postal Code, Country Name
         public static final int ADDR_MAX_DATA_SIZE = 7;
         private final String[] dataArray;
         public final String pobox;
@@ -248,10 +243,11 @@
         }
     }
 
-    static public class OrganizationData {
+    public static class OrganizationData {
         public final int type;
         // non-final is Intentional: we may change the values since this info is separated into
-        // two parts in vCard: "ORG" + "TITLE".
+        // two parts in vCard: "ORG" + "TITLE", and we have to cope with each field in
+        // different timing.
         public String companyName;
         public String departmentName;
         public String titleName;
@@ -313,7 +309,7 @@
         }
     }
 
-    static public class ImData {
+    public static class ImData {
         public final int protocol;
         public final String customProtocol;
         public final int type;
@@ -441,7 +437,7 @@
     private String mSuffix;
 
     // Used only when no family nor given name is found.
-    private String mFullName;
+    private String mFormattedName;
 
     private String mPhoneticFamilyName;
     private String mPhoneticGivenName;
@@ -469,7 +465,7 @@
     private final Account mAccount;
 
     public VCardEntry() {
-        this(VCardConfig.VCARD_TYPE_V21_GENERIC_UTF8);
+        this(VCardConfig.VCARD_TYPE_V21_GENERIC);
     }
 
     public VCardEntry(int vcardType) {
@@ -488,7 +484,7 @@
         final StringBuilder builder = new StringBuilder();
         final String trimed = data.trim();
         final String formattedNumber;
-        if (type == Phone.TYPE_PAGER) {
+        if (type == Phone.TYPE_PAGER || VCardConfig.refrainPhoneNumberFormatting(mVCardType)) {
             formattedNumber = trimed;
         } else {
             final int length = trimed.length();
@@ -499,9 +495,7 @@
                 }
             }
 
-            // Use NANP in default when there's no information about locale.
-            final int formattingType = (VCardConfig.isJapaneseDevice(mVCardType) ?
-                    PhoneNumberUtils.FORMAT_JAPAN : PhoneNumberUtils.FORMAT_NANP);
+            final int formattingType = VCardUtils.getPhoneNumberFormat(mVCardType);
             formattedNumber = PhoneNumberUtils.formatNumber(builder.toString(), formattingType);
         }
         PhoneData phoneData = new PhoneData(type, formattedNumber, label, isPrimary);
@@ -755,11 +749,11 @@
         if (propName.equals(VCardConstants.PROPERTY_VERSION)) {
             // vCard version. Ignore this.
         } else if (propName.equals(VCardConstants.PROPERTY_FN)) {
-            mFullName = propValue;
-        } else if (propName.equals(VCardConstants.PROPERTY_NAME) && mFullName == null) {
+            mFormattedName = propValue;
+        } else if (propName.equals(VCardConstants.PROPERTY_NAME) && mFormattedName == null) {
             // Only in vCard 3.0. Use this if FN, which must exist in vCard 3.0 but may not
             // actually exist in the real vCard data, does not exist.
-            mFullName = propValue;
+            mFormattedName = propValue;
         } else if (propName.equals(VCardConstants.PROPERTY_N)) {
             handleNProperty(propValueList);
         } else if (propName.equals(VCardConstants.PROPERTY_SORT_STRING)) {
@@ -1017,8 +1011,8 @@
      */
     private void constructDisplayName() {
         // FullName (created via "FN" or "NAME" field) is prefered.
-        if (!TextUtils.isEmpty(mFullName)) {
-            mDisplayName = mFullName;
+        if (!TextUtils.isEmpty(mFormattedName)) {
+            mDisplayName = mFormattedName;
         } else if (!(TextUtils.isEmpty(mFamilyName) && TextUtils.isEmpty(mGivenName))) {
             mDisplayName = VCardUtils.constructNameFromElements(mVCardType,
                     mFamilyName, mMiddleName, mGivenName, mPrefix, mSuffix);
@@ -1063,23 +1057,6 @@
         if (mAccount != null) {
             builder.withValue(RawContacts.ACCOUNT_NAME, mAccount.name);
             builder.withValue(RawContacts.ACCOUNT_TYPE, mAccount.type);
-
-            // Assume that caller side creates this group if it does not exist.
-            if (ACCOUNT_TYPE_GOOGLE.equals(mAccount.type)) {
-                final Cursor cursor = resolver.query(Groups.CONTENT_URI, new String[] {
-                        Groups.SOURCE_ID },
-                        Groups.TITLE + "=?", new String[] {
-                        GOOGLE_MY_CONTACTS_GROUP }, null);
-                try {
-                    if (cursor != null && cursor.moveToFirst()) {
-                        myGroupsId = cursor.getString(0);
-                    }
-                } finally {
-                    if (cursor != null) {
-                        cursor.close();
-                    }
-                }
-            }
         } else {
             builder.withValue(RawContacts.ACCOUNT_NAME, null);
             builder.withValue(RawContacts.ACCOUNT_TYPE, null);
@@ -1321,7 +1298,7 @@
                 && TextUtils.isEmpty(mGivenName)
                 && TextUtils.isEmpty(mPrefix)
                 && TextUtils.isEmpty(mSuffix)
-                && TextUtils.isEmpty(mFullName)
+                && TextUtils.isEmpty(mFormattedName)
                 && TextUtils.isEmpty(mPhoneticFamilyName)
                 && TextUtils.isEmpty(mPhoneticMiddleName)
                 && TextUtils.isEmpty(mPhoneticGivenName)
@@ -1380,7 +1357,7 @@
     }
 
     public String getFullName() {
-        return mFullName;
+        return mFormattedName;
     }
 
     public String getPhoneticFamilyName() {
diff --git a/core/java/android/pim/vcard/VCardEntryCommitter.java b/core/java/android/pim/vcard/VCardEntryCommitter.java
index 59a2baf..a8c8057 100644
--- a/core/java/android/pim/vcard/VCardEntryCommitter.java
+++ b/core/java/android/pim/vcard/VCardEntryCommitter.java
@@ -52,9 +52,9 @@
         }
     }
 
-    public void onEntryCreated(final VCardEntry contactStruct) {
+    public void onEntryCreated(final VCardEntry vcardEntry) {
         long start = System.currentTimeMillis();
-        mCreatedUris.add(contactStruct.pushIntoContentResolver(mContentResolver));
+        mCreatedUris.add(vcardEntry.pushIntoContentResolver(mContentResolver));
         mTimeToCommit += System.currentTimeMillis() - start;
     }
 
diff --git a/core/java/android/pim/vcard/VCardEntryConstructor.java b/core/java/android/pim/vcard/VCardEntryConstructor.java
index 290ca2b..a0abae8 100644
--- a/core/java/android/pim/vcard/VCardEntryConstructor.java
+++ b/core/java/android/pim/vcard/VCardEntryConstructor.java
@@ -16,12 +16,11 @@
 package android.pim.vcard;
 
 import android.accounts.Account;
+import android.text.TextUtils;
 import android.util.CharsetUtils;
 import android.util.Log;
 
-import org.apache.commons.codec.DecoderException;
 import org.apache.commons.codec.binary.Base64;
-import org.apache.commons.codec.net.QuotedPrintableCodec;
 
 import java.io.UnsupportedEncodingException;
 import java.nio.ByteBuffer;
@@ -30,64 +29,73 @@
 import java.util.Collection;
 import java.util.List;
 
+/**
+ * <p>
+ * The {@link VCardInterpreter} implementation which enables {@link VCardEntryHandler} objects
+ * to easily handle each vCard entry.
+ * </p>
+ * <p>
+ * This class understand details inside vCard and translates it to {@link VCardEntry}.
+ * Then the class throw it to {@link VCardEntryHandler} registered via
+ * {@link #addEntryHandler(VCardEntryHandler)}, so that all those registered objects
+ * are able to handle the {@link VCardEntry} object.
+ * </p>
+ * <p>
+ * If you want to know the detail inside vCard, it would be better to implement
+ * {@link VCardInterpreter} directly, instead of relying on this class and
+ * {@link VCardEntry} created by the object.
+ * </p>
+ */
 public class VCardEntryConstructor implements VCardInterpreter {
     private static String LOG_TAG = "VCardEntryConstructor";
 
-    /**
-     * If there's no other information available, this class uses this charset for encoding
-     * byte arrays to String.
-     */
-    /* package */ static final String DEFAULT_CHARSET_FOR_DECODED_BYTES = "UTF-8";
-
     private VCardEntry.Property mCurrentProperty = new VCardEntry.Property();
-    private VCardEntry mCurrentContactStruct;
+    private VCardEntry mCurrentVCardEntry;
     private String mParamType;
     
-    /**
-     * The charset using which {@link VCardInterpreter} parses the text.
-     */
-    private String mInputCharset;
+    // The charset using which {@link VCardInterpreter} parses the text.
+    // Each String is first decoded into binary stream with this charset, and encoded back
+    // to "target charset", which may be explicitly specified by the vCard with "CHARSET"
+    // property or implicitly mentioned by its version (e.g. vCard 3.0 recommends UTF-8).
+    private final String mSourceCharset;
 
-    /**
-     * The charset with which byte array is encoded to String.
-     */
-    final private String mCharsetForDecodedBytes;
-    final private boolean mStrictLineBreakParsing;
-    final private int mVCardType;
-    final private Account mAccount;
+    private final boolean mStrictLineBreaking;
+    private final int mVCardType;
+    private final Account mAccount;
     
-    /** For measuring performance. */
+    // For measuring performance.
     private long mTimePushIntoContentResolver;
 
-    final private List<VCardEntryHandler> mEntryHandlers = new ArrayList<VCardEntryHandler>();
+    private final List<VCardEntryHandler> mEntryHandlers = new ArrayList<VCardEntryHandler>();
 
     public VCardEntryConstructor() {
-        this(null, null, false, VCardConfig.VCARD_TYPE_V21_GENERIC_UTF8, null);
+        this(VCardConfig.VCARD_TYPE_V21_GENERIC, null, null, false);
     }
 
     public VCardEntryConstructor(final int vcardType) {
-        this(null, null, false, vcardType, null);
+        this(vcardType, null, null, false);
     }
 
-    public VCardEntryConstructor(final String charset, final boolean strictLineBreakParsing,
-            final int vcardType, final Account account) {
-        this(null, charset, strictLineBreakParsing, vcardType, account);
+    public VCardEntryConstructor(final int vcardType, final Account account) {
+        this(vcardType, account, null, false);
     }
 
-    public VCardEntryConstructor(final String inputCharset, final String charsetForDetodedBytes,
-            final boolean strictLineBreakParsing, final int vcardType,
-            final Account account) {
+    public VCardEntryConstructor(final int vcardType, final Account account,
+            final String inputCharset) {
+        this(vcardType, account, inputCharset, false);
+    }
+
+    /**
+     * @hide
+     */
+    public VCardEntryConstructor(final int vcardType, final Account account,
+            final String inputCharset, final boolean strictLineBreakParsing) {
         if (inputCharset != null) {
-            mInputCharset = inputCharset;
+            mSourceCharset = inputCharset;
         } else {
-            mInputCharset = VCardConfig.DEFAULT_CHARSET;
+            mSourceCharset = VCardConfig.DEFAULT_INTERMEDIATE_CHARSET;
         }
-        if (charsetForDetodedBytes != null) {
-            mCharsetForDecodedBytes = charsetForDetodedBytes;
-        } else {
-            mCharsetForDecodedBytes = DEFAULT_CHARSET_FOR_DECODED_BYTES;
-        }
-        mStrictLineBreakParsing = strictLineBreakParsing;
+        mStrictLineBreaking = strictLineBreakParsing;
         mVCardType = vcardType;
         mAccount = account;
     }
@@ -108,30 +116,24 @@
         }
     }
 
-    /**
-     * Called when the parse failed between {@link #startEntry()} and {@link #endEntry()}.
-     */
     public void clear() {
-        mCurrentContactStruct = null;
+        mCurrentVCardEntry = null;
         mCurrentProperty = new VCardEntry.Property();
     }
 
-    /**
-     * Assume that VCard is not nested. In other words, this code does not accept 
-     */
     public void startEntry() {
-        if (mCurrentContactStruct != null) {
+        if (mCurrentVCardEntry != null) {
             Log.e(LOG_TAG, "Nested VCard code is not supported now.");
         }
-        mCurrentContactStruct = new VCardEntry(mVCardType, mAccount);
+        mCurrentVCardEntry = new VCardEntry(mVCardType, mAccount);
     }
 
     public void endEntry() {
-        mCurrentContactStruct.consolidateFields();
+        mCurrentVCardEntry.consolidateFields();
         for (VCardEntryHandler entryHandler : mEntryHandlers) {
-            entryHandler.onEntryCreated(mCurrentContactStruct);
+            entryHandler.onEntryCreated(mCurrentVCardEntry);
         }
-        mCurrentContactStruct = null;
+        mCurrentVCardEntry = null;
     }
 
     public void startProperty() {
@@ -139,7 +141,7 @@
     }
 
     public void endProperty() {
-        mCurrentContactStruct.addProperty(mCurrentProperty);
+        mCurrentVCardEntry.addProperty(mCurrentProperty);
     }
     
     public void propertyName(String name) {
@@ -166,113 +168,41 @@
         mParamType = null;
     }
 
-    private String encodeString(String originalString, String charsetForDecodedBytes) {
-        if (mInputCharset.equalsIgnoreCase(charsetForDecodedBytes)) {
+    private static String encodeToSystemCharset(String originalString,
+            String sourceCharset, String targetCharset) {
+        if (sourceCharset.equalsIgnoreCase(targetCharset)) {
             return originalString;
         }
-        Charset charset = Charset.forName(mInputCharset);
-        ByteBuffer byteBuffer = charset.encode(originalString);
+        final Charset charset = Charset.forName(sourceCharset);
+        final ByteBuffer byteBuffer = charset.encode(originalString);
         // byteBuffer.array() "may" return byte array which is larger than
         // byteBuffer.remaining(). Here, we keep on the safe side.
-        byte[] bytes = new byte[byteBuffer.remaining()];
+        final byte[] bytes = new byte[byteBuffer.remaining()];
         byteBuffer.get(bytes);
         try {
-            return new String(bytes, charsetForDecodedBytes);
+            String ret = new String(bytes, targetCharset);
+            return ret;
         } catch (UnsupportedEncodingException e) {
-            Log.e(LOG_TAG, "Failed to encode: charset=" + charsetForDecodedBytes);
+            Log.e(LOG_TAG, "Failed to encode: charset=" + targetCharset);
             return null;
         }
     }
 
-    private String handleOneValue(String value, String charsetForDecodedBytes, String encoding) {
+    private String handleOneValue(String value,
+            String sourceCharset, String targetCharset, String encoding) {
         if (encoding != null) {
             if (encoding.equals("BASE64") || encoding.equals("B")) {
                 mCurrentProperty.setPropertyBytes(Base64.decodeBase64(value.getBytes()));
                 return value;
             } else if (encoding.equals("QUOTED-PRINTABLE")) {
-                // "= " -> " ", "=\t" -> "\t".
-                // Previous code had done this replacement. Keep on the safe side.
-                StringBuilder builder = new StringBuilder();
-                int length = value.length();
-                for (int i = 0; i < length; i++) {
-                    char ch = value.charAt(i);
-                    if (ch == '=' && i < length - 1) {
-                        char nextCh = value.charAt(i + 1);
-                        if (nextCh == ' ' || nextCh == '\t') {
-
-                            builder.append(nextCh);
-                            i++;
-                            continue;
-                        }
-                    }
-                    builder.append(ch);
-                }
-                String quotedPrintable = builder.toString();
-                
-                String[] lines;
-                if (mStrictLineBreakParsing) {
-                    lines = quotedPrintable.split("\r\n");
-                } else {
-                    builder = new StringBuilder();
-                    length = quotedPrintable.length();
-                    ArrayList<String> list = new ArrayList<String>();
-                    for (int i = 0; i < length; i++) {
-                        char ch = quotedPrintable.charAt(i);
-                        if (ch == '\n') {
-                            list.add(builder.toString());
-                            builder = new StringBuilder();
-                        } else if (ch == '\r') {
-                            list.add(builder.toString());
-                            builder = new StringBuilder();
-                            if (i < length - 1) {
-                                char nextCh = quotedPrintable.charAt(i + 1);
-                                if (nextCh == '\n') {
-                                    i++;
-                                }
-                            }
-                        } else {
-                            builder.append(ch);
-                        }
-                    }
-                    String finalLine = builder.toString();
-                    if (finalLine.length() > 0) {
-                        list.add(finalLine);
-                    }
-                    lines = list.toArray(new String[0]);
-                }
-                
-                builder = new StringBuilder();
-                for (String line : lines) {
-                    if (line.endsWith("=")) {
-                        line = line.substring(0, line.length() - 1);
-                    }
-                    builder.append(line);
-                }
-                byte[] bytes;
-                try {
-                    bytes = builder.toString().getBytes(mInputCharset);
-                } catch (UnsupportedEncodingException e1) {
-                    Log.e(LOG_TAG, "Failed to encode: charset=" + mInputCharset);
-                    bytes = builder.toString().getBytes();
-                }
-                
-                try {
-                    bytes = QuotedPrintableCodec.decodeQuotedPrintable(bytes);
-                } catch (DecoderException e) {
-                    Log.e(LOG_TAG, "Failed to decode quoted-printable: " + e);
-                    return "";
-                }
-
-                try {
-                    return new String(bytes, charsetForDecodedBytes);
-                } catch (UnsupportedEncodingException e) {
-                    Log.e(LOG_TAG, "Failed to encode: charset=" + charsetForDecodedBytes);
-                    return new String(bytes);
-                }
+                return VCardUtils.parseQuotedPrintable(
+                        value, mStrictLineBreaking, sourceCharset, targetCharset);
             }
-            // Unknown encoding. Fall back to default.
+            Log.w(LOG_TAG, "Unknown encoding. Fall back to default.");
         }
-        return encodeString(value, charsetForDecodedBytes);
+
+        // Just translate the charset of a given String from inputCharset to a system one. 
+        return encodeToSystemCharset(value, sourceCharset, targetCharset);
     }
     
     public void propertyValues(List<String> values) {
@@ -281,23 +211,24 @@
         }
 
         final Collection<String> charsetCollection = mCurrentProperty.getParameters("CHARSET");
-        final String charset =
-            ((charsetCollection != null) ? charsetCollection.iterator().next() : null);
         final Collection<String> encodingCollection = mCurrentProperty.getParameters("ENCODING");
         final String encoding =
             ((encodingCollection != null) ? encodingCollection.iterator().next() : null);
-
-        String charsetForDecodedBytes = CharsetUtils.nameForDefaultVendor(charset);
-        if (charsetForDecodedBytes == null || charsetForDecodedBytes.length() == 0) {
-            charsetForDecodedBytes = mCharsetForDecodedBytes;
+        String targetCharset = CharsetUtils.nameForDefaultVendor(
+                ((charsetCollection != null) ? charsetCollection.iterator().next() : null));
+        if (TextUtils.isEmpty(targetCharset)) {
+            targetCharset = VCardConfig.DEFAULT_IMPORT_CHARSET;
         }
 
         for (final String value : values) {
             mCurrentProperty.addToPropertyValueList(
-                    handleOneValue(value, charsetForDecodedBytes, encoding));
+                    handleOneValue(value, mSourceCharset, targetCharset, encoding));
         }
     }
 
+    /**
+     * @hide
+     */
     public void showPerformanceInfo() {
         Log.d(LOG_TAG, "time for insert ContactStruct to database: " + 
                 mTimePushIntoContentResolver + " ms");
diff --git a/core/java/android/pim/vcard/VCardEntryHandler.java b/core/java/android/pim/vcard/VCardEntryHandler.java
index 83a67fe..56bf69d 100644
--- a/core/java/android/pim/vcard/VCardEntryHandler.java
+++ b/core/java/android/pim/vcard/VCardEntryHandler.java
@@ -16,8 +16,13 @@
 package android.pim.vcard;
 
 /**
- * The interface called by {@link VCardEntryConstructor}. Useful when you don't want to
- * handle detailed information as what {@link VCardParser} provides via {@link VCardInterpreter}.
+ * <p>
+ * The interface called by {@link VCardEntryConstructor}.
+ * </p>
+ * <p>
+ * This class is useful when you don't want to know vCard data in detail. If you want to know
+ * it, it would be better to consider using {@link VCardInterpreter}.
+ * </p>
  */
 public interface VCardEntryHandler {
     /**
diff --git a/core/java/android/pim/vcard/VCardInterpreter.java b/core/java/android/pim/vcard/VCardInterpreter.java
index b5237c0..03704a2 100644
--- a/core/java/android/pim/vcard/VCardInterpreter.java
+++ b/core/java/android/pim/vcard/VCardInterpreter.java
@@ -20,7 +20,7 @@
 /**
  * <P>
  * The interface which should be implemented by the classes which have to analyze each
- * vCard entry more minutely than {@link VCardEntry} class analysis.
+ * vCard entry minutely.
  * </P>
  * <P>
  * Here, there are several terms specific to vCard (and this library).
diff --git a/core/java/android/pim/vcard/VCardInterpreterCollection.java b/core/java/android/pim/vcard/VCardInterpreterCollection.java
index 99f81f7..4952dc7 100644
--- a/core/java/android/pim/vcard/VCardInterpreterCollection.java
+++ b/core/java/android/pim/vcard/VCardInterpreterCollection.java
@@ -23,7 +23,7 @@
  * {@link VCardInterpreter} objects and make a user object treat them as one
  * {@link VCardInterpreter} object.
  */
-public class VCardInterpreterCollection implements VCardInterpreter {
+public final class VCardInterpreterCollection implements VCardInterpreter {
     private final Collection<VCardInterpreter> mInterpreterCollection;
     
     public VCardInterpreterCollection(Collection<VCardInterpreter> interpreterCollection) {
diff --git a/core/java/android/pim/vcard/VCardParser.java b/core/java/android/pim/vcard/VCardParser.java
index 57c52a6..31b9369 100644
--- a/core/java/android/pim/vcard/VCardParser.java
+++ b/core/java/android/pim/vcard/VCardParser.java
@@ -20,82 +20,36 @@
 import java.io.IOException;
 import java.io.InputStream;
 
-public abstract class VCardParser {
-    protected final int mParseType;
-    protected boolean mCanceled;
-
-    public VCardParser() {
-        this(VCardConfig.PARSE_TYPE_UNKNOWN);
-    }
-
-    public VCardParser(int parseType) {
-        mParseType = parseType;
-    }
-
+public interface VCardParser {
     /**
-     * <P>
-     * Parses the given stream and send the VCard data into VCardBuilderBase object.
-     * </P.
-     * <P>
+     * <p>
+     * Parses the given stream and send the vCard data into VCardBuilderBase object.
+     * </p>.
+     * <p>
      * Note that vCard 2.1 specification allows "CHARSET" parameter, and some career sets
      * local encoding to it. For example, Japanese phone career uses Shift_JIS, which is
-     * formally allowed in VCard 2.1, but not recommended in VCard 3.0. In VCard 2.1,
-     * In some exreme case, some VCard may have different charsets in one VCard (though
-     * we do not see any device which emits such kind of malicious data)
-     * </P>
-     * <P>
-     * In order to avoid "misunderstanding" charset as much as possible, this method
-     * use "ISO-8859-1" for reading the stream. When charset is specified in some property
-     * (with "CHARSET=..." parameter), the string is decoded to raw bytes and encoded to
-     * the charset. This method assumes that "ISO-8859-1" has 1 to 1 mapping in all 8bit
-     * characters, which is not completely sure. In some cases, this "decoding-encoding"
-     * scheme may fail. To avoid the case,
-     * </P>
-     * <P>
-     * We recommend you to use {@link VCardSourceDetector} and detect which kind of source the
-     * VCard comes from and explicitly specify a charset using the result.
-     * </P>
+     * formally allowed in vCard 2.1, but not allowed in vCard 3.0. In vCard 2.1,
+     * In some exreme case, it is allowed for vCard to have different charsets in one vCard.
+     * </p>
+     * <p>
+     * We recommend you use {@link VCardSourceDetector} and detect which kind of source the
+     * vCard comes from and explicitly specify a charset using the result.
+     * </p>
      *
      * @param is The source to parse.
      * @param interepreter A {@link VCardInterpreter} object which used to construct data.
-     * @return Returns true for success. Otherwise returns false.
      * @throws IOException, VCardException
      */
-    public abstract boolean parse(InputStream is, VCardInterpreter interepreter)
+    public void parse(InputStream is, VCardInterpreter interepreter)
             throws IOException, VCardException;
-    
+
     /**
-     * <P>
-     * The method variants which accept charset.
-     * </P>
-     * <P>
-     * RFC 2426 "recommends" (not forces) to use UTF-8, so it may be OK to use
-     * UTF-8 as an encoding when parsing vCard 3.0. But note that some Japanese
-     * phone uses Shift_JIS as a charset (e.g. W61SH), and another uses
-     * "CHARSET=SHIFT_JIS", which is explicitly prohibited in vCard 3.0 specification (e.g. W53K).
-     * </P>
-     *
-     * @param is The source to parse.
-     * @param charset Charset to be used.
-     * @param builder The VCardBuilderBase object.
-     * @return Returns true when successful. Otherwise returns false.
-     * @throws IOException, VCardException
+     * <p>
+     * Cancel parsing vCard. Useful when you want to stop the parse in the other threads.
+     * </p>
+     * <p>
+     * Actual cancel is done after parsing the current vcard.
+     * </p>
      */
-    public abstract boolean parse(InputStream is, String charset, VCardInterpreter builder)
-            throws IOException, VCardException;
-    
-    /**
-     * The method variants which tells this object the operation is already canceled.
-     */
-    public abstract void parse(InputStream is, String charset,
-            VCardInterpreter builder, boolean canceled)
-        throws IOException, VCardException;
-    
-    /**
-     * Cancel parsing.
-     * Actual cancel is done after the end of the current one vcard entry parsing.
-     */
-    public void cancel() {
-        mCanceled = true;
-    }
+    public abstract void cancel();
 }
diff --git a/core/java/android/pim/vcard/VCardParserImpl_V21.java b/core/java/android/pim/vcard/VCardParserImpl_V21.java
new file mode 100644
index 0000000..7d294cc
--- /dev/null
+++ b/core/java/android/pim/vcard/VCardParserImpl_V21.java
@@ -0,0 +1,967 @@
+/*
+ * 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.pim.vcard;
+
+import android.pim.vcard.exception.VCardAgentNotSupportedException;
+import android.pim.vcard.exception.VCardException;
+import android.pim.vcard.exception.VCardInvalidCommentLineException;
+import android.pim.vcard.exception.VCardInvalidLineException;
+import android.pim.vcard.exception.VCardNestedException;
+import android.pim.vcard.exception.VCardVersionException;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * <p>
+ * Basic implementation achieving vCard parsing. Based on vCard 2.1,
+ * </p>
+ * @hide
+ */
+/* package */ class VCardParserImpl_V21 {
+    private static final String LOG_TAG = "VCardParserImpl_V21";
+
+    private static final class CustomBufferedReader extends BufferedReader {
+        private long mTime;
+
+        public CustomBufferedReader(Reader in) {
+            super(in);
+        }
+
+        @Override
+        public String readLine() throws IOException {
+            long start = System.currentTimeMillis();
+            String ret = super.readLine();
+            long end = System.currentTimeMillis();
+            mTime += end - start;
+            return ret;
+        }
+
+        public long getTotalmillisecond() {
+            return mTime;
+        }
+    }
+
+    private static final String sDefaultEncoding = "8BIT";
+
+    protected boolean mCanceled;
+    protected VCardInterpreter mInterpreter;
+
+    protected final String mImportCharset;
+
+    /**
+     * <p>
+     * The encoding type for deconding byte streams. This member variable is
+     * reset to a default encoding every time when a new item comes.
+     * </p>
+     * <p>
+     * "Encoding" in vCard is different from "Charset". It is mainly used for
+     * addresses, notes, images. "7BIT", "8BIT", "BASE64", and
+     * "QUOTED-PRINTABLE" are known examples.
+     * </p>
+     */
+    protected String mCurrentEncoding;
+
+    /**
+     * <p>
+     * The reader object to be used internally.
+     * </p>
+     * <p>
+     * Developers should not directly read a line from this object. Use
+     * getLine() unless there some reason.
+     * </p>
+     */
+    protected BufferedReader mReader;
+
+    /**
+     * <p>
+     * Set for storing unkonwn TYPE attributes, which is not acceptable in vCard
+     * specification, but happens to be seen in real world vCard.
+     * </p>
+     */
+    protected final Set<String> mUnknownTypeSet = new HashSet<String>();
+
+    /**
+     * <p>
+     * Set for storing unkonwn VALUE attributes, which is not acceptable in
+     * vCard specification, but happens to be seen in real world vCard.
+     * </p>
+     */
+    protected final Set<String> mUnknownValueSet = new HashSet<String>();
+
+
+    // In some cases, vCard is nested. Currently, we only consider the most
+    // interior vCard data.
+    // See v21_foma_1.vcf in test directory for more information.
+    // TODO: Don't ignore by using count, but read all of information outside vCard.
+    private int mNestCount;
+
+    // Used only for parsing END:VCARD.
+    private String mPreviousLine;
+
+    // For measuring performance.
+    private long mTimeTotal;
+    private long mTimeReadStartRecord;
+    private long mTimeReadEndRecord;
+    private long mTimeStartProperty;
+    private long mTimeEndProperty;
+    private long mTimeParseItems;
+    private long mTimeParseLineAndHandleGroup;
+    private long mTimeParsePropertyValues;
+    private long mTimeParseAdrOrgN;
+    private long mTimeHandleMiscPropertyValue;
+    private long mTimeHandleQuotedPrintable;
+    private long mTimeHandleBase64;
+
+    public VCardParserImpl_V21() {
+        this(VCardConfig.VCARD_TYPE_DEFAULT, null);
+    }
+
+    public VCardParserImpl_V21(int vcardType) {
+        this(vcardType, null);
+    }
+
+    public VCardParserImpl_V21(int vcardType, String importCharset) {
+        if ((vcardType & VCardConfig.FLAG_TORELATE_NEST) != 0) {
+            mNestCount = 1;
+        }
+
+        mImportCharset = (!TextUtils.isEmpty(importCharset) ? importCharset :
+            VCardConfig.DEFAULT_INTERMEDIATE_CHARSET);
+    }
+
+    /**
+     * <p>
+     * Parses the file at the given position.
+     * </p>
+     */
+    // <pre class="prettyprint">vcard_file = [wsls] vcard [wsls]</pre>
+    protected void parseVCardFile() throws IOException, VCardException {
+        boolean readingFirstFile = true;
+        while (true) {
+            if (mCanceled) {
+                break;
+            }
+            if (!parseOneVCard(readingFirstFile)) {
+                break;
+            }
+            readingFirstFile = false;
+        }
+
+        if (mNestCount > 0) {
+            boolean useCache = true;
+            for (int i = 0; i < mNestCount; i++) {
+                readEndVCard(useCache, true);
+                useCache = false;
+            }
+        }
+    }
+
+    /**
+     * @return true when a given property name is a valid property name.
+     */
+    protected boolean isValidPropertyName(final String propertyName) {
+        if (!(getKnownPropertyNameSet().contains(propertyName.toUpperCase()) ||
+                propertyName.startsWith("X-"))
+                && !mUnknownTypeSet.contains(propertyName)) {
+            mUnknownTypeSet.add(propertyName);
+            Log.w(LOG_TAG, "Property name unsupported by vCard 2.1: " + propertyName);
+        }
+        return true;
+    }
+
+    /**
+     * @return String. It may be null, or its length may be 0
+     * @throws IOException
+     */
+    protected String getLine() throws IOException {
+        return mReader.readLine();
+    }
+
+    /**
+     * @return String with it's length > 0
+     * @throws IOException
+     * @throws VCardException when the stream reached end of line
+     */
+    protected String getNonEmptyLine() throws IOException, VCardException {
+        String line;
+        while (true) {
+            line = getLine();
+            if (line == null) {
+                throw new VCardException("Reached end of buffer.");
+            } else if (line.trim().length() > 0) {
+                return line;
+            }
+        }
+    }
+
+    /*
+     * vcard = "BEGIN" [ws] ":" [ws] "VCARD" [ws] 1*CRLF
+     *         items *CRLF
+     *         "END" [ws] ":" [ws] "VCARD"
+     */
+    private boolean parseOneVCard(boolean firstRead) throws IOException, VCardException {
+        boolean allowGarbage = false;
+        if (firstRead) {
+            if (mNestCount > 0) {
+                for (int i = 0; i < mNestCount; i++) {
+                    if (!readBeginVCard(allowGarbage)) {
+                        return false;
+                    }
+                    allowGarbage = true;
+                }
+            }
+        }
+
+        if (!readBeginVCard(allowGarbage)) {
+            return false;
+        }
+        long start;
+        if (mInterpreter != null) {
+            start = System.currentTimeMillis();
+            mInterpreter.startEntry();
+            mTimeReadStartRecord += System.currentTimeMillis() - start;
+        }
+        start = System.currentTimeMillis();
+        parseItems();
+        mTimeParseItems += System.currentTimeMillis() - start;
+        readEndVCard(true, false);
+        if (mInterpreter != null) {
+            start = System.currentTimeMillis();
+            mInterpreter.endEntry();
+            mTimeReadEndRecord += System.currentTimeMillis() - start;
+        }
+        return true;
+    }
+
+    /**
+     * @return True when successful. False when reaching the end of line
+     * @throws IOException
+     * @throws VCardException
+     */
+    protected boolean readBeginVCard(boolean allowGarbage) throws IOException, VCardException {
+        String line;
+        do {
+            while (true) {
+                line = getLine();
+                if (line == null) {
+                    return false;
+                } else if (line.trim().length() > 0) {
+                    break;
+                }
+            }
+            String[] strArray = line.split(":", 2);
+            int length = strArray.length;
+
+            // Though vCard 2.1/3.0 specification does not allow lower cases,
+            // vCard file emitted by some external vCard expoter have such
+            // invalid Strings.
+            // So we allow it.
+            // e.g. BEGIN:vCard
+            if (length == 2 && strArray[0].trim().equalsIgnoreCase("BEGIN")
+                    && strArray[1].trim().equalsIgnoreCase("VCARD")) {
+                return true;
+            } else if (!allowGarbage) {
+                if (mNestCount > 0) {
+                    mPreviousLine = line;
+                    return false;
+                } else {
+                    throw new VCardException("Expected String \"BEGIN:VCARD\" did not come "
+                            + "(Instead, \"" + line + "\" came)");
+                }
+            }
+        } while (allowGarbage);
+
+        throw new VCardException("Reached where must not be reached.");
+    }
+
+    /**
+     * <p>
+     * The arguments useCache and allowGarbase are usually true and false
+     * accordingly when this function is called outside this function itself.
+     * </p>
+     * 
+     * @param useCache When true, line is obtained from mPreviousline.
+     *            Otherwise, getLine() is used.
+     * @param allowGarbage When true, ignore non "END:VCARD" line.
+     * @throws IOException
+     * @throws VCardException
+     */
+    protected void readEndVCard(boolean useCache, boolean allowGarbage) throws IOException,
+            VCardException {
+        String line;
+        do {
+            if (useCache) {
+                // Though vCard specification does not allow lower cases,
+                // some data may have them, so we allow it.
+                line = mPreviousLine;
+            } else {
+                while (true) {
+                    line = getLine();
+                    if (line == null) {
+                        throw new VCardException("Expected END:VCARD was not found.");
+                    } else if (line.trim().length() > 0) {
+                        break;
+                    }
+                }
+            }
+
+            String[] strArray = line.split(":", 2);
+            if (strArray.length == 2 && strArray[0].trim().equalsIgnoreCase("END")
+                    && strArray[1].trim().equalsIgnoreCase("VCARD")) {
+                return;
+            } else if (!allowGarbage) {
+                throw new VCardException("END:VCARD != \"" + mPreviousLine + "\"");
+            }
+            useCache = false;
+        } while (allowGarbage);
+    }
+
+    /*
+     * items = *CRLF item / item
+     */
+    protected void parseItems() throws IOException, VCardException {
+        boolean ended = false;
+
+        if (mInterpreter != null) {
+            long start = System.currentTimeMillis();
+            mInterpreter.startProperty();
+            mTimeStartProperty += System.currentTimeMillis() - start;
+        }
+        ended = parseItem();
+        if (mInterpreter != null && !ended) {
+            long start = System.currentTimeMillis();
+            mInterpreter.endProperty();
+            mTimeEndProperty += System.currentTimeMillis() - start;
+        }
+
+        while (!ended) {
+            // follow VCARD ,it wont reach endProperty
+            if (mInterpreter != null) {
+                long start = System.currentTimeMillis();
+                mInterpreter.startProperty();
+                mTimeStartProperty += System.currentTimeMillis() - start;
+            }
+            try {
+                ended = parseItem();
+            } catch (VCardInvalidCommentLineException e) {
+                Log.e(LOG_TAG, "Invalid line which looks like some comment was found. Ignored.");
+                ended = false;
+            }
+            if (mInterpreter != null && !ended) {
+                long start = System.currentTimeMillis();
+                mInterpreter.endProperty();
+                mTimeEndProperty += System.currentTimeMillis() - start;
+            }
+        }
+    }
+
+    /*
+     * item = [groups "."] name [params] ":" value CRLF / [groups "."] "ADR"
+     * [params] ":" addressparts CRLF / [groups "."] "ORG" [params] ":" orgparts
+     * CRLF / [groups "."] "N" [params] ":" nameparts CRLF / [groups "."]
+     * "AGENT" [params] ":" vcard CRLF
+     */
+    protected boolean parseItem() throws IOException, VCardException {
+        mCurrentEncoding = sDefaultEncoding;
+
+        final String line = getNonEmptyLine();
+        long start = System.currentTimeMillis();
+
+        String[] propertyNameAndValue = separateLineAndHandleGroup(line);
+        if (propertyNameAndValue == null) {
+            return true;
+        }
+        if (propertyNameAndValue.length != 2) {
+            throw new VCardInvalidLineException("Invalid line \"" + line + "\"");
+        }
+        String propertyName = propertyNameAndValue[0].toUpperCase();
+        String propertyValue = propertyNameAndValue[1];
+
+        mTimeParseLineAndHandleGroup += System.currentTimeMillis() - start;
+
+        if (propertyName.equals("ADR") || propertyName.equals("ORG") || propertyName.equals("N")) {
+            start = System.currentTimeMillis();
+            handleMultiplePropertyValue(propertyName, propertyValue);
+            mTimeParseAdrOrgN += System.currentTimeMillis() - start;
+            return false;
+        } else if (propertyName.equals("AGENT")) {
+            handleAgent(propertyValue);
+            return false;
+        } else if (isValidPropertyName(propertyName)) {
+            if (propertyName.equals("BEGIN")) {
+                if (propertyValue.equals("VCARD")) {
+                    throw new VCardNestedException("This vCard has nested vCard data in it.");
+                } else {
+                    throw new VCardException("Unknown BEGIN type: " + propertyValue);
+                }
+            } else if (propertyName.equals("VERSION") && !propertyValue.equals(getVersionString())) {
+                throw new VCardVersionException("Incompatible version: " + propertyValue + " != "
+                        + getVersionString());
+            }
+            start = System.currentTimeMillis();
+            handlePropertyValue(propertyName, propertyValue);
+            mTimeParsePropertyValues += System.currentTimeMillis() - start;
+            return false;
+        }
+
+        throw new VCardException("Unknown property name: \"" + propertyName + "\"");
+    }
+
+    // For performance reason, the states for group and property name are merged into one.
+    static private final int STATE_GROUP_OR_PROPERTY_NAME = 0;
+    static private final int STATE_PARAMS = 1;
+    // vCard 3.0 specification allows double-quoted parameters, while vCard 2.1 does not.
+    static private final int STATE_PARAMS_IN_DQUOTE = 2;
+
+    protected String[] separateLineAndHandleGroup(String line) throws VCardException {
+        final String[] propertyNameAndValue = new String[2];
+        final int length = line.length();
+        if (length > 0 && line.charAt(0) == '#') {
+            throw new VCardInvalidCommentLineException();
+        }
+
+        int state = STATE_GROUP_OR_PROPERTY_NAME;
+        int nameIndex = 0;
+
+        // This loop is developed so that we don't have to take care of bottle neck here.
+        // Refactor carefully when you need to do so.
+        for (int i = 0; i < length; i++) {
+            final char ch = line.charAt(i);
+            switch (state) {
+                case STATE_GROUP_OR_PROPERTY_NAME: {
+                    if (ch == ':') {  // End of a property name.
+                        final String propertyName = line.substring(nameIndex, i);
+                        if (propertyName.equalsIgnoreCase("END")) {
+                            mPreviousLine = line;
+                            return null;
+                        }
+                        if (mInterpreter != null) {
+                            mInterpreter.propertyName(propertyName);
+                        }
+                        propertyNameAndValue[0] = propertyName;
+                        if (i < length - 1) {
+                            propertyNameAndValue[1] = line.substring(i + 1);
+                        } else {
+                            propertyNameAndValue[1] = "";
+                        }
+                        return propertyNameAndValue;
+                    } else if (ch == '.') {  // Each group is followed by the dot.
+                        final String groupName = line.substring(nameIndex, i);
+                        if (groupName.length() == 0) {
+                            Log.w(LOG_TAG, "Empty group found. Ignoring.");
+                        } else if (mInterpreter != null) {
+                            mInterpreter.propertyGroup(groupName);
+                        }
+                        nameIndex = i + 1;  // Next should be another group or a property name.
+                    } else if (ch == ';') {  // End of property name and beginneng of parameters.  
+                        final String propertyName = line.substring(nameIndex, i);
+                        if (propertyName.equalsIgnoreCase("END")) {
+                            mPreviousLine = line;
+                            return null;
+                        }
+                        if (mInterpreter != null) {
+                            mInterpreter.propertyName(propertyName);
+                        }
+                        propertyNameAndValue[0] = propertyName;
+                        nameIndex = i + 1;
+                        state = STATE_PARAMS;  // Start parameter parsing.
+                    }
+                    break;
+                }
+                case STATE_PARAMS: {
+                    if (ch == '"') {
+                        if (VCardConstants.VERSION_V21.equalsIgnoreCase(getVersionString())) {
+                            Log.w(LOG_TAG, "Double-quoted params found in vCard 2.1. " +
+                                    "Silently allow it");
+                        }
+                        state = STATE_PARAMS_IN_DQUOTE;
+                    } else if (ch == ';') {  // Starts another param.
+                        handleParams(line.substring(nameIndex, i));
+                        nameIndex = i + 1;
+                    } else if (ch == ':') {  // End of param and beginenning of values.
+                        handleParams(line.substring(nameIndex, i));
+                        if (i < length - 1) {
+                            propertyNameAndValue[1] = line.substring(i + 1);
+                        } else {
+                            propertyNameAndValue[1] = "";
+                        }
+                        return propertyNameAndValue;
+                    }
+                    break;
+                }
+                case STATE_PARAMS_IN_DQUOTE: {
+                    if (ch == '"') {
+                        if (VCardConstants.VERSION_V21.equalsIgnoreCase(getVersionString())) {
+                            Log.w(LOG_TAG, "Double-quoted params found in vCard 2.1. " +
+                                    "Silently allow it");
+                        }
+                        state = STATE_PARAMS;
+                    }
+                    break;
+                }
+            }
+        }
+
+        throw new VCardInvalidLineException("Invalid line: \"" + line + "\"");
+    }
+
+    /*
+     * params = ";" [ws] paramlist paramlist = paramlist [ws] ";" [ws] param /
+     * param param = "TYPE" [ws] "=" [ws] ptypeval / "VALUE" [ws] "=" [ws]
+     * pvalueval / "ENCODING" [ws] "=" [ws] pencodingval / "CHARSET" [ws] "="
+     * [ws] charsetval / "LANGUAGE" [ws] "=" [ws] langval / "X-" word [ws] "="
+     * [ws] word / knowntype
+     */
+    protected void handleParams(String params) throws VCardException {
+        final String[] strArray = params.split("=", 2);
+        if (strArray.length == 2) {
+            final String paramName = strArray[0].trim().toUpperCase();
+            String paramValue = strArray[1].trim();
+            if (paramName.equals("TYPE")) {
+                handleType(paramValue);
+            } else if (paramName.equals("VALUE")) {
+                handleValue(paramValue);
+            } else if (paramName.equals("ENCODING")) {
+                handleEncoding(paramValue);
+            } else if (paramName.equals("CHARSET")) {
+                handleCharset(paramValue);
+            } else if (paramName.equals("LANGUAGE")) {
+                handleLanguage(paramValue);
+            } else if (paramName.startsWith("X-")) {
+                handleAnyParam(paramName, paramValue);
+            } else {
+                throw new VCardException("Unknown type \"" + paramName + "\"");
+            }
+        } else {
+            handleParamWithoutName(strArray[0]);
+        }
+    }
+
+    /**
+     * vCard 3.0 parser implementation may throw VCardException.
+     */
+    @SuppressWarnings("unused")
+    protected void handleParamWithoutName(final String paramValue) throws VCardException {
+        handleType(paramValue);
+    }
+
+    /*
+     * ptypeval = knowntype / "X-" word
+     */
+    protected void handleType(final String ptypeval) {
+        if (!(getKnownTypeSet().contains(ptypeval.toUpperCase())
+                || ptypeval.startsWith("X-"))
+                && !mUnknownTypeSet.contains(ptypeval)) {
+            mUnknownTypeSet.add(ptypeval);
+            Log.w(LOG_TAG, String.format("TYPE unsupported by %s: ", getVersion(), ptypeval));
+        }
+        if (mInterpreter != null) {
+            mInterpreter.propertyParamType("TYPE");
+            mInterpreter.propertyParamValue(ptypeval);
+        }
+    }
+
+    /*
+     * pvalueval = "INLINE" / "URL" / "CONTENT-ID" / "CID" / "X-" word
+     */
+    protected void handleValue(final String pvalueval) {
+        if (!(getKnownValueSet().contains(pvalueval.toUpperCase())
+                || pvalueval.startsWith("X-")
+                || mUnknownValueSet.contains(pvalueval))) {
+            mUnknownValueSet.add(pvalueval);
+            Log.w(LOG_TAG, String.format(
+                    "The value unsupported by TYPE of %s: ", getVersion(), pvalueval));
+        }
+        if (mInterpreter != null) {
+            mInterpreter.propertyParamType("VALUE");
+            mInterpreter.propertyParamValue(pvalueval);
+        }
+    }
+
+    /*
+     * pencodingval = "7BIT" / "8BIT" / "QUOTED-PRINTABLE" / "BASE64" / "X-" word
+     */
+    protected void handleEncoding(String pencodingval) throws VCardException {
+        if (getAvailableEncodingSet().contains(pencodingval) ||
+                pencodingval.startsWith("X-")) {
+            if (mInterpreter != null) {
+                mInterpreter.propertyParamType("ENCODING");
+                mInterpreter.propertyParamValue(pencodingval);
+            }
+            mCurrentEncoding = pencodingval;
+        } else {
+            throw new VCardException("Unknown encoding \"" + pencodingval + "\"");
+        }
+    }
+
+    /**
+     * <p>
+     * vCard 2.1 specification only allows us-ascii and iso-8859-xxx (See RFC 1521),
+     * but recent vCard files often contain other charset like UTF-8, SHIFT_JIS, etc.
+     * We allow any charset.
+     * </p>
+     */
+    protected void handleCharset(String charsetval) {
+        if (mInterpreter != null) {
+            mInterpreter.propertyParamType("CHARSET");
+            mInterpreter.propertyParamValue(charsetval);
+        }
+    }
+
+    /**
+     * See also Section 7.1 of RFC 1521
+     */
+    protected void handleLanguage(String langval) throws VCardException {
+        String[] strArray = langval.split("-");
+        if (strArray.length != 2) {
+            throw new VCardException("Invalid Language: \"" + langval + "\"");
+        }
+        String tmp = strArray[0];
+        int length = tmp.length();
+        for (int i = 0; i < length; i++) {
+            if (!isAsciiLetter(tmp.charAt(i))) {
+                throw new VCardException("Invalid Language: \"" + langval + "\"");
+            }
+        }
+        tmp = strArray[1];
+        length = tmp.length();
+        for (int i = 0; i < length; i++) {
+            if (!isAsciiLetter(tmp.charAt(i))) {
+                throw new VCardException("Invalid Language: \"" + langval + "\"");
+            }
+        }
+        if (mInterpreter != null) {
+            mInterpreter.propertyParamType("LANGUAGE");
+            mInterpreter.propertyParamValue(langval);
+        }
+    }
+
+    private boolean isAsciiLetter(char ch) {
+        if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Mainly for "X-" type. This accepts any kind of type without check.
+     */
+    protected void handleAnyParam(String paramName, String paramValue) {
+        if (mInterpreter != null) {
+            mInterpreter.propertyParamType(paramName);
+            mInterpreter.propertyParamValue(paramValue);
+        }
+    }
+
+    protected void handlePropertyValue(String propertyName, String propertyValue)
+            throws IOException, VCardException {
+        final String upperEncoding = mCurrentEncoding.toUpperCase();
+        if (upperEncoding.equals(VCardConstants.PARAM_ENCODING_QP)) {
+            final long start = System.currentTimeMillis();
+            final String result = getQuotedPrintable(propertyValue);
+            if (mInterpreter != null) {
+                ArrayList<String> v = new ArrayList<String>();
+                v.add(result);
+                mInterpreter.propertyValues(v);
+            }
+            mTimeHandleQuotedPrintable += System.currentTimeMillis() - start;
+        } else if (upperEncoding.equals(VCardConstants.PARAM_ENCODING_BASE64)
+                || upperEncoding.equals(VCardConstants.PARAM_ENCODING_B)) {
+            final long start = System.currentTimeMillis();
+            // It is very rare, but some BASE64 data may be so big that
+            // OutOfMemoryError occurs. To ignore such cases, use try-catch.
+            try {
+                final String result = getBase64(propertyValue);
+                if (mInterpreter != null) {
+                    ArrayList<String> arrayList = new ArrayList<String>();
+                    arrayList.add(result);
+                    mInterpreter.propertyValues(arrayList);
+                }
+            } catch (OutOfMemoryError error) {
+                Log.e(LOG_TAG, "OutOfMemoryError happened during parsing BASE64 data!");
+                if (mInterpreter != null) {
+                    mInterpreter.propertyValues(null);
+                }
+            }
+            mTimeHandleBase64 += System.currentTimeMillis() - start;
+        } else {
+            if (!(upperEncoding.equals("7BIT") || upperEncoding.equals("8BIT") ||
+                    upperEncoding.startsWith("X-"))) {
+                Log.w(LOG_TAG,
+                        String.format("The encoding \"%s\" is unsupported by vCard %s",
+                                mCurrentEncoding, getVersionString()));
+            }
+
+            final long start = System.currentTimeMillis();
+            if (mInterpreter != null) {
+                ArrayList<String> v = new ArrayList<String>();
+                v.add(maybeUnescapeText(propertyValue));
+                mInterpreter.propertyValues(v);
+            }
+            mTimeHandleMiscPropertyValue += System.currentTimeMillis() - start;
+        }
+    }
+
+    /**
+     * <p>
+     * Parses and returns Quoted-Printable.
+     * </p>
+     *
+     * @param firstString The string following a parameter name and attributes.
+     *            Example: "string" in
+     *            "ADR:ENCODING=QUOTED-PRINTABLE:string\n\r".
+     * @return whole Quoted-Printable string, including a given argument and
+     *         following lines. Excludes the last empty line following to Quoted
+     *         Printable lines.
+     * @throws IOException
+     * @throws VCardException
+     */
+    private String getQuotedPrintable(String firstString) throws IOException, VCardException {
+        // Specifically, there may be some padding between = and CRLF.
+        // See the following:
+        //
+        // qp-line := *(qp-segment transport-padding CRLF)
+        // qp-part transport-padding
+        // qp-segment := qp-section *(SPACE / TAB) "="
+        // ; Maximum length of 76 characters
+        //
+        // e.g. (from RFC 2045)
+        // Now's the time =
+        // for all folk to come=
+        // to the aid of their country.
+        if (firstString.trim().endsWith("=")) {
+            // remove "transport-padding"
+            int pos = firstString.length() - 1;
+            while (firstString.charAt(pos) != '=') {
+            }
+            StringBuilder builder = new StringBuilder();
+            builder.append(firstString.substring(0, pos + 1));
+            builder.append("\r\n");
+            String line;
+            while (true) {
+                line = getLine();
+                if (line == null) {
+                    throw new VCardException("File ended during parsing a Quoted-Printable String");
+                }
+                if (line.trim().endsWith("=")) {
+                    // remove "transport-padding"
+                    pos = line.length() - 1;
+                    while (line.charAt(pos) != '=') {
+                    }
+                    builder.append(line.substring(0, pos + 1));
+                    builder.append("\r\n");
+                } else {
+                    builder.append(line);
+                    break;
+                }
+            }
+            return builder.toString();
+        } else {
+            return firstString;
+        }
+    }
+
+    protected String getBase64(String firstString) throws IOException, VCardException {
+        StringBuilder builder = new StringBuilder();
+        builder.append(firstString);
+
+        while (true) {
+            String line = getLine();
+            if (line == null) {
+                throw new VCardException("File ended during parsing BASE64 binary");
+            }
+            if (line.length() == 0) {
+                break;
+            }
+            builder.append(line);
+        }
+
+        return builder.toString();
+    }
+
+    /**
+     * <p>
+     * Mainly for "ADR", "ORG", and "N"
+     * </p>
+     */
+    /*
+     * addressparts = 0*6(strnosemi ";") strnosemi ; PO Box, Extended Addr,
+     * Street, Locality, Region, Postal Code, Country Name orgparts =
+     * *(strnosemi ";") strnosemi ; First is Organization Name, remainder are
+     * Organization Units. nameparts = 0*4(strnosemi ";") strnosemi ; Family,
+     * Given, Middle, Prefix, Suffix. ; Example:Public;John;Q.;Reverend Dr.;III,
+     * Esq. strnosemi = *(*nonsemi ("\;" / "\" CRLF)) *nonsemi ; To include a
+     * semicolon in this string, it must be escaped ; with a "\" character. We
+     * do not care the number of "strnosemi" here. We are not sure whether we
+     * should add "\" CRLF to each value. We exclude them for now.
+     */
+    protected void handleMultiplePropertyValue(String propertyName, String propertyValue)
+            throws IOException, VCardException {
+        // vCard 2.1 does not allow QUOTED-PRINTABLE here, but some
+        // softwares/devices
+        // emit such data.
+        if (mCurrentEncoding.equalsIgnoreCase("QUOTED-PRINTABLE")) {
+            propertyValue = getQuotedPrintable(propertyValue);
+        }
+
+        if (mInterpreter != null) {
+            mInterpreter.propertyValues(VCardUtils.constructListFromValue(propertyValue,
+                    (getVersion() == VCardConfig.FLAG_V30)));
+        }
+    }
+
+    /*
+     * vCard 2.1 specifies AGENT allows one vcard entry. Currently we emit an
+     * error toward the AGENT property.
+     * // TODO: Support AGENT property.
+     * item =
+     * ... / [groups "."] "AGENT" [params] ":" vcard CRLF vcard = "BEGIN" [ws]
+     * ":" [ws] "VCARD" [ws] 1*CRLF items *CRLF "END" [ws] ":" [ws] "VCARD"
+     */
+    protected void handleAgent(final String propertyValue) throws VCardException {
+        if (!propertyValue.toUpperCase().contains("BEGIN:VCARD")) {
+            // Apparently invalid line seen in Windows Mobile 6.5. Ignore them.
+            return;
+        } else {
+            throw new VCardAgentNotSupportedException("AGENT Property is not supported now.");
+        }
+    }
+
+    /**
+     * For vCard 3.0.
+     */
+    protected String maybeUnescapeText(final String text) {
+        return text;
+    }
+
+    /**
+     * Returns unescaped String if the character should be unescaped. Return
+     * null otherwise. e.g. In vCard 2.1, "\;" should be unescaped into ";"
+     * while "\x" should not be.
+     */
+    protected String maybeUnescapeCharacter(final char ch) {
+        return unescapeCharacter(ch);
+    }
+
+    /* package */ static String unescapeCharacter(final char ch) {
+        // Original vCard 2.1 specification does not allow transformation
+        // "\:" -> ":", "\," -> ",", and "\\" -> "\", but previous
+        // implementation of
+        // this class allowed them, so keep it as is.
+        if (ch == '\\' || ch == ';' || ch == ':' || ch == ',') {
+            return String.valueOf(ch);
+        } else {
+            return null;
+        }
+    }
+
+    private void showPerformanceInfo() {
+        Log.d(LOG_TAG, "Total parsing time:  " + mTimeTotal + " ms");
+        if (mReader instanceof CustomBufferedReader) {
+            Log.d(LOG_TAG, "Total readLine time: "
+                    + ((CustomBufferedReader) mReader).getTotalmillisecond() + " ms");
+        }
+        Log.d(LOG_TAG, "Time for handling the beggining of the record: " + mTimeReadStartRecord
+                + " ms");
+        Log.d(LOG_TAG, "Time for handling the end of the record: " + mTimeReadEndRecord + " ms");
+        Log.d(LOG_TAG, "Time for parsing line, and handling group: " + mTimeParseLineAndHandleGroup
+                + " ms");
+        Log.d(LOG_TAG, "Time for parsing ADR, ORG, and N fields:" + mTimeParseAdrOrgN + " ms");
+        Log.d(LOG_TAG, "Time for parsing property values: " + mTimeParsePropertyValues + " ms");
+        Log.d(LOG_TAG, "Time for handling normal property values: " + mTimeHandleMiscPropertyValue
+                + " ms");
+        Log.d(LOG_TAG, "Time for handling Quoted-Printable: " + mTimeHandleQuotedPrintable + " ms");
+        Log.d(LOG_TAG, "Time for handling Base64: " + mTimeHandleBase64 + " ms");
+    }
+
+    /**
+     * @return {@link VCardConfig#FLAG_V21}
+     */
+    protected int getVersion() {
+        return VCardConfig.FLAG_V21;
+    }
+
+    /**
+     * @return {@link VCardConfig#FLAG_V30}
+     */
+    protected String getVersionString() {
+        return VCardConstants.VERSION_V21;
+    }
+
+    protected Set<String> getKnownPropertyNameSet() {
+        return VCardParser_V21.sKnownPropertyNameSet;
+    }
+
+    protected Set<String> getKnownTypeSet() {
+        return VCardParser_V21.sKnownTypeSet;
+    }
+
+    protected Set<String> getKnownValueSet() {
+        return VCardParser_V21.sKnownValueSet;
+    }
+
+    protected Set<String> getAvailableEncodingSet() {
+        return VCardParser_V21.sAvailableEncoding;
+    }
+
+    protected String getDefaultEncoding() {
+        return sDefaultEncoding;
+    }
+
+
+    public void parse(InputStream is, VCardInterpreter interpreter)
+            throws IOException, VCardException {
+        if (is == null) {
+            throw new NullPointerException("InputStream must not be null.");
+        }
+
+        final InputStreamReader tmpReader = new InputStreamReader(is, mImportCharset);
+        if (VCardConfig.showPerformanceLog()) {
+            mReader = new CustomBufferedReader(tmpReader);
+        } else {
+            mReader = new BufferedReader(tmpReader);
+        }
+
+        mInterpreter = interpreter;
+
+        final long start = System.currentTimeMillis();
+        if (mInterpreter != null) {
+            mInterpreter.start();
+        }
+        parseVCardFile();
+        if (mInterpreter != null) {
+            mInterpreter.end();
+        }
+        mTimeTotal += System.currentTimeMillis() - start;
+
+        if (VCardConfig.showPerformanceLog()) {
+            showPerformanceInfo();
+        }
+    }
+
+    public final void cancel() {
+        mCanceled = true;
+    }
+}
diff --git a/core/java/android/pim/vcard/VCardParserImpl_V30.java b/core/java/android/pim/vcard/VCardParserImpl_V30.java
new file mode 100644
index 0000000..a48a3b4
--- /dev/null
+++ b/core/java/android/pim/vcard/VCardParserImpl_V30.java
@@ -0,0 +1,316 @@
+/*
+ * 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.pim.vcard;
+
+import java.io.IOException;
+import java.util.Set;
+
+import android.pim.vcard.exception.VCardException;
+import android.util.Log;
+
+/**
+ * <p>
+ * Basic implementation achieving vCard 3.0 parsing.
+ * </p>
+ * <p>
+ * This class inherits vCard 2.1 implementation since technically they are similar,
+ * while specifically there's logical no relevance between them.
+ * So that developers are not confused with the inheritance,
+ * {@link VCardParser_V30} does not inherit {@link VCardParser_V21}, while
+ * {@link VCardParserImpl_V30} inherits {@link VCardParserImpl_V21}.
+ * </p>
+ * @hide
+ */
+/* package */ class VCardParserImpl_V30 extends VCardParserImpl_V21 {
+    private static final String LOG_TAG = "VCardParserImpl_V30";
+
+    private String mPreviousLine;
+    private boolean mEmittedAgentWarning = false;
+
+    public VCardParserImpl_V30() {
+        super();
+    }
+
+    public VCardParserImpl_V30(int vcardType) {
+        super(vcardType, null);
+    }
+
+    public VCardParserImpl_V30(int vcardType, String importCharset) {
+        super(vcardType, importCharset);
+    }
+
+    @Override
+    protected int getVersion() {
+        return VCardConfig.FLAG_V30;
+    }
+
+    @Override
+    protected String getVersionString() {
+        return VCardConstants.VERSION_V30;
+    }
+
+    @Override
+    protected String getLine() throws IOException {
+        if (mPreviousLine != null) {
+            String ret = mPreviousLine;
+            mPreviousLine = null;
+            return ret;
+        } else {
+            return mReader.readLine();
+        }
+    }
+
+    /**
+     * vCard 3.0 requires that the line with space at the beginning of the line
+     * must be combined with previous line.
+     */
+    @Override
+    protected String getNonEmptyLine() throws IOException, VCardException {
+        String line;
+        StringBuilder builder = null;
+        while (true) {
+            line = mReader.readLine();
+            if (line == null) {
+                if (builder != null) {
+                    return builder.toString();
+                } else if (mPreviousLine != null) {
+                    String ret = mPreviousLine;
+                    mPreviousLine = null;
+                    return ret;
+                }
+                throw new VCardException("Reached end of buffer.");
+            } else if (line.length() == 0) {
+                if (builder != null) {
+                    return builder.toString();
+                } else if (mPreviousLine != null) {
+                    String ret = mPreviousLine;
+                    mPreviousLine = null;
+                    return ret;
+                }
+            } else if (line.charAt(0) == ' ' || line.charAt(0) == '\t') {
+                if (builder != null) {
+                    // See Section 5.8.1 of RFC 2425 (MIME-DIR document).
+                    // Following is the excerpts from it.
+                    //
+                    // DESCRIPTION:This is a long description that exists on a long line.
+                    //
+                    // Can be represented as:
+                    //
+                    // DESCRIPTION:This is a long description
+                    //  that exists on a long line.
+                    //
+                    // It could also be represented as:
+                    //
+                    // DESCRIPTION:This is a long descrip
+                    //  tion that exists o
+                    //  n a long line.
+                    builder.append(line.substring(1));
+                } else if (mPreviousLine != null) {
+                    builder = new StringBuilder();
+                    builder.append(mPreviousLine);
+                    mPreviousLine = null;
+                    builder.append(line.substring(1));
+                } else {
+                    throw new VCardException("Space exists at the beginning of the line");
+                }
+            } else {
+                if (mPreviousLine == null) {
+                    mPreviousLine = line;
+                    if (builder != null) {
+                        return builder.toString();
+                    }
+                } else {
+                    String ret = mPreviousLine;
+                    mPreviousLine = line;
+                    return ret;
+                }
+            }
+        }
+    }
+
+    /*
+     * vcard = [group "."] "BEGIN" ":" "VCARD" 1 * CRLF
+     *         1 * (contentline)
+     *         ;A vCard object MUST include the VERSION, FN and N types.
+     *         [group "."] "END" ":" "VCARD" 1 * CRLF
+     */
+    @Override
+    protected boolean readBeginVCard(boolean allowGarbage) throws IOException, VCardException {
+        // TODO: vCard 3.0 supports group.
+        return super.readBeginVCard(allowGarbage);
+    }
+
+    @Override
+    protected void readEndVCard(boolean useCache, boolean allowGarbage)
+            throws IOException, VCardException {
+        // TODO: vCard 3.0 supports group.
+        super.readEndVCard(useCache, allowGarbage);
+    }
+
+    /**
+     * vCard 3.0 allows iana-token as paramType, while vCard 2.1 does not.
+     */
+    @Override
+    protected void handleParams(final String params) throws VCardException {
+        try {
+            super.handleParams(params);
+        } catch (VCardException e) {
+            // maybe IANA type
+            String[] strArray = params.split("=", 2);
+            if (strArray.length == 2) {
+                handleAnyParam(strArray[0], strArray[1]);
+            } else {
+                // Must not come here in the current implementation.
+                throw new VCardException(
+                        "Unknown params value: " + params);
+            }
+        }
+    }
+
+    @Override
+    protected void handleAnyParam(final String paramName, final String paramValue) {
+        super.handleAnyParam(paramName, paramValue);
+    }
+
+    @Override
+    protected void handleParamWithoutName(final String paramValue) throws VCardException {
+        super.handleParamWithoutName(paramValue);
+    }
+
+    /*
+     *  vCard 3.0 defines
+     *
+     *  param         = param-name "=" param-value *("," param-value)
+     *  param-name    = iana-token / x-name
+     *  param-value   = ptext / quoted-string
+     *  quoted-string = DQUOTE QSAFE-CHAR DQUOTE
+     */
+    @Override
+    protected void handleType(final String ptypevalues) {
+        String[] ptypeArray = ptypevalues.split(",");
+        mInterpreter.propertyParamType("TYPE");
+        for (String value : ptypeArray) {
+            int length = value.length();
+            if (length >= 2 && value.startsWith("\"") && value.endsWith("\"")) {
+                mInterpreter.propertyParamValue(value.substring(1, value.length() - 1));
+            } else {
+                mInterpreter.propertyParamValue(value);
+            }
+        }
+    }
+
+    @Override
+    protected void handleAgent(final String propertyValue) {
+        // The way how vCard 3.0 supports "AGENT" is completely different from vCard 2.1.
+        //
+        // e.g.
+        // AGENT:BEGIN:VCARD\nFN:Joe Friday\nTEL:+1-919-555-7878\n
+        //  TITLE:Area Administrator\, Assistant\n EMAIL\;TYPE=INTERN\n
+        //  ET:jfriday@host.com\nEND:VCARD\n
+        //
+        // TODO: fix this.
+        //
+        // issue:
+        //  vCard 3.0 also allows this as an example.
+        //
+        // AGENT;VALUE=uri:
+        //  CID:JQPUBLIC.part3.960129T083020.xyzMail@host3.com
+        //
+        // This is not vCard. Should we support this?
+        //
+        // Just ignore the line for now, since we cannot know how to handle it...
+        if (!mEmittedAgentWarning) {
+            Log.w(LOG_TAG, "AGENT in vCard 3.0 is not supported yet. Ignore it");
+            mEmittedAgentWarning = true;
+        }
+    }
+
+    /**
+     * vCard 3.0 does not require two CRLF at the last of BASE64 data.
+     * It only requires that data should be MIME-encoded.
+     */
+    @Override
+    protected String getBase64(final String firstString)
+            throws IOException, VCardException {
+        final StringBuilder builder = new StringBuilder();
+        builder.append(firstString);
+
+        while (true) {
+            final String line = getLine();
+            if (line == null) {
+                throw new VCardException("File ended during parsing BASE64 binary");
+            }
+            if (line.length() == 0) {
+                break;
+            } else if (!line.startsWith(" ") && !line.startsWith("\t")) {
+                mPreviousLine = line;
+                break;
+            }
+            builder.append(line);
+        }
+
+        return builder.toString();
+    }
+
+    /**
+     * ESCAPED-CHAR = "\\" / "\;" / "\," / "\n" / "\N")
+     *              ; \\ encodes \, \n or \N encodes newline
+     *              ; \; encodes ;, \, encodes ,
+     *
+     * Note: Apple escapes ':' into '\:' while does not escape '\'
+     */
+    @Override
+    protected String maybeUnescapeText(final String text) {
+        return unescapeText(text);
+    }
+
+    public static String unescapeText(final String text) {
+        StringBuilder builder = new StringBuilder();
+        final int length = text.length();
+        for (int i = 0; i < length; i++) {
+            char ch = text.charAt(i);
+            if (ch == '\\' && i < length - 1) {
+                final char next_ch = text.charAt(++i);
+                if (next_ch == 'n' || next_ch == 'N') {
+                    builder.append("\n");
+                } else {
+                    builder.append(next_ch);
+                }
+            } else {
+                builder.append(ch);
+            }
+        }
+        return builder.toString();
+    }
+
+    @Override
+    protected String maybeUnescapeCharacter(final char ch) {
+        return unescapeCharacter(ch);
+    }
+
+    public static String unescapeCharacter(final char ch) {
+        if (ch == 'n' || ch == 'N') {
+            return "\n";
+        } else {
+            return String.valueOf(ch);
+        }
+    }
+
+    @Override
+    protected Set<String> getKnownPropertyNameSet() {
+        return VCardParser_V30.sKnownPropertyNameSet;
+    }
+}
diff --git a/core/java/android/pim/vcard/VCardParser_V21.java b/core/java/android/pim/vcard/VCardParser_V21.java
index fe8cfb0..b625695 100644
--- a/core/java/android/pim/vcard/VCardParser_V21.java
+++ b/core/java/android/pim/vcard/VCardParser_V21.java
@@ -15,922 +15,99 @@
  */
 package android.pim.vcard;
 
-import android.pim.vcard.exception.VCardAgentNotSupportedException;
 import android.pim.vcard.exception.VCardException;
-import android.pim.vcard.exception.VCardInvalidCommentLineException;
-import android.pim.vcard.exception.VCardInvalidLineException;
-import android.pim.vcard.exception.VCardNestedException;
-import android.pim.vcard.exception.VCardVersionException;
-import android.util.Log;
 
-import java.io.BufferedReader;
 import java.io.IOException;
 import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.Reader;
-import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashSet;
 import java.util.Set;
 
 /**
- * This class is used to parse vCard. Please refer to vCard Specification 2.1 for more detail.
+ * </p>
+ * vCard parser for vCard 2.1. See the specification for more detail about the spec itself.
+ * </p>
+ * <p>
+ * The spec is written in 1996, and currently various types of "vCard 2.1" exist.
+ * To handle real the world vCard formats appropriately and effectively, this class does not
+ * obey with strict vCard 2.1.
+ * In stead, not only vCard spec but also real world vCard is considered.
+ * </p>
+ * e.g. A lot of devices and softwares let vCard importer/exporter to use
+ * the PNG format to determine the type of image, while it is not allowed in
+ * the original specification. As of 2010, we can see even the FLV format
+ * (possible in Japanese mobile phones).
+ * </p>
  */
-public class VCardParser_V21 extends VCardParser {
-    private static final String LOG_TAG = "VCardParser_V21";
-
-    /** Store the known-type */
-    private static final HashSet<String> sKnownTypeSet = new HashSet<String>(
-            Arrays.asList("DOM", "INTL", "POSTAL", "PARCEL", "HOME", "WORK",
-                    "PREF", "VOICE", "FAX", "MSG", "CELL", "PAGER", "BBS",
-                    "MODEM", "CAR", "ISDN", "VIDEO", "AOL", "APPLELINK",
-                    "ATTMAIL", "CIS", "EWORLD", "INTERNET", "IBMMAIL",
-                    "MCIMAIL", "POWERSHARE", "PRODIGY", "TLX", "X400", "GIF",
-                    "CGM", "WMF", "BMP", "MET", "PMB", "DIB", "PICT", "TIFF",
-                    "PDF", "PS", "JPEG", "QTIME", "MPEG", "MPEG2", "AVI",
-                    "WAVE", "AIFF", "PCM", "X509", "PGP"));
-
-    /** Store the known-value */
-    private static final HashSet<String> sKnownValueSet = new HashSet<String>(
-            Arrays.asList("INLINE", "URL", "CONTENT-ID", "CID"));
-
-    /** Store the property names available in vCard 2.1 */
-    private static final HashSet<String> sAvailablePropertyNameSetV21 =
-        new HashSet<String>(Arrays.asList(
-                "BEGIN", "LOGO", "PHOTO", "LABEL", "FN", "TITLE", "SOUND",
-                "VERSION", "TEL", "EMAIL", "TZ", "GEO", "NOTE", "URL",
-                "BDAY", "ROLE", "REV", "UID", "KEY", "MAILER"));
+public final class VCardParser_V21 implements VCardParser {
+    /**
+     * A unmodifiable Set storing the property names available in the vCard 2.1 specification.
+     */
+    /* package */ static final Set<String> sKnownPropertyNameSet =
+            Collections.unmodifiableSet(new HashSet<String>(
+                    Arrays.asList("BEGIN", "LOGO", "PHOTO", "LABEL", "FN", "TITLE", "SOUND",
+                            "VERSION", "TEL", "EMAIL", "TZ", "GEO", "NOTE", "URL",
+                            "BDAY", "ROLE", "REV", "UID", "KEY", "MAILER")));
 
     /**
+     * A unmodifiable Set storing the types known in vCard 2.1.
+     */
+    /* package */ static final Set<String> sKnownTypeSet =
+            Collections.unmodifiableSet(new HashSet<String>(
+                    Arrays.asList("DOM", "INTL", "POSTAL", "PARCEL", "HOME", "WORK",
+                            "PREF", "VOICE", "FAX", "MSG", "CELL", "PAGER", "BBS",
+                            "MODEM", "CAR", "ISDN", "VIDEO", "AOL", "APPLELINK",
+                            "ATTMAIL", "CIS", "EWORLD", "INTERNET", "IBMMAIL",
+                            "MCIMAIL", "POWERSHARE", "PRODIGY", "TLX", "X400", "GIF",
+                            "CGM", "WMF", "BMP", "MET", "PMB", "DIB", "PICT", "TIFF",
+                            "PDF", "PS", "JPEG", "QTIME", "MPEG", "MPEG2", "AVI",
+                            "WAVE", "AIFF", "PCM", "X509", "PGP")));
+
+    /**
+     * A unmodifiable Set storing the values for the type "VALUE", available in the vCard 2.1.
+     */
+    /* package */ static final Set<String> sKnownValueSet =
+            Collections.unmodifiableSet(new HashSet<String>(
+                    Arrays.asList("INLINE", "URL", "CONTENT-ID", "CID")));
+
+    /**
+     * <p>
+     * A unmodifiable Set storing the values for the type "ENCODING", available in the vCard 2.1.
+     * </p>
+     * <p>
      * Though vCard 2.1 specification does not allow "B" encoding, some data may have it.
-     * We allow it for safety...
+     * We allow it for safety.
+     * </p>
      */
-    private static final HashSet<String> sAvailableEncodingV21 =
-        new HashSet<String>(Arrays.asList(
-                "7BIT", "8BIT", "QUOTED-PRINTABLE", "BASE64", "B"));
-    
-    // Used only for parsing END:VCARD.
-    private String mPreviousLine;
-    
-    /** The builder to build parsed data */
-    protected VCardInterpreter mBuilder = null;
+    /* package */ static final Set<String> sAvailableEncoding =
+        Collections.unmodifiableSet(new HashSet<String>(
+                Arrays.asList(VCardConstants.PARAM_ENCODING_7BIT,
+                        VCardConstants.PARAM_ENCODING_8BIT,
+                        VCardConstants.PARAM_ENCODING_QP,
+                        VCardConstants.PARAM_ENCODING_BASE64,
+                        VCardConstants.PARAM_ENCODING_B)));
 
-    /** 
-     * The encoding type. "Encoding" in vCard is different from "Charset".
-     * e.g. 7BIT, 8BIT, QUOTED-PRINTABLE. 
-     */
-    protected String mEncoding = null;
-    
-    protected final String sDefaultEncoding = "8BIT";
-    
-    // Should not directly read a line from this object. Use getLine() instead.
-    protected BufferedReader mReader;
-    
-    // In some cases, vCard is nested. Currently, we only consider the most interior vCard data.
-    // See v21_foma_1.vcf in test directory for more information.
-    private int mNestCount;
-    
-    // In order to reduce warning message as much as possible, we hold the value which made Logger
-    // emit a warning message.
-    protected Set<String> mUnknownTypeMap = new HashSet<String>();
-    protected Set<String> mUnknownValueMap = new HashSet<String>();
-
-    // For measuring performance.
-    private long mTimeTotal;
-    private long mTimeReadStartRecord;
-    private long mTimeReadEndRecord;
-    private long mTimeStartProperty;
-    private long mTimeEndProperty;
-    private long mTimeParseItems;
-    private long mTimeParseLineAndHandleGroup;
-    private long mTimeParsePropertyValues;
-    private long mTimeParseAdrOrgN;
-    private long mTimeHandleMiscPropertyValue;
-    private long mTimeHandleQuotedPrintable;
-    private long mTimeHandleBase64;
+    private final VCardParserImpl_V21 mVCardParserImpl;
 
     public VCardParser_V21() {
-        this(null);
+        mVCardParserImpl = new VCardParserImpl_V21();
     }
 
-    public VCardParser_V21(VCardSourceDetector detector) {
-        this(detector != null ? detector.getEstimatedType() : VCardConfig.PARSE_TYPE_UNKNOWN);
+    public VCardParser_V21(int vcardType) {
+        mVCardParserImpl = new VCardParserImpl_V21(vcardType);
     }
 
-    public VCardParser_V21(int parseType) {
-        super(parseType);
-        if (parseType == VCardConfig.PARSE_TYPE_FOMA) {
-            mNestCount = 1;
-        }
+    public VCardParser_V21(int parseType, String inputCharset) {
+        mVCardParserImpl = new VCardParserImpl_V21(parseType, null);
     }
 
-    /**
-     * Parses the file at the given position.
-     *
-     * vcard_file = [wsls] vcard [wsls]
-     */
-    protected void parseVCardFile() throws IOException, VCardException {
-        boolean firstReading = true;
-        while (true) {
-            if (mCanceled) {
-                break;
-            }
-            if (!parseOneVCard(firstReading)) {
-                break;
-            }
-            firstReading = false;
-        }
-
-        if (mNestCount > 0) {
-            boolean useCache = true;
-            for (int i = 0; i < mNestCount; i++) {
-                readEndVCard(useCache, true);
-                useCache = false;
-            }
-        }
-    }
-
-    protected int getVersion() {
-        return VCardConfig.FLAG_V21;
-    }
-
-    protected String getVersionString() {
-        return VCardConstants.VERSION_V21;
-    }
-
-    /**
-     * @return true when the propertyName is a valid property name.
-     */
-    protected boolean isValidPropertyName(String propertyName) {
-        if (!(sAvailablePropertyNameSetV21.contains(propertyName.toUpperCase()) ||
-                propertyName.startsWith("X-")) && 
-                !mUnknownTypeMap.contains(propertyName)) {
-            mUnknownTypeMap.add(propertyName);
-            Log.w(LOG_TAG, "Property name unsupported by vCard 2.1: " + propertyName);
-        }
-        return true;
-    }
-
-    /**
-     * @return true when the encoding is a valid encoding.
-     */
-    protected boolean isValidEncoding(String encoding) {
-        return sAvailableEncodingV21.contains(encoding.toUpperCase());
-    }
-    
-    /**
-     * @return String. It may be null, or its length may be 0
-     * @throws IOException
-     */
-    protected String getLine() throws IOException {
-        return mReader.readLine();
-    }
-    
-    /**
-     * @return String with it's length > 0
-     * @throws IOException
-     * @throws VCardException when the stream reached end of line
-     */
-    protected String getNonEmptyLine() throws IOException, VCardException {
-        String line;
-        while (true) {
-            line = getLine();
-            if (line == null) {
-                throw new VCardException("Reached end of buffer.");
-            } else if (line.trim().length() > 0) {                
-                return line;
-            }
-        }
-    }
-
-    /**
-     * vcard = "BEGIN" [ws] ":" [ws] "VCARD" [ws] 1*CRLF
-     *         items *CRLF
-     *         "END" [ws] ":" [ws] "VCARD"
-     */
-    private boolean parseOneVCard(boolean firstReading) throws IOException, VCardException {
-        boolean allowGarbage = false;
-        if (firstReading) {
-            if (mNestCount > 0) {
-                for (int i = 0; i < mNestCount; i++) {
-                    if (!readBeginVCard(allowGarbage)) {
-                        return false;
-                    }
-                    allowGarbage = true;
-                }
-            }
-        }
-
-        if (!readBeginVCard(allowGarbage)) {
-            return false;
-        }
-        long start;
-        if (mBuilder != null) {
-            start = System.currentTimeMillis();
-            mBuilder.startEntry();
-            mTimeReadStartRecord += System.currentTimeMillis() - start;
-        }
-        start = System.currentTimeMillis();
-        parseItems();
-        mTimeParseItems += System.currentTimeMillis() - start;
-        readEndVCard(true, false);
-        if (mBuilder != null) {
-            start = System.currentTimeMillis();
-            mBuilder.endEntry();
-            mTimeReadEndRecord += System.currentTimeMillis() - start;
-        }
-        return true;
-    }
-    
-    /**
-     * @return True when successful. False when reaching the end of line  
-     * @throws IOException
-     * @throws VCardException
-     */
-    protected boolean readBeginVCard(boolean allowGarbage) throws IOException, VCardException {
-        String line;
-        do {
-            while (true) {
-                line = getLine();
-                if (line == null) {
-                    return false;
-                } else if (line.trim().length() > 0) {
-                    break;
-                }
-            }
-            String[] strArray = line.split(":", 2);
-            int length = strArray.length;
-
-            // Though vCard 2.1/3.0 specification does not allow lower cases,
-            // vCard file emitted by some external vCard expoter have such invalid Strings.
-            // So we allow it.
-            // e.g. BEGIN:vCard
-            if (length == 2 &&
-                    strArray[0].trim().equalsIgnoreCase("BEGIN") &&
-                    strArray[1].trim().equalsIgnoreCase("VCARD")) {
-                return true;
-            } else if (!allowGarbage) {
-                if (mNestCount > 0) {
-                    mPreviousLine = line;
-                    return false;
-                } else {
-                    throw new VCardException(
-                            "Expected String \"BEGIN:VCARD\" did not come "
-                            + "(Instead, \"" + line + "\" came)");
-                }
-            }
-        } while(allowGarbage);
-
-        throw new VCardException("Reached where must not be reached.");
-    }
-
-    /**
-     * The arguments useCache and allowGarbase are usually true and false accordingly when
-     * this function is called outside this function itself. 
-     *
-     * @param useCache When true, line is obtained from mPreviousline. Otherwise, getLine()
-     * is used.
-     * @param allowGarbage When true, ignore non "END:VCARD" line.
-     * @throws IOException
-     * @throws VCardException
-     */
-    protected void readEndVCard(boolean useCache, boolean allowGarbage)
+    public void parse(InputStream is, VCardInterpreter interepreter)
             throws IOException, VCardException {
-        String line;
-        do {
-            if (useCache) {
-                // Though vCard specification does not allow lower cases,
-                // some data may have them, so we allow it.
-                line = mPreviousLine;
-            } else {
-                while (true) {
-                    line = getLine();
-                    if (line == null) {
-                        throw new VCardException("Expected END:VCARD was not found.");
-                    } else if (line.trim().length() > 0) {
-                        break;
-                    }
-                }
-            }
-
-            String[] strArray = line.split(":", 2);
-            if (strArray.length == 2 &&
-                    strArray[0].trim().equalsIgnoreCase("END") &&
-                    strArray[1].trim().equalsIgnoreCase("VCARD")) {
-                return;
-            } else if (!allowGarbage) {
-                throw new VCardException("END:VCARD != \"" + mPreviousLine + "\"");
-            }
-            useCache = false;
-        } while (allowGarbage);
-    }
-    
-    /**
-     * items = *CRLF item 
-     *       / item
-     */
-    protected void parseItems() throws IOException, VCardException {
-        boolean ended = false;
-        
-        if (mBuilder != null) {
-            long start = System.currentTimeMillis();
-            mBuilder.startProperty();
-            mTimeStartProperty += System.currentTimeMillis() - start;
-        }
-        ended = parseItem();
-        if (mBuilder != null && !ended) {
-            long start = System.currentTimeMillis();
-            mBuilder.endProperty();
-            mTimeEndProperty += System.currentTimeMillis() - start;
-        }
-
-        while (!ended) {
-            // follow VCARD ,it wont reach endProperty
-            if (mBuilder != null) {
-                long start = System.currentTimeMillis();
-                mBuilder.startProperty();
-                mTimeStartProperty += System.currentTimeMillis() - start;
-            }
-            try {
-                ended = parseItem();
-            } catch (VCardInvalidCommentLineException e) {
-                Log.e(LOG_TAG, "Invalid line which looks like some comment was found. Ignored.");
-                ended = false;
-            }
-            if (mBuilder != null && !ended) {
-                long start = System.currentTimeMillis();
-                mBuilder.endProperty();
-                mTimeEndProperty += System.currentTimeMillis() - start;
-            }
-        }
-    }
-    
-    /**
-     * item = [groups "."] name    [params] ":" value CRLF
-     *      / [groups "."] "ADR"   [params] ":" addressparts CRLF
-     *      / [groups "."] "ORG"   [params] ":" orgparts CRLF
-     *      / [groups "."] "N"     [params] ":" nameparts CRLF
-     *      / [groups "."] "AGENT" [params] ":" vcard CRLF
-     */
-    protected boolean parseItem() throws IOException, VCardException {
-        mEncoding = sDefaultEncoding;
-
-        final String line = getNonEmptyLine();
-        long start = System.currentTimeMillis();
-
-        String[] propertyNameAndValue = separateLineAndHandleGroup(line);
-        if (propertyNameAndValue == null) {
-            return true;
-        }
-        if (propertyNameAndValue.length != 2) {
-            throw new VCardInvalidLineException("Invalid line \"" + line + "\"");
-        }
-        String propertyName = propertyNameAndValue[0].toUpperCase();
-        String propertyValue = propertyNameAndValue[1];
-
-        mTimeParseLineAndHandleGroup += System.currentTimeMillis() - start;
-
-        if (propertyName.equals("ADR") || propertyName.equals("ORG") ||
-                propertyName.equals("N")) {
-            start = System.currentTimeMillis();
-            handleMultiplePropertyValue(propertyName, propertyValue);
-            mTimeParseAdrOrgN += System.currentTimeMillis() - start;
-            return false;
-        } else if (propertyName.equals("AGENT")) {
-            handleAgent(propertyValue);
-            return false;
-        } else if (isValidPropertyName(propertyName)) {
-            if (propertyName.equals("BEGIN")) {
-                if (propertyValue.equals("VCARD")) {
-                    throw new VCardNestedException("This vCard has nested vCard data in it.");
-                } else {
-                    throw new VCardException("Unknown BEGIN type: " + propertyValue);
-                }
-            } else if (propertyName.equals("VERSION") &&
-                    !propertyValue.equals(getVersionString())) {
-                throw new VCardVersionException("Incompatible version: " + 
-                        propertyValue + " != " + getVersionString());
-            }
-            start = System.currentTimeMillis();
-            handlePropertyValue(propertyName, propertyValue);
-            mTimeParsePropertyValues += System.currentTimeMillis() - start;
-            return false;
-        }
-        
-        throw new VCardException("Unknown property name: \"" + propertyName + "\"");
+        mVCardParserImpl.parse(is, interepreter);
     }
 
-    static private final int STATE_GROUP_OR_PROPNAME = 0;
-    static private final int STATE_PARAMS = 1;
-    // vCard 3.0 specification allows double-quoted param-value, while vCard 2.1 does not.
-    // This is just for safety.
-    static private final int STATE_PARAMS_IN_DQUOTE = 2;
-
-    protected String[] separateLineAndHandleGroup(String line) throws VCardException {
-        int state = STATE_GROUP_OR_PROPNAME;
-        int nameIndex = 0;
-
-        final String[] propertyNameAndValue = new String[2];
-
-        final int length = line.length();
-        if (length > 0 && line.charAt(0) == '#') {
-            throw new VCardInvalidCommentLineException();
-        }
-
-        for (int i = 0; i < length; i++) {
-            char ch = line.charAt(i); 
-            switch (state) {
-                case STATE_GROUP_OR_PROPNAME: {
-                    if (ch == ':') {
-                        final String propertyName = line.substring(nameIndex, i);
-                        if (propertyName.equalsIgnoreCase("END")) {
-                            mPreviousLine = line;
-                            return null;
-                        }
-                        if (mBuilder != null) {
-                            mBuilder.propertyName(propertyName);
-                        }
-                        propertyNameAndValue[0] = propertyName;
-                        if (i < length - 1) {
-                            propertyNameAndValue[1] = line.substring(i + 1);
-                        } else {
-                            propertyNameAndValue[1] = "";
-                        }
-                        return propertyNameAndValue;
-                    } else if (ch == '.') {
-                        String groupName = line.substring(nameIndex, i);
-                        if (mBuilder != null) {
-                            mBuilder.propertyGroup(groupName);
-                        }
-                        nameIndex = i + 1;
-                    } else if (ch == ';') {
-                        String propertyName = line.substring(nameIndex, i);
-                        if (propertyName.equalsIgnoreCase("END")) {
-                            mPreviousLine = line;
-                            return null;
-                        }
-                        if (mBuilder != null) {
-                            mBuilder.propertyName(propertyName);
-                        }
-                        propertyNameAndValue[0] = propertyName;
-                        nameIndex = i + 1;
-                        state = STATE_PARAMS;
-                    }
-                    break;
-                }
-                case STATE_PARAMS: {
-                    if (ch == '"') {
-                        state = STATE_PARAMS_IN_DQUOTE;
-                    } else if (ch == ';') {
-                        handleParams(line.substring(nameIndex, i));
-                        nameIndex = i + 1;
-                    } else if (ch == ':') {
-                        handleParams(line.substring(nameIndex, i));
-                        if (i < length - 1) {
-                            propertyNameAndValue[1] = line.substring(i + 1);
-                        } else {
-                            propertyNameAndValue[1] = "";
-                        }
-                        return propertyNameAndValue;
-                    }
-                    break;
-                }
-                case STATE_PARAMS_IN_DQUOTE: {
-                    if (ch == '"') {
-                        state = STATE_PARAMS;
-                    }
-                    break;
-                }
-            }
-        }
-        
-        throw new VCardInvalidLineException("Invalid line: \"" + line + "\"");
-    }
-
-    /**
-     * params     = ";" [ws] paramlist
-     * paramlist  = paramlist [ws] ";" [ws] param
-     *            / param
-     * param      = "TYPE" [ws] "=" [ws] ptypeval
-     *            / "VALUE" [ws] "=" [ws] pvalueval
-     *            / "ENCODING" [ws] "=" [ws] pencodingval
-     *            / "CHARSET" [ws] "=" [ws] charsetval
-     *            / "LANGUAGE" [ws] "=" [ws] langval
-     *            / "X-" word [ws] "=" [ws] word
-     *            / knowntype
-     */
-    protected void handleParams(String params) throws VCardException {
-        String[] strArray = params.split("=", 2);
-        if (strArray.length == 2) {
-            final String paramName = strArray[0].trim().toUpperCase();
-            String paramValue = strArray[1].trim();
-            if (paramName.equals("TYPE")) {
-                handleType(paramValue);
-            } else if (paramName.equals("VALUE")) {
-                handleValue(paramValue);
-            } else if (paramName.equals("ENCODING")) {
-                handleEncoding(paramValue);
-            } else if (paramName.equals("CHARSET")) {
-                handleCharset(paramValue);
-            } else if (paramName.equals("LANGUAGE")) {
-                handleLanguage(paramValue);
-            } else if (paramName.startsWith("X-")) {
-                handleAnyParam(paramName, paramValue);
-            } else {
-                throw new VCardException("Unknown type \"" + paramName + "\"");
-            }
-        } else {
-            handleParamWithoutName(strArray[0]);
-        }
-    }
-    
-    /**
-     * vCard 3.0 parser may throw VCardException.
-     */
-    @SuppressWarnings("unused")
-    protected void handleParamWithoutName(final String paramValue) throws VCardException {
-        handleType(paramValue);
-    }
-
-    /**
-     * ptypeval = knowntype / "X-" word
-     */
-    protected void handleType(final String ptypeval) {
-        String upperTypeValue = ptypeval;
-        if (!(sKnownTypeSet.contains(upperTypeValue) || upperTypeValue.startsWith("X-")) && 
-                !mUnknownTypeMap.contains(ptypeval)) {
-            mUnknownTypeMap.add(ptypeval);
-            Log.w(LOG_TAG, "TYPE unsupported by vCard 2.1: " + ptypeval);
-        }
-        if (mBuilder != null) {
-            mBuilder.propertyParamType("TYPE");
-            mBuilder.propertyParamValue(upperTypeValue);
-        }
-    }
-    
-    /**
-     * pvalueval = "INLINE" / "URL" / "CONTENT-ID" / "CID" / "X-" word
-     */
-    protected void handleValue(final String pvalueval) {
-        if (!sKnownValueSet.contains(pvalueval.toUpperCase()) &&
-                pvalueval.startsWith("X-") &&
-                !mUnknownValueMap.contains(pvalueval)) {
-            mUnknownValueMap.add(pvalueval);
-            Log.w(LOG_TAG, "VALUE unsupported by vCard 2.1: " + pvalueval);
-        }
-        if (mBuilder != null) {
-            mBuilder.propertyParamType("VALUE");
-            mBuilder.propertyParamValue(pvalueval);
-        }
-    }
-    
-    /**
-     * pencodingval = "7BIT" / "8BIT" / "QUOTED-PRINTABLE" / "BASE64" / "X-" word
-     */
-    protected void handleEncoding(String pencodingval) throws VCardException {
-        if (isValidEncoding(pencodingval) ||
-                pencodingval.startsWith("X-")) {
-            if (mBuilder != null) {
-                mBuilder.propertyParamType("ENCODING");
-                mBuilder.propertyParamValue(pencodingval);
-            }
-            mEncoding = pencodingval;
-        } else {
-            throw new VCardException("Unknown encoding \"" + pencodingval + "\"");
-        }
-    }
-    
-    /**
-     * vCard 2.1 specification only allows us-ascii and iso-8859-xxx (See RFC 1521),
-     * but today's vCard often contains other charset, so we allow them.
-     */
-    protected void handleCharset(String charsetval) {
-        if (mBuilder != null) {
-            mBuilder.propertyParamType("CHARSET");
-            mBuilder.propertyParamValue(charsetval);
-        }
-    }
-
-    /**
-     * See also Section 7.1 of RFC 1521
-     */
-    protected void handleLanguage(String langval) throws VCardException {
-        String[] strArray = langval.split("-");
-        if (strArray.length != 2) {
-            throw new VCardException("Invalid Language: \"" + langval + "\"");
-        }
-        String tmp = strArray[0];
-        int length = tmp.length();
-        for (int i = 0; i < length; i++) {
-            if (!isLetter(tmp.charAt(i))) {
-                throw new VCardException("Invalid Language: \"" + langval + "\"");
-            }
-        }
-        tmp = strArray[1];
-        length = tmp.length();
-        for (int i = 0; i < length; i++) {
-            if (!isLetter(tmp.charAt(i))) {
-                throw new VCardException("Invalid Language: \"" + langval + "\"");
-            }
-        }
-        if (mBuilder != null) {
-            mBuilder.propertyParamType("LANGUAGE");
-            mBuilder.propertyParamValue(langval);
-        }
-    }
-
-    /**
-     * Mainly for "X-" type. This accepts any kind of type without check.
-     */
-    protected void handleAnyParam(String paramName, String paramValue) {
-        if (mBuilder != null) {
-            mBuilder.propertyParamType(paramName);
-            mBuilder.propertyParamValue(paramValue);
-        }
-    }
-
-    protected void handlePropertyValue(String propertyName, String propertyValue)
-            throws IOException, VCardException {
-        if (mEncoding.equalsIgnoreCase("QUOTED-PRINTABLE")) {
-            final long start = System.currentTimeMillis();
-            final String result = getQuotedPrintable(propertyValue);
-            if (mBuilder != null) {
-                ArrayList<String> v = new ArrayList<String>();
-                v.add(result);
-                mBuilder.propertyValues(v);
-            }
-            mTimeHandleQuotedPrintable += System.currentTimeMillis() - start;
-        } else if (mEncoding.equalsIgnoreCase("BASE64") ||
-                mEncoding.equalsIgnoreCase("B")) {
-            final long start = System.currentTimeMillis();
-            // It is very rare, but some BASE64 data may be so big that
-            // OutOfMemoryError occurs. To ignore such cases, use try-catch.
-            try {
-                final String result = getBase64(propertyValue);
-                if (mBuilder != null) {
-                    ArrayList<String> v = new ArrayList<String>();
-                    v.add(result);
-                    mBuilder.propertyValues(v);
-                }
-            } catch (OutOfMemoryError error) {
-                Log.e(LOG_TAG, "OutOfMemoryError happened during parsing BASE64 data!");
-                if (mBuilder != null) {
-                    mBuilder.propertyValues(null);
-                }
-            }
-            mTimeHandleBase64 += System.currentTimeMillis() - start;
-        } else {
-            if (!(mEncoding == null || mEncoding.equalsIgnoreCase("7BIT")
-                    || mEncoding.equalsIgnoreCase("8BIT")
-                    || mEncoding.toUpperCase().startsWith("X-"))) {
-                Log.w(LOG_TAG, "The encoding unsupported by vCard spec: \"" + mEncoding + "\".");
-            }
-
-            final long start = System.currentTimeMillis();
-            if (mBuilder != null) {
-                ArrayList<String> v = new ArrayList<String>();
-                v.add(maybeUnescapeText(propertyValue));
-                mBuilder.propertyValues(v);
-            }
-            mTimeHandleMiscPropertyValue += System.currentTimeMillis() - start;
-        }
-    }
-    
-    protected String getQuotedPrintable(String firstString) throws IOException, VCardException {
-        // Specifically, there may be some padding between = and CRLF.
-        // See the following:
-        //
-        // qp-line := *(qp-segment transport-padding CRLF)
-        //            qp-part transport-padding
-        // qp-segment := qp-section *(SPACE / TAB) "="
-        //             ; Maximum length of 76 characters
-        //
-        // e.g. (from RFC 2045)
-        // Now's the time =
-        // for all folk to come=
-        //  to the aid of their country.
-        if (firstString.trim().endsWith("=")) {
-            // remove "transport-padding"
-            int pos = firstString.length() - 1;
-            while(firstString.charAt(pos) != '=') {
-            }
-            StringBuilder builder = new StringBuilder();
-            builder.append(firstString.substring(0, pos + 1));
-            builder.append("\r\n");
-            String line;
-            while (true) {
-                line = getLine();
-                if (line == null) {
-                    throw new VCardException(
-                            "File ended during parsing quoted-printable String");
-                }
-                if (line.trim().endsWith("=")) {
-                    // remove "transport-padding"
-                    pos = line.length() - 1;
-                    while(line.charAt(pos) != '=') {
-                    }
-                    builder.append(line.substring(0, pos + 1));
-                    builder.append("\r\n");
-                } else {
-                    builder.append(line);
-                    break;
-                }
-            }
-            return builder.toString(); 
-        } else {
-            return firstString;
-        }
-    }
-    
-    protected String getBase64(String firstString) throws IOException, VCardException {
-        StringBuilder builder = new StringBuilder();
-        builder.append(firstString);
-        
-        while (true) {
-            String line = getLine();
-            if (line == null) {
-                throw new VCardException(
-                        "File ended during parsing BASE64 binary");
-            }
-            if (line.length() == 0) {
-                break;
-            }
-            builder.append(line);
-        }
-        
-        return builder.toString();
-    }
-    
-    /**
-     * Mainly for "ADR", "ORG", and "N"
-     * We do not care the number of strnosemi here.
-     * 
-     * addressparts = 0*6(strnosemi ";") strnosemi
-     *              ; PO Box, Extended Addr, Street, Locality, Region,
-     *                Postal Code, Country Name
-     * orgparts     = *(strnosemi ";") strnosemi
-     *              ; First is Organization Name,
-     *                remainder are Organization Units.
-     * nameparts    = 0*4(strnosemi ";") strnosemi
-     *              ; Family, Given, Middle, Prefix, Suffix.
-     *              ; Example:Public;John;Q.;Reverend Dr.;III, Esq.
-     * strnosemi    = *(*nonsemi ("\;" / "\" CRLF)) *nonsemi
-     *              ; To include a semicolon in this string, it must be escaped
-     *              ; with a "\" character.
-     *              
-     * We are not sure whether we should add "\" CRLF to each value.
-     * For now, we exclude them.               
-     */
-    protected void handleMultiplePropertyValue(String propertyName, String propertyValue)
-            throws IOException, VCardException {
-        // vCard 2.1 does not allow QUOTED-PRINTABLE here,
-        // but some softwares/devices emit such data.
-        if (mEncoding.equalsIgnoreCase("QUOTED-PRINTABLE")) {
-            propertyValue = getQuotedPrintable(propertyValue);
-        }
-
-        if (mBuilder != null) {
-            mBuilder.propertyValues(VCardUtils.constructListFromValue(
-                    propertyValue, (getVersion() == VCardConfig.FLAG_V30)));
-        }
-    }
-
-    /**
-     * vCard 2.1 specifies AGENT allows one vcard entry. It is not encoded at all.
-     *
-     * item  = ...
-     *       / [groups "."] "AGENT"
-     *         [params] ":" vcard CRLF
-     * vcard = "BEGIN" [ws] ":" [ws] "VCARD" [ws] 1*CRLF
-     *         items *CRLF "END" [ws] ":" [ws] "VCARD"
-     */
-    protected void handleAgent(final String propertyValue) throws VCardException {
-        if (!propertyValue.toUpperCase().contains("BEGIN:VCARD")) {
-            // Apparently invalid line seen in Windows Mobile 6.5. Ignore them.
-            return;
-        } else {
-            throw new VCardAgentNotSupportedException("AGENT Property is not supported now.");
-        }
-        // TODO: Support AGENT property.
-    }
-    
-    /**
-     * For vCard 3.0.
-     */
-    protected String maybeUnescapeText(final String text) {
-        return text;
-    }
-
-    /**
-     * Returns unescaped String if the character should be unescaped. Return null otherwise.
-     * e.g. In vCard 2.1, "\;" should be unescaped into ";" while "\x" should not be.
-     */
-    protected String maybeUnescapeCharacter(final char ch) {
-        return unescapeCharacter(ch);
-    }
-
-    public static String unescapeCharacter(final char ch) {
-        // Original vCard 2.1 specification does not allow transformation
-        // "\:" -> ":", "\," -> ",", and "\\" -> "\", but previous implementation of
-        // this class allowed them, so keep it as is.
-        if (ch == '\\' || ch == ';' || ch == ':' || ch == ',') {
-            return String.valueOf(ch);
-        } else {
-            return null;
-        }
-    }
-    
-    @Override
-    public boolean parse(final InputStream is, final VCardInterpreter builder)
-            throws IOException, VCardException {
-        return parse(is, VCardConfig.DEFAULT_CHARSET, builder);
-    }
-    
-    @Override
-    public boolean parse(InputStream is, String charset, VCardInterpreter builder)
-            throws IOException, VCardException {
-        if (charset == null) {
-            charset = VCardConfig.DEFAULT_CHARSET;
-        }
-        final InputStreamReader tmpReader = new InputStreamReader(is, charset);
-        if (VCardConfig.showPerformanceLog()) {
-            mReader = new CustomBufferedReader(tmpReader);
-        } else {
-            mReader = new BufferedReader(tmpReader);
-        }
-        
-        mBuilder = builder;
-
-        long start = System.currentTimeMillis();
-        if (mBuilder != null) {
-            mBuilder.start();
-        }
-        parseVCardFile();
-        if (mBuilder != null) {
-            mBuilder.end();
-        }
-        mTimeTotal += System.currentTimeMillis() - start;
-        
-        if (VCardConfig.showPerformanceLog()) {
-            showPerformanceInfo();
-        }
-        
-        return true;
-    }
-    
-    @Override
-    public void parse(InputStream is, String charset, VCardInterpreter builder, boolean canceled)
-            throws IOException, VCardException {
-        mCanceled = canceled;
-        parse(is, charset, builder);
-    }
-        
-    private void showPerformanceInfo() {
-        Log.d(LOG_TAG, "Total parsing time:  " + mTimeTotal + " ms");
-        if (mReader instanceof CustomBufferedReader) {
-            Log.d(LOG_TAG, "Total readLine time: " +
-                    ((CustomBufferedReader)mReader).getTotalmillisecond() + " ms");
-        }
-        Log.d(LOG_TAG, "Time for handling the beggining of the record: " +
-                mTimeReadStartRecord + " ms");
-        Log.d(LOG_TAG, "Time for handling the end of the record: " +
-                mTimeReadEndRecord + " ms");
-        Log.d(LOG_TAG, "Time for parsing line, and handling group: " +
-                mTimeParseLineAndHandleGroup + " ms");
-        Log.d(LOG_TAG, "Time for parsing ADR, ORG, and N fields:" + mTimeParseAdrOrgN + " ms");
-        Log.d(LOG_TAG, "Time for parsing property values: " + mTimeParsePropertyValues + " ms");
-        Log.d(LOG_TAG, "Time for handling normal property values: " +
-                mTimeHandleMiscPropertyValue + " ms");
-        Log.d(LOG_TAG, "Time for handling Quoted-Printable: " +
-                mTimeHandleQuotedPrintable + " ms");
-        Log.d(LOG_TAG, "Time for handling Base64: " + mTimeHandleBase64 + " ms");
-    }
-
-    private boolean isLetter(char ch) {
-        if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) {
-            return true;
-        }
-        return false;
-    }
-}
-
-class CustomBufferedReader extends BufferedReader {
-    private long mTime;
-    
-    public CustomBufferedReader(Reader in) {
-        super(in);
-    }
-    
-    @Override
-    public String readLine() throws IOException {
-        long start = System.currentTimeMillis();
-        String ret = super.readLine();
-        long end = System.currentTimeMillis();
-        mTime += end - start;
-        return ret;
-    }
-    
-    public long getTotalmillisecond() {
-        return mTime;
+    public void cancel() {
+        mVCardParserImpl.cancel();
     }
 }
diff --git a/core/java/android/pim/vcard/VCardParser_V30.java b/core/java/android/pim/vcard/VCardParser_V30.java
index 4ecfe97..40792ab 100644
--- a/core/java/android/pim/vcard/VCardParser_V30.java
+++ b/core/java/android/pim/vcard/VCardParser_V30.java
@@ -16,343 +16,76 @@
 package android.pim.vcard;
 
 import android.pim.vcard.exception.VCardException;
-import android.util.Log;
 
 import java.io.IOException;
+import java.io.InputStream;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashSet;
+import java.util.Set;
 
 /**
- * The class used to parse vCard 3.0.
- * Please refer to vCard Specification 3.0 (http://tools.ietf.org/html/rfc2426).
+ * <p>
+ * vCard parser for vCard 3.0. See RFC 2426 for more detail.
+ * </p>
+ * <p>
+ * This parser allows vCard format which is not allowed in the RFC, since
+ * we have seen several vCard 3.0 files which don't comply with it.
+ * </p>
+ * <p>
+ * e.g. vCard 3.0 does not allow "CHARSET" attribute, but some actual files
+ * have it and they uses non UTF-8 charsets. UTF-8 is recommended in RFC 2426,
+ * but it is not a must. We silently allow "CHARSET".
+ * </p>
  */
-public class VCardParser_V30 extends VCardParser_V21 {
-    private static final String LOG_TAG = "VCardParser_V30";
-
-    private static final HashSet<String> sAcceptablePropsWithParam = new HashSet<String>(
-            Arrays.asList(
+public class VCardParser_V30 implements VCardParser {
+    /* package */ static final Set<String> sKnownPropertyNameSet =
+            Collections.unmodifiableSet(new HashSet<String>(Arrays.asList(
                     "BEGIN", "LOGO", "PHOTO", "LABEL", "FN", "TITLE", "SOUND", 
                     "VERSION", "TEL", "EMAIL", "TZ", "GEO", "NOTE", "URL",
                     "BDAY", "ROLE", "REV", "UID", "KEY", "MAILER", // 2.1
                     "NAME", "PROFILE", "SOURCE", "NICKNAME", "CLASS",
-                    "SORT-STRING", "CATEGORIES", "PRODID")); // 3.0
-    
-    // Although "7bit" and "BASE64" is not allowed in vCard 3.0, we allow it for safety.
-    private static final HashSet<String> sAcceptableEncodingV30 = new HashSet<String>(
-            Arrays.asList("7BIT", "8BIT", "BASE64", "B"));
-    
-    // Although RFC 2426 specifies some property must not have parameters, we allow it, 
-    // since there may be some careers which violates the RFC...
-    private static final HashSet<String> acceptablePropsWithoutParam = new HashSet<String>();
-
-    private String mPreviousLine;
-
-    private boolean mEmittedAgentWarning = false;
+                    "SORT-STRING", "CATEGORIES", "PRODID"))); // 3.0
 
     /**
-     * True when the caller wants the parser to be strict about the input.
-     * Currently this is only for testing.
+     * <p>
+     * A unmodifiable Set storing the values for the type "ENCODING", available in the vCard 3.0.
+     * </p>
+     * <p>
+     * Though vCard 2.1 specification does not allow "7BIT" or "BASE64", we allow them for safety.
+     * </p>
+     * <p>
+     * "QUOTED-PRINTABLE" is not allowed in vCard 3.0 and not in this parser either,
+     * because the encoding ambiguates how the vCard file to be parsed.
+     * </p>
      */
-    private final boolean mStrictParsing;
+    /* package */ static final Set<String> sAcceptableEncoding =
+            Collections.unmodifiableSet(new HashSet<String>(Arrays.asList(
+                    VCardConstants.PARAM_ENCODING_7BIT,
+                    VCardConstants.PARAM_ENCODING_8BIT,
+                    VCardConstants.PARAM_ENCODING_BASE64,
+                    VCardConstants.PARAM_ENCODING_B)));
+
+    private final VCardParserImpl_V30 mVCardParserImpl;
 
     public VCardParser_V30() {
-        super();
-        mStrictParsing = false;
+        mVCardParserImpl = new VCardParserImpl_V30();
     }
 
-    /**
-     * @param strictParsing when true, this object throws VCardException when the vcard is not
-     * valid from the view of vCard 3.0 specification (defined in RFC 2426). Note that this class
-     * is not fully yet for being used with this flag and may not notice invalid line(s).
-     *
-     * @hide currently only for testing! 
-     */
-    public VCardParser_V30(boolean strictParsing) {
-        super();
-        mStrictParsing = strictParsing;
+    public VCardParser_V30(int vcardType) {
+        mVCardParserImpl = new VCardParserImpl_V30(vcardType);
     }
 
-    public VCardParser_V30(int parseMode) {
-        super(parseMode);
-        mStrictParsing = false;
+    public VCardParser_V30(int vcardType, String importCharset) {
+        mVCardParserImpl = new VCardParserImpl_V30(vcardType, importCharset);
     }
 
-    @Override
-    protected int getVersion() {
-        return VCardConfig.FLAG_V30;
-    }
-
-    @Override
-    protected String getVersionString() {
-        return VCardConstants.VERSION_V30;
-    }
-
-    @Override
-    protected boolean isValidPropertyName(String propertyName) {
-        if (!(sAcceptablePropsWithParam.contains(propertyName) ||
-                acceptablePropsWithoutParam.contains(propertyName) ||
-                propertyName.startsWith("X-")) &&
-                !mUnknownTypeMap.contains(propertyName)) {
-            mUnknownTypeMap.add(propertyName);
-            Log.w(LOG_TAG, "Property name unsupported by vCard 3.0: " + propertyName);
-        }
-        return true;
-    }
-
-    @Override
-    protected boolean isValidEncoding(String encoding) {
-        return sAcceptableEncodingV30.contains(encoding.toUpperCase());
-    }
-
-    @Override
-    protected String getLine() throws IOException {
-        if (mPreviousLine != null) {
-            String ret = mPreviousLine;
-            mPreviousLine = null;
-            return ret;
-        } else {
-            return mReader.readLine();
-        }
-    }
-    
-    /**
-     * vCard 3.0 requires that the line with space at the beginning of the line
-     * must be combined with previous line. 
-     */
-    @Override
-    protected String getNonEmptyLine() throws IOException, VCardException {
-        String line;
-        StringBuilder builder = null;
-        while (true) {
-            line = mReader.readLine();
-            if (line == null) {
-                if (builder != null) {
-                    return builder.toString();
-                } else if (mPreviousLine != null) {
-                    String ret = mPreviousLine;
-                    mPreviousLine = null;
-                    return ret;
-                }
-                throw new VCardException("Reached end of buffer.");
-            } else if (line.length() == 0) {
-                if (builder != null) {
-                    return builder.toString();
-                } else if (mPreviousLine != null) {
-                    String ret = mPreviousLine;
-                    mPreviousLine = null;
-                    return ret;
-                }
-            } else if (line.charAt(0) == ' ' || line.charAt(0) == '\t') {
-                if (builder != null) {
-                    // See Section 5.8.1 of RFC 2425 (MIME-DIR document).
-                    // Following is the excerpts from it.  
-                    //
-                    // DESCRIPTION:This is a long description that exists on a long line.
-                    // 
-                    // Can be represented as:
-                    //
-                    // DESCRIPTION:This is a long description
-                    //  that exists on a long line.
-                    //
-                    // It could also be represented as:
-                    //
-                    // DESCRIPTION:This is a long descrip
-                    //  tion that exists o
-                    //  n a long line.
-                    builder.append(line.substring(1));
-                } else if (mPreviousLine != null) {
-                    builder = new StringBuilder();
-                    builder.append(mPreviousLine);
-                    mPreviousLine = null;
-                    builder.append(line.substring(1));
-                } else {
-                    throw new VCardException("Space exists at the beginning of the line");
-                }
-            } else {
-                if (mPreviousLine == null) {
-                    mPreviousLine = line;
-                    if (builder != null) {
-                        return builder.toString();
-                    }
-                } else {
-                    String ret = mPreviousLine;
-                    mPreviousLine = line;
-                    return ret;
-                }
-            }
-        }
-    }
-    
-    
-    /**
-     * vcard = [group "."] "BEGIN" ":" "VCARD" 1 * CRLF
-     *         1 * (contentline)
-     *         ;A vCard object MUST include the VERSION, FN and N types.
-     *         [group "."] "END" ":" "VCARD" 1 * CRLF
-     */
-    @Override
-    protected boolean readBeginVCard(boolean allowGarbage) throws IOException, VCardException {
-        // TODO: vCard 3.0 supports group.
-        return super.readBeginVCard(allowGarbage);
-    }
-
-    @Override
-    protected void readEndVCard(boolean useCache, boolean allowGarbage)
+    public void parse(InputStream is, VCardInterpreter interepreter)
             throws IOException, VCardException {
-        // TODO: vCard 3.0 supports group.
-        super.readEndVCard(useCache, allowGarbage);
+        mVCardParserImpl.parse(is, interepreter);
     }
 
-    /**
-     * vCard 3.0 allows iana-token as paramType, while vCard 2.1 does not.
-     */
-    @Override
-    protected void handleParams(String params) throws VCardException {
-        try {
-            super.handleParams(params);
-        } catch (VCardException e) {
-            // maybe IANA type
-            String[] strArray = params.split("=", 2);
-            if (strArray.length == 2) {
-                handleAnyParam(strArray[0], strArray[1]);
-            } else {
-                // Must not come here in the current implementation.
-                throw new VCardException(
-                        "Unknown params value: " + params);
-            }
-        }
-    }
-
-    @Override
-    protected void handleAnyParam(String paramName, String paramValue) {
-        super.handleAnyParam(paramName, paramValue);
-    }
-
-    @Override
-    protected void handleParamWithoutName(final String paramValue) throws VCardException {
-        if (mStrictParsing) {
-            throw new VCardException("Parameter without name is not acceptable in vCard 3.0");
-        } else {
-            super.handleParamWithoutName(paramValue);
-        }
-    }
-
-    /**
-     *  vCard 3.0 defines
-     *  
-     *  param         = param-name "=" param-value *("," param-value)
-     *  param-name    = iana-token / x-name
-     *  param-value   = ptext / quoted-string
-     *  quoted-string = DQUOTE QSAFE-CHAR DQUOTE
-     */
-    @Override
-    protected void handleType(String ptypevalues) {
-        String[] ptypeArray = ptypevalues.split(",");
-        mBuilder.propertyParamType("TYPE");
-        for (String value : ptypeArray) {
-            int length = value.length();
-            if (length >= 2 && value.startsWith("\"") && value.endsWith("\"")) {
-                mBuilder.propertyParamValue(value.substring(1, value.length() - 1));
-            } else {
-                mBuilder.propertyParamValue(value);
-            }
-        }
-    }
-
-    @Override
-    protected void handleAgent(String propertyValue) {
-        // The way how vCard 3.0 supports "AGENT" is completely different from vCard 2.1.
-        //
-        // e.g.
-        // AGENT:BEGIN:VCARD\nFN:Joe Friday\nTEL:+1-919-555-7878\n
-        //  TITLE:Area Administrator\, Assistant\n EMAIL\;TYPE=INTERN\n
-        //  ET:jfriday@host.com\nEND:VCARD\n
-        //
-        // TODO: fix this.
-        //
-        // issue:
-        //  vCard 3.0 also allows this as an example.
-        //
-        // AGENT;VALUE=uri:
-        //  CID:JQPUBLIC.part3.960129T083020.xyzMail@host3.com
-        //
-        // This is not vCard. Should we support this?
-        //
-        // Just ignore the line for now, since we cannot know how to handle it...
-        if (!mEmittedAgentWarning) {
-            Log.w(LOG_TAG, "AGENT in vCard 3.0 is not supported yet. Ignore it");
-            mEmittedAgentWarning = true;
-        }
-    }
-    
-    /**
-     * vCard 3.0 does not require two CRLF at the last of BASE64 data.
-     * It only requires that data should be MIME-encoded.
-     */
-    @Override
-    protected String getBase64(String firstString) throws IOException, VCardException {
-        StringBuilder builder = new StringBuilder();
-        builder.append(firstString);
-
-        while (true) {
-            String line = getLine();
-            if (line == null) {
-                throw new VCardException(
-                        "File ended during parsing BASE64 binary");
-            }
-            if (line.length() == 0) {
-                break;
-            } else if (!line.startsWith(" ") && !line.startsWith("\t")) {
-                mPreviousLine = line;
-                break;
-            }
-            builder.append(line);
-        }
-        
-        return builder.toString();
-    }
-    
-    /**
-     * ESCAPED-CHAR = "\\" / "\;" / "\," / "\n" / "\N")
-     *              ; \\ encodes \, \n or \N encodes newline
-     *              ; \; encodes ;, \, encodes ,
-     *              
-     * Note: Apple escapes ':' into '\:' while does not escape '\'
-     */
-    @Override
-    protected String maybeUnescapeText(String text) {
-        return unescapeText(text);
-    }
-
-    public static String unescapeText(String text) {
-        StringBuilder builder = new StringBuilder();
-        int length = text.length();
-        for (int i = 0; i < length; i++) {
-            char ch = text.charAt(i);
-            if (ch == '\\' && i < length - 1) {
-                char next_ch = text.charAt(++i); 
-                if (next_ch == 'n' || next_ch == 'N') {
-                    builder.append("\n");
-                } else {
-                    builder.append(next_ch);
-                }
-            } else {
-                builder.append(ch);
-            }
-        }
-        return builder.toString();        
-    }
-
-    @Override
-    protected String maybeUnescapeCharacter(char ch) {
-        return unescapeCharacter(ch);
-    }
-
-    public static String unescapeCharacter(char ch) {
-        if (ch == 'n' || ch == 'N') {
-            return "\n";
-        } else {
-            return String.valueOf(ch);
-        }        
+    public void cancel() {
+        mVCardParserImpl.cancel();
     }
 }
diff --git a/core/java/android/pim/vcard/VCardSourceDetector.java b/core/java/android/pim/vcard/VCardSourceDetector.java
index 7297c50..291deca 100644
--- a/core/java/android/pim/vcard/VCardSourceDetector.java
+++ b/core/java/android/pim/vcard/VCardSourceDetector.java
@@ -15,15 +15,28 @@
  */
 package android.pim.vcard;
 
+import android.text.TextUtils;
+
 import java.util.Arrays;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 
 /**
- * Class which tries to detects the source of the vCard from its properties.
- * Currently this implementation is very premature.
- * @hide
+ * <p>
+ * The class which tries to detects the source of a vCard file from its contents.
+ * </p>
+ * <p>
+ * The specification of vCard (including both 2.1 and 3.0) is not so strict as to
+ * guess its format just by reading beginning few lines (usually we can, but in
+ * some most pessimistic case, we cannot until at almost the end of the file).
+ * Also we cannot store all vCard entries in memory, while there's no specification
+ * how big the vCard entry would become after the parse.
+ * </p>
+ * <p>
+ * This class is usually used for the "first scan", in which we can understand which vCard
+ * version is used (and how many entries exist in a file).
+ * </p>
  */
 public class VCardSourceDetector implements VCardInterpreter {
     private static Set<String> APPLE_SIGNS = new HashSet<String>(Arrays.asList(
@@ -42,8 +55,22 @@
             "X-SD-VERN", "X-SD-FORMAT_VER", "X-SD-CATEGORIES", "X-SD-CLASS", "X-SD-DCREATED",
             "X-SD-DESCRIPTION"));
     private static String TYPE_FOMA_CHARSET_SIGN = "X-SD-CHAR_CODE";
-    
-    private int mType = VCardConfig.PARSE_TYPE_UNKNOWN;
+
+
+    // TODO: Should replace this with types in VCardConfig
+    private static final int PARSE_TYPE_UNKNOWN = 0;
+    // For Apple's software, which does not mean this type is effective for all its products.
+    // We confirmed they usually use UTF-8, but not sure about vCard type.
+    private static final int PARSE_TYPE_APPLE = 1;
+    // For Japanese mobile phones, which are usually using Shift_JIS as a charset.
+    private static final int PARSE_TYPE_MOBILE_PHONE_JP = 2;
+    // For some of mobile phones released from DoCoMo, which use nested vCard. 
+    private static final int PARSE_TYPE_DOCOMO_TORELATE_NEST = 3;
+    // For Japanese Windows Mobel phones. It's version is supposed to be 6.5.
+    private static final int PARSE_TYPE_WINDOWS_MOBILE_V65_JP = 4;
+
+    private int mParseType = 0;  // Not sure.
+
     // Some mobile phones (like FOMA) tells us the charset of the data.
     private boolean mNeedParseSpecifiedCharset;
     private String mSpecifiedCharset;
@@ -72,21 +99,22 @@
     
     public void propertyName(String name) {
         if (name.equalsIgnoreCase(TYPE_FOMA_CHARSET_SIGN)) {
-            mType = VCardConfig.PARSE_TYPE_FOMA;
+            mParseType = PARSE_TYPE_DOCOMO_TORELATE_NEST;
+            // Probably Shift_JIS is used, but we should double confirm.
             mNeedParseSpecifiedCharset = true;
             return;
         }
-        if (mType != VCardConfig.PARSE_TYPE_UNKNOWN) {
+        if (mParseType != PARSE_TYPE_UNKNOWN) {
             return;
         }
         if (WINDOWS_MOBILE_PHONE_SIGNS.contains(name)) {
-            mType = VCardConfig.PARSE_TYPE_WINDOWS_MOBILE_JP;
+            mParseType = PARSE_TYPE_WINDOWS_MOBILE_V65_JP;
         } else if (FOMA_SIGNS.contains(name)) {
-            mType = VCardConfig.PARSE_TYPE_FOMA;
+            mParseType = PARSE_TYPE_DOCOMO_TORELATE_NEST;
         } else if (JAPANESE_MOBILE_PHONE_SIGNS.contains(name)) {
-            mType = VCardConfig.PARSE_TYPE_MOBILE_PHONE_JP;
+            mParseType = PARSE_TYPE_MOBILE_PHONE_JP;
         } else if (APPLE_SIGNS.contains(name)) {
-            mType = VCardConfig.PARSE_TYPE_APPLE;
+            mParseType = PARSE_TYPE_APPLE;
         }
     }
 
@@ -102,25 +130,40 @@
         }
     }
 
-    /* package */ int getEstimatedType() {
-        return mType;
-    }
-    
     /**
-     * Return charset String guessed from the source's properties.
+     * @return The available type can be used with vCard parser. You probably need to
+     * use {{@link #getEstimatedCharset()} to understand the charset to be used.
+     */
+    public int getEstimatedType() {
+        switch (mParseType) {
+            case PARSE_TYPE_DOCOMO_TORELATE_NEST:
+                return VCardConfig.VCARD_TYPE_DOCOMO | VCardConfig.FLAG_TORELATE_NEST;
+            case PARSE_TYPE_MOBILE_PHONE_JP:
+                return VCardConfig.VCARD_TYPE_V21_JAPANESE_MOBILE;
+            case PARSE_TYPE_APPLE:
+            case PARSE_TYPE_WINDOWS_MOBILE_V65_JP:
+            default:
+                return VCardConfig.VCARD_TYPE_UNKNOWN;
+        }
+    }
+
+    /**
+     * <p>
+     * Returns charset String guessed from the source's properties.
      * This method must be called after parsing target file(s).
+     * </p>
      * @return Charset String. Null is returned if guessing the source fails.
      */
     public String getEstimatedCharset() {
-        if (mSpecifiedCharset != null) {
+        if (TextUtils.isEmpty(mSpecifiedCharset)) {
             return mSpecifiedCharset;
         }
-        switch (mType) {
-            case VCardConfig.PARSE_TYPE_WINDOWS_MOBILE_JP:
-            case VCardConfig.PARSE_TYPE_FOMA:
-            case VCardConfig.PARSE_TYPE_MOBILE_PHONE_JP:
+        switch (mParseType) {
+            case PARSE_TYPE_WINDOWS_MOBILE_V65_JP:
+            case PARSE_TYPE_DOCOMO_TORELATE_NEST:
+            case PARSE_TYPE_MOBILE_PHONE_JP:
                 return "SHIFT_JIS";
-            case VCardConfig.PARSE_TYPE_APPLE:
+            case PARSE_TYPE_APPLE:
                 return "UTF-8";
             default:
                 return null;
diff --git a/core/java/android/pim/vcard/VCardUtils.java b/core/java/android/pim/vcard/VCardUtils.java
index 11b112b..680ef6f 100644
--- a/core/java/android/pim/vcard/VCardUtils.java
+++ b/core/java/android/pim/vcard/VCardUtils.java
@@ -22,7 +22,12 @@
 import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
 import android.telephony.PhoneNumberUtils;
 import android.text.TextUtils;
+import android.util.Log;
 
+import org.apache.commons.codec.DecoderException;
+import org.apache.commons.codec.net.QuotedPrintableCodec;
+
+import java.io.UnsupportedEncodingException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -34,8 +39,11 @@
 
 /**
  * Utilities for VCard handling codes.
+ * @hide
  */
 public class VCardUtils {
+    private static final String LOG_TAG = "VCardUtils";
+
     // Note that not all types are included in this map/set, since, for example, TYPE_HOME_FAX is
     // converted to two parameter Strings. These only contain some minor fields valid in both
     // vCard and current (as of 2009-08-07) Contacts structure. 
@@ -240,10 +248,13 @@
     }
 
     /**
+     * <p>
      * Inserts postal data into the builder object.
-     * 
+     * </p>
+     * <p>
      * Note that the data structure of ContactsContract is different from that defined in vCard.
      * So some conversion may be performed in this method.
+     * </p>
      */
     public static void insertStructuredPostalDataUsingContactsStruct(int vcardType,
             final ContentProviderOperation.Builder builder,
@@ -329,8 +340,8 @@
             if (ch == '\\' && i < length - 1) {
                 char nextCh = value.charAt(i + 1);
                 final String unescapedString =
-                    (isV30 ? VCardParser_V30.unescapeCharacter(nextCh) :
-                        VCardParser_V21.unescapeCharacter(nextCh));
+                    (isV30 ? VCardParserImpl_V30.unescapeCharacter(nextCh) :
+                        VCardParserImpl_V21.unescapeCharacter(nextCh));
                 if (unescapedString != null) {
                     builder.append(unescapedString);
                     i++;
@@ -371,9 +382,13 @@
     }
 
     /**
+     * <p>
      * This is useful when checking the string should be encoded into quoted-printable
      * or not, which is required by vCard 2.1.
+     * </p>
+     * <p>
      * See the definition of "7bit" in vCard 2.1 spec for more information.
+     * </p>
      */
     public static boolean containsOnlyNonCrLfPrintableAscii(final String...values) {
         if (values == null) {
@@ -407,13 +422,16 @@
         new HashSet<Character>(Arrays.asList('[', ']', '=', ':', '.', ',', ' '));
 
     /**
+     * <p>
      * This is useful since vCard 3.0 often requires the ("X-") properties and groups
      * should contain only alphabets, digits, and hyphen.
-     * 
+     * </p>
+     * <p> 
      * Note: It is already known some devices (wrongly) outputs properties with characters
      *       which should not be in the field. One example is "X-GOOGLE TALK". We accept
      *       such kind of input but must never output it unless the target is very specific
-     *       to the device which is able to parse the malformed input. 
+     *       to the device which is able to parse the malformed input.
+     * </p>
      */
     public static boolean containsOnlyAlphaDigitHyphen(final String...values) {
         if (values == null) {
@@ -452,13 +470,13 @@
     }
 
     /**
-     * <P>
+     * <p>
      * Returns true when the given String is categorized as "word" specified in vCard spec 2.1.
-     * </P>
-     * <P>
-     * vCard 2.1 specifies:<BR />
+     * </p>
+     * <p>
+     * vCard 2.1 specifies:<br />
      * word = &lt;any printable 7bit us-ascii except []=:., &gt;
-     * </P>
+     * </p>
      */
     public static boolean isV21Word(final String value) {
         if (TextUtils.isEmpty(value)) {
@@ -540,6 +558,96 @@
         return true;
     }
 
+    //// The methods bellow may be used by unit test.
+
+    /**
+     * @hide 
+     */
+    public static String parseQuotedPrintable(String value, boolean strictLineBreaking,
+            String sourceCharset, String targetCharset) {
+        // "= " -> " ", "=\t" -> "\t".
+        // Previous code had done this replacement. Keep on the safe side.
+        final String quotedPrintable;
+        {
+            final StringBuilder builder = new StringBuilder();
+            final int length = value.length();
+            for (int i = 0; i < length; i++) {
+                char ch = value.charAt(i);
+                if (ch == '=' && i < length - 1) {
+                    char nextCh = value.charAt(i + 1);
+                    if (nextCh == ' ' || nextCh == '\t') {
+                        builder.append(nextCh);
+                        i++;
+                        continue;
+                    }
+                }
+                builder.append(ch);
+            }
+            quotedPrintable = builder.toString();
+        }
+
+        String[] lines;
+        if (strictLineBreaking) {
+            lines = quotedPrintable.split("\r\n");
+        } else {
+            StringBuilder builder = new StringBuilder();
+            final int length = quotedPrintable.length();
+            ArrayList<String> list = new ArrayList<String>();
+            for (int i = 0; i < length; i++) {
+                char ch = quotedPrintable.charAt(i);
+                if (ch == '\n') {
+                    list.add(builder.toString());
+                    builder = new StringBuilder();
+                } else if (ch == '\r') {
+                    list.add(builder.toString());
+                    builder = new StringBuilder();
+                    if (i < length - 1) {
+                        char nextCh = quotedPrintable.charAt(i + 1);
+                        if (nextCh == '\n') {
+                            i++;
+                        }
+                    }
+                } else {
+                    builder.append(ch);
+                }
+            }
+            final String lastLine = builder.toString();
+            if (lastLine.length() > 0) {
+                list.add(lastLine);
+            }
+            lines = list.toArray(new String[0]);
+        }
+
+        final StringBuilder builder = new StringBuilder();
+        for (String line : lines) {
+            if (line.endsWith("=")) {
+                line = line.substring(0, line.length() - 1);
+            }
+            builder.append(line);
+        }
+        byte[] bytes;
+        try {
+            bytes = builder.toString().getBytes(sourceCharset);
+        } catch (UnsupportedEncodingException e1) {
+            Log.e(LOG_TAG, "Failed to encode: charset=" + targetCharset);
+            bytes = builder.toString().getBytes();
+        }
+
+        try {
+            bytes = QuotedPrintableCodec.decodeQuotedPrintable(bytes);
+        } catch (DecoderException e) {
+            Log.e(LOG_TAG, "Failed to decode quoted-printable: " + e);
+            return "";
+        }
+
+        try {
+            return new String(bytes, targetCharset);
+        } catch (UnsupportedEncodingException e) {
+            Log.e(LOG_TAG, "Failed to encode: charset=" + targetCharset);
+            return new String(bytes);
+        }
+    }
+
     private VCardUtils() {
     }
 }
diff --git a/core/java/android/preference/MultiSelectListPreference.java b/core/java/android/preference/MultiSelectListPreference.java
new file mode 100644
index 0000000..42d555cf
--- /dev/null
+++ b/core/java/android/preference/MultiSelectListPreference.java
@@ -0,0 +1,274 @@
+/*
+ * 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.preference;
+
+import android.app.AlertDialog.Builder;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.res.TypedArray;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.AttributeSet;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * A {@link Preference} that displays a list of entries as
+ * a dialog.
+ * <p>
+ * This preference will store a set of strings into the SharedPreferences.
+ * This set will contain one or more values from the
+ * {@link #setEntryValues(CharSequence[])} array.
+ * 
+ * @attr ref android.R.styleable#MultiSelectListPreference_entries
+ * @attr ref android.R.styleable#MultiSelectListPreference_entryValues
+ */
+public class MultiSelectListPreference extends DialogPreference {
+    private CharSequence[] mEntries;
+    private CharSequence[] mEntryValues;
+    private Set<String> mValues = new HashSet<String>();
+    private Set<String> mNewValues = new HashSet<String>();
+    private boolean mPreferenceChanged;
+    
+    public MultiSelectListPreference(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        
+        TypedArray a = context.obtainStyledAttributes(attrs,
+                com.android.internal.R.styleable.MultiSelectListPreference, 0, 0);
+        mEntries = a.getTextArray(com.android.internal.R.styleable.MultiSelectListPreference_entries);
+        mEntryValues = a.getTextArray(com.android.internal.R.styleable.MultiSelectListPreference_entryValues);
+        a.recycle();
+    }
+    
+    public MultiSelectListPreference(Context context) {
+        this(context, null);
+    }
+    
+    /**
+     * Sets the human-readable entries to be shown in the list. This will be
+     * shown in subsequent dialogs.
+     * <p>
+     * Each entry must have a corresponding index in
+     * {@link #setEntryValues(CharSequence[])}.
+     * 
+     * @param entries The entries.
+     * @see #setEntryValues(CharSequence[])
+     */
+    public void setEntries(CharSequence[] entries) {
+        mEntries = entries;
+    }
+    
+    /**
+     * @see #setEntries(CharSequence[])
+     * @param entriesResId The entries array as a resource.
+     */
+    public void setEntries(int entriesResId) {
+        setEntries(getContext().getResources().getTextArray(entriesResId));
+    }
+    
+    /**
+     * The list of entries to be shown in the list in subsequent dialogs.
+     * 
+     * @return The list as an array.
+     */
+    public CharSequence[] getEntries() {
+        return mEntries;
+    }
+    
+    /**
+     * The array to find the value to save for a preference when an entry from
+     * entries is selected. If a user clicks on the second item in entries, the
+     * second item in this array will be saved to the preference.
+     * 
+     * @param entryValues The array to be used as values to save for the preference.
+     */
+    public void setEntryValues(CharSequence[] entryValues) {
+        mEntryValues = entryValues;
+    }
+
+    /**
+     * @see #setEntryValues(CharSequence[])
+     * @param entryValuesResId The entry values array as a resource.
+     */
+    public void setEntryValues(int entryValuesResId) {
+        setEntryValues(getContext().getResources().getTextArray(entryValuesResId));
+    }
+    
+    /**
+     * Returns the array of values to be saved for the preference.
+     * 
+     * @return The array of values.
+     */
+    public CharSequence[] getEntryValues() {
+        return mEntryValues;
+    }
+    
+    /**
+     * Sets the value of the key. This should contain entries in
+     * {@link #getEntryValues()}.
+     * 
+     * @param values The values to set for the key.
+     */
+    public void setValues(Set<String> values) {
+        mValues = values;
+        
+        persistStringSet(values);
+    }
+    
+    /**
+     * Retrieves the current value of the key.
+     */
+    public Set<String> getValues() {
+        return mValues;
+    }
+    
+    /**
+     * Returns the index of the given value (in the entry values array).
+     * 
+     * @param value The value whose index should be returned.
+     * @return The index of the value, or -1 if not found.
+     */
+    public int findIndexOfValue(String value) {
+        if (value != null && mEntryValues != null) {
+            for (int i = mEntryValues.length - 1; i >= 0; i--) {
+                if (mEntryValues[i].equals(value)) {
+                    return i;
+                }
+            }
+        }
+        return -1;
+    }
+    
+    @Override
+    protected void onPrepareDialogBuilder(Builder builder) {
+        super.onPrepareDialogBuilder(builder);
+        
+        if (mEntries == null || mEntryValues == null) {
+            throw new IllegalStateException(
+                    "MultiSelectListPreference requires an entries array and " +
+                    "an entryValues array.");
+        }
+        
+        boolean[] checkedItems = getSelectedItems();
+        builder.setMultiChoiceItems(mEntries, checkedItems,
+                new DialogInterface.OnMultiChoiceClickListener() {
+                    public void onClick(DialogInterface dialog, int which, boolean isChecked) {
+                        if (isChecked) {
+                            mPreferenceChanged |= mNewValues.add(mEntries[which].toString());
+                        } else {
+                            mPreferenceChanged |= mNewValues.remove(mEntries[which].toString());
+                        }
+                    }
+                });
+        mNewValues.clear();
+        mNewValues.addAll(mValues);
+    }
+    
+    private boolean[] getSelectedItems() {
+        final CharSequence[] entries = mEntries;
+        final int entryCount = entries.length;
+        final Set<String> values = mValues;
+        boolean[] result = new boolean[entryCount];
+        
+        for (int i = 0; i < entryCount; i++) {
+            result[i] = values.contains(entries[i].toString());
+        }
+        
+        return result;
+    }
+    
+    @Override
+    protected void onDialogClosed(boolean positiveResult) {
+        super.onDialogClosed(positiveResult);
+        
+        if (positiveResult && mPreferenceChanged) {
+            final Set<String> values = mNewValues;
+            if (callChangeListener(values)) {
+                setValues(values);
+            }
+        }
+        mPreferenceChanged = false;
+    }
+    
+    @Override
+    protected Object onGetDefaultValue(TypedArray a, int index) {
+        final CharSequence[] defaultValues = a.getTextArray(index);
+        final int valueCount = defaultValues.length;
+        final Set<String> result = new HashSet<String>();
+        
+        for (int i = 0; i < valueCount; i++) {
+            result.add(defaultValues[i].toString());
+        }
+        
+        return result;
+    }
+    
+    @Override
+    protected void onSetInitialValue(boolean restoreValue, Object defaultValue) {
+        setValues(restoreValue ? getPersistedStringSet(mValues) : (Set<String>) defaultValue);
+    }
+    
+    @Override
+    protected Parcelable onSaveInstanceState() {
+        final Parcelable superState = super.onSaveInstanceState();
+        if (isPersistent()) {
+            // No need to save instance state
+            return superState;
+        }
+        
+        final SavedState myState = new SavedState(superState);
+        myState.values = getValues();
+        return myState;
+    }
+    
+    private static class SavedState extends BaseSavedState {
+        Set<String> values;
+        
+        public SavedState(Parcel source) {
+            super(source);
+            values = new HashSet<String>();
+            String[] strings = source.readStringArray();
+            
+            final int stringCount = strings.length;
+            for (int i = 0; i < stringCount; i++) {
+                values.add(strings[i]);
+            }
+        }
+        
+        public SavedState(Parcelable superState) {
+            super(superState);
+        }
+        
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            super.writeToParcel(dest, flags);
+            dest.writeStringArray(values.toArray(new String[0]));
+        }
+        
+        public static final Parcelable.Creator<SavedState> CREATOR =
+                new Parcelable.Creator<SavedState>() {
+            public SavedState createFromParcel(Parcel in) {
+                return new SavedState(in);
+            }
+            
+            public SavedState[] newArray(int size) {
+                return new SavedState[size];
+            }
+        };
+    }
+}
diff --git a/core/java/android/preference/Preference.java b/core/java/android/preference/Preference.java
index 197d976..381f794 100644
--- a/core/java/android/preference/Preference.java
+++ b/core/java/android/preference/Preference.java
@@ -16,8 +16,7 @@
 
 package android.preference;
 
-import java.util.ArrayList;
-import java.util.List;
+import com.android.internal.util.CharSequences;
 
 import android.content.Context;
 import android.content.Intent;
@@ -28,7 +27,6 @@
 import android.os.Parcelable;
 import android.text.TextUtils;
 import android.util.AttributeSet;
-import com.android.internal.util.CharSequences;
 import android.view.AbsSavedState;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -36,6 +34,10 @@
 import android.widget.ListView;
 import android.widget.TextView;
 
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
 /**
  * Represents the basic Preference UI building
  * block displayed by a {@link PreferenceActivity} in the form of a
@@ -1250,6 +1252,61 @@
     }
     
     /**
+     * Attempts to persist a set of Strings to the {@link android.content.SharedPreferences}.
+     * <p>
+     * This will check if this Preference is persistent, get an editor from
+     * the {@link PreferenceManager}, put in the strings, and check if we should commit (and
+     * commit if so).
+     * 
+     * @param values The values to persist.
+     * @return True if the Preference is persistent. (This is not whether the
+     *         value was persisted, since we may not necessarily commit if there
+     *         will be a batch commit later.)
+     * @see #getPersistedString(Set)
+     * 
+     * @hide Pending API approval
+     */
+    protected boolean persistStringSet(Set<String> values) {
+        if (shouldPersist()) {
+            // Shouldn't store null
+            if (values.equals(getPersistedStringSet(null))) {
+                // It's already there, so the same as persisting
+                return true;
+            }
+            
+            SharedPreferences.Editor editor = mPreferenceManager.getEditor();
+            editor.putStringSet(mKey, values);
+            tryCommit(editor);
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Attempts to get a persisted set of Strings from the
+     * {@link android.content.SharedPreferences}.
+     * <p>
+     * This will check if this Preference is persistent, get the SharedPreferences
+     * from the {@link PreferenceManager}, and get the value.
+     * 
+     * @param defaultReturnValue The default value to return if either the
+     *            Preference is not persistent or the Preference is not in the
+     *            shared preferences.
+     * @return The value from the SharedPreferences or the default return
+     *         value.
+     * @see #persistStringSet(Set)
+     * 
+     * @hide Pending API approval
+     */
+    protected Set<String> getPersistedStringSet(Set<String> defaultReturnValue) {
+        if (!shouldPersist()) {
+            return defaultReturnValue;
+        }
+        
+        return mPreferenceManager.getSharedPreferences().getStringSet(mKey, defaultReturnValue);
+    }
+    
+    /**
      * Attempts to persist an int to the {@link android.content.SharedPreferences}.
      * 
      * @param value The value to persist.
diff --git a/core/java/android/preference/PreferenceActivity.java b/core/java/android/preference/PreferenceActivity.java
index 726793d..4686978 100644
--- a/core/java/android/preference/PreferenceActivity.java
+++ b/core/java/android/preference/PreferenceActivity.java
@@ -23,7 +23,10 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Message;
+import android.text.TextUtils;
 import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.Button;
 
 /**
  * Shows a hierarchy of {@link Preference} objects as
@@ -69,30 +72,43 @@
  * As a convenience, this activity implements a click listener for any
  * preference in the current hierarchy, see
  * {@link #onPreferenceTreeClick(PreferenceScreen, Preference)}.
- * 
+ *
  * @see Preference
  * @see PreferenceScreen
  */
 public abstract class PreferenceActivity extends ListActivity implements
         PreferenceManager.OnPreferenceTreeClickListener {
-    
+
     private static final String PREFERENCES_TAG = "android:preferences";
-    
+
+    // extras that allow any preference activity to be launched as part of a wizard
+
+    // show Back and Next buttons? takes boolean parameter
+    // Back will then return RESULT_CANCELED and Next RESULT_OK
+    private static final String EXTRA_PREFS_SHOW_BUTTON_BAR = "extra_prefs_show_button_bar";
+
+    // specify custom text for the Back or Next buttons, or cause a button to not appear
+    // at all by setting it to null
+    private static final String EXTRA_PREFS_SET_NEXT_TEXT = "extra_prefs_set_next_text";
+    private static final String EXTRA_PREFS_SET_BACK_TEXT = "extra_prefs_set_back_text";
+
+    private Button mNextButton;
+
     private PreferenceManager mPreferenceManager;
-    
+
     private Bundle mSavedInstanceState;
 
     /**
      * The starting request code given out to preference framework.
      */
     private static final int FIRST_REQUEST_CODE = 100;
-    
+
     private static final int MSG_BIND_PREFERENCES = 0;
     private Handler mHandler = new Handler() {
         @Override
         public void handleMessage(Message msg) {
             switch (msg.what) {
-                
+
                 case MSG_BIND_PREFERENCES:
                     bindPreferences();
                     break;
@@ -105,7 +121,49 @@
         super.onCreate(savedInstanceState);
 
         setContentView(com.android.internal.R.layout.preference_list_content);
-        
+
+        // see if we should show Back/Next buttons
+        Intent intent = getIntent();
+        if (intent.getBooleanExtra(EXTRA_PREFS_SHOW_BUTTON_BAR, false)) {
+
+            findViewById(com.android.internal.R.id.button_bar).setVisibility(View.VISIBLE);
+
+            Button backButton = (Button)findViewById(com.android.internal.R.id.back_button);
+            backButton.setOnClickListener(new OnClickListener() {
+                public void onClick(View v) {
+                    setResult(RESULT_CANCELED);
+                    finish();
+                }
+            });
+            mNextButton = (Button)findViewById(com.android.internal.R.id.next_button);
+            mNextButton.setOnClickListener(new OnClickListener() {
+                public void onClick(View v) {
+                    setResult(RESULT_OK);
+                    finish();
+                }
+            });
+
+            // set our various button parameters
+            if (intent.hasExtra(EXTRA_PREFS_SET_NEXT_TEXT)) {
+                String buttonText = intent.getStringExtra(EXTRA_PREFS_SET_NEXT_TEXT);
+                if (TextUtils.isEmpty(buttonText)) {
+                    mNextButton.setVisibility(View.GONE);
+                }
+                else {
+                    mNextButton.setText(buttonText);
+                }
+            }
+            if (intent.hasExtra(EXTRA_PREFS_SET_BACK_TEXT)) {
+                String buttonText = intent.getStringExtra(EXTRA_PREFS_SET_BACK_TEXT);
+                if (TextUtils.isEmpty(buttonText)) {
+                    backButton.setVisibility(View.GONE);
+                }
+                else {
+                    backButton.setText(buttonText);
+                }
+            }
+        }
+
         mPreferenceManager = onCreatePreferenceManager();
         getListView().setScrollBarStyle(View.SCROLLBARS_INSIDE_OVERLAY);
     }
@@ -113,14 +171,13 @@
     @Override
     protected void onStop() {
         super.onStop();
-        
+
         mPreferenceManager.dispatchActivityStop();
     }
 
     @Override
     protected void onDestroy() {
         super.onDestroy();
-        
         mPreferenceManager.dispatchActivityDestroy();
     }
 
@@ -156,7 +213,7 @@
     @Override
     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
         super.onActivityResult(requestCode, resultCode, data);
-        
+
         mPreferenceManager.dispatchActivityResult(requestCode, resultCode, data);
     }
 
@@ -176,7 +233,7 @@
         if (mHandler.hasMessages(MSG_BIND_PREFERENCES)) return;
         mHandler.obtainMessage(MSG_BIND_PREFERENCES).sendToTarget();
     }
-    
+
     private void bindPreferences() {
         final PreferenceScreen preferenceScreen = getPreferenceScreen();
         if (preferenceScreen != null) {
@@ -187,10 +244,10 @@
             }
         }
     }
-    
+
     /**
      * Creates the {@link PreferenceManager}.
-     * 
+     *
      * @return The {@link PreferenceManager} used by this activity.
      */
     private PreferenceManager onCreatePreferenceManager() {
@@ -198,7 +255,7 @@
         preferenceManager.setOnPreferenceTreeClickListener(this);
         return preferenceManager;
     }
-    
+
     /**
      * Returns the {@link PreferenceManager} used by this activity.
      * @return The {@link PreferenceManager}.
@@ -206,7 +263,7 @@
     public PreferenceManager getPreferenceManager() {
         return mPreferenceManager;
     }
-    
+
     private void requirePreferenceManager() {
         if (mPreferenceManager == null) {
             throw new RuntimeException("This should be called after super.onCreate.");
@@ -215,7 +272,7 @@
 
     /**
      * Sets the root of the preference hierarchy that this activity is showing.
-     * 
+     *
      * @param preferenceScreen The root {@link PreferenceScreen} of the preference hierarchy.
      */
     public void setPreferenceScreen(PreferenceScreen preferenceScreen) {
@@ -228,37 +285,37 @@
             }
         }
     }
-    
+
     /**
      * Gets the root of the preference hierarchy that this activity is showing.
-     * 
+     *
      * @return The {@link PreferenceScreen} that is the root of the preference
      *         hierarchy.
      */
     public PreferenceScreen getPreferenceScreen() {
         return mPreferenceManager.getPreferenceScreen();
     }
-    
+
     /**
      * Adds preferences from activities that match the given {@link Intent}.
-     * 
+     *
      * @param intent The {@link Intent} to query activities.
      */
     public void addPreferencesFromIntent(Intent intent) {
         requirePreferenceManager();
-        
+
         setPreferenceScreen(mPreferenceManager.inflateFromIntent(intent, getPreferenceScreen()));
     }
-    
+
     /**
      * Inflates the given XML resource and adds the preference hierarchy to the current
      * preference hierarchy.
-     * 
+     *
      * @param preferencesResId The XML resource ID to inflate.
      */
     public void addPreferencesFromResource(int preferencesResId) {
         requirePreferenceManager();
-        
+
         setPreferenceScreen(mPreferenceManager.inflateFromResource(this, preferencesResId,
                 getPreferenceScreen()));
     }
@@ -269,20 +326,20 @@
     public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
         return false;
     }
-    
+
     /**
      * Finds a {@link Preference} based on its key.
-     * 
+     *
      * @param key The key of the preference to retrieve.
      * @return The {@link Preference} with the key, or null.
      * @see PreferenceGroup#findPreference(CharSequence)
      */
     public Preference findPreference(CharSequence key) {
-        
+
         if (mPreferenceManager == null) {
             return null;
         }
-        
+
         return mPreferenceManager.findPreference(key);
     }
 
@@ -292,5 +349,14 @@
             mPreferenceManager.dispatchNewIntent(intent);
         }
     }
-    
+
+    // give subclasses access to the Next button
+    /** @hide */
+    protected boolean hasNextButton() {
+        return mNextButton != null;
+    }
+    /** @hide */
+    protected Button getNextButton() {
+        return mNextButton;
+    }
 }
diff --git a/core/java/android/provider/Calendar.java b/core/java/android/provider/Calendar.java
index 9a09805..8459b2d 100644
--- a/core/java/android/provider/Calendar.java
+++ b/core/java/android/provider/Calendar.java
@@ -438,6 +438,18 @@
         public static final String DTEND = "dtend";
 
         /**
+         * The time the event starts with allDay events in a local tz
+         * <P>Type: INTEGER (long; millis since epoch)</P>
+         */
+        public static final String DTSTART2 = "dtstart2";
+
+        /**
+         * The time the event ends with allDay events in a local tz
+         * <P>Type: INTEGER (long; millis since epoch)</P>
+         */
+        public static final String DTEND2 = "dtend2";
+
+        /**
          * The duration of the event
          * <P>Type: TEXT (duration in RFC2445 format)</P>
          */
@@ -450,6 +462,12 @@
         public static final String EVENT_TIMEZONE = "eventTimezone";
 
         /**
+         * The timezone for the event, allDay events will have a local tz instead of UTC
+         * <P>Type: TEXT
+         */
+        public static final String EVENT_TIMEZONE2 = "eventTimezone2";
+
+        /**
          * Whether the event lasts all day or not
          * <P>Type: INTEGER (boolean)</P>
          */
diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java
index 40a408a..abeb931 100644
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -4902,6 +4902,23 @@
          * Type: INTEGER (boolean)
          */
         public static final String SHOULD_SYNC = "should_sync";
+
+        /**
+         * Any newly created contacts will automatically be added to groups that have this
+         * flag set to true.
+         * <p>
+         * Type: INTEGER (boolean)
+         */
+        public static final String AUTO_ADD = "auto_add";
+
+        /**
+         * When a contacts is marked as a favorites it will be automatically added
+         * to the groups that have this flag set, and when it is removed from favorites
+         * it will be removed from these groups.
+         * <p>
+         * Type: INTEGER (boolean)
+         */
+        public static final String FAVORITES = "favorites";
     }
 
     /**
@@ -5042,6 +5059,8 @@
                 DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, values, DELETED);
                 DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, values, NOTES);
                 DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, values, SHOULD_SYNC);
+                DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, values, FAVORITES);
+                DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, values, AUTO_ADD);
                 cursor.moveToNext();
                 return new Entity(values);
             }
@@ -5558,6 +5577,28 @@
                 "com.android.contacts.action.SHOW_OR_CREATE_CONTACT";
 
         /**
+         * Starts an Activity that lets the user select the multiple phones from a
+         * list of phone numbers which come from the contacts or
+         * {@link #EXTRA_PHONE_URIS}.
+         * <p>
+         * The phone numbers being passed in through {@link #EXTRA_PHONE_URIS}
+         * could belong to the contacts or not, and will be selected by default.
+         * <p>
+         * The user's selection will be returned from
+         * {@link android.app.Activity#onActivityResult(int, int, android.content.Intent)}
+         * if the resultCode is
+         * {@link android.app.Activity#RESULT_OK}, the array of picked phone
+         * numbers are in the Intent's
+         * {@link #EXTRA_PHONE_URIS}; otherwise, the
+         * {@link android.app.Activity#RESULT_CANCELED} is returned if the user
+         * left the Activity without changing the selection.
+         *
+         * @hide
+         */
+        public static final String ACTION_GET_MULTIPLE_PHONES =
+                "com.android.contacts.action.GET_MULTIPLE_PHONES";
+
+        /**
          * Used with {@link #SHOW_OR_CREATE_CONTACT} to force creating a new
          * contact if no matching contact found. Otherwise, default behavior is
          * to prompt user with dialog before creating.
@@ -5578,6 +5619,23 @@
             "com.android.contacts.action.CREATE_DESCRIPTION";
 
         /**
+         * Used with {@link #ACTION_GET_MULTIPLE_PHONES} as the input or output value.
+         * <p>
+         * The phone numbers want to be picked by default should be passed in as
+         * input value. These phone numbers could belong to the contacts or not.
+         * <p>
+         * The phone numbers which were picked by the user are returned as output
+         * value.
+         * <p>
+         * Type: array of URIs, the tel URI is used for the phone numbers which don't
+         * belong to any contact, the content URI is used for phone id in contacts.
+         *
+         * @hide
+         */
+        public static final String EXTRA_PHONE_URIS =
+            "com.android.contacts.extra.PHONE_URIS";
+
+        /**
          * Optional extra used with {@link #SHOW_OR_CREATE_CONTACT} to specify a
          * dialog location using screen coordinates. When not specified, the
          * dialog will be centered.
diff --git a/core/java/android/server/BluetoothA2dpService.java b/core/java/android/server/BluetoothA2dpService.java
index 893db2e..ac89934 100644
--- a/core/java/android/server/BluetoothA2dpService.java
+++ b/core/java/android/server/BluetoothA2dpService.java
@@ -554,6 +554,7 @@
         if (!result) {
             if (deviceObjectPath != null) {
                 String address = mBluetoothService.getAddressFromObjectPath(deviceObjectPath);
+                if (address == null) return;
                 BluetoothDevice device = mAdapter.getRemoteDevice(address);
                 int state = getSinkState(device);
                 handleSinkStateChange(device, state, BluetoothA2dp.STATE_DISCONNECTED);
diff --git a/core/java/android/text/AndroidBidi.java b/core/java/android/text/AndroidBidi.java
index e4f934e..eacd40d 100644
--- a/core/java/android/text/AndroidBidi.java
+++ b/core/java/android/text/AndroidBidi.java
@@ -16,6 +16,8 @@
 
 package android.text;
 
+import android.text.Layout.Directions;
+
 /**
  * Access the ICU bidi implementation.
  * @hide
@@ -44,5 +46,132 @@
         return result;
     }
 
+    /**
+     * Returns run direction information for a line within a paragraph.
+     *
+     * @param dir base line direction, either Layout.DIR_LEFT_TO_RIGHT or
+     *     Layout.DIR_RIGHT_TO_LEFT
+     * @param levels levels as returned from {@link #bidi}
+     * @param lstart start of the line in the levels array
+     * @param chars the character array (used to determine whitespace)
+     * @param cstart the start of the line in the chars array
+     * @param len the length of the line
+     * @return the directions
+     */
+    public static Directions directions(int dir, byte[] levels, int lstart,
+            char[] chars, int cstart, int len) {
+
+        int baseLevel = dir == Layout.DIR_LEFT_TO_RIGHT ? 0 : 1;
+        int curLevel = levels[lstart];
+        int minLevel = curLevel;
+        int runCount = 1;
+        for (int i = lstart + 1, e = lstart + len; i < e; ++i) {
+            int level = levels[i];
+            if (level != curLevel) {
+                curLevel = level;
+                ++runCount;
+            }
+        }
+
+        // add final run for trailing counter-directional whitespace
+        int visLen = len;
+        if ((curLevel & 1) != (baseLevel & 1)) {
+            // look for visible end
+            while (--visLen >= 0) {
+                char ch = chars[cstart + visLen];
+
+                if (ch == '\n') {
+                    --visLen;
+                    break;
+                }
+
+                if (ch != ' ' && ch != '\t') {
+                    break;
+                }
+            }
+            ++visLen;
+            if (visLen != len) {
+                ++runCount;
+            }
+        }
+
+        if (runCount == 1 && minLevel == baseLevel) {
+            // we're done, only one run on this line
+            if ((minLevel & 1) != 0) {
+                return Layout.DIRS_ALL_RIGHT_TO_LEFT;
+            }
+            return Layout.DIRS_ALL_LEFT_TO_RIGHT;
+        }
+
+        int[] ld = new int[runCount * 2];
+        int maxLevel = minLevel;
+        int levelBits = minLevel << Layout.RUN_LEVEL_SHIFT;
+        {
+            // Start of first pair is always 0, we write
+            // length then start at each new run, and the
+            // last run length after we're done.
+            int n = 1;
+            int prev = lstart;
+            curLevel = minLevel;
+            for (int i = lstart, e = lstart + visLen; i < e; ++i) {
+                int level = levels[i];
+                if (level != curLevel) {
+                    curLevel = level;
+                    if (level > maxLevel) {
+                        maxLevel = level;
+                    } else if (level < minLevel) {
+                        minLevel = level;
+                    }
+                    // XXX ignore run length limit of 2^RUN_LEVEL_SHIFT
+                    ld[n++] = (i - prev) | levelBits;
+                    ld[n++] = i - lstart;
+                    levelBits = curLevel << Layout.RUN_LEVEL_SHIFT;
+                    prev = i;
+                }
+            }
+            ld[n] = (lstart + visLen - prev) | levelBits;
+            if (visLen < len) {
+                ld[++n] = visLen;
+                ld[++n] = (len - visLen) | (baseLevel << Layout.RUN_LEVEL_SHIFT);
+            }
+        }
+
+        // See if we need to swap any runs.
+        // If the min level run direction doesn't match the base
+        // direction, we always need to swap (at this point
+        // we have more than one run).
+        // Otherwise, we don't need to swap the lowest level.
+        // Since there are no logically adjacent runs at the same
+        // level, if the max level is the same as the (new) min
+        // level, we have a series of alternating levels that
+        // is already in order, so there's no more to do.
+        //
+        boolean swap;
+        if ((minLevel & 1) == baseLevel) {
+            minLevel += 1;
+            swap = maxLevel > minLevel;
+        } else {
+            swap = runCount > 1;
+        }
+        if (swap) {
+            for (int level = maxLevel - 1; level >= minLevel; --level) {
+                for (int i = 0; i < ld.length; i += 2) {
+                    if (levels[ld[i]] >= level) {
+                        int e = i + 2;
+                        while (e < ld.length && levels[ld[e]] >= level) {
+                            e += 2;
+                        }
+                        for (int low = i, hi = e - 2; low < hi; low += 2, hi -= 2) {
+                            int x = ld[low]; ld[low] = ld[hi]; ld[hi] = x;
+                            x = ld[low+1]; ld[low+1] = ld[hi+1]; ld[hi+1] = x;
+                        }
+                        i = e + 2;
+                    }
+                }
+            }
+        }
+        return new Directions(ld);
+    }
+
     private native static int runBidi(int dir, char[] chs, byte[] chInfo, int n, boolean haveInfo);
 }
\ No newline at end of file
diff --git a/core/java/android/text/BoringLayout.java b/core/java/android/text/BoringLayout.java
index 944f735..9309b05 100644
--- a/core/java/android/text/BoringLayout.java
+++ b/core/java/android/text/BoringLayout.java
@@ -208,11 +208,11 @@
              * width because the width that was passed in was for the
              * full text, not the ellipsized form.
              */
-            synchronized (sTemp) {
-                mMax = (int) (FloatMath.ceil(Styled.measureText(paint, sTemp,
-                                                source, 0, source.length(),
-                                                null)));
-            }
+            TextLine line = TextLine.obtain();
+            line.set(paint, source, 0, source.length(), Layout.DIR_LEFT_TO_RIGHT,
+                    Layout.DIRS_ALL_LEFT_TO_RIGHT, false, null);
+            mMax = (int) FloatMath.ceil(line.metrics(null));
+            TextLine.recycle(line);
         }
 
         if (includepad) {
@@ -276,14 +276,13 @@
             if (fm == null) {
                 fm = new Metrics();
             }
-    
-            int wid;
 
-            synchronized (sTemp) {
-                wid = (int) (FloatMath.ceil(Styled.measureText(paint, sTemp,
-                                                text, 0, text.length(), fm)));
-            }
-            fm.width = wid;
+            TextLine line = TextLine.obtain();
+            line.set(paint, text, 0, text.length(), Layout.DIR_LEFT_TO_RIGHT,
+                    Layout.DIRS_ALL_LEFT_TO_RIGHT, false, null);
+            fm.width = (int) FloatMath.ceil(line.metrics(fm));
+            TextLine.recycle(line);
+
             return fm;
         } else {
             return null;
@@ -389,7 +388,7 @@
 
     public static class Metrics extends Paint.FontMetricsInt {
         public int width;
-        
+
         @Override public String toString() {
             return super.toString() + " width=" + width;
         }
diff --git a/core/java/android/text/DynamicLayout.java b/core/java/android/text/DynamicLayout.java
index 14e5655..b6aa03a 100644
--- a/core/java/android/text/DynamicLayout.java
+++ b/core/java/android/text/DynamicLayout.java
@@ -310,7 +310,6 @@
 
         Directions[] objects = new Directions[1];
 
-
         for (int i = 0; i < n; i++) {
             ints[START] = reflowed.getLineStart(i) |
                           (reflowed.getParagraphDirection(i) << DIR_SHIFT) |
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index 38ac9b7..3b8f295 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -16,25 +16,29 @@
 
 package android.text;
 
-import android.emoji.EmojiFactory;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.graphics.Path;
 import com.android.internal.util.ArrayUtils;
 
-import junit.framework.Assert;
-import android.text.style.*;
+import android.emoji.EmojiFactory;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.Rect;
 import android.text.method.TextKeyListener;
+import android.text.style.AlignmentSpan;
+import android.text.style.LeadingMarginSpan;
+import android.text.style.LineBackgroundSpan;
+import android.text.style.ParagraphStyle;
+import android.text.style.ReplacementSpan;
+import android.text.style.TabStopSpan;
 import android.view.KeyEvent;
 
+import junit.framework.Assert;
+
 /**
- * A base class that manages text layout in visual elements on 
- * the screen. 
- * <p>For text that will be edited, use a {@link DynamicLayout}, 
- * which will be updated as the text changes.  
+ * A base class that manages text layout in visual elements on
+ * the screen.
+ * <p>For text that will be edited, use a {@link DynamicLayout},
+ * which will be updated as the text changes.
  * For text that will not change, use a {@link StaticLayout}.
  */
 public abstract class Layout {
@@ -54,9 +58,7 @@
             MIN_EMOJI = -1;
             MAX_EMOJI = -1;
         }
-    };
-
-    private RectF mEmojiRect;
+    }
 
     /**
      * Return how wide a layout must be in order to display the
@@ -66,7 +68,7 @@
                                         TextPaint paint) {
         return getDesiredWidth(source, 0, source.length(), paint);
     }
-    
+
     /**
      * Return how wide a layout must be in order to display the
      * specified text slice with one line per paragraph.
@@ -85,8 +87,8 @@
                 next = end;
 
             // note, omits trailing paragraph char
-            float w = measureText(paint, workPaint,
-                                  source, i, next, null, true, null);
+            float w = measurePara(paint, workPaint,
+                                  source, i, next, true, null);
 
             if (w > need)
                 need = w;
@@ -116,6 +118,15 @@
         if (width < 0)
             throw new IllegalArgumentException("Layout: " + width + " < 0");
 
+        // Ensure paint doesn't have baselineShift set.
+        // While normally we don't modify the paint the user passed in,
+        // we were already doing this in Styled.drawUniformRun with both
+        // baselineShift and bgColor.  We probably should reevaluate bgColor.
+        if (paint != null) {
+            paint.bgColor = 0;
+            paint.baselineShift = 0;
+        }
+
         mText = text;
         mPaint = paint;
         mWorkPaint = new TextPaint();
@@ -185,13 +196,13 @@
         if (dbottom < bottom) {
             bottom = dbottom;
         }
-        
-        int first = getLineForVertical(top); 
+
+        int first = getLineForVertical(top);
         int last = getLineForVertical(bottom);
-        
+
         int previousLineBottom = getLineTop(first);
         int previousLineEnd = getLineStart(first);
-        
+
         TextPaint paint = mPaint;
         CharSequence buf = mText;
         int width = mWidth;
@@ -238,7 +249,7 @@
             previousLineBottom = getLineTop(first);
             previousLineEnd = getLineStart(first);
             spans = NO_PARA_SPANS;
-        } 
+        }
 
         // There can be a highlight even without spans if we are drawing
         // a non-spanned transformation of a spanned editing buffer.
@@ -255,7 +266,8 @@
         }
 
         Alignment align = mAlignment;
-        
+
+        TextLine tl = TextLine.obtain();
         // Next draw the lines, one at a time.
         // the baseline is the top of the following line minus the current
         // line's descent.
@@ -271,7 +283,7 @@
             int lbaseline = lbottom - getLineDescent(i);
 
             boolean isFirstParaLine = false;
-            if (spannedText) { 
+            if (spannedText) {
                 if (start == 0 || buf.charAt(start - 1) == '\n') {
                     isFirstParaLine = true;
                 }
@@ -282,7 +294,7 @@
                     spanend = sp.nextSpanTransition(start, textLength,
                                                     ParagraphStyle.class);
                     spans = sp.getSpans(start, spanend, ParagraphStyle.class);
-                    
+
                     align = mAlignment;
                     for (int n = spans.length-1; n >= 0; n--) {
                         if (spans[n] instanceof AlignmentSpan) {
@@ -292,7 +304,7 @@
                     }
                 }
             }
-            
+
             int dir = getParagraphDirection(i);
             int left = 0;
             int right = mWidth;
@@ -309,7 +321,7 @@
                             margin.drawLeadingMargin(c, paint, right, dir, ltop,
                                                      lbaseline, lbottom, buf,
                                                      start, end, isFirstParaLine, this);
-                                
+
                             right -= margin.getLeadingMargin(isFirstParaLine);
                         } else {
                             margin.drawLeadingMargin(c, paint, left, dir, ltop,
@@ -367,11 +379,11 @@
                 // XXX: assumes there's nothing additional to be done
                 c.drawText(buf, start, end, x, lbaseline, paint);
             } else {
-                drawText(c, buf, start, end, dir, directions,
-                    x, ltop, lbaseline, lbottom, paint, mWorkPaint,
-                    hasTab, spans);
+                tl.set(paint, buf, start, end, dir, directions, hasTab, spans);
+                tl.draw(c, x, ltop, lbaseline, lbottom);
             }
         }
+        TextLine.recycle(tl);
     }
 
     /**
@@ -417,7 +429,7 @@
 
         mWidth = wid;
     }
-    
+
     /**
      * Return the total height of this layout.
      */
@@ -450,7 +462,7 @@
      * Return the number of lines of text in this layout.
      */
     public abstract int getLineCount();
-    
+
     /**
      * Return the baseline for the specified line (0&hellip;getLineCount() - 1)
      * If bounds is not null, return the top, left, right, bottom extents
@@ -524,13 +536,95 @@
      */
     public abstract int getBottomPadding();
 
+
+    /**
+     * Returns true if the character at offset and the preceding character
+     * are at different run levels (and thus there's a split caret).
+     * @param offset the offset
+     * @return true if at a level boundary
+     */
+    private boolean isLevelBoundary(int offset) {
+        int line = getLineForOffset(offset);
+        Directions dirs = getLineDirections(line);
+        if (dirs == DIRS_ALL_LEFT_TO_RIGHT || dirs == DIRS_ALL_RIGHT_TO_LEFT) {
+            return false;
+        }
+
+        int[] runs = dirs.mDirections;
+        int lineStart = getLineStart(line);
+        int lineEnd = getLineEnd(line);
+        if (offset == lineStart || offset == lineEnd) {
+            int paraLevel = getParagraphDirection(line) == 1 ? 0 : 1;
+            int runIndex = offset == lineStart ? 0 : runs.length - 2;
+            return ((runs[runIndex + 1] >>> RUN_LEVEL_SHIFT) & RUN_LEVEL_MASK) != paraLevel;
+        }
+
+        offset -= lineStart;
+        for (int i = 0; i < runs.length; i += 2) {
+            if (offset == runs[i]) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private boolean primaryIsTrailingPrevious(int offset) {
+        int line = getLineForOffset(offset);
+        int lineStart = getLineStart(line);
+        int lineEnd = getLineEnd(line);
+        int[] runs = getLineDirections(line).mDirections;
+
+        int levelAt = -1;
+        for (int i = 0; i < runs.length; i += 2) {
+            int start = lineStart + runs[i];
+            int limit = start + (runs[i+1] & RUN_LENGTH_MASK);
+            if (limit > lineEnd) {
+                limit = lineEnd;
+            }
+            if (offset >= start && offset < limit) {
+                if (offset > start) {
+                    // Previous character is at same level, so don't use trailing.
+                    return false;
+                }
+                levelAt = (runs[i+1] >>> RUN_LEVEL_SHIFT) & RUN_LEVEL_MASK;
+                break;
+            }
+        }
+        if (levelAt == -1) {
+            // Offset was limit of line.
+            levelAt = getParagraphDirection(line) == 1 ? 0 : 1;
+        }
+
+        // At level boundary, check previous level.
+        int levelBefore = -1;
+        if (offset == lineStart) {
+            levelBefore = getParagraphDirection(line) == 1 ? 0 : 1;
+        } else {
+            offset -= 1;
+            for (int i = 0; i < runs.length; i += 2) {
+                int start = lineStart + runs[i];
+                int limit = start + (runs[i+1] & RUN_LENGTH_MASK);
+                if (limit > lineEnd) {
+                    limit = lineEnd;
+                }
+                if (offset >= start && offset < limit) {
+                    levelBefore = (runs[i+1] >>> RUN_LEVEL_SHIFT) & RUN_LEVEL_MASK;
+                    break;
+                }
+            }
+        }
+
+        return levelBefore < levelAt;
+    }
+
     /**
      * Get the primary horizontal position for the specified text offset.
      * This is the location where a new character would be inserted in
      * the paragraph's primary direction.
      */
     public float getPrimaryHorizontal(int offset) {
-        return getHorizontal(offset, false, true);
+        boolean trailing = primaryIsTrailingPrevious(offset);
+        return getHorizontal(offset, trailing);
     }
 
     /**
@@ -539,19 +633,19 @@
      * the direction other than the paragraph's primary direction.
      */
     public float getSecondaryHorizontal(int offset) {
-        return getHorizontal(offset, true, true);
+        boolean trailing = primaryIsTrailingPrevious(offset);
+        return getHorizontal(offset, !trailing);
     }
 
-    private float getHorizontal(int offset, boolean trailing, boolean alt) {
+    private float getHorizontal(int offset, boolean trailing) {
         int line = getLineForOffset(offset);
 
-        return getHorizontal(offset, trailing, alt, line);
+        return getHorizontal(offset, trailing, line);
     }
 
-    private float getHorizontal(int offset, boolean trailing, boolean alt,
-                                int line) {
+    private float getHorizontal(int offset, boolean trailing, int line) {
         int start = getLineStart(line);
-        int end = getLineVisibleEnd(line);
+        int end = getLineEnd(line);
         int dir = getParagraphDirection(line);
         boolean tab = getLineContainsTab(line);
         Directions directions = getLineDirections(line);
@@ -561,17 +655,10 @@
             tabs = ((Spanned) mText).getSpans(start, end, TabStopSpan.class);
         }
 
-        float wid = measureText(mPaint, mWorkPaint, mText, start, offset, end,
-                                dir, directions, trailing, alt, tab, tabs);
-
-        if (offset > end) {
-            if (dir == DIR_RIGHT_TO_LEFT)
-                wid -= measureText(mPaint, mWorkPaint,
-                                   mText, end, offset, null, tab, tabs);
-            else
-                wid += measureText(mPaint, mWorkPaint,
-                                   mText, end, offset, null, tab, tabs);
-        }
+        TextLine tl = TextLine.obtain();
+        tl.set(mPaint, mText, start, end, dir, directions, tab, tabs);
+        float wid = tl.measure(offset - start, trailing, null);
+        TextLine.recycle(tl);
 
         Alignment align = getParagraphAlignment(line);
         int left = getParagraphLeft(line);
@@ -673,21 +760,15 @@
 
     private float getLineMax(int line, Object[] tabs, boolean full) {
         int start = getLineStart(line);
-        int end;
+        int end = full ? getLineEnd(line) : getLineVisibleEnd(line);
+        boolean hasTabs = getLineContainsTab(line);
+        Directions directions = getLineDirections(line);
 
-        if (full) {
-            end = getLineEnd(line);
-        } else {
-            end = getLineVisibleEnd(line);
-        } 
-        boolean tab = getLineContainsTab(line);
-
-        if (tabs == null && tab && mText instanceof Spanned) {
-            tabs = ((Spanned) mText).getSpans(start, end, TabStopSpan.class);
-        }
-
-        return measureText(mPaint, mWorkPaint,
-                           mText, start, end, null, tab, tabs);
+        TextLine tl = TextLine.obtain();
+        tl.set(mPaint, mText, start, end, 1, directions, hasTabs, tabs);
+        float width = tl.metrics(null);
+        TextLine.recycle(tl);
+        return width;
     }
 
     /**
@@ -738,7 +819,7 @@
     }
 
     /**
-     * Get the character offset on the specfied line whose position is
+     * Get the character offset on the specified line whose position is
      * closest to the specified horizontal position.
      */
     public int getOffsetForHorizontal(int line, float horiz) {
@@ -752,14 +833,13 @@
         int best = min;
         float bestdist = Math.abs(getPrimaryHorizontal(best) - horiz);
 
-        int here = min;
-        for (int i = 0; i < dirs.mDirections.length; i++) {
-            int there = here + dirs.mDirections[i];
-            int swap = ((i & 1) == 0) ? 1 : -1;
+        for (int i = 0; i < dirs.mDirections.length; i += 2) {
+            int here = min + dirs.mDirections[i];
+            int there = here + (dirs.mDirections[i+1] & RUN_LENGTH_MASK);
+            int swap = (dirs.mDirections[i+1] & RUN_RTL_FLAG) != 0 ? -1 : 1;
 
             if (there > max)
                 there = max;
-
             int high = there - 1 + 1, low = here + 1 - 1, guess;
 
             while (high - low > 1) {
@@ -792,7 +872,7 @@
 
                 if (dist < bestdist) {
                     bestdist = dist;
-                    best = low;   
+                    best = low;
                 }
             }
 
@@ -802,8 +882,6 @@
                 bestdist = dist;
                 best = here;
             }
-
-            here = there;
         }
 
         float dist = Math.abs(getPrimaryHorizontal(max) - horiz);
@@ -823,14 +901,14 @@
         return getLineStart(line + 1);
     }
 
-    /** 
+    /**
      * Return the text offset after the last visible character (so whitespace
      * is not counted) on the specified line.
      */
     public int getLineVisibleEnd(int line) {
         return getLineVisibleEnd(line, getLineStart(line), getLineStart(line+1));
     }
-    
+
     private int getLineVisibleEnd(int line, int start, int end) {
         if (DEBUG) {
             Assert.assertTrue(getLineStart(line) == start && getLineStart(line+1) == end);
@@ -882,207 +960,62 @@
         return getLineTop(line) - (getLineTop(line+1) - getLineDescent(line));
     }
 
-    /**
-     * Return the text offset that would be reached by moving left
-     * (possibly onto another line) from the specified offset.
-     */
     public int getOffsetToLeftOf(int offset) {
-        int line = getLineForOffset(offset);
-        int start = getLineStart(line);
-        int end = getLineEnd(line);
-        Directions dirs = getLineDirections(line);
-
-        if (line != getLineCount() - 1)
-            end--;
-
-        float horiz = getPrimaryHorizontal(offset);
-
-        int best = offset;
-        float besth = Integer.MIN_VALUE;
-        int candidate;
-
-        candidate = TextUtils.getOffsetBefore(mText, offset);
-        if (candidate >= start && candidate <= end) {
-            float h = getPrimaryHorizontal(candidate);
-
-            if (h < horiz && h > besth) {
-                best = candidate;
-                besth = h;
-            }
-        }
-
-        candidate = TextUtils.getOffsetAfter(mText, offset);
-        if (candidate >= start && candidate <= end) {
-            float h = getPrimaryHorizontal(candidate);
-
-            if (h < horiz && h > besth) {
-                best = candidate;
-                besth = h;
-            }
-        }
-
-        int here = start;
-        for (int i = 0; i < dirs.mDirections.length; i++) {
-            int there = here + dirs.mDirections[i];
-            if (there > end)
-                there = end;
-
-            float h = getPrimaryHorizontal(here);
-
-            if (h < horiz && h > besth) {
-                best = here;
-                besth = h;
-            }
-
-            candidate = TextUtils.getOffsetAfter(mText, here);
-            if (candidate >= start && candidate <= end) {
-                h = getPrimaryHorizontal(candidate);
-
-                if (h < horiz && h > besth) {
-                    best = candidate;
-                    besth = h;
-                }
-            }
-
-            candidate = TextUtils.getOffsetBefore(mText, there);
-            if (candidate >= start && candidate <= end) {
-                h = getPrimaryHorizontal(candidate);
-
-                if (h < horiz && h > besth) {
-                    best = candidate;
-                    besth = h;
-                }
-            }
-
-            here = there;
-        }
-
-        float h = getPrimaryHorizontal(end);
-
-        if (h < horiz && h > besth) {
-            best = end;
-            besth = h;
-        }
-
-        if (best != offset)
-            return best;
-
-        int dir = getParagraphDirection(line);
-
-        if (dir > 0) {
-            if (line == 0)
-                return best;
-            else
-                return getOffsetForHorizontal(line - 1, 10000);
-        } else {
-            if (line == getLineCount() - 1)
-                return best;
-            else
-                return getOffsetForHorizontal(line + 1, 10000);
-        }
+        return getOffsetToLeftRightOf(offset, true);
     }
 
-    /**
-     * Return the text offset that would be reached by moving right
-     * (possibly onto another line) from the specified offset.
-     */
     public int getOffsetToRightOf(int offset) {
-        int line = getLineForOffset(offset);
-        int start = getLineStart(line);
-        int end = getLineEnd(line);
-        Directions dirs = getLineDirections(line);
+        return getOffsetToLeftRightOf(offset, false);
+    }
 
-        if (line != getLineCount() - 1)
-            end--;
+    private int getOffsetToLeftRightOf(int caret, boolean toLeft) {
+        int line = getLineForOffset(caret);
+        int lineStart = getLineStart(line);
+        int lineEnd = getLineEnd(line);
+        int lineDir = getParagraphDirection(line);
 
-        float horiz = getPrimaryHorizontal(offset);
-
-        int best = offset;
-        float besth = Integer.MAX_VALUE;
-        int candidate;
-
-        candidate = TextUtils.getOffsetBefore(mText, offset);
-        if (candidate >= start && candidate <= end) {
-            float h = getPrimaryHorizontal(candidate);
-
-            if (h > horiz && h < besth) {
-                best = candidate;
-                besth = h;
-            }
-        }
-
-        candidate = TextUtils.getOffsetAfter(mText, offset);
-        if (candidate >= start && candidate <= end) {
-            float h = getPrimaryHorizontal(candidate);
-
-            if (h > horiz && h < besth) {
-                best = candidate;
-                besth = h;
-            }
-        }
-
-        int here = start;
-        for (int i = 0; i < dirs.mDirections.length; i++) {
-            int there = here + dirs.mDirections[i];
-            if (there > end)
-                there = end;
-
-            float h = getPrimaryHorizontal(here);
-
-            if (h > horiz && h < besth) {
-                best = here;
-                besth = h;
-            }
-
-            candidate = TextUtils.getOffsetAfter(mText, here);
-            if (candidate >= start && candidate <= end) {
-                h = getPrimaryHorizontal(candidate);
-
-                if (h > horiz && h < besth) {
-                    best = candidate;
-                    besth = h;
+        boolean advance = toLeft == (lineDir == DIR_RIGHT_TO_LEFT);
+        if (caret == (advance ? lineEnd : lineStart)) {
+            // walking off line, so look at the line we're headed to
+            if (caret == lineStart) {
+                if (line > 0) {
+                    --line;
+                } else {
+                    return caret; // at very start, don't move
+                }
+            } else {
+                if (line < getLineCount() - 1) {
+                    ++line;
+                } else {
+                    return caret; // at very end, don't move
                 }
             }
 
-            candidate = TextUtils.getOffsetBefore(mText, there);
-            if (candidate >= start && candidate <= end) {
-                h = getPrimaryHorizontal(candidate);
-
-                if (h > horiz && h < besth) {
-                    best = candidate;
-                    besth = h;
-                }
+            lineStart = getLineStart(line);
+            lineEnd = getLineEnd(line);
+            int newDir = getParagraphDirection(line);
+            if (newDir != lineDir) {
+                // unusual case.  we want to walk onto the line, but it runs
+                // in a different direction than this one, so we fake movement
+                // in the opposite direction.
+                toLeft = !toLeft;
+                lineDir = newDir;
             }
-
-            here = there;
         }
 
-        float h = getPrimaryHorizontal(end);
+        Directions directions = getLineDirections(line);
 
-        if (h > horiz && h < besth) {
-            best = end;
-            besth = h;
-        }
-
-        if (best != offset)
-            return best;
-
-        int dir = getParagraphDirection(line);
-
-        if (dir > 0) {
-            if (line == getLineCount() - 1)
-                return best;
-            else
-                return getOffsetForHorizontal(line + 1, -10000);
-        } else {
-            if (line == 0)
-                return best;
-            else
-                return getOffsetForHorizontal(line - 1, -10000);
-        }
+        TextLine tl = TextLine.obtain();
+        // XXX: we don't care about tabs
+        tl.set(mPaint, mText, lineStart, lineEnd, lineDir, directions, false, null);
+        caret = lineStart + tl.getOffsetToLeftRightOf(caret - lineStart, toLeft);
+        tl = TextLine.recycle(tl);
+        return caret;
     }
 
     private int getOffsetAtStartOf(int offset) {
+        // XXX this probably should skip local reorderings and
+        // zero-width characters, look at callers
         if (offset == 0)
             return 0;
 
@@ -1115,7 +1048,7 @@
     /**
      * Fills in the specified Path with a representation of a cursor
      * at the specified offset.  This will often be a vertical line
-     * but can be multiple discontinous lines in text with multiple
+     * but can be multiple discontinuous lines in text with multiple
      * directionalities.
      */
     public void getCursorPath(int point, Path dest,
@@ -1127,7 +1060,8 @@
         int bottom = getLineTop(line+1);
 
         float h1 = getPrimaryHorizontal(point) - 0.5f;
-        float h2 = getSecondaryHorizontal(point) - 0.5f;
+        float h2 = isLevelBoundary(point) ?
+                    getSecondaryHorizontal(point) - 0.5f : h1;
 
         int caps = TextKeyListener.getMetaState(editingBuffer,
                                                 KeyEvent.META_SHIFT_ON) |
@@ -1204,9 +1138,10 @@
         if (lineend > linestart && mText.charAt(lineend - 1) == '\n')
             lineend--;
 
-        int here = linestart;
-        for (int i = 0; i < dirs.mDirections.length; i++) {
-            int there = here + dirs.mDirections[i];
+        for (int i = 0; i < dirs.mDirections.length; i += 2) {
+            int here = linestart + dirs.mDirections[i];
+            int there = here + (dirs.mDirections[i+1] & RUN_LENGTH_MASK);
+
             if (there > lineend)
                 there = lineend;
 
@@ -1215,14 +1150,12 @@
                 int en = Math.min(end, there);
 
                 if (st != en) {
-                    float h1 = getHorizontal(st, false, false, line);
-                    float h2 = getHorizontal(en, true, false, line);
+                    float h1 = getHorizontal(st, false, line);
+                    float h2 = getHorizontal(en, true, line);
 
                     dest.addRect(h1, top, h2, bottom, Path.Direction.CW);
                 }
             }
-
-            here = there;
         }
     }
 
@@ -1257,7 +1190,7 @@
 
             addSelection(startline, start, getLineEnd(startline),
                          top, getLineBottom(startline), dest);
-            
+
             if (getParagraphDirection(startline) == DIR_RIGHT_TO_LEFT)
                 dest.addRect(getLineLeft(startline), top,
                               0, getLineBottom(startline), Path.Direction.CW);
@@ -1371,361 +1304,28 @@
         return right;
     }
 
-    private void drawText(Canvas canvas,
-                                 CharSequence text, int start, int end,
-                                 int dir, Directions directions,
-                                 float x, int top, int y, int bottom,
-                                 TextPaint paint,
-                                 TextPaint workPaint,
-                                 boolean hasTabs, Object[] parspans) {
-        char[] buf;
-        if (!hasTabs) {
-            if (directions == DIRS_ALL_LEFT_TO_RIGHT) {
-                if (DEBUG) {
-                    Assert.assertTrue(DIR_LEFT_TO_RIGHT == dir);
-                }
-                Styled.drawText(canvas, text, start, end, dir, false, x, top, y, bottom, paint, workPaint, false);
-                return;
+    /* package */
+    static float measurePara(TextPaint paint, TextPaint workPaint,
+            CharSequence text, int start, int end, boolean hasTabs,
+            Object[] tabs) {
+
+        MeasuredText mt = MeasuredText.obtain();
+        TextLine tl = TextLine.obtain();
+        try {
+            mt.setPara(text, start, end, DIR_REQUEST_LTR);
+            Directions directions;
+            if (mt.mEasy){
+                directions = DIRS_ALL_LEFT_TO_RIGHT;
+            } else {
+                directions = AndroidBidi.directions(mt.mDir, mt.mLevels,
+                    0, mt.mChars, 0, mt.mLen);
             }
-            buf = null;
-        } else {
-            buf = TextUtils.obtain(end - start);
-            TextUtils.getChars(text, start, end, buf, 0);
+            tl.set(paint, text, start, end, 1, directions, hasTabs, tabs);
+            return tl.metrics(null);
+        } finally {
+            TextLine.recycle(tl);
+            MeasuredText.recycle(mt);
         }
-
-        float h = 0;
-
-        int here = 0;
-        for (int i = 0; i < directions.mDirections.length; i++) {
-            int there = here + directions.mDirections[i];
-            if (there > end - start)
-                there = end - start;
-
-            int segstart = here;
-            for (int j = hasTabs ? here : there; j <= there; j++) {
-                if (j == there || buf[j] == '\t') {
-                    h += Styled.drawText(canvas, text,
-                                         start + segstart, start + j,
-                                         dir, (i & 1) != 0, x + h,
-                                         top, y, bottom, paint, workPaint,
-                                         start + j != end);
-
-                    if (j != there && buf[j] == '\t')
-                        h = dir * nextTab(text, start, end, h * dir, parspans);
-
-                    segstart = j + 1;
-                } else if (hasTabs && buf[j] >= 0xD800 && buf[j] <= 0xDFFF && j + 1 < there) {
-                    int emoji = Character.codePointAt(buf, j);
-
-                    if (emoji >= MIN_EMOJI && emoji <= MAX_EMOJI) {
-                        Bitmap bm = EMOJI_FACTORY.
-                            getBitmapFromAndroidPua(emoji);
-
-                        if (bm != null) {
-                            h += Styled.drawText(canvas, text,
-                                                 start + segstart, start + j,
-                                                 dir, (i & 1) != 0, x + h,
-                                                 top, y, bottom, paint, workPaint,
-                                                 start + j != end);
-
-                            if (mEmojiRect == null) {
-                                mEmojiRect = new RectF();
-                            }
-
-                            workPaint.set(paint);
-                            Styled.measureText(paint, workPaint, text,
-                                               start + j, start + j + 1,
-                                               null);
-                                        
-                            float bitmapHeight = bm.getHeight();
-                            float textHeight = -workPaint.ascent();
-                            float scale = textHeight / bitmapHeight;
-                            float width = bm.getWidth() * scale;
-
-                            mEmojiRect.set(x + h, y - textHeight,
-                                           x + h + width, y);
-
-                            canvas.drawBitmap(bm, null, mEmojiRect, paint);
-                            h += width;
-
-                            j++;
-                            segstart = j + 1;
-                        }
-                    }
-                }
-            }
-
-            here = there;
-        }
-
-        if (hasTabs)
-            TextUtils.recycle(buf);
-    }
-
-    private static float measureText(TextPaint paint,
-                                     TextPaint workPaint,
-                                     CharSequence text,
-                                     int start, int offset, int end,
-                                     int dir, Directions directions,
-                                     boolean trailing, boolean alt,
-                                     boolean hasTabs, Object[] tabs) {
-        char[] buf = null;
-
-        if (hasTabs) {
-            buf = TextUtils.obtain(end - start);
-            TextUtils.getChars(text, start, end, buf, 0);
-        }
-
-        float h = 0;
-
-        if (alt) {
-            if (dir == DIR_RIGHT_TO_LEFT)
-                trailing = !trailing;
-        }
-
-        int here = 0;
-        for (int i = 0; i < directions.mDirections.length; i++) {
-            if (alt)
-                trailing = !trailing;
-
-            int there = here + directions.mDirections[i];
-            if (there > end - start)
-                there = end - start;
-
-            int segstart = here;
-            for (int j = hasTabs ? here : there; j <= there; j++) {
-                int codept = 0;
-                Bitmap bm = null;
-
-                if (hasTabs && j < there) {
-                    codept = buf[j];
-                }
-
-                if (codept >= 0xD800 && codept <= 0xDFFF && j + 1 < there) {
-                    codept = Character.codePointAt(buf, j);
-
-                    if (codept >= MIN_EMOJI && codept <= MAX_EMOJI) {
-                        bm = EMOJI_FACTORY.getBitmapFromAndroidPua(codept);
-                    }
-                }
-
-                if (j == there || codept == '\t' || bm != null) {
-                    float segw;
-
-                    if (offset < start + j ||
-                       (trailing && offset <= start + j)) {
-                        if (dir == DIR_LEFT_TO_RIGHT && (i & 1) == 0) {
-                            h += Styled.measureText(paint, workPaint, text,
-                                                    start + segstart, offset,
-                                                    null);
-                            return h;
-                        }
-
-                        if (dir == DIR_RIGHT_TO_LEFT && (i & 1) != 0) {
-                            h -= Styled.measureText(paint, workPaint, text,
-                                                    start + segstart, offset,
-                                                    null);
-                            return h;
-                        }
-                    }
-
-                    segw = Styled.measureText(paint, workPaint, text,
-                                              start + segstart, start + j,
-                                              null);
-
-                    if (offset < start + j ||
-                        (trailing && offset <= start + j)) {
-                        if (dir == DIR_LEFT_TO_RIGHT) {
-                            h += segw - Styled.measureText(paint, workPaint,
-                                                           text,
-                                                           start + segstart,
-                                                           offset, null);
-                            return h;
-                        }
-
-                        if (dir == DIR_RIGHT_TO_LEFT) {
-                            h -= segw - Styled.measureText(paint, workPaint,
-                                                           text,
-                                                           start + segstart,
-                                                           offset, null);
-                            return h;
-                        }
-                    }
-
-                    if (dir == DIR_RIGHT_TO_LEFT)
-                        h -= segw;
-                    else
-                        h += segw;
-
-                    if (j != there && buf[j] == '\t') {
-                        if (offset == start + j)
-                            return h;
-
-                        h = dir * nextTab(text, start, end, h * dir, tabs);
-                    }
-
-                    if (bm != null) {
-                        workPaint.set(paint);
-                        Styled.measureText(paint, workPaint, text,
-                                           j, j + 2, null);
-
-                        float wid = (float) bm.getWidth() *
-                                    -workPaint.ascent() / bm.getHeight();
-
-                        if (dir == DIR_RIGHT_TO_LEFT) {
-                            h -= wid;
-                        } else {
-                            h += wid;
-                        }
-
-                        j++;
-                    }
-
-                    segstart = j + 1;
-                }
-            }
-
-            here = there;
-        }
-
-        if (hasTabs)
-            TextUtils.recycle(buf);
-
-        return h;
-    }
-
-    /**
-     * Measure width of a run of text on a single line that is known to all be
-     * in the same direction as the paragraph base direction. Returns the width,
-     * and the line metrics in fm if fm is not null.
-     *
-     * @param paint the paint for the text; will not be modified
-     * @param workPaint paint available for modification
-     * @param text text
-     * @param start start of the line
-     * @param end limit of the line
-     * @param fm object to return integer metrics in, can be null
-     * @param hasTabs true if it is known that the line has tabs
-     * @param tabs tab position information
-     * @return the width of the text from start to end
-     */
-    /* package */ static float measureText(TextPaint paint,
-                                           TextPaint workPaint,
-                                           CharSequence text,
-                                           int start, int end,
-                                           Paint.FontMetricsInt fm,
-                                           boolean hasTabs, Object[] tabs) {
-        char[] buf = null;
-  
-        if (hasTabs) {
-            buf = TextUtils.obtain(end - start);
-            TextUtils.getChars(text, start, end, buf, 0);
-        }
-
-        int len = end - start;
-
-        int lastPos = 0;
-        float width = 0;
-        int ascent = 0, descent = 0, top = 0, bottom = 0;
-
-        if (fm != null) {
-            fm.ascent = 0;
-            fm.descent = 0;
-        }
-
-        for (int pos = hasTabs ? 0 : len; pos <= len; pos++) {
-            int codept = 0;
-            Bitmap bm = null;
-
-            if (hasTabs && pos < len) {
-                codept = buf[pos];
-            }
-
-            if (codept >= 0xD800 && codept <= 0xDFFF && pos < len) {
-                codept = Character.codePointAt(buf, pos);
-
-                if (codept >= MIN_EMOJI && codept <= MAX_EMOJI) {
-                    bm = EMOJI_FACTORY.getBitmapFromAndroidPua(codept);
-                }
-            }
-
-            if (pos == len || codept == '\t' || bm != null) {
-                workPaint.baselineShift = 0;
-
-                width += Styled.measureText(paint, workPaint, text,
-                                        start + lastPos, start + pos,
-                                        fm);
-
-                if (fm != null) {
-                    if (workPaint.baselineShift < 0) {
-                        fm.ascent += workPaint.baselineShift;
-                        fm.top += workPaint.baselineShift;
-                    } else {
-                        fm.descent += workPaint.baselineShift;
-                        fm.bottom += workPaint.baselineShift;
-                    }
-                }
-
-                if (pos != len) {
-                    if (bm == null) {
-                        // no emoji, must have hit a tab
-                        width = nextTab(text, start, end, width, tabs);
-                    } else {
-                        // This sets up workPaint with the font on the emoji
-                        // text, so that we can extract the ascent and scale.
-
-                        // We can't use the result of the previous call to
-                        // measureText because the emoji might have its own style.
-                        // We have to initialize workPaint here because if the
-                        // text is unstyled measureText might not use workPaint
-                        // at all.
-                        workPaint.set(paint);
-                        Styled.measureText(paint, workPaint, text,
-                                           start + pos, start + pos + 1, null);
-
-                        width += (float) bm.getWidth() *
-                                    -workPaint.ascent() / bm.getHeight();
-
-                        // Since we had an emoji, we bump past the second half
-                        // of the surrogate pair.
-                        pos++;
-                    }
-                }
-
-                if (fm != null) {
-                    if (fm.ascent < ascent) {
-                        ascent = fm.ascent;
-                    }
-                    if (fm.descent > descent) {
-                        descent = fm.descent;
-                    }
-
-                    if (fm.top < top) {
-                        top = fm.top;
-                    }
-                    if (fm.bottom > bottom) {
-                        bottom = fm.bottom;
-                    }
-
-                    // No need to take bitmap height into account here,
-                    // since it is scaled to match the text height.
-                }
-
-                lastPos = pos + 1;
-            }
-        }
-
-        if (fm != null) {
-            fm.ascent = ascent;
-            fm.descent = descent;
-            fm.top = top;
-            fm.bottom = bottom;
-        }
-
-        if (hasTabs)
-            TextUtils.recycle(buf);
-
-        return width;
     }
 
     /**
@@ -1804,23 +1404,22 @@
 
     /**
      * Stores information about bidirectional (left-to-right or right-to-left)
-     * text within the layout of a line.  TODO: This work is not complete
-     * or correct and will be fleshed out in a later revision.
+     * text within the layout of a line.
      */
     public static class Directions {
-        private short[] mDirections;
+        // Directions represents directional runs within a line of text.
+        // Runs are pairs of ints listed in visual order, starting from the
+        // leading margin.  The first int of each pair is the offset from
+        // the first character of the line to the start of the run.  The
+        // second int represents both the length and level of the run.
+        // The length is in the lower bits, accessed by masking with
+        // DIR_LENGTH_MASK.  The level is in the higher bits, accessed
+        // by shifting by DIR_LEVEL_SHIFT and masking by DIR_LEVEL_MASK.
+        // To simply test for an RTL direction, test the bit using
+        // DIR_RTL_FLAG, if set then the direction is rtl.
 
-        // The values in mDirections are the offsets from the first character
-        // in the line to the next flip in direction.  Runs at even indices
-        // are left-to-right, the others are right-to-left.  So, for example,
-        // a line that starts with a right-to-left run has 0 at mDirections[0],
-        // since the 'first' (ltr) run is zero length.
-        //
-        // The code currently assumes that each run is adjacent to the previous
-        // one, progressing in the base line direction.  This isn't sufficient
-        // to handle nested runs, for example numeric text in an rtl context
-        // in an ltr paragraph.
-        /* package */ Directions(short[] dirs) {
+        /* package */ int[] mDirections;
+        /* package */ Directions(int[] dirs) {
             mDirections = dirs;
         }
     }
@@ -1831,6 +1430,7 @@
      * line is ellipsized, not getLineStart().)
      */
     public abstract int getEllipsisStart(int line);
+
     /**
      * Returns the number of characters to be ellipsized away, or 0 if
      * no ellipsis is to take place.
@@ -1870,7 +1470,7 @@
         public int length() {
             return mText.length();
         }
-    
+
         public CharSequence subSequence(int start, int end) {
             char[] s = new char[end - start];
             getChars(start, end, s, 0);
@@ -1936,12 +1536,17 @@
 
     public static final int DIR_LEFT_TO_RIGHT = 1;
     public static final int DIR_RIGHT_TO_LEFT = -1;
-    
+
     /* package */ static final int DIR_REQUEST_LTR = 1;
     /* package */ static final int DIR_REQUEST_RTL = -1;
     /* package */ static final int DIR_REQUEST_DEFAULT_LTR = 2;
     /* package */ static final int DIR_REQUEST_DEFAULT_RTL = -2;
 
+    /* package */ static final int RUN_LENGTH_MASK = 0x03ffffff;
+    /* package */ static final int RUN_LEVEL_SHIFT = 26;
+    /* package */ static final int RUN_LEVEL_MASK = 0x3f;
+    /* package */ static final int RUN_RTL_FLAG = 1 << RUN_LEVEL_SHIFT;
+
     public enum Alignment {
         ALIGN_NORMAL,
         ALIGN_OPPOSITE,
@@ -1953,9 +1558,8 @@
     private static final int TAB_INCREMENT = 20;
 
     /* package */ static final Directions DIRS_ALL_LEFT_TO_RIGHT =
-                                       new Directions(new short[] { 32767 });
+        new Directions(new int[] { 0, RUN_LENGTH_MASK });
     /* package */ static final Directions DIRS_ALL_RIGHT_TO_LEFT =
-                                       new Directions(new short[] { 0, 32767 });
-
+        new Directions(new int[] { 0, RUN_LENGTH_MASK | RUN_RTL_FLAG });
 }
 
diff --git a/core/java/android/text/MeasuredText.java b/core/java/android/text/MeasuredText.java
new file mode 100644
index 0000000..e3a113d
--- /dev/null
+++ b/core/java/android/text/MeasuredText.java
@@ -0,0 +1,250 @@
+/*
+ * 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.text;
+
+import com.android.internal.util.ArrayUtils;
+
+import android.graphics.Paint;
+import android.icu.text.ArabicShaping;
+import android.text.style.MetricAffectingSpan;
+import android.text.style.ReplacementSpan;
+import android.util.Log;
+
+/**
+ * @hide
+ */
+class MeasuredText {
+    /* package */ CharSequence mText;
+    /* package */ int mTextStart;
+    /* package */ float[] mWidths;
+    /* package */ char[] mChars;
+    /* package */ byte[] mLevels;
+    /* package */ int mDir;
+    /* package */ boolean mEasy;
+    /* package */ int mLen;
+    private int mPos;
+    private float[] mWorkWidths; // temp buffer for Paint.measureText, arrgh
+    private TextPaint mWorkPaint;
+
+    private MeasuredText() {
+        mWorkPaint = new TextPaint();
+    }
+
+    private static MeasuredText[] cached = new MeasuredText[3];
+
+    /* package */
+    static MeasuredText obtain() {
+        MeasuredText mt;
+        synchronized (cached) {
+            for (int i = cached.length; --i >= 0;) {
+                if (cached[i] != null) {
+                    mt = cached[i];
+                    cached[i] = null;
+                    return mt;
+                }
+            }
+        }
+        mt = new MeasuredText();
+        Log.e("MEAS", "new: " + mt);
+        return mt;
+    }
+
+    /* package */
+    static MeasuredText recycle(MeasuredText mt) {
+        mt.mText = null;
+        if (mt.mLen < 1000) {
+            synchronized(cached) {
+                for (int i = 0; i < cached.length; ++i) {
+                    if (cached[i] == null) {
+                        cached[i] = mt;
+                        break;
+                    }
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Analyzes text for
+     * bidirectional runs.  Allocates working buffers.
+     */
+    /* package */
+    void setPara(CharSequence text, int start, int end, int bidiRequest) {
+        mText = text;
+        mTextStart = start;
+
+        int len = end - start;
+        mLen = len;
+        mPos = 0;
+
+        if (mWidths == null || mWidths.length < len) {
+            mWidths = new float[ArrayUtils.idealFloatArraySize(len)];
+            mWorkWidths = new float[mWidths.length];
+        }
+        if (mChars == null || mChars.length < len) {
+            mChars = new char[ArrayUtils.idealCharArraySize(len)];
+        }
+        TextUtils.getChars(text, start, end, mChars, 0);
+
+        if (text instanceof Spanned) {
+            Spanned spanned = (Spanned) text;
+            ReplacementSpan[] spans = spanned.getSpans(start, end,
+                    ReplacementSpan.class);
+
+            for (int i = 0; i < spans.length; i++) {
+                int startInPara = spanned.getSpanStart(spans[i]) - start;
+                int endInPara = spanned.getSpanEnd(spans[i]) - start;
+                for (int j = startInPara; j < endInPara; j++) {
+                    mChars[j] = '\uFFFC';
+                }
+            }
+        }
+
+        if (TextUtils.doesNotNeedBidi(mChars, 0, len)) {
+            mDir = 1;
+            mEasy = true;
+        } else {
+            if (mLevels == null || mLevels.length < len) {
+                mLevels = new byte[ArrayUtils.idealByteArraySize(len)];
+            }
+            mDir = AndroidBidi.bidi(bidiRequest, mChars, mLevels, len, false);
+            mEasy = false;
+
+            // shape
+            if (mLen > 0) {
+                byte[] levels = mLevels;
+                char[] chars = mChars;
+                byte level = levels[0];
+                int pi = 0;
+                for (int i = 1, e = mLen;; ++i) {
+                    if (i == e || levels[i] != level) {
+                        if ((level & 0x1) != 0) {
+                            AndroidCharacter.mirror(chars, pi, i - pi);
+                            ArabicShaping.SHAPER.shape(chars, pi, i - pi);
+                        }
+                        if (i == e) {
+                            break;
+                        }
+                        pi = i;
+                        level = levels[i];
+                    }
+                }
+            }
+        }
+    }
+
+    float addStyleRun(TextPaint paint, int len, Paint.FontMetricsInt fm) {
+        int p = mPos;
+        float[] w = mWidths, ww = mWorkWidths;
+        int count = paint.getTextWidths(mChars, p, len, ww);
+        int width = 0;
+        if (count < len) {
+            // must have surrogate pairs in here, pad out the array with zero
+            // for the trailing surrogates
+            char[] chars = mChars;
+            for (int i = 0, e = mLen; i < count; ++i) {
+                width += (w[p++] = ww[i]);
+                if (p < e && chars[p] >= '\udc00' && chars[p] < '\ue000' &&
+                        chars[p-1] >= '\ud800' && chars[p-1] < '\udc00') {
+                    w[p++] = 0;
+                }
+            }
+        } else {
+            for (int i = 0; i < len; ++i) {
+                width += (w[p++] = ww[i]);
+            }
+        }
+        mPos = p;
+        if (fm != null) {
+            paint.getFontMetricsInt(fm);
+        }
+        return width;
+    }
+
+    float addStyleRun(TextPaint paint, MetricAffectingSpan[] spans, int len,
+            Paint.FontMetricsInt fm) {
+
+        TextPaint workPaint = mWorkPaint;
+        workPaint.set(paint);
+        // XXX paint should not have a baseline shift, but...
+        workPaint.baselineShift = 0;
+
+        ReplacementSpan replacement = null;
+        for (int i = 0; i < spans.length; i++) {
+            MetricAffectingSpan span = spans[i];
+            if (span instanceof ReplacementSpan) {
+                replacement = (ReplacementSpan)span;
+            } else {
+                span.updateMeasureState(workPaint);
+            }
+        }
+
+        float wid;
+        if (replacement == null) {
+            wid = addStyleRun(workPaint, len, fm);
+        } else {
+            // Use original text.  Shouldn't matter.
+            wid = replacement.getSize(workPaint, mText, mTextStart + mPos,
+                    mTextStart + mPos + len, fm);
+            float[] w = mWidths;
+            w[mPos] = wid;
+            for (int i = mPos + 1, e = mPos + len; i < e; i++)
+                w[i] = 0;
+        }
+
+        if (fm != null) {
+            if (workPaint.baselineShift < 0) {
+                fm.ascent += workPaint.baselineShift;
+                fm.top += workPaint.baselineShift;
+            } else {
+                fm.descent += workPaint.baselineShift;
+                fm.bottom += workPaint.baselineShift;
+            }
+        }
+
+        return wid;
+    }
+
+    int breakText(int start, int limit, boolean forwards, float width) {
+        float[] w = mWidths;
+        if (forwards) {
+            for (int i = start; i < limit; ++i) {
+                if ((width -= w[i]) < 0) {
+                    return i - start;
+                }
+            }
+        } else {
+            for (int i = limit; --i >= start;) {
+                if ((width -= w[i]) < 0) {
+                    return limit - i -1;
+                }
+            }
+        }
+
+        return limit - start;
+    }
+
+    float measure(int start, int limit) {
+        float width = 0;
+        float[] w = mWidths;
+        for (int i = start; i < limit; ++i) {
+            width += w[i];
+        }
+        return width;
+    }
+}
\ No newline at end of file
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index f02ad2a..0c6c545 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -16,14 +16,13 @@
 
 package android.text;
 
+import com.android.internal.util.ArrayUtils;
+
 import android.graphics.Bitmap;
 import android.graphics.Paint;
-import com.android.internal.util.ArrayUtils;
-import android.util.Log;
 import android.text.style.LeadingMarginSpan;
 import android.text.style.LineHeightSpan;
 import android.text.style.MetricAffectingSpan;
-import android.text.style.ReplacementSpan;
 
 /**
  * StaticLayout is a Layout for text that will not be edited after it
@@ -31,8 +30,9 @@
  * <p>This is used by widgets to control text layout. You should not need
  * to use this class directly unless you are implementing your own widget
  * or custom display object, or would be tempted to call
- * {@link android.graphics.Canvas#drawText(java.lang.CharSequence, int, int, float, float, android.graphics.Paint)
- *  Canvas.drawText()} directly.</p>
+ * {@link android.graphics.Canvas#drawText(java.lang.CharSequence, int, int,
+ * float, float, android.graphics.Paint)
+ * Canvas.drawText()} directly.</p>
  */
 public class
 StaticLayout
@@ -62,7 +62,7 @@
                         boolean includepad,
                         TextUtils.TruncateAt ellipsize, int ellipsizedWidth) {
         super((ellipsize == null)
-                ? source 
+                ? source
                 : (source instanceof Spanned)
                     ? new SpannedEllipsizer(source)
                     : new Ellipsizer(source),
@@ -72,7 +72,7 @@
          * This is annoying, but we can't refer to the layout until
          * superclass construction is finished, and the superclass
          * constructor wants the reference to the display text.
-         * 
+         *
          * This will break if the superclass constructor ever actually
          * cares about the content instead of just holding the reference.
          */
@@ -94,13 +94,13 @@
         mLineDirections = new Directions[
                              ArrayUtils.idealIntArraySize(2 * mColumns)];
 
+        mMeasured = MeasuredText.obtain();
+
         generate(source, bufstart, bufend, paint, outerwidth, align,
                  spacingmult, spacingadd, includepad, includepad,
                  ellipsize != null, ellipsizedWidth, ellipsize);
 
-        mChdirs = null;
-        mChs = null;
-        mWidths = null;
+        mMeasured = MeasuredText.recycle(mMeasured);
         mFontMetricsInt = null;
     }
 
@@ -111,6 +111,7 @@
         mLines = new int[ArrayUtils.idealIntArraySize(2 * mColumns)];
         mLineDirections = new Directions[
                              ArrayUtils.idealIntArraySize(2 * mColumns)];
+        mMeasured = MeasuredText.obtain();
     }
 
     /* package */ void generate(CharSequence source, int bufstart, int bufend,
@@ -128,38 +129,22 @@
         Paint.FontMetricsInt fm = mFontMetricsInt;
         int[] choosehtv = null;
 
-        int end = TextUtils.indexOf(source, '\n', bufstart, bufend);
-        int bufsiz = end >= 0 ? end - bufstart : bufend - bufstart;
-        boolean first = true;
+        MeasuredText measured = mMeasured;
 
-        if (mChdirs == null) {
-            mChdirs = new byte[ArrayUtils.idealByteArraySize(bufsiz + 1)];
-            mChs = new char[ArrayUtils.idealCharArraySize(bufsiz + 1)];
-            mWidths = new float[ArrayUtils.idealIntArraySize((bufsiz + 1) * 2)];
-        }
-
-        byte[] chdirs = mChdirs;
-        char[] chs = mChs;
-        float[] widths = mWidths;
-
-        AlteredCharSequence alter = null;
         Spanned spanned = null;
-
         if (source instanceof Spanned)
             spanned = (Spanned) source;
 
         int DEFAULT_DIR = DIR_LEFT_TO_RIGHT; // XXX
 
-        for (int start = bufstart; start <= bufend; start = end) {
-            if (first)
-                first = false;
+        int paraEnd;
+        for (int paraStart = bufstart; paraStart <= bufend; paraStart = paraEnd) {
+            paraEnd = TextUtils.indexOf(source, '\n', paraStart, bufend);
+            if (paraEnd < 0)
+                paraEnd = bufend;
             else
-                end = TextUtils.indexOf(source, '\n', start, bufend);
-
-            if (end < 0)
-                end = bufend;
-            else
-                end++;
+                paraEnd++;
+            int paraLen = paraEnd - paraStart;
 
             int firstWidthLineCount = 1;
             int firstwidth = outerwidth;
@@ -168,19 +153,20 @@
             LineHeightSpan[] chooseht = null;
 
             if (spanned != null) {
-                LeadingMarginSpan[] sp;
-
-                sp = spanned.getSpans(start, end, LeadingMarginSpan.class);
+                LeadingMarginSpan[] sp = spanned.getSpans(paraStart, paraEnd,
+                        LeadingMarginSpan.class);
                 for (int i = 0; i < sp.length; i++) {
                     LeadingMarginSpan lms = sp[i];
                     firstwidth -= sp[i].getLeadingMargin(true);
                     restwidth -= sp[i].getLeadingMargin(false);
                     if (lms instanceof LeadingMarginSpan.LeadingMarginSpan2) {
-                        firstWidthLineCount = ((LeadingMarginSpan.LeadingMarginSpan2)lms).getLeadingMarginLineCount();
+                        firstWidthLineCount =
+                            ((LeadingMarginSpan.LeadingMarginSpan2)lms)
+                            .getLeadingMarginLineCount();
                     }
                 }
 
-                chooseht = spanned.getSpans(start, end, LineHeightSpan.class);
+                chooseht = spanned.getSpans(paraStart, paraEnd, LineHeightSpan.class);
 
                 if (chooseht.length != 0) {
                     if (choosehtv == null ||
@@ -192,11 +178,11 @@
                     for (int i = 0; i < chooseht.length; i++) {
                         int o = spanned.getSpanStart(chooseht[i]);
 
-                        if (o < start) {
+                        if (o < paraStart) {
                             // starts in this layout, before the
                             // current paragraph
 
-                            choosehtv[i] = getLineTop(getLineForOffset(o)); 
+                            choosehtv[i] = getLineTop(getLineForOffset(o));
                         } else {
                             // starts in this paragraph
 
@@ -206,134 +192,48 @@
                 }
             }
 
-            if (end - start > chdirs.length) {
-                chdirs = new byte[ArrayUtils.idealByteArraySize(end - start)];
-                mChdirs = chdirs;
-            }
-            if (end - start > chs.length) {
-                chs = new char[ArrayUtils.idealCharArraySize(end - start)];
-                mChs = chs;
-            }
-            if ((end - start) * 2 > widths.length) {
-                widths = new float[ArrayUtils.idealIntArraySize((end - start) * 2)];
-                mWidths = widths;
-            }
+            measured.setPara(source, paraStart, paraEnd, DIR_REQUEST_DEFAULT_LTR);
+            char[] chs = measured.mChars;
+            float[] widths = measured.mWidths;
+            byte[] chdirs = measured.mLevels;
+            int dir = measured.mDir;
+            boolean easy = measured.mEasy;
 
-            TextUtils.getChars(source, start, end, chs, 0);
-            final int n = end - start;
-
-            boolean easy = true;
-            boolean altered = false;
-            int dir = DEFAULT_DIR; // XXX
-
-            for (int i = 0; i < n; i++) {
-                if (chs[i] >= FIRST_RIGHT_TO_LEFT) {
-                    easy = false;
-                    break;
-                }
-            }
-
-            // Ensure that none of the underlying characters are treated
-            // as viable breakpoints, and that the entire run gets the
-            // same bidi direction.
-
-            if (source instanceof Spanned) {
-                Spanned sp = (Spanned) source;
-                ReplacementSpan[] spans = sp.getSpans(start, end, ReplacementSpan.class);
-
-                for (int y = 0; y < spans.length; y++) {
-                    int a = sp.getSpanStart(spans[y]);
-                    int b = sp.getSpanEnd(spans[y]);
-
-                    for (int x = a; x < b; x++) {
-                        chs[x - start] = '\uFFFC';
-                    }
-                }
-            }
-
-            if (!easy) {
-                // XXX put override flags, etc. into chdirs
-                dir = bidi(dir, chs, chdirs, n, false);
-
-                // Do mirroring for right-to-left segments
-
-                for (int i = 0; i < n; i++) {
-                    if (chdirs[i] == Character.DIRECTIONALITY_RIGHT_TO_LEFT) {
-                        int j;
-
-                        for (j = i; j < n; j++) {
-                            if (chdirs[j] !=
-                                Character.DIRECTIONALITY_RIGHT_TO_LEFT)
-                                break;
-                        }
-
-                        if (AndroidCharacter.mirror(chs, i, j - i))
-                            altered = true;
-
-                        i = j - 1;
-                    }
-                }
-            }
-
-            CharSequence sub;
-
-            if (altered) {
-                if (alter == null)
-                    alter = AlteredCharSequence.make(source, chs, start, end);
-                else
-                    alter.update(chs, start, end);
-
-                sub = alter;
-            } else {
-                sub = source;
-            }
+            CharSequence sub = source;
 
             int width = firstwidth;
 
             float w = 0;
-            int here = start;
+            int here = paraStart;
 
-            int ok = start;
+            int ok = paraStart;
             float okwidth = w;
             int okascent = 0, okdescent = 0, oktop = 0, okbottom = 0;
 
-            int fit = start;
+            int fit = paraStart;
             float fitwidth = w;
             int fitascent = 0, fitdescent = 0, fittop = 0, fitbottom = 0;
 
             boolean tab = false;
 
-            int next;
-            for (int i = start; i < end; i = next) {
+            int spanEnd;
+            for (int spanStart = paraStart; spanStart < paraEnd; spanStart = spanEnd) {
                 if (spanned == null)
-                    next = end;
+                    spanEnd = paraEnd;
                 else
-                    next = spanned.nextSpanTransition(i, end,
-                                                      MetricAffectingSpan.
-                                                      class);
+                    spanEnd = spanned.nextSpanTransition(spanStart, paraEnd,
+                            MetricAffectingSpan.class);
+
+                int spanLen = spanEnd - spanStart;
+                int startInPara = spanStart - paraStart;
+                int endInPara = spanEnd - paraStart;
 
                 if (spanned == null) {
-                    paint.getTextWidths(sub, i, next, widths);
-                    System.arraycopy(widths, 0, widths,
-                                     end - start + (i - start), next - i);
-                                     
-                    paint.getFontMetricsInt(fm);
+                    measured.addStyleRun(paint, spanLen, fm);
                 } else {
-                    mWorkPaint.baselineShift = 0;
-
-                    Styled.getTextWidths(paint, mWorkPaint,
-                                         spanned, i, next,
-                                         widths, fm);
-                    System.arraycopy(widths, 0, widths,
-                                     end - start + (i - start), next - i);
-
-                    if (mWorkPaint.baselineShift < 0) {
-                        fm.ascent += mWorkPaint.baselineShift;
-                        fm.top += mWorkPaint.baselineShift;
-                    } else {
-                        fm.descent += mWorkPaint.baselineShift;
-                        fm.bottom += mWorkPaint.baselineShift;
-                    }
+                    MetricAffectingSpan[] spans =
+                        spanned.getSpans(spanStart, spanEnd, MetricAffectingSpan.class);
+                    measured.addStyleRun(paint, spans, spanLen, fm);
                 }
 
                 int fmtop = fm.top;
@@ -341,27 +241,17 @@
                 int fmascent = fm.ascent;
                 int fmdescent = fm.descent;
 
-                if (false) {
-                    StringBuilder sb = new StringBuilder();
-                    for (int j = i; j < next; j++) {
-                        sb.append(widths[j - start + (end - start)]);
-                        sb.append(' ');
-                    }
-
-                    Log.e("text", sb.toString());
-                }
-
-                for (int j = i; j < next; j++) {
-                    char c = chs[j - start];
+                for (int j = spanStart; j < spanEnd; j++) {
+                    char c = chs[j - paraStart];
                     float before = w;
 
                     if (c == '\n') {
                         ;
                     } else if (c == '\t') {
-                        w = Layout.nextTab(sub, start, end, w, null);
+                        w = Layout.nextTab(sub, paraStart, paraEnd, w, null);
                         tab = true;
-                    } else if (c >= 0xD800 && c <= 0xDFFF && j + 1 < next) {
-                        int emoji = Character.codePointAt(chs, j - start);
+                    } else if (c >= 0xD800 && c <= 0xDFFF && j + 1 < spanEnd) {
+                        int emoji = Character.codePointAt(chs, j - paraStart);
 
                         if (emoji >= MIN_EMOJI && emoji <= MAX_EMOJI) {
                             Bitmap bm = EMOJI_FACTORY.
@@ -376,7 +266,7 @@
                                     whichPaint = mWorkPaint;
                                 }
 
-                                float wid = (float) bm.getWidth() *
+                                float wid = bm.getWidth() *
                                             -whichPaint.ascent() /
                                             bm.getHeight();
 
@@ -384,13 +274,13 @@
                                 tab = true;
                                 j++;
                             } else {
-                                w += widths[j - start + (end - start)];
+                                w += widths[j - paraStart];
                             }
                         } else {
-                            w += widths[j - start + (end - start)];
+                            w += widths[j - paraStart];
                         }
                     } else {
-                        w += widths[j - start + (end - start)];
+                        w += widths[j - paraStart];
                     }
 
                     // Log.e("text", "was " + before + " now " + w + " after " + c + " within " + width);
@@ -411,7 +301,7 @@
                         /*
                          * From the Unicode Line Breaking Algorithm:
                          * (at least approximately)
-                         *  
+                         *
                          * .,:; are class IS: breakpoints
                          *      except when adjacent to digits
                          * /    is class SY: a breakpoint
@@ -426,12 +316,12 @@
 
                         if (c == ' ' || c == '\t' ||
                             ((c == '.'  || c == ',' || c == ':' || c == ';') &&
-                             (j - 1 < here || !Character.isDigit(chs[j - 1 - start])) &&
-                             (j + 1 >= next || !Character.isDigit(chs[j + 1 - start]))) ||
+                             (j - 1 < here || !Character.isDigit(chs[j - 1 - paraStart])) &&
+                             (j + 1 >= spanEnd || !Character.isDigit(chs[j + 1 - paraStart]))) ||
                             ((c == '/' || c == '-') &&
-                             (j + 1 >= next || !Character.isDigit(chs[j + 1 - start]))) ||
+                             (j + 1 >= spanEnd || !Character.isDigit(chs[j + 1 - paraStart]))) ||
                             (c >= FIRST_CJK && isIdeographic(c, true) &&
-                             j + 1 < next && isIdeographic(chs[j + 1 - start], false))) {
+                             j + 1 < spanEnd && isIdeographic(chs[j + 1 - paraStart], false))) {
                             okwidth = w;
                             ok = j + 1;
 
@@ -448,7 +338,7 @@
                         if (ok != here) {
                             // Log.e("text", "output ok " + here + " to " +ok);
 
-                            while (ok < next && chs[ok - start] == ' ') {
+                            while (ok < spanEnd && chs[ok - paraStart] == ' ') {
                                 ok++;
                             }
 
@@ -458,9 +348,9 @@
                                     v,
                                     spacingmult, spacingadd, chooseht,
                                     choosehtv, fm, tab,
-                                    needMultiply, start, chdirs, dir, easy,
+                                    needMultiply, paraStart, chdirs, dir, easy,
                                     ok == bufend, includepad, trackpad,
-                                    widths, start, end - start,
+                                    chs, widths, here - paraStart,
                                     where, ellipsizedWidth, okwidth,
                                     paint);
 
@@ -484,7 +374,7 @@
                         if (ok != here) {
                             // Log.e("text", "output ok " + here + " to " +ok);
 
-                            while (ok < next && chs[ok - start] == ' ') {
+                            while (ok < spanEnd && chs[ok - paraStart] == ' ') {
                                 ok++;
                             }
 
@@ -494,9 +384,9 @@
                                     v,
                                     spacingmult, spacingadd, chooseht,
                                     choosehtv, fm, tab,
-                                    needMultiply, start, chdirs, dir, easy,
+                                    needMultiply, paraStart, chdirs, dir, easy,
                                     ok == bufend, includepad, trackpad,
-                                    widths, start, end - start,
+                                    chs, widths, here - paraStart,
                                     where, ellipsizedWidth, okwidth,
                                     paint);
 
@@ -510,18 +400,19 @@
                                     v,
                                     spacingmult, spacingadd, chooseht,
                                     choosehtv, fm, tab,
-                                    needMultiply, start, chdirs, dir, easy,
+                                    needMultiply, paraStart, chdirs, dir, easy,
                                     fit == bufend, includepad, trackpad,
-                                    widths, start, end - start,
+                                    chs, widths, here - paraStart,
                                     where, ellipsizedWidth, fitwidth,
                                     paint);
 
                             here = fit;
                         } else {
                             // Log.e("text", "output one " + here + " to " +(here + 1));
-                            measureText(paint, mWorkPaint,
-                                        source, here, here + 1, fm, tab,
-                                        null);
+                            // XXX not sure why the existing fm wasn't ok.
+                            // measureText(paint, mWorkPaint,
+                            //             source, here, here + 1, fm, tab,
+                            //             null);
 
                             v = out(source,
                                     here, here+1,
@@ -530,18 +421,18 @@
                                     v,
                                     spacingmult, spacingadd, chooseht,
                                     choosehtv, fm, tab,
-                                    needMultiply, start, chdirs, dir, easy,
+                                    needMultiply, paraStart, chdirs, dir, easy,
                                     here + 1 == bufend, includepad,
                                     trackpad,
-                                    widths, start, end - start,
+                                    chs, widths, here - paraStart,
                                     where, ellipsizedWidth,
-                                    widths[here - start], paint);
+                                    widths[here - paraStart], paint);
 
                             here = here + 1;
                         }
 
-                        if (here < i) {
-                            j = next = here; // must remeasure
+                        if (here < spanStart) {
+                            j = spanEnd = here; // must remeasure
                         } else {
                             j = here - 1;    // continue looping
                         }
@@ -558,7 +449,7 @@
                 }
             }
 
-            if (end != here) {
+            if (paraEnd != here) {
                 if ((fittop | fitbottom | fitdescent | fitascent) == 0) {
                     paint.getFontMetricsInt(fm);
 
@@ -571,20 +462,20 @@
                 // Log.e("text", "output rest " + here + " to " + end);
 
                 v = out(source,
-                        here, end, fitascent, fitdescent,
+                        here, paraEnd, fitascent, fitdescent,
                         fittop, fitbottom,
                         v,
                         spacingmult, spacingadd, chooseht,
                         choosehtv, fm, tab,
-                        needMultiply, start, chdirs, dir, easy,
-                        end == bufend, includepad, trackpad,
-                        widths, start, end - start,
+                        needMultiply, paraStart, chdirs, dir, easy,
+                        paraEnd == bufend, includepad, trackpad,
+                        chs, widths, here - paraStart,
                         where, ellipsizedWidth, w, paint);
             }
 
-            start = end;
+            paraStart = paraEnd;
 
-            if (end == bufend)
+            if (paraEnd == bufend)
                 break;
         }
 
@@ -599,246 +490,13 @@
                     v,
                     spacingmult, spacingadd, null,
                     null, fm, false,
-                    needMultiply, bufend, chdirs, DEFAULT_DIR, true,
+                    needMultiply, bufend, null, DEFAULT_DIR, true,
                     true, includepad, trackpad,
-                    widths, bufstart, 0,
+                    null, null, bufstart,
                     where, ellipsizedWidth, 0, paint);
         }
     }
 
-    /**
-     * Runs the unicode bidi algorithm on the first n chars in chs, returning
-     * the char dirs in chInfo and the base line direction of the first
-     * paragraph.
-     * 
-     * XXX change result from dirs to levels
-     *  
-     * @param dir the direction flag, either DIR_REQUEST_LTR,
-     * DIR_REQUEST_RTL, DIR_REQUEST_DEFAULT_LTR, or DIR_REQUEST_DEFAULT_RTL.
-     * @param chs the text to examine
-     * @param chInfo on input, if hasInfo is true, override and other flags 
-     * representing out-of-band embedding information. On output, the generated 
-     * dirs of the text.
-     * @param n the length of the text/information in chs and chInfo
-     * @param hasInfo true if chInfo has input information, otherwise the
-     * input data in chInfo is ignored.
-     * @return the resolved direction level of the first paragraph, either
-     * DIR_LEFT_TO_RIGHT or DIR_RIGHT_TO_LEFT.
-     */
-    /* package */ static int bidi(int dir, char[] chs, byte[] chInfo, int n, 
-            boolean hasInfo) {
-        
-        AndroidCharacter.getDirectionalities(chs, chInfo, n);
-
-        /*
-         * Determine primary paragraph direction if not specified
-         */
-        if (dir != DIR_REQUEST_LTR && dir != DIR_REQUEST_RTL) {
-            // set up default
-            dir = dir >= 0 ? DIR_LEFT_TO_RIGHT : DIR_RIGHT_TO_LEFT;
-            for (int j = 0; j < n; j++) {
-                int d = chInfo[j];
-
-                if (d == Character.DIRECTIONALITY_LEFT_TO_RIGHT) {
-                    dir = DIR_LEFT_TO_RIGHT;
-                    break;
-                }
-                if (d == Character.DIRECTIONALITY_RIGHT_TO_LEFT) {
-                    dir = DIR_RIGHT_TO_LEFT;
-                    break;
-                }
-            }
-        }
-
-        final byte SOR = dir == DIR_LEFT_TO_RIGHT ?
-                Character.DIRECTIONALITY_LEFT_TO_RIGHT :
-                Character.DIRECTIONALITY_RIGHT_TO_LEFT;
-
-        /*
-         * XXX Explicit overrides should go here
-         */
-
-        /*
-         * Weak type resolution
-         */
-
-        // dump(chdirs, n, "initial");
-
-        // W1 non spacing marks
-        for (int j = 0; j < n; j++) {
-            if (chInfo[j] == Character.NON_SPACING_MARK) {
-                if (j == 0)
-                    chInfo[j] = SOR;
-                else
-                    chInfo[j] = chInfo[j - 1];
-            }
-        }
-
-        // dump(chdirs, n, "W1");
-
-        // W2 european numbers
-        byte cur = SOR;
-        for (int j = 0; j < n; j++) {
-            byte d = chInfo[j];
-
-            if (d == Character.DIRECTIONALITY_LEFT_TO_RIGHT ||
-                d == Character.DIRECTIONALITY_RIGHT_TO_LEFT ||
-                d == Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC)
-                cur = d;
-            else if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER) {
-                 if (cur ==
-                    Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC)
-                    chInfo[j] = Character.DIRECTIONALITY_ARABIC_NUMBER;
-            }
-        }
-
-        // dump(chdirs, n, "W2");
-
-        // W3 arabic letters
-        for (int j = 0; j < n; j++) {
-            if (chInfo[j] == Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC)
-                chInfo[j] = Character.DIRECTIONALITY_RIGHT_TO_LEFT;
-        }
-
-        // dump(chdirs, n, "W3");
-
-        // W4 single separator between numbers
-        for (int j = 1; j < n - 1; j++) {
-            byte d = chInfo[j];
-            byte prev = chInfo[j - 1];
-            byte next = chInfo[j + 1];
-
-            if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER_SEPARATOR) {
-                if (prev == Character.DIRECTIONALITY_EUROPEAN_NUMBER &&
-                    next == Character.DIRECTIONALITY_EUROPEAN_NUMBER)
-                    chInfo[j] = Character.DIRECTIONALITY_EUROPEAN_NUMBER;
-            } else if (d == Character.DIRECTIONALITY_COMMON_NUMBER_SEPARATOR) {
-                if (prev == Character.DIRECTIONALITY_EUROPEAN_NUMBER &&
-                    next == Character.DIRECTIONALITY_EUROPEAN_NUMBER)
-                    chInfo[j] = Character.DIRECTIONALITY_EUROPEAN_NUMBER;
-                if (prev == Character.DIRECTIONALITY_ARABIC_NUMBER &&
-                    next == Character.DIRECTIONALITY_ARABIC_NUMBER)
-                    chInfo[j] = Character.DIRECTIONALITY_ARABIC_NUMBER;
-            }
-        }
-
-        // dump(chdirs, n, "W4");
-
-        // W5 european number terminators
-        boolean adjacent = false;
-        for (int j = 0; j < n; j++) {
-            byte d = chInfo[j];
-
-            if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER)
-                adjacent = true;
-            else if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER_TERMINATOR && adjacent)
-                chInfo[j] = Character.DIRECTIONALITY_EUROPEAN_NUMBER;
-            else
-                adjacent = false;
-        }
-
-        //dump(chdirs, n, "W5");
-
-        // W5 european number terminators part 2,
-        // W6 separators and terminators
-        adjacent = false;
-        for (int j = n - 1; j >= 0; j--) {
-            byte d = chInfo[j];
-
-            if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER)
-                adjacent = true;
-            else if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER_TERMINATOR) {
-                if (adjacent)
-                    chInfo[j] = Character.DIRECTIONALITY_EUROPEAN_NUMBER;
-                else
-                    chInfo[j] = Character.DIRECTIONALITY_OTHER_NEUTRALS;
-            }
-            else {
-                adjacent = false;
-
-                if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER_SEPARATOR ||
-                    d == Character.DIRECTIONALITY_COMMON_NUMBER_SEPARATOR ||
-                    d == Character.DIRECTIONALITY_PARAGRAPH_SEPARATOR ||
-                    d == Character.DIRECTIONALITY_SEGMENT_SEPARATOR)
-                    chInfo[j] = Character.DIRECTIONALITY_OTHER_NEUTRALS;
-            }
-        }
-
-        // dump(chdirs, n, "W6");
-
-        // W7 strong direction of european numbers
-        cur = SOR;
-        for (int j = 0; j < n; j++) {
-            byte d = chInfo[j];
-
-            if (d == SOR ||
-                d == Character.DIRECTIONALITY_LEFT_TO_RIGHT ||
-                d == Character.DIRECTIONALITY_RIGHT_TO_LEFT)
-                cur = d;
-
-            if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER)
-                chInfo[j] = cur;
-        }
-
-        // dump(chdirs, n, "W7");
-
-        // N1, N2 neutrals
-        cur = SOR;
-        for (int j = 0; j < n; j++) {
-            byte d = chInfo[j];
-
-            if (d == Character.DIRECTIONALITY_LEFT_TO_RIGHT ||
-                d == Character.DIRECTIONALITY_RIGHT_TO_LEFT) {
-                cur = d;
-            } else if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER ||
-                       d == Character.DIRECTIONALITY_ARABIC_NUMBER) {
-                cur = Character.DIRECTIONALITY_RIGHT_TO_LEFT;
-            } else {
-                byte dd = SOR;
-                int k;
-
-                for (k = j + 1; k < n; k++) {
-                    dd = chInfo[k];
-
-                    if (dd == Character.DIRECTIONALITY_LEFT_TO_RIGHT ||
-                        dd == Character.DIRECTIONALITY_RIGHT_TO_LEFT) {
-                        break;
-                    }
-                    if (dd == Character.DIRECTIONALITY_EUROPEAN_NUMBER ||
-                        dd == Character.DIRECTIONALITY_ARABIC_NUMBER) {
-                        dd = Character.DIRECTIONALITY_RIGHT_TO_LEFT;
-                        break;
-                    }
-                }
-
-                for (int y = j; y < k; y++) {
-                    if (dd == cur)
-                        chInfo[y] = cur;
-                    else
-                        chInfo[y] = SOR;
-                }
-
-                j = k - 1;
-            }
-        }
-
-        // dump(chdirs, n, "final");
-
-        // extra: enforce that all tabs and surrogate characters go the
-        // primary direction
-        // TODO: actually do directions right for surrogates
-
-        for (int j = 0; j < n; j++) {
-            char c = chs[j];
-
-            if (c == '\t' || (c >= 0xD800 && c <= 0xDFFF)) {
-                chInfo[j] = SOR;
-            }
-        }
-        
-        return dir;
-    }
-
     private static final char FIRST_CJK = '\u2E80';
     /**
      * Returns true if the specified character is one of those specified
@@ -944,28 +602,6 @@
     }
 */
 
-    private static int getFit(TextPaint paint,
-                              TextPaint workPaint,
-                       CharSequence text, int start, int end,
-                       float wid) {
-        int high = end + 1, low = start - 1, guess;
-
-        while (high - low > 1) {
-            guess = (high + low) / 2;
-
-            if (measureText(paint, workPaint,
-                            text, start, guess, null, true, null) > wid)
-                high = guess;
-            else
-                low = guess;
-        }
-
-        if (low < start)
-            return start;
-        else
-            return low;
-    }
-
     private int out(CharSequence text, int start, int end,
                       int above, int below, int top, int bottom, int v,
                       float spacingmult, float spacingadd,
@@ -974,7 +610,7 @@
                       boolean needMultiply, int pstart, byte[] chdirs,
                       int dir, boolean easy, boolean last,
                       boolean includepad, boolean trackpad,
-                      float[] widths, int widstart, int widoff,
+                      char[] chs, float[] widths, int widstart,
                       TextUtils.TruncateAt ellipsize, float ellipsiswidth,
                       float textwidth, TextPaint paint) {
         int j = mLineCount;
@@ -982,8 +618,6 @@
         int want = off + mColumns + TOP;
         int[] lines = mLines;
 
-        // Log.e("text", "line " + start + " to " + end + (last ? "===" : ""));
-
         if (want >= lines.length) {
             int nlen = ArrayUtils.idealIntArraySize(want + 1);
             int[] grow = new int[nlen];
@@ -1062,56 +696,20 @@
         if (tab)
             lines[off + TAB] |= TAB_MASK;
 
-        {
-            lines[off + DIR] |= dir << DIR_SHIFT;
-
-            int cur = Character.DIRECTIONALITY_LEFT_TO_RIGHT;
-            int count = 0;
-
-            if (!easy) {
-                for (int k = start; k < end; k++) {
-                    if (chdirs[k - pstart] != cur) {
-                        count++;
-                        cur = chdirs[k - pstart];
-                    }
-                }
-            }
-
-            Directions linedirs;
-
-            if (count == 0) {
-                linedirs = DIRS_ALL_LEFT_TO_RIGHT;
-            } else {
-                short[] ld = new short[count + 1];
-
-                cur = Character.DIRECTIONALITY_LEFT_TO_RIGHT;
-                count = 0;
-                int here = start;
-
-                for (int k = start; k < end; k++) {
-                    if (chdirs[k - pstart] != cur) {
-                        // XXX check to make sure we don't
-                        //     overflow short
-                        ld[count++] = (short) (k - here);
-                        cur = chdirs[k - pstart];
-                        here = k;
-                    }
-                }
-
-                ld[count] = (short) (end - here);
-
-                if (count == 1 && ld[0] == 0) {
-                    linedirs = DIRS_ALL_RIGHT_TO_LEFT;
-                } else {
-                    linedirs = new Directions(ld);
-                }
-            }
-
+        lines[off + DIR] |= dir << DIR_SHIFT;
+        Directions linedirs = DIRS_ALL_LEFT_TO_RIGHT;
+        // easy means all chars < the first RTL, so no emoji, no nothing
+        // XXX a run with no text or all spaces is easy but might be an empty
+        // RTL paragraph.  Make sure easy is false if this is the case.
+        if (easy) {
             mLineDirections[j] = linedirs;
+        } else {
+            mLineDirections[j] = AndroidBidi.directions(dir, chdirs, widstart, chs,
+                    widstart, end - start);
 
             // If ellipsize is in marquee mode, do not apply ellipsis on the first line
             if (ellipsize != null && (ellipsize != TextUtils.TruncateAt.MARQUEE || j != 0)) {
-                calculateEllipsis(start, end, widths, widstart, widoff,
+                calculateEllipsis(start, end, widths, widstart,
                                   ellipsiswidth, ellipsize, j,
                                   textwidth, paint);
             }
@@ -1122,7 +720,7 @@
     }
 
     private void calculateEllipsis(int linestart, int lineend,
-                                   float[] widths, int widstart, int widoff,
+                                   float[] widths, int widstart,
                                    float avail, TextUtils.TruncateAt where,
                                    int line, float textwidth, TextPaint paint) {
         int len = lineend - linestart;
@@ -1142,7 +740,7 @@
             int i;
 
             for (i = len; i >= 0; i--) {
-                float w = widths[i - 1 + linestart - widstart + widoff];
+                float w = widths[i - 1 + linestart - widstart];
 
                 if (w + sum + ellipsiswid > avail) {
                     break;
@@ -1158,7 +756,7 @@
             int i;
 
             for (i = 0; i < len; i++) {
-                float w = widths[i + linestart - widstart + widoff];
+                float w = widths[i + linestart - widstart];
 
                 if (w + sum + ellipsiswid > avail) {
                     break;
@@ -1175,7 +773,7 @@
 
             float ravail = (avail - ellipsiswid) / 2;
             for (right = len; right >= 0; right--) {
-                float w = widths[right - 1 + linestart - widstart + widoff];
+                float w = widths[right - 1 + linestart - widstart];
 
                 if (w + rsum > ravail) {
                     break;
@@ -1186,7 +784,7 @@
 
             float lavail = avail - ellipsiswid - rsum;
             for (left = 0; left < right; left++) {
-                float w = widths[left + linestart - widstart + widoff];
+                float w = widths[left + linestart - widstart];
 
                 if (w + lsum > lavail) {
                     break;
@@ -1203,7 +801,7 @@
         mLines[mColumns * line + ELLIPSIS_COUNT] = ellipsisCount;
     }
 
-    // Override the baseclass so we can directly access our members,
+    // Override the base class so we can directly access our members,
     // rather than relying on member functions.
     // The logic mirrors that of Layout.getLineForVertical
     // FIXME: It may be faster to do a linear search for layouts without many lines.
@@ -1232,11 +830,11 @@
     }
 
     public int getLineTop(int line) {
-        return mLines[mColumns * line + TOP];    
+        return mLines[mColumns * line + TOP];
     }
 
     public int getLineDescent(int line) {
-        return mLines[mColumns * line + DESCENT];   
+        return mLines[mColumns * line + DESCENT];
     }
 
     public int getLineStart(int line) {
@@ -1312,10 +910,8 @@
     private static final char FIRST_RIGHT_TO_LEFT = '\u0590';
 
     /*
-     * These are reused across calls to generate()
+     * This is reused across calls to generate()
      */
-    private byte[] mChdirs;
-    private char[] mChs;
-    private float[] mWidths;
+    private MeasuredText mMeasured;
     private Paint.FontMetricsInt mFontMetricsInt = new Paint.FontMetricsInt();
 }
diff --git a/core/java/android/text/Styled.java b/core/java/android/text/Styled.java
deleted file mode 100644
index 513b2cd..0000000
--- a/core/java/android/text/Styled.java
+++ /dev/null
@@ -1,434 +0,0 @@
-/*
- * Copyright (C) 2006 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.text;
-
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.text.style.CharacterStyle;
-import android.text.style.MetricAffectingSpan;
-import android.text.style.ReplacementSpan;
-
-/**
- * This class provides static methods for drawing and measuring styled text,
- * like {@link android.text.Spanned} object with
- * {@link android.text.style.ReplacementSpan}.
- *
- * @hide
- */
-public class Styled
-{
-    /**
-     * Draws and/or measures a uniform run of text on a single line. No span of
-     * interest should start or end in the middle of this run (if not
-     * drawing, character spans that don't affect metrics can be ignored).
-     * Neither should the run direction change in the middle of the run.
-     *
-     * <p>The x position is the leading edge of the text. In a right-to-left
-     * paragraph, this will be to the right of the text to be drawn. Paint
-     * should not have an Align value other than LEFT or positioning will get
-     * confused.
-     *
-     * <p>On return, workPaint will reflect the original paint plus any
-     * modifications made by character styles on the run.
-     *
-     * <p>The returned width is signed and will be < 0 if the paragraph
-     * direction is right-to-left.
-     */
-    private static float drawUniformRun(Canvas canvas,
-                              Spanned text, int start, int end,
-                              int dir, boolean runIsRtl,
-                              float x, int top, int y, int bottom,
-                              Paint.FontMetricsInt fmi,
-                              TextPaint paint,
-                              TextPaint workPaint,
-                              boolean needWidth) {
-
-        boolean haveWidth = false;
-        float ret = 0;
-        CharacterStyle[] spans = text.getSpans(start, end, CharacterStyle.class);
-
-        ReplacementSpan replacement = null;
-
-        // XXX: This shouldn't be modifying paint, only workPaint.
-        // However, the members belonging to TextPaint should have default
-        // values anyway.  Better to ensure this in the Layout constructor.
-        paint.bgColor = 0;
-        paint.baselineShift = 0;
-        workPaint.set(paint);
-
-		if (spans.length > 0) {
-			for (int i = 0; i < spans.length; i++) {
-				CharacterStyle span = spans[i];
-
-				if (span instanceof ReplacementSpan) {
-					replacement = (ReplacementSpan)span;
-				}
-				else {
-					span.updateDrawState(workPaint);
-				}
-			}
-		}
-
-        if (replacement == null) {
-            CharSequence tmp;
-            int tmpstart, tmpend;
-
-            if (runIsRtl) {
-                tmp = TextUtils.getReverse(text, start, end);
-                tmpstart = 0;
-                // XXX: assumes getReverse doesn't change the length of the text
-                tmpend = end - start;
-            } else {
-                tmp = text;
-                tmpstart = start;
-                tmpend = end;
-            }
-
-            if (fmi != null) {
-                workPaint.getFontMetricsInt(fmi);
-            }
-
-            if (canvas != null) {
-                if (workPaint.bgColor != 0) {
-                    int c = workPaint.getColor();
-                    Paint.Style s = workPaint.getStyle();
-                    workPaint.setColor(workPaint.bgColor);
-                    workPaint.setStyle(Paint.Style.FILL);
-
-                    if (!haveWidth) {
-                        ret = workPaint.measureText(tmp, tmpstart, tmpend);
-                        haveWidth = true;
-                    }
-
-                    if (dir == Layout.DIR_RIGHT_TO_LEFT)
-                        canvas.drawRect(x - ret, top, x, bottom, workPaint);
-                    else
-                        canvas.drawRect(x, top, x + ret, bottom, workPaint);
-
-                    workPaint.setStyle(s);
-                    workPaint.setColor(c);
-                }
-
-                if (dir == Layout.DIR_RIGHT_TO_LEFT) {
-                    if (!haveWidth) {
-                        ret = workPaint.measureText(tmp, tmpstart, tmpend);
-                        haveWidth = true;
-                    }
-
-                    canvas.drawText(tmp, tmpstart, tmpend,
-                                    x - ret, y + workPaint.baselineShift, workPaint);
-                } else {
-                    if (needWidth) {
-                        if (!haveWidth) {
-                            ret = workPaint.measureText(tmp, tmpstart, tmpend);
-                            haveWidth = true;
-                        }
-                    }
-
-                    canvas.drawText(tmp, tmpstart, tmpend,
-                                    x, y + workPaint.baselineShift, workPaint);
-                }
-            } else {
-                if (needWidth && !haveWidth) {
-                    ret = workPaint.measureText(tmp, tmpstart, tmpend);
-                    haveWidth = true;
-                }
-            }
-        } else {
-            ret = replacement.getSize(workPaint, text, start, end, fmi);
-
-            if (canvas != null) {
-                if (dir == Layout.DIR_RIGHT_TO_LEFT)
-                    replacement.draw(canvas, text, start, end,
-                                     x - ret, top, y, bottom, workPaint);
-                else
-                    replacement.draw(canvas, text, start, end,
-                                     x, top, y, bottom, workPaint);
-            }
-        }
-
-        if (dir == Layout.DIR_RIGHT_TO_LEFT)
-            return -ret;
-        else
-            return ret;
-    }
-
-    /**
-     * Returns the advance widths for a uniform left-to-right run of text with
-     * no style changes in the middle of the run. If any style is replacement
-     * text, the first character will get the width of the replacement and the
-     * remaining characters will get a width of 0.
-     * 
-     * @param paint the paint, will not be modified
-     * @param workPaint a paint to modify; on return will reflect the original
-     *        paint plus the effect of all spans on the run
-     * @param text the text
-     * @param start the start of the run
-     * @param end the limit of the run
-     * @param widths array to receive the advance widths of the characters. Must
-     *        be at least a large as (end - start).
-     * @param fmi FontMetrics information; can be null
-     * @return the actual number of widths returned
-     */
-    public static int getTextWidths(TextPaint paint,
-                                    TextPaint workPaint,
-                                    Spanned text, int start, int end,
-                                    float[] widths, Paint.FontMetricsInt fmi) {
-        MetricAffectingSpan[] spans =
-            text.getSpans(start, end, MetricAffectingSpan.class);
-
-		ReplacementSpan replacement = null;
-        workPaint.set(paint);
-		
-		for (int i = 0; i < spans.length; i++) {
-			MetricAffectingSpan span = spans[i];
-			if (span instanceof ReplacementSpan) {
-				replacement = (ReplacementSpan)span;
-			}
-			else {
-				span.updateMeasureState(workPaint);
-			}
-		}
-	
-        if (replacement == null) {
-            workPaint.getFontMetricsInt(fmi);
-            workPaint.getTextWidths(text, start, end, widths);
-        } else {
-            int wid = replacement.getSize(workPaint, text, start, end, fmi);
-
-            if (end > start) {
-                widths[0] = wid;
-                for (int i = start + 1; i < end; i++)
-                    widths[i - start] = 0;
-            }
-        }
-        return end - start;
-    }
-
-    /**
-     * Renders and/or measures a directional run of text on a single line.
-     * Unlike {@link #drawUniformRun}, this can render runs that cross style
-     * boundaries.  Returns the signed advance width, if requested.
-     *
-     * <p>The x position is the leading edge of the text. In a right-to-left
-     * paragraph, this will be to the right of the text to be drawn. Paint
-     * should not have an Align value other than LEFT or positioning will get
-     * confused.
-     *
-     * <p>This optimizes for unstyled text and so workPaint might not be
-     * modified by this call.
-     *
-     * <p>The returned advance width will be < 0 if the paragraph
-     * direction is right-to-left.
-     */
-    private static float drawDirectionalRun(Canvas canvas,
-                                 CharSequence text, int start, int end,
-                                 int dir, boolean runIsRtl,
-                                 float x, int top, int y, int bottom,
-                                 Paint.FontMetricsInt fmi,
-                                 TextPaint paint,
-                                 TextPaint workPaint,
-                                 boolean needWidth) {
-
-        // XXX: It looks like all calls to this API match dir and runIsRtl, so
-        // having both parameters is redundant and confusing.
-
-        // fast path for unstyled text
-        if (!(text instanceof Spanned)) {
-            float ret = 0;
-
-            if (runIsRtl) {
-                CharSequence tmp = TextUtils.getReverse(text, start, end);
-                // XXX: this assumes getReverse doesn't tweak the length of
-                // the text
-                int tmpend = end - start;
-
-                if (canvas != null || needWidth)
-                    ret = paint.measureText(tmp, 0, tmpend);
-
-                if (canvas != null)
-                    canvas.drawText(tmp, 0, tmpend,
-                                    x - ret, y, paint);
-            } else {
-                if (needWidth)
-                    ret = paint.measureText(text, start, end);
-
-                if (canvas != null)
-                    canvas.drawText(text, start, end, x, y, paint);
-            }
-
-            if (fmi != null) {
-                paint.getFontMetricsInt(fmi);
-            }
-
-            return ret * dir;   // Layout.DIR_RIGHT_TO_LEFT == -1
-        }
-        
-        float ox = x;
-        int minAscent = 0, maxDescent = 0, minTop = 0, maxBottom = 0;
-
-        Spanned sp = (Spanned) text;
-        Class<?> division;
-
-        if (canvas == null)
-            division = MetricAffectingSpan.class;
-        else
-            division = CharacterStyle.class;
-
-        int next;
-        for (int i = start; i < end; i = next) {
-            next = sp.nextSpanTransition(i, end, division);
-
-            // XXX: if dir and runIsRtl were not the same, this would draw
-            // spans in the wrong order, but no one appears to call it this
-            // way.
-            x += drawUniformRun(canvas, sp, i, next, dir, runIsRtl,
-                  x, top, y, bottom, fmi, paint, workPaint,
-                  needWidth || next != end);
-
-            if (fmi != null) {
-                if (fmi.ascent < minAscent)
-                    minAscent = fmi.ascent;
-                if (fmi.descent > maxDescent)
-                    maxDescent = fmi.descent;
-
-                if (fmi.top < minTop)
-                    minTop = fmi.top;
-                if (fmi.bottom > maxBottom)
-                    maxBottom = fmi.bottom;
-            }
-        }
-
-        if (fmi != null) {
-            if (start == end) {
-                paint.getFontMetricsInt(fmi);
-            } else {
-                fmi.ascent = minAscent;
-                fmi.descent = maxDescent;
-                fmi.top = minTop;
-                fmi.bottom = maxBottom;
-            }
-        }
-
-        return x - ox;
-    }
-
-    /**
-     * Draws a unidirectional run of text on a single line, and optionally
-     * returns the signed advance.  Unlike drawDirectionalRun, the paragraph
-     * direction and run direction can be different.
-     */
-    /* package */ static float drawText(Canvas canvas,
-                                       CharSequence text, int start, int end,
-                                       int dir, boolean runIsRtl,
-                                       float x, int top, int y, int bottom,
-                                       TextPaint paint,
-                                       TextPaint workPaint,
-                                       boolean needWidth) {
-        // XXX this logic is (dir == DIR_LEFT_TO_RIGHT) == runIsRtl
-        if ((dir == Layout.DIR_RIGHT_TO_LEFT && !runIsRtl) ||
-            (runIsRtl && dir == Layout.DIR_LEFT_TO_RIGHT)) {
-            // TODO: this needs the real direction
-            float ch = drawDirectionalRun(null, text, start, end,
-                    Layout.DIR_LEFT_TO_RIGHT, false, 0, 0, 0, 0, null, paint,
-                    workPaint, true);
-
-            ch *= dir;  // DIR_RIGHT_TO_LEFT == -1
-            drawDirectionalRun(canvas, text, start, end, -dir,
-                    runIsRtl, x + ch, top, y, bottom, null, paint,
-                    workPaint, true);
-
-            return ch;
-        }
-
-        return drawDirectionalRun(canvas, text, start, end, dir, runIsRtl,
-                       x, top, y, bottom, null, paint, workPaint,
-                       needWidth);
-    }
-    
-    /**
-     * Draws a run of text on a single line, with its
-     * origin at (x,y), in the specified Paint. The origin is interpreted based
-     * on the Align setting in the Paint.
-     *
-     * This method considers style information in the text (e.g. even when text
-     * is an instance of {@link android.text.Spanned}, this method correctly
-     * draws the text). See also
-     * {@link android.graphics.Canvas#drawText(CharSequence, int, int, float,
-     * float, Paint)} and
-     * {@link android.graphics.Canvas#drawRect(float, float, float, float,
-     * Paint)}.
-     * 
-     * @param canvas The target canvas
-     * @param text The text to be drawn
-     * @param start The index of the first character in text to draw
-     * @param end (end - 1) is the index of the last character in text to draw
-     * @param direction The direction of the text. This must be
-     *        {@link android.text.Layout#DIR_LEFT_TO_RIGHT} or
-     *        {@link android.text.Layout#DIR_RIGHT_TO_LEFT}.
-     * @param x The x-coordinate of origin for where to draw the text
-     * @param top The top side of the rectangle to be drawn
-     * @param y The y-coordinate of origin for where to draw the text
-     * @param bottom The bottom side of the rectangle to be drawn
-     * @param paint The main {@link TextPaint} object.
-     * @param workPaint The {@link TextPaint} object used for temporal
-     *        workspace.
-     * @param needWidth If true, this method returns the width of drawn text
-     * @return Width of the drawn text if needWidth is true
-     */
-    public static float drawText(Canvas canvas,
-                                 CharSequence text, int start, int end,
-                                 int direction,
-                                 float x, int top, int y, int bottom,
-                                 TextPaint paint,
-                                 TextPaint workPaint,
-                                 boolean needWidth) {
-        // For safety.
-        direction = direction >= 0 ? Layout.DIR_LEFT_TO_RIGHT
-                : Layout.DIR_RIGHT_TO_LEFT;
-
-        // Hide runIsRtl parameter since it is meaningless for external
-        // developers.
-        // XXX: the runIsRtl probably ought to be the same as direction, then
-        // this could draw rtl text.
-        return drawText(canvas, text, start, end, direction, false,
-                        x, top, y, bottom, paint, workPaint, needWidth);
-    }
-    
-    /**
-     * Returns the width of a run of left-to-right text on a single line,
-     * considering style information in the text (e.g. even when text is an
-     * instance of {@link android.text.Spanned}, this method correctly measures
-     * the width of the text).
-     * 
-     * @param paint the main {@link TextPaint} object; will not be modified
-     * @param workPaint the {@link TextPaint} object available for modification;
-     *        will not necessarily be used
-     * @param text the text to measure
-     * @param start the index of the first character to start measuring
-     * @param end 1 beyond the index of the last character to measure
-     * @param fmi FontMetrics information; can be null
-     * @return The width of the text
-     */
-    public static float measureText(TextPaint paint,
-                                    TextPaint workPaint,
-                                    CharSequence text, int start, int end,
-                                    Paint.FontMetricsInt fmi) {
-        return drawDirectionalRun(null, text, start, end,
-                       Layout.DIR_LEFT_TO_RIGHT, false,
-                       0, 0, 0, 0, fmi, paint, workPaint, true);
-    }
-}
diff --git a/core/java/android/text/TextLine.java b/core/java/android/text/TextLine.java
new file mode 100644
index 0000000..8ab481b
--- /dev/null
+++ b/core/java/android/text/TextLine.java
@@ -0,0 +1,1053 @@
+/*
+ * 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.text;
+
+import com.android.internal.util.ArrayUtils;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.RectF;
+import android.graphics.Paint.FontMetricsInt;
+import android.icu.text.ArabicShaping;
+import android.text.Layout.Directions;
+import android.text.style.CharacterStyle;
+import android.text.style.MetricAffectingSpan;
+import android.text.style.ReplacementSpan;
+import android.text.style.TabStopSpan;
+import android.util.Log;
+
+/**
+ * Represents a line of styled text, for measuring in visual order and
+ * for rendering.
+ *
+ * <p>Get a new instance using obtain(), and when finished with it, return it
+ * to the pool using recycle().
+ *
+ * <p>Call set to prepare the instance for use, then either draw, measure,
+ * metrics, or caretToLeftRightOf.
+ *
+ * @hide
+ */
+class TextLine {
+    private TextPaint mPaint;
+    private CharSequence mText;
+    private int mStart;
+    private int mLen;
+    private int mDir;
+    private Directions mDirections;
+    private boolean mHasTabs;
+    private TabStopSpan[] mTabs;
+
+    private char[] mChars;
+    private boolean mCharsValid;
+    private Spanned mSpanned;
+    private TextPaint mWorkPaint = new TextPaint();
+    private int mPreppedIndex;
+    private int mPreppedLimit;
+
+    private static TextLine[] cached = new TextLine[3];
+
+    /**
+     * Returns a new TextLine from the shared pool.
+     *
+     * @return an uninitialized TextLine
+     */
+    static TextLine obtain() {
+        TextLine tl;
+        synchronized (cached) {
+            for (int i = cached.length; --i >= 0;) {
+                if (cached[i] != null) {
+                    tl = cached[i];
+                    cached[i] = null;
+                    return tl;
+                }
+            }
+        }
+        tl = new TextLine();
+        Log.e("TLINE", "new: " + tl);
+        return tl;
+    }
+
+    /**
+     * Puts a TextLine back into the shared pool. Do not use this TextLine once
+     * it has been returned.
+     * @param tl the textLine
+     * @return null, as a convenience from clearing references to the provided
+     * TextLine
+     */
+    static TextLine recycle(TextLine tl) {
+        tl.mText = null;
+        tl.mPaint = null;
+        tl.mDirections = null;
+        if (tl.mLen < 250) {
+            synchronized(cached) {
+                for (int i = 0; i < cached.length; ++i) {
+                    if (cached[i] == null) {
+                        cached[i] = tl;
+                        break;
+                    }
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Initializes a TextLine and prepares it for use.
+     *
+     * @param paint the base paint for the line
+     * @param text the text, can be Styled
+     * @param start the start of the line relative to the text
+     * @param limit the limit of the line relative to the text
+     * @param dir the paragraph direction of this line
+     * @param directions the directions information of this line
+     * @param hasTabs true if the line might contain tabs or emoji
+     * @param spans array of paragraph-level spans, of which only TabStopSpans
+     * are used.  Can be null.
+     */
+    void set(TextPaint paint, CharSequence text, int start, int limit, int dir,
+            Directions directions, boolean hasTabs, Object[] spans) {
+        mPaint = paint;
+        mText = text;
+        mStart = start;
+        mLen = limit - start;
+        mDir = dir;
+        mDirections = directions;
+        mHasTabs = hasTabs;
+        mSpanned = null;
+        mPreppedIndex = 0;
+        mPreppedLimit = 0;
+
+        boolean hasReplacement = false;
+        if (text instanceof Spanned) {
+            mSpanned = (Spanned) text;
+            hasReplacement = mSpanned.getSpans(start, limit,
+                    ReplacementSpan.class).length > 0;
+        }
+
+        mCharsValid = hasReplacement || hasTabs ||
+            directions != Layout.DIRS_ALL_LEFT_TO_RIGHT;
+
+        if (mCharsValid) {
+            if (mChars == null || mChars.length < mLen) {
+                mChars = new char[ArrayUtils.idealCharArraySize(mLen)];
+            }
+            TextUtils.getChars(text, start, limit, mChars, 0);
+
+            if (hasTabs) {
+                TabStopSpan[] tabs = mTabs;
+                int tabLen = 0;
+                if (mSpanned != null && spans == null) {
+                    TabStopSpan[] newTabs = mSpanned.getSpans(start, limit,
+                            TabStopSpan.class);
+                    if (tabs == null || tabs.length < newTabs.length) {
+                        tabs = newTabs;
+                    } else {
+                        for (int i = 0; i < newTabs.length; ++i) {
+                            tabs[i] = newTabs[i];
+                        }
+                    }
+                    tabLen = newTabs.length;
+                } else if (spans != null) {
+                    if (tabs == null || tabs.length < spans.length) {
+                        tabs = new TabStopSpan[spans.length];
+                    }
+                    for (int i = 0; i < spans.length; ++i) {
+                        if (spans[i] instanceof TabStopSpan) {
+                            tabs[tabLen++] = (TabStopSpan) spans[i];
+                        }
+                    }
+                }
+
+                if (tabs != null && tabLen < tabs.length){
+                    tabs[tabLen] = null;
+                }
+                mTabs = tabs;
+            }
+        }
+    }
+
+    /**
+     * Renders the TextLine.
+     *
+     * @param c the canvas to render on
+     * @param x the leading margin position
+     * @param top the top of the line
+     * @param y the baseline
+     * @param bottom the bottom of the line
+     */
+    void draw(Canvas c, float x, int top, int y, int bottom) {
+        if (!mHasTabs) {
+            if (mDirections == Layout.DIRS_ALL_LEFT_TO_RIGHT) {
+                drawRun(c, 0, 0, mLen, false, x, top, y, bottom, false);
+                return;
+            }
+            if (mDirections == Layout.DIRS_ALL_RIGHT_TO_LEFT) {
+                drawRun(c, 0, 0, mLen, true, x, top, y, bottom, false);
+                return;
+            }
+        }
+
+        float h = 0;
+        int[] runs = mDirections.mDirections;
+        RectF emojiRect = null;
+
+        int lastRunIndex = runs.length - 2;
+        for (int i = 0; i < runs.length; i += 2) {
+            int runStart = runs[i];
+            int runLimit = runStart + (runs[i+1] & Layout.RUN_LENGTH_MASK);
+            if (runLimit > mLen) {
+                runLimit = mLen;
+            }
+            boolean runIsRtl = (runs[i+1] & Layout.RUN_RTL_FLAG) != 0;
+
+            int segstart = runStart;
+            char[] chars = mChars;
+            for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; j++) {
+                int codept = 0;
+                Bitmap bm = null;
+
+                if (mHasTabs && j < runLimit) {
+                    codept = mChars[j];
+                    if (codept >= 0xd800 && codept < 0xdc00 && j + 1 < runLimit) {
+                        codept = Character.codePointAt(mChars, j);
+                        if (codept >= Layout.MIN_EMOJI && codept <= Layout.MAX_EMOJI) {
+                            bm = Layout.EMOJI_FACTORY.getBitmapFromAndroidPua(codept);
+                        } else if (codept > 0xffff) {
+                            ++j;
+                            continue;
+                        }
+                    }
+                }
+
+                if (j == runLimit || codept == '\t' || bm != null) {
+                    h += drawRun(c, i, segstart, j, runIsRtl, x+h, top, y, bottom,
+                            i != lastRunIndex || j != mLen);
+
+                    if (codept == '\t') {
+                        h = mDir * nextTab(h * mDir);
+                    } else if (bm != null) {
+                        float bmAscent = ascent(j);
+                        float bitmapHeight = bm.getHeight();
+                        float scale = -bmAscent / bitmapHeight;
+                        float width = bm.getWidth() * scale;
+
+                        if (emojiRect == null) {
+                            emojiRect = new RectF();
+                        }
+                        emojiRect.set(x + h, y + bmAscent,
+                                x + h + width, y);
+                        c.drawBitmap(bm, null, emojiRect, mPaint);
+                        h += width;
+                        j++;
+                    }
+                    segstart = j + 1;
+                }
+            }
+        }
+    }
+
+    /**
+     * Returns metrics information for the entire line.
+     *
+     * @param fmi receives font metrics information, can be null
+     * @return the signed width of the line
+     */
+    float metrics(FontMetricsInt fmi) {
+        return measure(mLen, false, fmi);
+    }
+
+    /**
+     * Returns information about a position on the line.
+     *
+     * @param offset the line-relative character offset, between 0 and the
+     * line length, inclusive
+     * @param trailing true to measure the trailing edge of the character
+     * before offset, false to measure the leading edge of the character
+     * at offset.
+     * @param fmi receives metrics information about the requested
+     * character, can be null.
+     * @return the signed offset from the leading margin to the requested
+     * character edge.
+     */
+    float measure(int offset, boolean trailing, FontMetricsInt fmi) {
+        int target = trailing ? offset - 1 : offset;
+        if (target < 0) {
+            return 0;
+        }
+
+        float h = 0;
+
+        if (!mHasTabs) {
+            if (mDirections == Layout.DIRS_ALL_LEFT_TO_RIGHT) {
+                return measureRun( 0, 0, target, mLen, false, fmi);
+            }
+            if (mDirections == Layout.DIRS_ALL_RIGHT_TO_LEFT) {
+                return measureRun(0, 0, target, mLen, true, fmi);
+            }
+        }
+
+        char[] chars = mChars;
+        int[] runs = mDirections.mDirections;
+        for (int i = 0; i < runs.length; i += 2) {
+            int runStart = runs[i];
+            int runLimit = runStart + (runs[i+1] & Layout.RUN_LENGTH_MASK);
+            if (runLimit > mLen) {
+                runLimit = mLen;
+            }
+            boolean runIsRtl = (runs[i+1] & Layout.RUN_RTL_FLAG) != 0;
+
+            int segstart = runStart;
+            for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; j++) {
+                int codept = 0;
+                Bitmap bm = null;
+
+                if (mHasTabs && j < runLimit) {
+                    codept = chars[j];
+                    if (codept >= 0xd800 && codept < 0xdc00 && j + 1 < runLimit) {
+                        codept = Character.codePointAt(chars, j);
+                        if (codept >= Layout.MIN_EMOJI && codept <= Layout.MAX_EMOJI) {
+                            bm = Layout.EMOJI_FACTORY.getBitmapFromAndroidPua(codept);
+                        } else if (codept > 0xffff) {
+                            ++j;
+                            continue;
+                        }
+                    }
+                }
+
+                if (j == runLimit || codept == '\t' || bm != null) {
+                    boolean inSegment = target >= segstart && target < j;
+
+                    boolean advance = (mDir == Layout.DIR_RIGHT_TO_LEFT) == runIsRtl;
+                    if (inSegment && advance) {
+                        return h += measureRun(i, segstart, offset, j, runIsRtl, fmi);
+                    }
+
+                    float w = measureRun(i, segstart, j, j, runIsRtl, fmi);
+                    h += advance ? w : -w;
+
+                    if (inSegment) {
+                        return h += measureRun(i, segstart, offset, j, runIsRtl, null);
+                    }
+
+                    if (codept == '\t') {
+                        if (offset == j) {
+                            return h;
+                        }
+                        h = mDir * nextTab(h * mDir);
+                        if (target == j) {
+                            return h;
+                        }
+                    }
+
+                    if (bm != null) {
+                        float bmAscent = ascent(j);
+                        float wid = bm.getWidth() * -bmAscent / bm.getHeight();
+                        h += mDir * wid;
+                        j++;
+                    }
+
+                    segstart = j + 1;
+                }
+            }
+        }
+
+        return h;
+    }
+
+    /**
+     * Draws a unidirectional (but possibly multi-styled) run of text.
+     *
+     * @param c the canvas to draw on
+     * @param runIndex the index of this directional run
+     * @param start the line-relative start
+     * @param limit the line-relative limit
+     * @param runIsRtl true if the run is right-to-left
+     * @param x the position of the run that is closest to the leading margin
+     * @param top the top of the line
+     * @param y the baseline
+     * @param bottom the bottom of the line
+     * @param needWidth true if the width value is required.
+     * @return the signed width of the run, based on the paragraph direction.
+     * Only valid if needWidth is true.
+     */
+    private float drawRun(Canvas c, int runIndex, int start,
+            int limit, boolean runIsRtl, float x, int top, int y, int bottom,
+            boolean needWidth) {
+
+        if ((mDir == Layout.DIR_LEFT_TO_RIGHT) == runIsRtl) {
+            float w = -measureRun(runIndex, start, limit, limit, runIsRtl, null);
+            handleRun(runIndex, start, limit, limit, runIsRtl, c, x + w, top,
+                    y, bottom, null, false, PREP_NONE);
+            return w;
+        }
+
+        return handleRun(runIndex, start, limit, limit, runIsRtl, c, x, top,
+                y, bottom, null, needWidth, PREP_NEEDED);
+    }
+
+    /**
+     * Measures a unidirectional (but possibly multi-styled) run of text.
+     *
+     * @param runIndex the run index
+     * @param start the line-relative start of the run
+     * @param offset the offset to measure to, between start and limit inclusive
+     * @param limit the line-relative limit of the run
+     * @param runIsRtl true if the run is right-to-left
+     * @param fmi receives metrics information about the requested
+     * run, can be null.
+     * @return the signed width from the start of the run to the leading edge
+     * of the character at offset, based on the run (not paragraph) direction
+     */
+    private float measureRun(int runIndex, int start,
+            int offset, int limit, boolean runIsRtl, FontMetricsInt fmi) {
+        return handleRun(runIndex, start, offset, limit, runIsRtl, null,
+                0, 0, 0, 0, fmi, true, PREP_NEEDED);
+    }
+
+    /**
+     * Prepares a run for measurement or rendering.  This ensures that any
+     * required shaping of the text in the run has been performed so that
+     * measurements reflect the shaped text.
+     *
+     * @param runIndex the run index
+     * @param start the line-relative start of the run
+     * @param limit the line-relative limit of the run
+     * @param runIsRtl true if the run is right-to-left
+     */
+    private void prepRun(int runIndex, int start, int limit,
+            boolean runIsRtl) {
+        handleRun(runIndex, start, limit, limit, runIsRtl, null, 0, 0, 0,
+                0, null, false, PREP_ONLY);
+    }
+
+    /**
+     * Walk the cursor through this line, skipping conjuncts and
+     * zero-width characters.
+     *
+     * <p>This function cannot properly walk the cursor off the ends of the line
+     * since it does not know about any shaping on the previous/following line
+     * that might affect the cursor position. Callers must either avoid these
+     * situations or handle the result specially.
+     *
+     * <p>The paint is required because the region around the cursor might not
+     * have been formatted yet, and the valid positions can depend on the glyphs
+     * used to render the text, which in turn depends on the paint.
+     *
+     * @param paint the base paint of the line
+     * @param cursor the starting position of the cursor, between 0 and the
+     * length of the line, inclusive
+     * @param toLeft true if the caret is moving to the left.
+     * @return the new offset.  If it is less than 0 or greater than the length
+     * of the line, the previous/following line should be examined to get the
+     * actual offset.
+     */
+    int getOffsetToLeftRightOf(int cursor, boolean toLeft) {
+        // 1) The caret marks the leading edge of a character. The character
+        // logically before it might be on a different level, and the active caret
+        // position is on the character at the lower level. If that character
+        // was the previous character, the caret is on its trailing edge.
+        // 2) Take this character/edge and move it in the indicated direction.
+        // This gives you a new character and a new edge.
+        // 3) This position is between two visually adjacent characters.  One of
+        // these might be at a lower level.  The active position is on the
+        // character at the lower level.
+        // 4) If the active position is on the trailing edge of the character,
+        // the new caret position is the following logical character, else it
+        // is the character.
+
+        int lineStart = 0;
+        int lineEnd = mLen;
+        boolean paraIsRtl = mDir == -1;
+        int[] runs = mDirections.mDirections;
+
+        int runIndex, runLevel = 0, runStart = lineStart, runLimit = lineEnd, newCaret = -1;
+        boolean trailing = false;
+
+        if (cursor == lineStart) {
+            runIndex = -2;
+        } else if (cursor == lineEnd) {
+            runIndex = runs.length;
+        } else {
+          // First, get information about the run containing the character with
+          // the active caret.
+          for (runIndex = 0; runIndex < runs.length; runIndex += 2) {
+            runStart = lineStart + runs[runIndex];
+            if (cursor >= runStart) {
+              runLimit = runStart + (runs[runIndex+1] & Layout.RUN_LENGTH_MASK);
+              if (runLimit > lineEnd) {
+                  runLimit = lineEnd;
+              }
+              if (cursor < runLimit) {
+                runLevel = (runs[runIndex+1] >>> Layout.RUN_LEVEL_SHIFT) &
+                    Layout.RUN_LEVEL_MASK;
+                if (cursor == runStart) {
+                  // The caret is on a run boundary, see if we should
+                  // use the position on the trailing edge of the previous
+                  // logical character instead.
+                  int prevRunIndex, prevRunLevel, prevRunStart, prevRunLimit;
+                  int pos = cursor - 1;
+                  for (prevRunIndex = 0; prevRunIndex < runs.length; prevRunIndex += 2) {
+                    prevRunStart = lineStart + runs[prevRunIndex];
+                    if (pos >= prevRunStart) {
+                      prevRunLimit = prevRunStart +
+                          (runs[prevRunIndex+1] & Layout.RUN_LENGTH_MASK);
+                      if (prevRunLimit > lineEnd) {
+                          prevRunLimit = lineEnd;
+                      }
+                      if (pos < prevRunLimit) {
+                        prevRunLevel = (runs[prevRunIndex+1] >>> Layout.RUN_LEVEL_SHIFT)
+                            & Layout.RUN_LEVEL_MASK;
+                        if (prevRunLevel < runLevel) {
+                          // Start from logically previous character.
+                          runIndex = prevRunIndex;
+                          runLevel = prevRunLevel;
+                          runStart = prevRunStart;
+                          runLimit = prevRunLimit;
+                          trailing = true;
+                          break;
+                        }
+                      }
+                    }
+                  }
+                }
+                break;
+              }
+            }
+          }
+
+          // caret might be == lineEnd.  This is generally a space or paragraph
+          // separator and has an associated run, but might be the end of
+          // text, in which case it doesn't.  If that happens, we ran off the
+          // end of the run list, and runIndex == runs.length.  In this case,
+          // we are at a run boundary so we skip the below test.
+          if (runIndex != runs.length) {
+              boolean runIsRtl = (runLevel & 0x1) != 0;
+              boolean advance = toLeft == runIsRtl;
+              if (cursor != (advance ? runLimit : runStart) || advance != trailing) {
+                  // Moving within or into the run, so we can move logically.
+                  prepRun(runIndex, runStart, runLimit, runIsRtl);
+                  newCaret = getOffsetBeforeAfter(runIndex, cursor, advance);
+                  // If the new position is internal to the run, we're at the strong
+                  // position already so we're finished.
+                  if (newCaret != (advance ? runLimit : runStart)) {
+                      return newCaret;
+                  }
+              }
+          }
+        }
+
+        // If newCaret is -1, we're starting at a run boundary and crossing
+        // into another run. Otherwise we've arrived at a run boundary, and
+        // need to figure out which character to attach to.  Note we might
+        // need to run this twice, if we cross a run boundary and end up at
+        // another run boundary.
+        while (true) {
+          boolean advance = toLeft == paraIsRtl;
+          int otherRunIndex = runIndex + (advance ? 2 : -2);
+          if (otherRunIndex >= 0 && otherRunIndex < runs.length) {
+            int otherRunStart = lineStart + runs[otherRunIndex];
+            int otherRunLimit = otherRunStart +
+            (runs[otherRunIndex+1] & Layout.RUN_LENGTH_MASK);
+            if (otherRunLimit > lineEnd) {
+                otherRunLimit = lineEnd;
+            }
+            int otherRunLevel = (runs[otherRunIndex+1] >>> Layout.RUN_LEVEL_SHIFT) &
+                Layout.RUN_LEVEL_MASK;
+            boolean otherRunIsRtl = (otherRunLevel & 1) != 0;
+
+            advance = toLeft == otherRunIsRtl;
+            if (newCaret == -1) {
+                prepRun(otherRunIndex, otherRunStart, otherRunLimit,
+                        otherRunIsRtl);
+                newCaret = getOffsetBeforeAfter(otherRunIndex,
+                        advance ? otherRunStart : otherRunLimit, advance);
+                if (newCaret == (advance ? otherRunLimit : otherRunStart)) {
+                    // Crossed and ended up at a new boundary,
+                    // repeat a second and final time.
+                    runIndex = otherRunIndex;
+                    runLevel = otherRunLevel;
+                    continue;
+                }
+                break;
+            }
+
+            // The new caret is at a boundary.
+            if (otherRunLevel < runLevel) {
+              // The strong character is in the other run.
+              newCaret = advance ? otherRunStart : otherRunLimit;
+            }
+            break;
+          }
+
+          if (newCaret == -1) {
+              // We're walking off the end of the line.  The paragraph
+              // level is always equal to or lower than any internal level, so
+              // the boundaries get the strong caret.
+              newCaret = getOffsetBeforeAfter(-1, cursor, advance);
+              break;
+          }
+
+          // Else we've arrived at the end of the line.  That's a strong position.
+          // We might have arrived here by crossing over a run with no internal
+          // breaks and dropping out of the above loop before advancing one final
+          // time, so reset the caret.
+          // Note, we use '<=' below to handle a situation where the only run
+          // on the line is a counter-directional run.  If we're not advancing,
+          // we can end up at the 'lineEnd' position but the caret we want is at
+          // the lineStart.
+          if (newCaret <= lineEnd) {
+              newCaret = advance ? lineEnd : lineStart;
+          }
+          break;
+        }
+
+        return newCaret;
+    }
+
+    /**
+     * Returns the next valid offset within this directional run, skipping
+     * conjuncts and zero-width characters.  This should not be called to walk
+     * off the end of the run.
+     *
+     * @param runIndex the run index
+     * @param offset the offset
+     * @param after true if the new offset should logically follow the provided
+     * offset
+     * @return the new offset
+     */
+    private int getOffsetBeforeAfter(int runIndex, int offset, boolean after) {
+        // XXX note currently there is no special handling of zero-width
+        // combining marks, since the only analysis involves mock shaping.
+
+        boolean offEnd = offset == (after ? mLen : 0);
+        if (runIndex >= 0 && !offEnd && mCharsValid) {
+            char[] chars = mChars;
+            if (after) {
+                int cp = Character.codePointAt(chars, offset, mLen);
+                if (cp >= 0x10000) {
+                    ++offset;
+                }
+                while (++offset < mLen && chars[offset] == '\ufeff'){}
+            } else {
+                while (--offset >= 0 && chars[offset] == '\ufeff'){}
+                int cp = Character.codePointBefore(chars, offset + 1);
+                if (cp >= 0x10000) {
+                    --offset;
+                }
+            }
+            return offset;
+        }
+
+        if (after) {
+            return TextUtils.getOffsetAfter(mText, offset + mStart) - mStart;
+        }
+        return TextUtils.getOffsetBefore(mText, offset + mStart) - mStart;
+    }
+
+    /**
+     * Utility function for measuring and rendering text.  The text must
+     * not include a tab or emoji.
+     *
+     * @param wp the working paint
+     * @param start the start of the text
+     * @param limit the limit of the text
+     * @param runIsRtl true if the run is right-to-left
+     * @param c the canvas, can be null if rendering is not needed
+     * @param x the edge of the run closest to the leading margin
+     * @param top the top of the line
+     * @param y the baseline
+     * @param bottom the bottom of the line
+     * @param fmi receives metrics information, can be null
+     * @param needWidth true if the width of the run is needed
+     * @return the signed width of the run based on the run direction; only
+     * valid if needWidth is true
+     */
+    private float handleText(TextPaint wp, int start, int limit,
+            boolean runIsRtl, Canvas c, float x, int top, int y, int bottom,
+            FontMetricsInt fmi, boolean needWidth) {
+
+        float ret = 0;
+
+        int runLen = limit - start;
+        if (needWidth || (c != null && (wp.bgColor != 0 || runIsRtl))) {
+            if (mCharsValid) {
+                ret = wp.measureText(mChars, start, runLen);
+            } else {
+                ret = wp.measureText(mText, mStart + start,
+                        mStart + start + runLen);
+            }
+        }
+
+        if (fmi != null) {
+            wp.getFontMetricsInt(fmi);
+        }
+
+        if (c != null) {
+            if (runIsRtl) {
+                x -= ret;
+            }
+
+            if (wp.bgColor != 0) {
+                int color = wp.getColor();
+                Paint.Style s = wp.getStyle();
+                wp.setColor(wp.bgColor);
+                wp.setStyle(Paint.Style.FILL);
+
+                c.drawRect(x, top, x + ret, bottom, wp);
+
+                wp.setStyle(s);
+                wp.setColor(color);
+            }
+
+            drawTextRun(c, wp, start, limit, runIsRtl, x, y + wp.baselineShift);
+        }
+
+        return runIsRtl ? -ret : ret;
+    }
+
+    /**
+     * Utility function for measuring and rendering a replacement.
+     *
+     * @param replacement the replacement
+     * @param wp the work paint
+     * @param runIndex the run index
+     * @param start the start of the run
+     * @param limit the limit of the run
+     * @param runIsRtl true if the run is right-to-left
+     * @param c the canvas, can be null if not rendering
+     * @param x the edge of the replacement closest to the leading margin
+     * @param top the top of the line
+     * @param y the baseline
+     * @param bottom the bottom of the line
+     * @param fmi receives metrics information, can be null
+     * @param needWidth true if the width of the replacement is needed
+     * @param prepFlags one of PREP_NONE, PREP_REQUIRED, or PREP_ONLY
+     * @return the signed width of the run based on the run direction; only
+     * valid if needWidth is true
+     */
+    private float handleReplacement(ReplacementSpan replacement, TextPaint wp,
+            int runIndex, int start, int limit, boolean runIsRtl, Canvas c,
+            float x, int top, int y, int bottom, FontMetricsInt fmi,
+            boolean needWidth, int prepFlags) {
+
+        float ret = 0;
+
+        // Preparation replaces the first character of the series with the
+        // object-replacement character and the remainder with zero width
+        // non-break space aka BOM.  Cursor movement code skips over the BOMs
+        // so that the replacement character is the only character 'seen'.
+        if (prepFlags != PREP_NONE && limit > start &&
+                (runIndex > mPreppedIndex ||
+                        (runIndex == mPreppedIndex && start >= mPreppedLimit))) {
+            char[] chars = mChars;
+            chars[start] = '\ufffc';
+            for (int i = start + 1; i < limit; ++i) {
+                chars[i] = '\ufeff'; // used as ZWNBS, marks positions to skip
+            }
+            mPreppedIndex = runIndex;
+            mPreppedLimit = limit;
+        }
+
+        if (prepFlags != PREP_ONLY) {
+            int textStart = mStart + start;
+            int textLimit = mStart + limit;
+
+            if (needWidth || (c != null && runIsRtl)) {
+                ret = replacement.getSize(wp, mText, textStart, textLimit, fmi);
+            }
+
+            if (c != null) {
+                if (runIsRtl) {
+                    x -= ret;
+                }
+                replacement.draw(c, mText, textStart, textLimit,
+                        x, top, y, bottom, wp);
+            }
+        }
+
+        return runIsRtl ? -ret : ret;
+    }
+
+    /**
+     * Utility function for handling a unidirectional run.  The run must not
+     * contain tabs or emoji but can contain styles.
+     *
+     * @param p the base paint
+     * @param runIndex the run index
+     * @param start the line-relative start of the run
+     * @param offset the offset to measure to, between start and limit inclusive
+     * @param limit the limit of the run
+     * @param runIsRtl true if the run is right-to-left
+     * @param c the canvas, can be null
+     * @param x the end of the run closest to the leading margin
+     * @param top the top of the line
+     * @param y the baseline
+     * @param bottom the bottom of the line
+     * @param fmi receives metrics information, can be null
+     * @param needWidth true if the width is required
+     * @param prepFlags one of PREP_NONE, PREP_REQUIRED, or PREP_ONLY
+     * @return the signed width of the run based on the run direction; only
+     * valid if needWidth is true
+     */
+    private float handleRun(int runIndex, int start, int offset,
+            int limit, boolean runIsRtl, Canvas c, float x, int top, int y,
+            int bottom, FontMetricsInt fmi, boolean needWidth, int prepFlags) {
+
+        // Shaping needs to take into account context up to metric boundaries,
+        // but rendering needs to take into account character style boundaries.
+        // So we iterate through metric runs, shape using the initial
+        // paint (the same typeface is used up to the next metric boundary),
+        // then within each metric run iterate through character style runs.
+        float ox = x;
+        for (int i = start, inext; i < offset; i = inext) {
+            TextPaint wp = mWorkPaint;
+            wp.set(mPaint);
+
+            int mnext;
+            if (mSpanned == null) {
+                inext = limit;
+                mnext = offset;
+            } else {
+                inext = mSpanned.nextSpanTransition(mStart + i, mStart + limit,
+                        MetricAffectingSpan.class) - mStart;
+
+                mnext = inext < offset ? inext : offset;
+                MetricAffectingSpan[] spans = mSpanned.getSpans(mStart + i,
+                        mStart + mnext, MetricAffectingSpan.class);
+
+                if (spans.length > 0) {
+                    ReplacementSpan replacement = null;
+                    for (int j = 0; j < spans.length; j++) {
+                        MetricAffectingSpan span = spans[j];
+                        if (span instanceof ReplacementSpan) {
+                            replacement = (ReplacementSpan)span;
+                        } else {
+                            span.updateDrawState(wp); // XXX or measureState?
+                        }
+                    }
+
+                    if (replacement != null) {
+                        x += handleReplacement(replacement, wp, runIndex, i,
+                                mnext, runIsRtl, c, x, top, y, bottom, fmi,
+                                needWidth || mnext < offset, prepFlags);
+                        continue;
+                    }
+                }
+            }
+
+            if (prepFlags != PREP_NONE) {
+                handlePrep(wp, runIndex, i, inext, runIsRtl);
+            }
+
+            if (prepFlags != PREP_ONLY) {
+                if (mSpanned == null || c == null) {
+                    x += handleText(wp, i, mnext, runIsRtl, c, x, top,
+                            y, bottom, fmi, needWidth || mnext < offset);
+                } else {
+                    for (int j = i, jnext; j < mnext; j = jnext) {
+                        jnext = mSpanned.nextSpanTransition(mStart + j,
+                                mStart + mnext, CharacterStyle.class) - mStart;
+
+                        CharacterStyle[] spans = mSpanned.getSpans(mStart + j,
+                                mStart + jnext, CharacterStyle.class);
+
+                        wp.set(mPaint);
+                        for (int k = 0; k < spans.length; k++) {
+                            CharacterStyle span = spans[k];
+                            span.updateDrawState(wp);
+                        }
+
+                        x += handleText(wp, j, jnext, runIsRtl, c, x,
+                                top, y, bottom, fmi, needWidth || jnext < offset);
+                    }
+                }
+            }
+        }
+
+        return x - ox;
+    }
+
+    private static final int PREP_NONE = 0;
+    private static final int PREP_NEEDED = 1;
+    private static final int PREP_ONLY = 2;
+
+    /**
+     * Prepares text for measuring or rendering.
+     *
+     * @param paint the paint used to shape the text
+     * @param runIndex the run index
+     * @param start the start of the text to prepare
+     * @param limit the limit of the text to prepare
+     * @param runIsRtl true if the run is right-to-left
+     */
+    private void handlePrep(TextPaint paint, int runIndex, int start, int limit,
+            boolean runIsRtl) {
+
+        // The current implementation 'prepares' text by manipulating the
+        // character array.  In order to keep track of what ranges have
+        // already been prepared, it uses the runIndex and the limit of
+        // the prepared text within that run.  This index is required
+        // since operations that prepare the text always proceed in visual
+        // order and the limit itself does not let us know which runs have
+        // been processed and which have not.
+        //
+        // This bookkeeping is an attempt to let us process a line partially,
+        // for example, by only shaping up to the cursor position.  This may
+        // not make sense if we can reuse the line, say by caching repeated
+        // accesses to the same line for both measuring and drawing, since in
+        // those cases we'd always prepare the entire line.  At the
+        // opposite extreme, we might shape and then immediately discard only
+        // the run of text we're working with at the moment, instead of retaining
+        // the results of shaping (as the chars array is).  In this case as well
+        // we would not need to do the index/limit bookkeeping.
+        //
+        // Technically, the only reason for bookkeeping is so that we don't
+        // re-mirror already-mirrored glyphs, since the shaping and object
+        // replacement operations will not change already-processed text.
+
+        if (runIndex > mPreppedIndex ||
+                (runIndex == mPreppedIndex && start >= mPreppedLimit)) {
+            if (runIsRtl) {
+                int runLen = limit - start;
+                AndroidCharacter.mirror(mChars, start, runLen);
+                ArabicShaping.SHAPER.shape(mChars, start, runLen);
+
+                // Note: tweaked MockShaper to put '\ufeff' in place of
+                // alef when it forms lam-alef ligatures, so no extra
+                // processing is necessary here.
+            }
+            mPreppedIndex = runIndex;
+            mPreppedLimit = limit;
+        }
+    }
+
+    /**
+     * Render a text run with the set-up paint.
+     *
+     * @param c the canvas
+     * @param wp the paint used to render the text
+     * @param start the run start
+     * @param limit the run limit
+     * @param runIsRtl true if the run is right-to-left
+     * @param x the x position of the left edge of the run
+     * @param y the baseline of the run
+     */
+    private void drawTextRun(Canvas c, TextPaint wp, int start, int limit,
+            boolean runIsRtl, float x, int y) {
+
+        // Since currently skia only renders text left-to-right, we need to
+        // put the shaped characters into visual order before rendering.
+        // Since we might want to re-render the line again, we swap them
+        // back when we're done.  If we left them swapped, measurement
+        // would be broken since it expects the characters in logical order.
+        if (runIsRtl) {
+            swapRun(start, limit);
+        }
+        if (mCharsValid) {
+            c.drawText(mChars, start, limit - start, x, y, wp);
+        } else {
+            c.drawText(mText, mStart + start, mStart + limit, x, y, wp);
+        }
+        if (runIsRtl) {
+            swapRun(start, limit);
+        }
+    }
+
+    /**
+     * Reverses the order of characters in the chars array between start and
+     * limit, used by drawTextRun.
+     * @param start the start of the run to reverse
+     * @param limit the limit of the run to reverse
+     */
+    private void swapRun(int start, int limit) {
+        // First we swap all the characters one for one, then we
+        // do another pass looking for surrogate pairs and swapping them
+        // back into their logical order.
+        char[] chars = mChars;
+        for (int s = start, e = limit - 1; s < e; ++s, --e) {
+            char ch = chars[s]; chars[s] = chars[e]; chars[e] = ch;
+        }
+
+        for (int s = start, e = limit - 1; s < e; ++s) {
+            char c1 = chars[s];
+            if (c1 >= 0xdc00 && c1 < 0xe000) {
+                char c2 = chars[s+1];
+                if (c2 >= 0xd800 && c2 < 0xdc00) {
+                    chars[s++] = c2;
+                    chars[s] = c1;
+                }
+            }
+        }
+    }
+
+    /**
+     * Returns the ascent of the text at start.  This is used for scaling
+     * emoji.
+     *
+     * @param pos the line-relative position
+     * @return the ascent of the text at start
+     */
+    float ascent(int pos) {
+        if (mSpanned == null) {
+            return mPaint.ascent();
+        }
+
+        pos += mStart;
+        MetricAffectingSpan[] spans = mSpanned.getSpans(pos, pos + 1,
+                MetricAffectingSpan.class);
+        if (spans.length == 0) {
+            return mPaint.ascent();
+        }
+
+        TextPaint wp = mWorkPaint;
+        wp.set(mPaint);
+        for (MetricAffectingSpan span : spans) {
+            span.updateMeasureState(wp);
+        }
+        return wp.ascent();
+    }
+
+    /**
+     * Returns the next tab position.
+     *
+     * @param h the (unsigned) offset from the leading margin
+     * @return the (unsigned) tab position after this offset
+     */
+    float nextTab(float h) {
+        float nh = Float.MAX_VALUE;
+        boolean alltabs = false;
+
+        if (mHasTabs && mTabs != null) {
+            TabStopSpan[] tabs = mTabs;
+            for (int i = 0; i < tabs.length && tabs[i] != null; ++i) {
+                int where = tabs[i].getTabStop();
+                if (where < nh && where > h) {
+                    nh = where;
+                }
+            }
+            if (nh != Float.MAX_VALUE) {
+                return nh;
+            }
+        }
+
+        return ((int) ((h + TAB_INCREMENT) / TAB_INCREMENT)) * TAB_INCREMENT;
+    }
+
+    private static final int TAB_INCREMENT = 20;
+}
diff --git a/core/java/android/text/TextUtils.java b/core/java/android/text/TextUtils.java
index 9589bf3..2d6c7b6 100644
--- a/core/java/android/text/TextUtils.java
+++ b/core/java/android/text/TextUtils.java
@@ -17,12 +17,11 @@
 package android.text;
 
 import com.android.internal.R;
+import com.android.internal.util.ArrayUtils;
 
-import android.content.res.ColorStateList;
 import android.content.res.Resources;
 import android.os.Parcel;
 import android.os.Parcelable;
-import android.text.method.TextKeyListener.Capitalize;
 import android.text.style.AbsoluteSizeSpan;
 import android.text.style.AlignmentSpan;
 import android.text.style.BackgroundColorSpan;
@@ -45,10 +44,8 @@
 import android.text.style.UnderlineSpan;
 import android.util.Printer;
 
-import com.android.internal.util.ArrayUtils;
-
-import java.util.regex.Pattern;
 import java.util.Iterator;
+import java.util.regex.Pattern;
 
 public class TextUtils {
     private TextUtils() { /* cannot be instantiated */ }
@@ -983,7 +980,7 @@
     /**
      * Returns the original text if it fits in the specified width
      * given the properties of the specified Paint,
-     * or, if it does not fit, a copy with ellipsis character added 
+     * or, if it does not fit, a copy with ellipsis character added
      * at the specified edge or center.
      * If <code>preserveLength</code> is specified, the returned copy
      * will be padded with zero-width spaces to preserve the original
@@ -992,7 +989,7 @@
      * report the start and end of the ellipsized range.
      */
     public static CharSequence ellipsize(CharSequence text,
-                                         TextPaint p,
+                                         TextPaint paint,
                                          float avail, TruncateAt where,
                                          boolean preserveLength,
                                          EllipsizeCallback callback) {
@@ -1003,13 +1000,12 @@
 
         int len = text.length();
 
-        // Use Paint.breakText() for the non-Spanned case to avoid having
-        // to allocate memory and accumulate the character widths ourselves.
+        MeasuredText mt = MeasuredText.obtain();
+        try {
+            float width = setPara(mt, paint, text, 0, text.length(),
+                    Layout.DIR_REQUEST_DEFAULT_LTR);
 
-        if (!(text instanceof Spanned)) {
-            float wid = p.measureText(text, 0, len);
-
-            if (wid <= avail) {
+            if (width <= avail) {
                 if (callback != null) {
                     callback.ellipsized(0, 0);
                 }
@@ -1017,250 +1013,69 @@
                 return text;
             }
 
-            float ellipsiswid = p.measureText(sEllipsis);
+            // XXX assumes ellipsis string does not require shaping and
+            // is unaffected by style
+            float ellipsiswid = paint.measureText(sEllipsis);
+            avail -= ellipsiswid;
 
-            if (ellipsiswid > avail) {
-                if (callback != null) {
-                    callback.ellipsized(0, len);
-                }
-
-                if (preserveLength) {
-                    char[] buf = obtain(len);
-                    for (int i = 0; i < len; i++) {
-                        buf[i] = '\uFEFF';
-                    }
-                    String ret = new String(buf, 0, len);
-                    recycle(buf);
-                    return ret;
-                } else {
-                    return "";
-                }
-            }
-
-            if (where == TruncateAt.START) {
-                int fit = p.breakText(text, 0, len, false,
-                                      avail - ellipsiswid, null);
-
-                if (callback != null) {
-                    callback.ellipsized(0, len - fit);
-                }
-
-                if (preserveLength) {
-                    return blank(text, 0, len - fit);
-                } else {
-                    return sEllipsis + text.toString().substring(len - fit, len);
-                }
+            int left = 0;
+            int right = len;
+            if (avail < 0) {
+                // it all goes
+            } else if (where == TruncateAt.START) {
+                right = len - mt.breakText(0, len, false, avail);
             } else if (where == TruncateAt.END) {
-                int fit = p.breakText(text, 0, len, true,
-                                      avail - ellipsiswid, null);
-
-                if (callback != null) {
-                    callback.ellipsized(fit, len);
-                }
-
-                if (preserveLength) {
-                    return blank(text, fit, len);
-                } else {
-                    return text.toString().substring(0, fit) + sEllipsis;
-                } 
-            } else /* where == TruncateAt.MIDDLE */ {
-                int right = p.breakText(text, 0, len, false,
-                                        (avail - ellipsiswid) / 2, null);
-                float used = p.measureText(text, len - right, len);
-                int left = p.breakText(text, 0, len - right, true,
-                                       avail - ellipsiswid - used, null);
-
-                if (callback != null) {
-                    callback.ellipsized(left, len - right);
-                }
-
-                if (preserveLength) {
-                    return blank(text, left, len - right);
-                } else {
-                    String s = text.toString();
-                    return s.substring(0, left) + sEllipsis +
-                           s.substring(len - right, len);
-                }
-            }
-        }
-
-        // But do the Spanned cases by hand, because it's such a pain
-        // to iterate the span transitions backwards and getTextWidths()
-        // will give us the information we need.
-
-        // getTextWidths() always writes into the start of the array,
-        // so measure each span into the first half and then copy the
-        // results into the second half to use later.
-
-        float[] wid = new float[len * 2];
-        TextPaint temppaint = new TextPaint();
-        Spanned sp = (Spanned) text;
-
-        int next;
-        for (int i = 0; i < len; i = next) {
-            next = sp.nextSpanTransition(i, len, MetricAffectingSpan.class);
-
-            Styled.getTextWidths(p, temppaint, sp, i, next, wid, null);
-            System.arraycopy(wid, 0, wid, len + i, next - i);
-        }
-
-        float sum = 0;
-        for (int i = 0; i < len; i++) {
-            sum += wid[len + i];
-        }
-
-        if (sum <= avail) {
-            if (callback != null) {
-                callback.ellipsized(0, 0);
-            }
-
-            return text;
-        }
-
-        float ellipsiswid = p.measureText(sEllipsis);
-
-        if (ellipsiswid > avail) {
-            if (callback != null) {
-                callback.ellipsized(0, len);
-            }
-
-            if (preserveLength) {
-                char[] buf = obtain(len);
-                for (int i = 0; i < len; i++) {
-                    buf[i] = '\uFEFF';
-                }
-                SpannableString ss = new SpannableString(new String(buf, 0, len));
-                recycle(buf);
-                copySpansFrom(sp, 0, len, Object.class, ss, 0);
-                return ss;
+                left = mt.breakText(0, len, true, avail);
             } else {
-                return "";
-            }
-        }
-
-        if (where == TruncateAt.START) {
-            sum = 0;
-            int i;
-
-            for (i = len; i >= 0; i--) {
-                float w = wid[len + i - 1];
-
-                if (w + sum + ellipsiswid > avail) {
-                    break;
-                }
-
-                sum += w;
-            }
-
-            if (callback != null) {
-                callback.ellipsized(0, i);
-            }
-
-            if (preserveLength) {
-                SpannableString ss = new SpannableString(blank(text, 0, i));
-                copySpansFrom(sp, 0, len, Object.class, ss, 0);
-                return ss;
-            } else {
-                SpannableStringBuilder out = new SpannableStringBuilder(sEllipsis);
-                out.insert(1, text, i, len);
-
-                return out;
-            }
-        } else if (where == TruncateAt.END) {
-            sum = 0;
-            int i;
-
-            for (i = 0; i < len; i++) {
-                float w = wid[len + i];
-
-                if (w + sum + ellipsiswid > avail) {
-                    break;
-                }
-
-                sum += w;
-            }
-
-            if (callback != null) {
-                callback.ellipsized(i, len);
-            }
-
-            if (preserveLength) {
-                SpannableString ss = new SpannableString(blank(text, i, len));
-                copySpansFrom(sp, 0, len, Object.class, ss, 0);
-                return ss;
-            } else {
-                SpannableStringBuilder out = new SpannableStringBuilder(sEllipsis);
-                out.insert(0, text, 0, i);
-
-                return out;
-            }
-        } else /* where = TruncateAt.MIDDLE */ {
-            float lsum = 0, rsum = 0;
-            int left = 0, right = len;
-
-            float ravail = (avail - ellipsiswid) / 2;
-            for (right = len; right >= 0; right--) {
-                float w = wid[len + right - 1];
-
-                if (w + rsum > ravail) {
-                    break;
-                }
-
-                rsum += w;
-            }
-
-            float lavail = avail - ellipsiswid - rsum;
-            for (left = 0; left < right; left++) {
-                float w = wid[len + left];
-
-                if (w + lsum > lavail) {
-                    break;
-                }
-
-                lsum += w;
+                right = len - mt.breakText(0, len, false, avail / 2);
+                avail -= mt.measure(right, len);
+                left = mt.breakText(0, right, true, avail);
             }
 
             if (callback != null) {
                 callback.ellipsized(left, right);
             }
 
+            char[] buf = mt.mChars;
+            Spanned sp = text instanceof Spanned ? (Spanned) text : null;
+
+            int remaining = len - (right - left);
             if (preserveLength) {
-                SpannableString ss = new SpannableString(blank(text, left, right));
+                if (remaining > 0) { // else eliminate the ellipsis too
+                    buf[left++] = '\u2026';
+                }
+                for (int i = left; i < right; i++) {
+                    buf[i] = '\uFEFF';
+                }
+                String s = new String(buf, 0, len);
+                if (sp == null) {
+                    return s;
+                }
+                SpannableString ss = new SpannableString(s);
                 copySpansFrom(sp, 0, len, Object.class, ss, 0);
                 return ss;
-            } else {
-                SpannableStringBuilder out = new SpannableStringBuilder(sEllipsis);
-                out.insert(0, text, 0, left);
-                out.insert(out.length(), text, right, len);
-
-                return out;
             }
-        }
-    }
 
-    private static String blank(CharSequence source, int start, int end) {
-        int len = source.length();
-        char[] buf = obtain(len);
-
-        if (start != 0) {
-            getChars(source, 0, start, buf, 0);
-        }
-        if (end != len) {
-            getChars(source, end, len, buf, end);
-        }
-
-        if (start != end) {
-            buf[start] = '\u2026';
-
-            for (int i = start + 1; i < end; i++) {
-                buf[i] = '\uFEFF';
+            if (remaining == 0) {
+                return "";
             }
-        }
-    
-        String ret = new String(buf, 0, len);
-        recycle(buf);
 
-        return ret;
+            if (sp == null) {
+                StringBuilder sb = new StringBuilder(remaining + sEllipsis.length());
+                sb.append(buf, 0, left);
+                sb.append(sEllipsis);
+                sb.append(buf, right, len - right);
+                return sb.toString();
+            }
+
+            SpannableStringBuilder ssb = new SpannableStringBuilder();
+            ssb.append(text, 0, left);
+            ssb.append(sEllipsis);
+            ssb.append(text, right, len);
+            return ssb;
+        } finally {
+            MeasuredText.recycle(mt);
+        }
     }
 
     /**
@@ -1278,80 +1093,121 @@
                                               TextPaint p, float avail,
                                               String oneMore,
                                               String more) {
-        int len = text.length();
-        char[] buf = new char[len];
-        TextUtils.getChars(text, 0, len, buf, 0);
 
-        int commaCount = 0;
-        for (int i = 0; i < len; i++) {
-            if (buf[i] == ',') {
-                commaCount++;
-            }
-        }
-
-        float[] wid;
-
-        if (text instanceof Spanned) {
-            Spanned sp = (Spanned) text;
-            TextPaint temppaint = new TextPaint();
-            wid = new float[len * 2];
-
-            int next;
-            for (int i = 0; i < len; i = next) {
-                next = sp.nextSpanTransition(i, len, MetricAffectingSpan.class);
-
-                Styled.getTextWidths(p, temppaint, sp, i, next, wid, null);
-                System.arraycopy(wid, 0, wid, len + i, next - i);
+        MeasuredText mt = MeasuredText.obtain();
+        try {
+            int len = text.length();
+            float width = setPara(mt, p, text, 0, len, Layout.DIR_REQUEST_DEFAULT_LTR);
+            if (width <= avail) {
+                return text;
             }
 
-            System.arraycopy(wid, len, wid, 0, len);
-        } else {
-            wid = new float[len];
-            p.getTextWidths(text, 0, len, wid);
-        }
+            char[] buf = mt.mChars;
 
-        int ok = 0;
-        int okRemaining = commaCount + 1;
-        String okFormat = "";
-
-        int w = 0;
-        int count = 0;
-
-        for (int i = 0; i < len; i++) {
-            w += wid[i];
-
-            if (buf[i] == ',') {
-                count++;
-
-                int remaining = commaCount - count + 1;
-                float moreWid;
-                String format;
-
-                if (remaining == 1) {
-                    format = " " + oneMore;
-                } else {
-                    format = " " + String.format(more, remaining);
-                }
-
-                moreWid = p.measureText(format);
-
-                if (w + moreWid <= avail) {
-                    ok = i + 1;
-                    okRemaining = remaining;
-                    okFormat = format;
+            int commaCount = 0;
+            for (int i = 0; i < len; i++) {
+                if (buf[i] == ',') {
+                    commaCount++;
                 }
             }
-        }
 
-        if (w <= avail) {
-            return text;
-        } else {
+            int remaining = commaCount + 1;
+
+            int ok = 0;
+            int okRemaining = remaining;
+            String okFormat = "";
+
+            int w = 0;
+            int count = 0;
+            float[] widths = mt.mWidths;
+
+            int request = mt.mDir == 1 ? Layout.DIR_REQUEST_LTR :
+                Layout.DIR_REQUEST_RTL;
+
+            MeasuredText tempMt = MeasuredText.obtain();
+            for (int i = 0; i < len; i++) {
+                w += widths[i];
+
+                if (buf[i] == ',') {
+                    count++;
+
+                    String format;
+                    // XXX should not insert spaces, should be part of string
+                    // XXX should use plural rules and not assume English plurals
+                    if (--remaining == 1) {
+                        format = " " + oneMore;
+                    } else {
+                        format = " " + String.format(more, remaining);
+                    }
+
+                    // XXX this is probably ok, but need to look at it more
+                    tempMt.setPara(format, 0, format.length(), request);
+                    float moreWid = mt.addStyleRun(p, mt.mLen, null);
+
+                    if (w + moreWid <= avail) {
+                        ok = i + 1;
+                        okRemaining = remaining;
+                        okFormat = format;
+                    }
+                }
+            }
+            MeasuredText.recycle(tempMt);
+
             SpannableStringBuilder out = new SpannableStringBuilder(okFormat);
             out.insert(0, text, 0, ok);
             return out;
+        } finally {
+            MeasuredText.recycle(mt);
         }
     }
 
+    private static float setPara(MeasuredText mt, TextPaint paint,
+            CharSequence text, int start, int end, int bidiRequest) {
+
+        mt.setPara(text, start, end, bidiRequest);
+
+        float width;
+        Spanned sp = text instanceof Spanned ? (Spanned) text : null;
+        int len = end - start;
+        if (sp == null) {
+            width = mt.addStyleRun(paint, len, null);
+        } else {
+            width = 0;
+            int spanEnd;
+            for (int spanStart = 0; spanStart < len; spanStart = spanEnd) {
+                spanEnd = sp.nextSpanTransition(spanStart, len,
+                        MetricAffectingSpan.class);
+                MetricAffectingSpan[] spans = sp.getSpans(
+                        spanStart, spanEnd, MetricAffectingSpan.class);
+                width += mt.addStyleRun(paint, spans, spanEnd - spanStart, null);
+            }
+        }
+
+        return width;
+    }
+
+    private static final char FIRST_RIGHT_TO_LEFT = '\u0590';
+
+    /* package */
+    static boolean doesNotNeedBidi(CharSequence s, int start, int end) {
+        for (int i = start; i < end; i++) {
+            if (s.charAt(i) >= FIRST_RIGHT_TO_LEFT) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /* package */
+    static boolean doesNotNeedBidi(char[] text, int start, int len) {
+        for (int i = start, e = i + len; i < e; i++) {
+            if (text[i] >= FIRST_RIGHT_TO_LEFT) {
+                return false;
+            }
+        }
+        return true;
+    }
+
     /* package */ static char[] obtain(int len) {
         char[] buf;
 
@@ -1529,7 +1385,7 @@
      */
     public static final int CAP_MODE_CHARACTERS
             = InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS;
-    
+
     /**
      * Capitalization mode for {@link #getCapsMode}: capitalize the first
      * character of all words.  This value is explicitly defined to be the same as
@@ -1537,7 +1393,7 @@
      */
     public static final int CAP_MODE_WORDS
             = InputType.TYPE_TEXT_FLAG_CAP_WORDS;
-    
+
     /**
      * Capitalization mode for {@link #getCapsMode}: capitalize the first
      * character of each sentence.  This value is explicitly defined to be the same as
@@ -1545,13 +1401,13 @@
      */
     public static final int CAP_MODE_SENTENCES
             = InputType.TYPE_TEXT_FLAG_CAP_SENTENCES;
-    
+
     /**
      * Determine what caps mode should be in effect at the current offset in
      * the text.  Only the mode bits set in <var>reqModes</var> will be
      * checked.  Note that the caps mode flags here are explicitly defined
      * to match those in {@link InputType}.
-     * 
+     *
      * @param cs The text that should be checked for caps modes.
      * @param off Location in the text at which to check.
      * @param reqModes The modes to be checked: may be any combination of
@@ -1651,7 +1507,7 @@
 
         return mode;
     }
-    
+
     private static Object sLock = new Object();
     private static char[] sTemp = null;
 }
diff --git a/core/java/android/view/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java
index c22f991..fc61700 100644
--- a/core/java/android/view/accessibility/AccessibilityEvent.java
+++ b/core/java/android/view/accessibility/AccessibilityEvent.java
@@ -622,6 +622,7 @@
         mPackageName = null;
         mContentDescription = null;
         mBeforeText = null;
+        mParcelableData = null;
         mText.clear();
     }
 
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index 0186270..f406da9 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -94,7 +94,9 @@
     public static AccessibilityManager getInstance(Context context) {
         synchronized (sInstanceSync) {
             if (sInstance == null) {
-                sInstance = new AccessibilityManager(context);
+                IBinder iBinder = ServiceManager.getService(Context.ACCESSIBILITY_SERVICE);
+                IAccessibilityManager service = IAccessibilityManager.Stub.asInterface(iBinder);
+                sInstance = new AccessibilityManager(context, service);
             }
         }
         return sInstance;
@@ -104,13 +106,16 @@
      * Create an instance.
      *
      * @param context A {@link Context}.
+     * @param service An interface to the backing service.
+     *
+     * @hide
      */
-    private AccessibilityManager(Context context) {
+    public AccessibilityManager(Context context, IAccessibilityManager service) {
         mHandler = new MyHandler(context.getMainLooper());
-        IBinder iBinder = ServiceManager.getService(Context.ACCESSIBILITY_SERVICE);
-        mService = IAccessibilityManager.Stub.asInterface(iBinder);
+        mService = service;
+
         try {
-            mService.addClient(mClient);
+            mIsEnabled = mService.addClient(mClient);
         } catch (RemoteException re) {
             Log.e(LOG_TAG, "AccessibilityManagerService is dead", re);
         }
@@ -128,6 +133,18 @@
     }
 
     /**
+     * Returns the client interface this instance registers in
+     * the centralized accessibility manager service.
+     *
+     * @return The client.
+     *
+     * @hide
+     */
+    public IAccessibilityManagerClient getClient() {
+       return (IAccessibilityManagerClient) mClient.asBinder(); 
+    }
+
+    /**
      * Sends an {@link AccessibilityEvent}. If this {@link AccessibilityManager} is not
      * enabled the call is a NOOP.
      *
diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl
index 32788be..7633569 100644
--- a/core/java/android/view/accessibility/IAccessibilityManager.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl
@@ -29,7 +29,7 @@
  */
 interface IAccessibilityManager {
 
-    void addClient(IAccessibilityManagerClient client);
+    boolean addClient(IAccessibilityManagerClient client);
 
     boolean sendAccessibilityEvent(in AccessibilityEvent uiEvent);
 
diff --git a/core/java/android/webkit/Network.java b/core/java/android/webkit/Network.java
index 598f20d..0f03258 100644
--- a/core/java/android/webkit/Network.java
+++ b/core/java/android/webkit/Network.java
@@ -16,7 +16,12 @@
 
 package android.webkit;
 
+import android.content.BroadcastReceiver;
 import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
 import android.net.http.*;
 import android.os.*;
 import android.util.Log;
@@ -76,6 +81,19 @@
      */
     private HttpAuthHandler mHttpAuthHandler;
 
+    private Context mContext;
+
+    /**
+     * True if the currently used network connection is a roaming phone
+     * connection.
+     */
+    private boolean mRoaming;
+
+    /**
+     * Tracks if we are roaming.
+     */
+    private RoamingMonitor mRoamingMonitor;
+
     /**
      * @return The singleton instance of the network.
      */
@@ -107,6 +125,7 @@
         if (++sPlatformNotificationEnableRefCount == 1) {
             if (sNetwork != null) {
                 sNetwork.mRequestQueue.enablePlatformNotifications();
+                sNetwork.monitorRoaming();
             } else {
                 sPlatformNotifications = true;
             }
@@ -121,6 +140,7 @@
         if (--sPlatformNotificationEnableRefCount == 0) {
             if (sNetwork != null) {
                 sNetwork.mRequestQueue.disablePlatformNotifications();
+                sNetwork.stopMonitoringRoaming();
             } else {
                 sPlatformNotifications = false;
             }
@@ -136,12 +156,39 @@
             Assert.assertTrue(Thread.currentThread().
                     getName().equals(WebViewCore.THREAD_NAME));
         }
+        mContext = context;
         mSslErrorHandler = new SslErrorHandler();
         mHttpAuthHandler = new HttpAuthHandler(this);
 
         mRequestQueue = new RequestQueue(context);
     }
 
+    private class RoamingMonitor extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (!ConnectivityManager.CONNECTIVITY_ACTION.equals(intent.getAction()))
+                return;
+
+            NetworkInfo info = (NetworkInfo)intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO);
+            if (info != null)
+                mRoaming = info.isRoaming();
+        };
+    };
+
+    private void monitorRoaming() {
+        mRoamingMonitor = new RoamingMonitor();
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
+        mContext.registerReceiver(sNetwork.mRoamingMonitor, filter);
+    }
+
+    private void stopMonitoringRoaming() {
+        if (mRoamingMonitor != null) {
+            mContext.unregisterReceiver(mRoamingMonitor);
+            mRoamingMonitor = null;
+        }
+    }
+
     /**
      * Request a url from either the network or the file system.
      * @param url The url to load.
@@ -170,6 +217,11 @@
             return false;
         }
 
+        // If this is a prefetch, abort it if we're roaming.
+        if (mRoaming && headers.containsKey("X-Moz") && "prefetch".equals(headers.get("X-Moz"))) {
+            return false;
+        }
+
         /* FIXME: this is lame.  Pass an InputStream in, rather than
            making this lame one here */
         InputStream bodyProvider = null;
diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java
index b767f11..6958f5c 100644
--- a/core/java/android/webkit/WebSettings.java
+++ b/core/java/android/webkit/WebSettings.java
@@ -296,13 +296,13 @@
             "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_5_7; en-us)"
             + " AppleWebKit/530.17 (KHTML, like Gecko) Version/4.0"
             + " Safari/530.17";
-    private static final String IPHONE_USERAGENT = 
+    private static final String IPHONE_USERAGENT =
             "Mozilla/5.0 (iPhone; U; CPU iPhone OS 3_0 like Mac OS X; en-us)"
             + " AppleWebKit/528.18 (KHTML, like Gecko) Version/4.0"
             + " Mobile/7A341 Safari/528.16";
     private static Locale sLocale;
     private static Object sLockForLocaleSettings;
-    
+
     /**
      * Package constructor to prevent clients from creating a new settings
      * instance.
@@ -327,6 +327,8 @@
                 android.os.Process.myUid()) != PackageManager.PERMISSION_GRANTED;
     }
 
+    private static final String ACCEPT_LANG_FOR_US_LOCALE = "en-US";
+
     /**
      * Looks at sLocale and returns current AcceptLanguage String.
      * @return Current AcceptLanguage String.
@@ -336,32 +338,53 @@
         synchronized(sLockForLocaleSettings) {
             locale = sLocale;
         }
-        StringBuffer buffer = new StringBuffer();
-        final String language = locale.getLanguage();
-        if (language != null) {
-            buffer.append(language);
-            final String country = locale.getCountry();
-            if (country != null) {
-                buffer.append("-");
-                buffer.append(country);
+        StringBuilder buffer = new StringBuilder();
+        addLocaleToHttpAcceptLanguage(buffer, locale);
+
+        if (!Locale.US.equals(locale)) {
+            if (buffer.length() > 0) {
+                buffer.append(", ");
             }
-        }
-        if (!locale.equals(Locale.US)) {
-            buffer.append(", ");
-            java.util.Locale us = Locale.US;
-            if (us.getLanguage() != null) {
-                buffer.append(us.getLanguage());
-                final String country = us.getCountry();
-                if (country != null) {
-                    buffer.append("-");
-                    buffer.append(country);
-                }
-            }
+            buffer.append(ACCEPT_LANG_FOR_US_LOCALE);
         }
 
         return buffer.toString();
     }
-    
+
+    /**
+     * Convert obsolete language codes, including Hebrew/Indonesian/Yiddish,
+     * to new standard.
+     */
+    private static String convertObsoleteLanguageCodeToNew(String langCode) {
+        if (langCode == null) {
+            return null;
+        }
+        if ("iw".equals(langCode)) {
+            // Hebrew
+            return "he";
+        } else if ("in".equals(langCode)) {
+            // Indonesian
+            return "id";
+        } else if ("ji".equals(langCode)) {
+            // Yiddish
+            return "yi";
+        }
+        return langCode;
+    }
+
+    private static void addLocaleToHttpAcceptLanguage(StringBuilder builder,
+                                                      Locale locale) {
+        String language = convertObsoleteLanguageCodeToNew(locale.getLanguage());
+        if (language != null) {
+            builder.append(language);
+            String country = locale.getCountry();
+            if (country != null) {
+                builder.append("-");
+                builder.append(country);
+            }
+        }
+    }
+
     /**
      * Looks at sLocale and mContext and returns current UserAgent String.
      * @return Current UserAgent String.
@@ -379,11 +402,11 @@
         } else {
             // default to "1.0"
             buffer.append("1.0");
-        }  
+        }
         buffer.append("; ");
         final String language = locale.getLanguage();
         if (language != null) {
-            buffer.append(language.toLowerCase());
+            buffer.append(convertObsoleteLanguageCodeToNew(language));
             final String country = locale.getCountry();
             if (country != null) {
                 buffer.append("-");
diff --git a/core/java/android/webkit/WebTextView.java b/core/java/android/webkit/WebTextView.java
index 19abec1..c809b5a 100644
--- a/core/java/android/webkit/WebTextView.java
+++ b/core/java/android/webkit/WebTextView.java
@@ -300,6 +300,33 @@
         return connection;
     }
 
+    /**
+     * In general, TextView makes a call to InputMethodManager.updateSelection
+     * in onDraw.  However, in the general case of WebTextView, we do not draw.
+     * This method is called by WebView.onDraw to take care of the part that
+     * needs to be called.
+     */
+    /* package */ void onDrawSubstitute() {
+        if (!willNotDraw()) {
+            // If the WebTextView is set to draw, such as in the case of a
+            // password, onDraw calls updateSelection(), so this code path is
+            // unnecessary.
+            return;
+        }
+        // This code is copied from TextView.onDraw().  That code does not get
+        // executed, however, because the WebTextView does not draw, allowing
+        // webkit's drawing to show through.
+        InputMethodManager imm = InputMethodManager.peekInstance();
+        if (imm != null && imm.isActive(this)) {
+            Spannable sp = (Spannable) getText();
+            int selStart = Selection.getSelectionStart(sp);
+            int selEnd = Selection.getSelectionEnd(sp);
+            int candStart = EditableInputConnection.getComposingSpanStart(sp);
+            int candEnd = EditableInputConnection.getComposingSpanEnd(sp);
+            imm.updateSelection(this, selStart, selEnd, candStart, candEnd);
+        }
+    }
+
     @Override
     protected void onDraw(Canvas canvas) {
         // onDraw should only be called for password fields.  If WebTextView is
@@ -360,19 +387,8 @@
 
     @Override
     protected void onSelectionChanged(int selStart, int selEnd) {
-        if (mInSetTextAndKeepSelection) return;
-        // This code is copied from TextView.onDraw().  That code does not get
-        // executed, however, because the WebTextView does not draw, allowing
-        // webkit's drawing to show through.
-        InputMethodManager imm = InputMethodManager.peekInstance();
-        if (imm != null && imm.isActive(this)) {
-            Spannable sp = (Spannable) getText();
-            int candStart = EditableInputConnection.getComposingSpanStart(sp);
-            int candEnd = EditableInputConnection.getComposingSpanEnd(sp);
-            imm.updateSelection(this, selStart, selEnd, candStart, candEnd);
-        }
         if (!mFromWebKit && !mFromFocusChange && !mFromSetInputType
-                && mWebView != null) {
+                && mWebView != null && !mInSetTextAndKeepSelection) {
             if (DebugFlags.WEB_TEXT_VIEW) {
                 Log.v(LOGTAG, "onSelectionChanged selStart=" + selStart
                         + " selEnd=" + selEnd);
@@ -667,6 +683,7 @@
         } else {
             Selection.setSelection(text, selection, selection);
         }
+        if (mWebView != null) mWebView.incrementTextGeneration();
     }
 
     /**
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 6b316ce..3b61d2f 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -312,6 +312,8 @@
     // more key events.
     private int mTextGeneration;
 
+    /* package */ void incrementTextGeneration() { mTextGeneration++; }
+
     // Used by WebViewCore to create child views.
     /* package */ final ViewManager mViewManager;
 
@@ -3154,6 +3156,7 @@
         if (AUTO_REDRAW_HACK && mAutoRedraw) {
             invalidate();
         }
+        if (inEditingMode()) mWebTextView.onDrawSubstitute();
         mWebViewCore.signalRepaintDone();
     }
 
@@ -3978,6 +3981,14 @@
         super.onDetachedFromWindow();
     }
 
+    @Override
+    protected void onVisibilityChanged(View changedView, int visibility) {
+        super.onVisibilityChanged(changedView, visibility);
+        if (visibility != View.VISIBLE) {
+            dismissZoomControl();
+        }
+    }
+
     /**
      * @deprecated WebView no longer needs to implement
      * ViewGroup.OnHierarchyChangeListener.  This method does nothing now.
@@ -4683,16 +4694,13 @@
                         ted.mY = contentY;
                         ted.mMetaState = ev.getMetaState();
                         ted.mReprocess = mDeferTouchProcess;
+                        mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted);
                         if (mDeferTouchProcess) {
                             // still needs to set them for compute deltaX/Y
                             mLastTouchX = x;
                             mLastTouchY = y;
-                            ted.mViewX = x;
-                            ted.mViewY = y;
-                            mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted);
                             break;
                         }
-                        mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted);
                         if (!inFullScreenMode()) {
                             mPrivateHandler.sendMessageDelayed(mPrivateHandler
                                     .obtainMessage(PREVENT_DEFAULT_TIMEOUT,
@@ -4718,20 +4726,17 @@
                 // pass the touch events from UI thread to WebCore thread
                 if (shouldForwardTouchEvent() && mConfirmMove && (firstMove
                         || eventTime - mLastSentTouchTime > mCurrentTouchInterval)) {
-                    mLastSentTouchTime = eventTime;
                     TouchEventData ted = new TouchEventData();
                     ted.mAction = action;
                     ted.mX = contentX;
                     ted.mY = contentY;
                     ted.mMetaState = ev.getMetaState();
                     ted.mReprocess = mDeferTouchProcess;
+                    mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted);
+                    mLastSentTouchTime = eventTime;
                     if (mDeferTouchProcess) {
-                        ted.mViewX = x;
-                        ted.mViewY = y;
-                        mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted);
                         break;
                     }
-                    mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted);
                     if (firstMove && !inFullScreenMode()) {
                         mPrivateHandler.sendMessageDelayed(mPrivateHandler
                                 .obtainMessage(PREVENT_DEFAULT_TIMEOUT,
@@ -4762,9 +4767,11 @@
                         invalidate();
                         break;
                     }
+
                     if (!mConfirmMove) {
                         break;
                     }
+
                     if (mPreventDefault == PREVENT_DEFAULT_MAYBE_YES
                             || mPreventDefault == PREVENT_DEFAULT_NO_FROM_TOUCH_DOWN) {
                         // track mLastTouchTime as we may need to do fling at
@@ -4892,6 +4899,7 @@
                 break;
             }
             case MotionEvent.ACTION_UP: {
+                if (!isFocused()) requestFocus();
                 // pass the touch events from UI thread to WebCore thread
                 if (shouldForwardTouchEvent()) {
                     TouchEventData ted = new TouchEventData();
@@ -4900,10 +4908,6 @@
                     ted.mY = contentY;
                     ted.mMetaState = ev.getMetaState();
                     ted.mReprocess = mDeferTouchProcess;
-                    if (mDeferTouchProcess) {
-                        ted.mViewX = x;
-                        ted.mViewY = y;
-                    }
                     mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted);
                 }
                 mLastTouchUpTime = eventTime;
@@ -4918,10 +4922,6 @@
                             ted.mY = contentY;
                             ted.mMetaState = ev.getMetaState();
                             ted.mReprocess = mDeferTouchProcess;
-                            if (mDeferTouchProcess) {
-                                ted.mViewX = x;
-                                ted.mViewY = y;
-                            }
                             mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted);
                         } else if (mPreventDefault != PREVENT_DEFAULT_YES){
                             doDoubleTap();
@@ -4943,9 +4943,17 @@
                             if (mPreventDefault != PREVENT_DEFAULT_YES
                                     && (computeMaxScrollX() > 0
                                             || computeMaxScrollY() > 0)) {
-                                // UI takes control back, cancel WebCore touch
-                                cancelWebCoreTouchEvent(contentX, contentY,
-                                        true);
+                                // If the user has performed a very quick touch
+                                // sequence it is possible that we may get here
+                                // before WebCore has had a chance to process the events.
+                                // In this case, any call to preventDefault in the
+                                // JS touch handler will not have been executed yet.
+                                // Hence we will see both the UI (now) and WebCore
+                                // (when context switches) handling the event,
+                                // regardless of whether the web developer actually
+                                // doeses preventDefault in their touch handler. This
+                                // is the nature of our asynchronous touch model.
+
                                 // we will not rewrite drag code here, but we
                                 // will try fling if it applies.
                                 WebViewCore.reducePriority();
@@ -6205,10 +6213,6 @@
                         // simplicity for now, we don't set it.
                         ted.mMetaState = 0;
                         ted.mReprocess = mDeferTouchProcess;
-                        if (mDeferTouchProcess) {
-                            ted.mViewX = mLastTouchX;
-                            ted.mViewY = mLastTouchY;
-                        }
                         mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted);
                     } else if (mPreventDefault != PREVENT_DEFAULT_YES) {
                         mTouchMode = TOUCH_DONE_MODE;
@@ -6389,14 +6393,8 @@
                     break;
                 case REQUEST_KEYBOARD_WITH_SELECTION_MSG_ID:
                     displaySoftKeyboard(true);
-                    updateTextSelectionFromMessage(msg.arg1, msg.arg2,
-                            (WebViewCore.TextSelectionData) msg.obj);
-                    break;
+                    // fall through to UPDATE_TEXT_SELECTION_MSG_ID
                 case UPDATE_TEXT_SELECTION_MSG_ID:
-                    // If no textfield was in focus, and the user touched one,
-                    // causing it to send this message, then WebTextView has not
-                    // been set up yet.  Rebuild it so it can set its selection.
-                    rebuildWebTextView();
                     updateTextSelectionFromMessage(msg.arg1, msg.arg2,
                             (WebViewCore.TextSelectionData) msg.obj);
                     break;
@@ -6505,27 +6503,31 @@
                         TouchEventData ted = (TouchEventData) msg.obj;
                         switch (ted.mAction) {
                             case MotionEvent.ACTION_DOWN:
-                                mLastDeferTouchX = ted.mViewX;
-                                mLastDeferTouchY = ted.mViewY;
+                                mLastDeferTouchX = contentToViewX(ted.mX)
+                                        - mScrollX;
+                                mLastDeferTouchY = contentToViewY(ted.mY)
+                                        - mScrollY;
                                 mDeferTouchMode = TOUCH_INIT_MODE;
                                 break;
                             case MotionEvent.ACTION_MOVE: {
                                 // no snapping in defer process
+                                int x = contentToViewX(ted.mX) - mScrollX;
+                                int y = contentToViewY(ted.mY) - mScrollY;
                                 if (mDeferTouchMode != TOUCH_DRAG_MODE) {
                                     mDeferTouchMode = TOUCH_DRAG_MODE;
-                                    mLastDeferTouchX = ted.mViewX;
-                                    mLastDeferTouchY = ted.mViewY;
+                                    mLastDeferTouchX = x;
+                                    mLastDeferTouchY = y;
                                     startDrag();
                                 }
                                 int deltaX = pinLocX((int) (mScrollX
-                                        + mLastDeferTouchX - ted.mViewX))
+                                        + mLastDeferTouchX - x))
                                         - mScrollX;
                                 int deltaY = pinLocY((int) (mScrollY
-                                        + mLastDeferTouchY - ted.mViewY))
+                                        + mLastDeferTouchY - y))
                                         - mScrollY;
                                 doDrag(deltaX, deltaY);
-                                if (deltaX != 0) mLastDeferTouchX = ted.mViewX;
-                                if (deltaY != 0) mLastDeferTouchY = ted.mViewY;
+                                if (deltaX != 0) mLastDeferTouchX = x;
+                                if (deltaY != 0) mLastDeferTouchY = y;
                                 break;
                             }
                             case MotionEvent.ACTION_UP:
@@ -6539,8 +6541,8 @@
                                 break;
                             case WebViewCore.ACTION_DOUBLETAP:
                                 // doDoubleTap() needs mLastTouchX/Y as anchor
-                                mLastTouchX = ted.mViewX;
-                                mLastTouchY = ted.mViewY;
+                                mLastTouchX = contentToViewX(ted.mX) - mScrollX;
+                                mLastTouchY = contentToViewY(ted.mY) - mScrollY;
                                 doDoubleTap();
                                 mDeferTouchMode = TOUCH_DONE_MODE;
                                 break;
diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java
index 4118119..28b5ae6 100644
--- a/core/java/android/webkit/WebViewCore.java
+++ b/core/java/android/webkit/WebViewCore.java
@@ -708,8 +708,6 @@
         int mY;
         int mMetaState;
         boolean mReprocess;
-        float mViewX;
-        float mViewY;
     }
 
     static class GeolocationPermissionsData {
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index 48e7f79..0a39ab6 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -559,6 +559,16 @@
 
         boolean smoothScrollbar = a.getBoolean(R.styleable.AbsListView_smoothScrollbar, true);
         setSmoothScrollbarEnabled(smoothScrollbar);
+        
+        final int adapterId = a.getResourceId(R.styleable.AbsListView_adapter, 0);
+        if (adapterId != 0) {
+            final Context c = context;
+            post(new Runnable() {
+                public void run() {
+                    setAdapter(Adapters.loadAdapter(c, adapterId));
+                }
+            });
+        }
 
         a.recycle();
     }
diff --git a/core/java/android/widget/Adapters.java b/core/java/android/widget/Adapters.java
new file mode 100644
index 0000000..05e501a
--- /dev/null
+++ b/core/java/android/widget/Adapters.java
@@ -0,0 +1,1191 @@
+/*
+ * 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.widget;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.database.Cursor;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.util.AttributeSet;
+import android.util.Xml;
+import android.view.View;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+import static com.android.internal.R.*;
+
+/**
+ * <p>This class can be used to load {@link android.widget.Adapter adapters} defined in
+ * XML resources. XML-defined adapters can be used to easily create adapters in your
+ * own application or to pass adapters to other processes.</p>
+ * 
+ * <h2>Types of adapters</h2>
+ * <p>Adapters defined using XML resources can only be one of the following supported
+ * types. Arbitrary adapters are not supported to guarantee the safety of the loaded
+ * code when adapters are loaded across packages.</p>
+ * <ul>
+ *  <li><a href="#xml-cursor-adapter">Cursor adapter</a>: a cursor adapter can be used
+ *  to display the content of a cursor, most often coming from a content provider</li>
+ * </ul>
+ * <p>The complete XML format definition of each adapter type is available below.</p>
+ * 
+ * <a name="xml-cursor-adapter" />
+ * <h2>Cursor adapter</h2>
+ * <p>A cursor adapter XML definition starts with the
+ * <a href="#xml-cursor-adapter-tag"><code>&lt;cursor-adapter /&gt;</code></a>
+ * tag and may contain one or more instances of the following tags:</p>
+ * <ul>
+ *  <li><a href="#xml-cursor-adapter-select-tag"><code>&lt;select /&gt;</code></a></li>
+ *  <li><a href="#xml-cursor-adapter-bind-tag"><code>&lt;bind /&gt;</code></a></li>
+ * </ul>
+ * 
+ * <a name="xml-cursor-adapter-tag" />
+ * <h3>&lt;cursor-adapter /&gt;</h3>
+ * <p>The <code>&lt;cursor-adapter /&gt;</code> element defines the beginning of the
+ * document and supports the following attributes:</p>
+ * <ul>
+ *  <li><code>android:layout</code>: Reference to the XML layout to be inflated for
+ *  each item of the adapter. This attribute is mandatory.</li>
+ *  <li><code>android:selection</code>: Selection expression, used when the
+ *  <code>android:uri</code> attribute is defined or when the adapter is loaded with
+ *  {@link android.widget.Adapters#loadCursorAdapter(android.content.Context, int, String, Object[])}.
+ *  This attribute is optional.</li>
+ *  <li><code>android:sortOrder</code>: Sort expression, used when the
+ *  <code>android:uri</code> attribute is defined or when the adapter is loaded with
+ *  {@link android.widget.Adapters#loadCursorAdapter(android.content.Context, int, String, Object[])}.
+ *  This attribute is optional.</li>
+ *  <li><code>android:uri</code>: URI of the content provider to query to retrieve a cursor.
+ *  Specifying this attribute is equivalent to calling {@link android.widget.Adapters#loadCursorAdapter(android.content.Context, int, String, Object[])}.
+ *  If you call this method, the value of the XML attribute is ignored. This attribute is
+ *  optional.</li>
+ * </ul>
+ * <p>In addition, you can specify one or more instances of
+ * <a href="#xml-cursor-adapter-select-tag"><code>&lt;select /&gt;</code></a> and
+ * <a href="#xml-cursor-adapter-bind-tag"><code>&lt;bind /&gt;</code></a> tags as children
+ * of <code>&lt;cursor-adapter /&gt;</code>.</p>
+ * 
+ * <a name="xml-cursor-adapter-select-tag" />
+ * <h3>&lt;select /&gt;</h3>
+ * <p>The <code>&lt;select /&gt;</code> tag is used to select columns from the cursor
+ * when doing the query. This can be very useful when using transformations in the
+ * <code>&lt;bind /&gt;</code> elements. It can also be very useful if you are providing
+ * your own <a href="#xml-cursor-adapter-bind-data-types">binder</a> or
+ * <a href="#xml-cursor-adapter-bind-data-types">transformation</a> classes.
+ * <code>&lt;select /&gt;</code> elements are ignored if you supply the cursor yourself.</p>
+ * <p>The <code>&lt;select /&gt;</code> supports the following attributes:</p>
+ * <ul>
+ *  <li><code>android:column</code>: Name of the column to select in the cursor during the
+ *  query operation</li>
+ * </ul>
+ * <p><strong>Note:</strong> The column named <code>_id</code> is always implicitely
+ * selected.</p>
+ * 
+ * <a name="xml-cursor-adapter-bind-tag" />
+ * <h3>&lt;bind /&gt;</h3>
+ * <p>The <code>&lt;bind /&gt;</code> tag is used to bind a column from the cursor to
+ * a {@link android.view.View}. A column bound using this tag is automatically selected
+ * during the query and a matching
+ * <a href="#xml-cursor-adapter-select-tag"><code>&lt;select /&gt;</code> tag is therefore
+ * not required.</p>
+ * 
+ * <p>Each binding is declared as a one to one matching but
+ * custom binder classes or special
+ * <a href="#xml-cursor-adapter-bind-data-transformation">data transformations</a> can
+ * allow you to bind several columns to a single view. In this case you must use the
+ * <a href="#xml-cursor-adapter-select-tag"><code>&lt;select /&gt;</code> tag to make
+ * sure any required column is part of the query.</p>
+ * 
+ * <p>The <code>&lt;bind /&gt;</code> tag supports the following attributes:</p>
+ * <ul>
+ *  <li><code>android:from</code>: The name of the column to bind from.
+ *  This attribute is mandatory.</li>
+ *  <li><code>android:to</code>: The id of the view to bind to. This attribute is mandatory.</li>
+ *  <li><code>android:as</code>: The <a href="#xml-cursor-adapter-bind-data-types">data type</a>
+ *  of the binding. This attribute is mandatory.</li>
+ * </ul>
+ * 
+ * <p>In addition, a <code>&lt;bind /&gt;</code> can contain zero or more instances of
+ * <a href="#xml-cursor-adapter-bind-data-transformation">data transformations</a> chilren
+ * tags.</p>
+ *
+ * <a name="xml-cursor-adapter-bind-data-types" />
+ * <h4>Binding data types</h4>
+ * <p>For a binding to occur the data type of the bound column/view pair must be specified.
+ * The following data types are currently supported:</p>
+ * <ul>
+ *  <li><code>string</code>: The content of the column is interpreted as a string and must be
+ *  bound to a {@link android.widget.TextView}</li>
+ *  <li><code>image</code>: The content of the column is interpreted as a blob describing an
+ *  image and must be bound to an {@link android.widget.ImageView}</li>
+ *  <li><code>image-uri</code>: The content of the column is interpreted as a URI to an image
+ *  and must be bound to an {@link android.widget.ImageView}</li>
+ *  <li><code>drawable</code>: The content of the column is interpreted as a resource id to a
+ *  drawable and must be bound to an {@link android.widget.ImageView}</li>
+ *  <li>A fully qualified class name: The name of a class corresponding to an implementation of
+ *  {@link android.widget.Adapters.CursorBinder}. Cursor binders can be used to provide
+ *  bindings not supported by default. Custom binders cannot be used with
+ *  {@link android.content.Context#isRestricted() restricted contexts}, for instance in an
+ *  app widget</li>
+ * </ul>
+ * 
+ * <a name="xml-cursor-adapter-bind-transformation" />
+ * <h4>Binding transformations</h4>
+ * <p>When defining a data binding you can specify an optional transformation by using one
+ * of the following tags as a child of a <code>&lt;bind /&gt;</code> elements:</p>
+ * <ul>
+ *  <li><code>&lt;map /&gt;</code>: Maps a constant string to a string or a resource. Use
+ *  one instance of this tag per value you want to map</li>
+ *  <li><code>&lt;transform /&gt;</code>: Transforms a column's value using an expression
+ *  or an instance of {@link android.widget.Adapters.CursorTransformation}</li>
+ * </ul>
+ * <p>While several <code>&lt;map /&gt;</code> tags can be used at the same time, you cannot
+ * mix <code>&lt;map /&gt;</code> and <code>&lt;transform /&gt;</code> tags. If several
+ * <code>&lt;transform /&gt;</code> tags are specified, only the last one is retained.</p>
+ * 
+ * <a name="xml-cursor-adapter-bind-transformation-map" />
+ * <p><strong>&lt;map /&gt;</strong></p>
+ * <p>A map element simply specifies a value to match from and a value to match to. When
+ * a column's value equals the value to match from, it is replaced with the value to match
+ * to. The following attributes are supported:</p>
+ * <ul>
+ *  <li><code>android:fromValue</code>: The value to match from. This attribute is mandatory</li>
+ *  <li><code>android:toValue</code>: The value to match to. This value can be either a string
+ *  or a resource identifier. This value is interpreted as a resource identifier when the
+ *  data binding is of type <code>drawable</code>. This attribute is mandatory</li>
+ * </ul>
+ * 
+ * <a name="xml-cursor-adapter-bind-transformation-transform" />
+ * <p><strong>&lt;transform /&gt;</strong></p>
+ * <p>A simple transform that occurs either by calling a specified class or by performing
+ * simple text substitution. The following attributes are supported:</p>
+ * <ul>
+ *  <li><code>android:withExpression</code>: The transformation expression. The expression is
+ *  a string containing column names surrounded with curly braces { and }. During the
+ *  transformation each column name is replaced by its value. All columns must have been
+ *  selected in the query. An example of expression is <code>"First name: {first_name},
+ *  last name: {last_name}"</code>. This attribute is mandatory
+ *  if <code>android:withClass</code> is not specified and ignored if <code>android:withClass</code>
+ *  is specified</li>
+ *  <li><code>android:withClass</code>: A fully qualified class name corresponding to an
+ *  implementation of {@link android.widget.Adapters.CursorTransformation}. Custom
+ *  transformationscannot be used with
+ *  {@link android.content.Context#isRestricted() restricted contexts}, for instance in
+ *  an app widget This attribute is mandatory if <code>android:withExpression</code> is
+ *  not specified</li>
+ * </ul>
+ * 
+ * <h3>Example</h3>
+ * <p>The following example defines a cursor adapter that queries all the contacts with
+ * a phone number using the contacts content provider. Each contact is displayed with
+ * its display name, its favorite status and its photo. To display photos, a custom data
+ * binder is declared:</p>
+ * 
+ * <pre class="prettyprint">
+ * &lt;cursor-adapter xmlns:android="http://schemas.android.com/apk/res/android"
+ *     android:uri="content://com.android.contacts/contacts"
+ *     android:selection="has_phone_number=1"
+ *     android:layout="@layout/contact_item"&gt;
+ *
+ *     &lt;bind android:from="display_name" android:to="@id/name" android:as="string" /&gt;
+ *     &lt;bind android:from="starred" android:to="@id/star" android:as="drawable"&gt;
+ *         &lt;map android:fromValue="0" android:toValue="@android:drawable/star_big_off" /&gt;
+ *         &lt;map android:fromValue="1" android:toValue="@android:drawable/star_big_on" /&gt;
+ *     &lt;/bind&gt;
+ *     &lt;bind android:from="_id" android:to="@id/name"
+ *              android:as="com.google.android.test.adapters.ContactPhotoBinder" /&gt;
+ *
+ * &lt;/cursor-adapter&gt;
+ * </pre>
+ * 
+ * <h3>Related APIs</h3>
+ * <ul>
+ *  <li>{@link android.widget.Adapters#loadAdapter(android.content.Context, int, Object[])}</li>
+ *  <li>{@link android.widget.Adapters#loadCursorAdapter(android.content.Context, int, android.database.Cursor, Object[])}</li>
+ *  <li>{@link android.widget.Adapters#loadCursorAdapter(android.content.Context, int, String, Object[])}</li>
+ *  <li>{@link android.widget.Adapters.CursorBinder}</li>
+ *  <li>{@link android.widget.Adapters.CursorTransformation}</li>
+ *  <li>{@link android.widget.CursorAdapter}</li>
+ * </ul>
+ * 
+ * @see android.widget.Adapter
+ * @see android.content.ContentProvider
+ * 
+ * @attr ref android.R.styleable#CursorAdapter_layout 
+ * @attr ref android.R.styleable#CursorAdapter_selection 
+ * @attr ref android.R.styleable#CursorAdapter_sortOrder 
+ * @attr ref android.R.styleable#CursorAdapter_uri 
+ * @attr ref android.R.styleable#CursorAdapter_BindItem_as 
+ * @attr ref android.R.styleable#CursorAdapter_BindItem_from 
+ * @attr ref android.R.styleable#CursorAdapter_BindItem_to
+ * @attr ref android.R.styleable#CursorAdapter_MapItem_fromValue 
+ * @attr ref android.R.styleable#CursorAdapter_MapItem_toValue 
+ * @attr ref android.R.styleable#CursorAdapter_SelectItem_column 
+ * @attr ref android.R.styleable#CursorAdapter_TransformItem_withClass 
+ * @attr ref android.R.styleable#CursorAdapter_TransformItem_withExpression 
+ */
+@SuppressWarnings({"JavadocReference"})
+public class Adapters {
+    private static final String ADAPTER_CURSOR = "cursor-adapter";
+
+    /**
+     * <p>Interface used to bind a {@link android.database.Cursor} column to a View. This
+     * interface can be used to provide bindings for data types not supported by the
+     * standard implementation of {@link android.widget.Adapters}.</p>
+     * 
+     * <p>A binder is provided with a cursor transformation which may or may not be used
+     * to transform the value retrieved from the cursor. The transformation is guaranteed
+     * to never be null so it's always safe to apply the transformation.</p>
+     * 
+     * <p>The binder is associated with a Context but can be re-used with multiple cursors.
+     * As such, the implementation should make no assumption about the Cursor in use.</p>
+     *
+     * @see android.view.View 
+     * @see android.database.Cursor
+     * @see android.widget.Adapters.CursorTransformation
+     */
+    public static abstract class CursorBinder {
+        /**
+         * <p>The context associated with this binder.</p>
+         */
+        protected final Context mContext;
+
+        /**
+         * <p>The transformation associated with this binder. This transformation is never
+         * null and may or may not be applied to the Cursor data during the
+         * {@link #bind(android.view.View, android.database.Cursor, int)} operation.</p>
+         * 
+         * @see #bind(android.view.View, android.database.Cursor, int) 
+         */
+        protected final CursorTransformation mTransformation;
+
+        /**
+         * <p>Creates a new Cursor binder.</p> 
+         * 
+         * @param context The context associated with this binder.
+         * @param transformation The transformation associated with this binder. This
+         *        transformation may or may not be applied by the binder and is guaranteed
+         *        to not be null.
+         */
+        public CursorBinder(Context context, CursorTransformation transformation) {
+            mContext = context;
+            mTransformation = transformation;
+        }
+
+        /**
+         * <p>Binds the specified Cursor column to the supplied View. The binding operation
+         * can query other Cursor columns as needed. During the binding operation, values
+         * retrieved from the Cursor may or may not be transformed using this binder's
+         * cursor transformation.</p>
+         * 
+         * @param view The view to bind data to.
+         * @param cursor The cursor to bind data from.
+         * @param columnIndex The column index in the cursor where the data to bind resides.
+         * 
+         * @see #mTransformation
+         * 
+         * @return True if the column was successfully bound to the View, false otherwise.
+         */
+        public abstract boolean bind(View view, Cursor cursor, int columnIndex);
+    }
+
+    /**
+     * <p>Interface used to transform data coming out of a {@link android.database.Cursor}
+     * before it is bound to a {@link android.view.View}.</p>
+     * 
+     * <p>Transformations are used to transform text-based data (in the form of a String),
+     * or to transform data into a resource identifier. A default implementation is provided
+     * to generate resource identifiers.</p>
+     * 
+     * @see android.database.Cursor
+     * @see android.widget.Adapters.CursorBinder
+     */
+    public static abstract class CursorTransformation {
+        /**
+         * <p>The context associated with this transformation.</p>
+         */
+        protected final Context mContext;
+
+        /**
+         * <p>Creates a new Cursor transformation.</p>
+         * 
+         * @param context The context associated with this transformation.
+         */
+        public CursorTransformation(Context context) {
+            mContext = context;
+        }
+
+        /**
+         * <p>Transforms the specified Cursor column into a String. The transformation
+         * can simply return the content of the column as a String (this is known
+         * as the identity transformation) or manipulate the content. For instance,
+         * a transformation can perform text substitutions or concatenate other
+         * columns with the specified column.</p>
+         * 
+         * @param cursor The cursor that contains the data to transform. 
+         * @param columnIndex The index of the column to transform.
+         * 
+         * @return A String containing the transformed value of the column.
+         */
+        public abstract String transform(Cursor cursor, int columnIndex);
+
+        /**
+         * <p>Transforms the specified Cursor column into a resource identifier.
+         * The default implementation simply interprets the content of the column
+         * as an integer.</p>
+         * 
+         * @param cursor The cursor that contains the data to transform. 
+         * @param columnIndex The index of the column to transform.
+         * 
+         * @return A resource identifier.
+         */
+        public int transformToResource(Cursor cursor, int columnIndex) {
+            return cursor.getInt(columnIndex);
+        }        
+    }
+
+    /**
+     * <p>Loads the {@link android.widget.CursorAdapter} defined in the specified
+     * XML resource. The content of the adapter is loaded from the content provider
+     * identified by the supplied URI.</p>
+     * 
+     * <p><strong>Note:</strong> If the supplied {@link android.content.Context} is
+     * an {@link android.app.Activity}, the cursor returned by the content provider
+     * will be automatically managed. Otherwise, you are responsible for managing the
+     * cursor yourself.</p>
+     * 
+     * <p>The format of the XML definition of the cursor adapter is documented at
+     * the top of this page.</p>
+     * 
+     * @param context The context to load the XML resource from.
+     * @param id The identifier of the XML resource declaring the adapter.
+     * @param uri The URI of the content provider.
+     * @param parameters Optional parameters to pass to the CursorAdapter, used
+     *        to substitute values in the selection expression.
+     * 
+     * @return A {@link android.widget.CursorAdapter}
+     * 
+     * @throws IllegalArgumentException If the XML resource does not contain
+     *         a valid &lt;cursor-adapter /&gt; definition.
+     * 
+     * @see android.content.ContentProvider
+     * @see android.widget.CursorAdapter
+     * @see #loadAdapter(android.content.Context, int, Object[]) 
+     */
+    public static CursorAdapter loadCursorAdapter(Context context, int id, String uri,
+            Object... parameters) {
+
+        XmlCursorAdapter adapter = (XmlCursorAdapter) loadAdapter(context, id, ADAPTER_CURSOR,
+                parameters);
+
+        if (uri != null) {
+            adapter.setUri(uri);
+        }
+        adapter.load();
+
+        return adapter;
+    }
+    
+    /**
+     * <p>Loads the {@link android.widget.CursorAdapter} defined in the specified
+     * XML resource. The content of the adapter is loaded from the specified cursor.
+     * You are responsible for managing the supplied cursor.</p>
+     * 
+     * <p>The format of the XML definition of the cursor adapter is documented at
+     * the top of this page.</p>
+     * 
+     * @param context The context to load the XML resource from.
+     * @param id The identifier of the XML resource declaring the adapter.
+     * @param cursor The cursor containing the data for the adapter.
+     * @param parameters Optional parameters to pass to the CursorAdapter, used
+     *        to substitute values in the selection expression.
+     * 
+     * @return A {@link android.widget.CursorAdapter}
+     * 
+     * @throws IllegalArgumentException If the XML resource does not contain
+     *         a valid &lt;cursor-adapter /&gt; definition.
+     * 
+     * @see android.content.ContentProvider
+     * @see android.widget.CursorAdapter
+     * @see android.database.Cursor
+     * @see #loadAdapter(android.content.Context, int, Object[]) 
+     */
+    public static CursorAdapter loadCursorAdapter(Context context, int id, Cursor cursor,
+            Object... parameters) {
+
+        XmlCursorAdapter adapter = (XmlCursorAdapter) loadAdapter(context, id, ADAPTER_CURSOR,
+                parameters);
+
+        if (cursor != null) {
+            adapter.changeCursor(cursor);
+        }
+
+        return adapter;
+    }
+
+    /**
+     * <p>Loads the adapter defined in the specified XML resource. The XML definition of
+     * the adapter must follow the format definition of one of the supported adapter
+     * types described at the top of this page.</p>
+     * 
+     * <p><strong>Note:</strong> If the loaded adapter is a {@link android.widget.CursorAdapter}
+     * and the supplied {@link android.content.Context} is an {@link android.app.Activity},
+     * the cursor returned by the content provider will be automatically managed. Otherwise,
+     * you are responsible for managing the cursor yourself.</p>
+     * 
+     * @param context The context to load the XML resource from.
+     * @param id The identifier of the XML resource declaring the adapter.
+     * @param parameters Optional parameters to pass to the adapter.
+     *  
+     * @return An adapter instance.
+     * 
+     * @see #loadCursorAdapter(android.content.Context, int, android.database.Cursor, Object[]) 
+     * @see #loadCursorAdapter(android.content.Context, int, String, Object[]) 
+     */
+    public static BaseAdapter loadAdapter(Context context, int id, Object... parameters) {
+        final BaseAdapter adapter = loadAdapter(context, id, null, parameters);
+        if (adapter instanceof ManagedAdapter) {
+            ((ManagedAdapter) adapter).load();
+        }
+        return adapter;
+    }
+
+    /**
+     * Loads an adapter from the specified XML resource. The optional assertName can
+     * be used to exit early if the adapter defined in the XML resource is not of the
+     * expected type.
+     * 
+     * @param context The context to associate with the adapter.
+     * @param id The resource id of the XML document defining the adapter.
+     * @param assertName The mandatory name of the adapter in the XML document.
+     *        Ignored if null.
+     * @param parameters Optional parameters passed to the adapter.
+     * 
+     * @return An instance of {@link android.widget.BaseAdapter}.
+     */
+    private static BaseAdapter loadAdapter(Context context, int id, String assertName,
+                Object... parameters) {
+
+        XmlResourceParser parser = null;
+        try {
+            parser = context.getResources().getXml(id);
+            return createAdapterFromXml(context, parser, Xml.asAttributeSet(parser),
+                    id, parameters, assertName);
+        } catch (XmlPullParserException ex) {
+            Resources.NotFoundException rnf = new Resources.NotFoundException(
+                    "Can't load adapter resource ID " +
+                            context.getResources().getResourceEntryName(id));
+            rnf.initCause(ex);
+            throw rnf;
+        } catch (IOException ex) {
+            Resources.NotFoundException rnf = new Resources.NotFoundException(
+                    "Can't load adapter resource ID " +
+                            context.getResources().getResourceEntryName(id));
+            rnf.initCause(ex);
+            throw rnf;
+        } finally {
+            if (parser != null) parser.close();
+        }
+    }
+
+    /**
+     * Generates an adapter using the specified XML parser. This method is responsible
+     * for choosing the type of the adapter to create based on the content of the
+     * XML parser.
+     * 
+     * This method will generate an {@link IllegalArgumentException} if
+     * <code>assertName</code> is not null and does not match the root tag of the XML
+     * document. 
+     */
+    private static BaseAdapter createAdapterFromXml(Context c,
+            XmlPullParser parser, AttributeSet attrs, int id, Object[] parameters,
+            String assertName) throws XmlPullParserException, IOException {
+        
+        BaseAdapter adapter = null;
+ 
+        // Make sure we are on a start tag.
+        int type;
+        int depth = parser.getDepth();
+
+        while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth) &&
+                type != XmlPullParser.END_DOCUMENT) {
+
+            if (type != XmlPullParser.START_TAG) {
+                continue;
+            }
+
+            String name = parser.getName();
+            if (assertName != null && !assertName.equals(name)) {
+                throw new IllegalArgumentException("The adapter defined in " +
+                        c.getResources().getResourceEntryName(id) + " must be a <" + name + " />");
+            }
+    
+            if (ADAPTER_CURSOR.equals(name)) {
+                adapter = createCursorAdapter(c, parser, attrs, id, parameters);
+            } else {
+                throw new IllegalArgumentException("Unknown adapter name " + parser.getName() +
+                        " in " + c.getResources().getResourceEntryName(id));
+            }
+        }
+    
+        return adapter;
+
+    }
+
+    /**
+     * Creates an XmlCursorAdapter using an XmlCursorAdapterParser.
+     */
+    private static XmlCursorAdapter createCursorAdapter(Context c, XmlPullParser parser,
+            AttributeSet attrs, int id, Object[] parameters)
+            throws IOException, XmlPullParserException {
+
+        return new XmlCursorAdapterParser(c, parser, attrs, id).parse(parameters);
+    }
+
+    /**
+     * Parser that can generate XmlCursorAdapter instances. This parser is responsible for
+     * handling all the attributes and child nodes for a &lt;cursor-adapter /&gt;.
+     */
+    private static class XmlCursorAdapterParser {
+        private static final String ADAPTER_CURSOR_BIND = "bind";
+        private static final String ADAPTER_CURSOR_SELECT = "select";
+        private static final String ADAPTER_CURSOR_AS_STRING = "string";
+        private static final String ADAPTER_CURSOR_AS_IMAGE = "image";
+        private static final String ADAPTER_CURSOR_AS_IMAGE_URI = "image-uri";
+        private static final String ADAPTER_CURSOR_AS_DRAWABLE = "drawable";
+        private static final String ADAPTER_CURSOR_MAP = "map";
+        private static final String ADAPTER_CURSOR_TRANSFORM = "transform";
+
+        private final Context mContext;
+        private final XmlPullParser mParser;
+        private final AttributeSet mAttrs;
+        private final int mId;
+
+        private final HashMap<String, CursorBinder> mBinders;
+        private final ArrayList<String> mFrom;
+        private final ArrayList<Integer> mTo;
+        private final CursorTransformation mIdentity;
+        private final Resources mResources;
+
+        public XmlCursorAdapterParser(Context c, XmlPullParser parser, AttributeSet attrs, int id) {
+            mContext = c;
+            mParser = parser;
+            mAttrs = attrs;
+            mId = id;
+
+            mResources = mContext.getResources();
+            mBinders = new HashMap<String, CursorBinder>();
+            mFrom = new ArrayList<String>();
+            mTo = new ArrayList<Integer>();
+            mIdentity = new IdentityTransformation(mContext);            
+        }
+
+        public XmlCursorAdapter parse(Object[] parameters)
+                throws IOException, XmlPullParserException {
+
+            Resources resources = mResources;
+            TypedArray a = resources.obtainAttributes(mAttrs, styleable.CursorAdapter);
+            
+            String uri = a.getString(styleable.CursorAdapter_uri);
+            String selection = a.getString(styleable.CursorAdapter_selection);
+            String sortOrder = a.getString(styleable.CursorAdapter_sortOrder);
+            int layout = a.getResourceId(styleable.CursorAdapter_layout, 0);
+            if (layout == 0) {
+                throw new IllegalArgumentException("The layout specified in " +
+                        resources.getResourceEntryName(mId) + " does not exist");
+            }
+
+            a.recycle();
+    
+            XmlPullParser parser = mParser;
+            int type;
+            int depth = parser.getDepth();
+    
+            while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth) &&
+                    type != XmlPullParser.END_DOCUMENT) {
+    
+                if (type != XmlPullParser.START_TAG) {
+                    continue;
+                }
+                
+                String name = parser.getName();
+                
+                if (ADAPTER_CURSOR_BIND.equals(name)) {
+                    parseBindTag();
+                } else if (ADAPTER_CURSOR_SELECT.equals(name)) {
+                    parseSelectTag();
+                } else {
+                    throw new RuntimeException("Unknown tag name " + parser.getName() + " in " +
+                        resources.getResourceEntryName(mId));
+                }
+            }
+    
+            String[] fromArray = mFrom.toArray(new String[mFrom.size()]);
+            int[] toArray = new int[mTo.size()];
+            for (int i = 0; i < toArray.length; i++) {
+                toArray[i] = mTo.get(i);
+            }
+
+            String[] selectionArgs = null;
+            if (parameters != null) {
+                selectionArgs = new String[parameters.length];
+                for (int i = 0; i < selectionArgs.length; i++) {
+                    selectionArgs[i] = (String) parameters[i];
+                }
+            }
+
+            return new XmlCursorAdapter(mContext, layout, uri, fromArray, toArray, selection,
+                    selectionArgs, sortOrder, mBinders);
+        }
+    
+        private void parseSelectTag() {
+            TypedArray a = mResources.obtainAttributes(mAttrs, styleable.CursorAdapter_SelectItem);
+    
+            String fromName = a.getString(styleable.CursorAdapter_SelectItem_column);
+            if (fromName == null) {
+                throw new IllegalArgumentException("A select item in " +
+                    mResources.getResourceEntryName(mId) + " does not have a 'column' attribute");
+            }
+            
+            a.recycle();
+            
+            mFrom.add(fromName);
+            mTo.add(View.NO_ID);
+        }
+    
+        private void parseBindTag() throws IOException, XmlPullParserException {
+            Resources resources = mResources;
+            TypedArray a = resources.obtainAttributes(mAttrs, styleable.CursorAdapter_BindItem);
+    
+            String fromName = a.getString(styleable.CursorAdapter_BindItem_from);
+            if (fromName == null) {
+                throw new IllegalArgumentException("A bind item in " +
+                    resources.getResourceEntryName(mId) + " does not have a 'from' attribute");
+            }
+    
+            int toName = a.getResourceId(styleable.CursorAdapter_BindItem_to, 0);
+            if (toName == 0) {
+                throw new IllegalArgumentException("A bind item in " +
+                    resources.getResourceEntryName(mId) + " does not have a 'to' attribute");
+            }
+            
+            String asType = a.getString(styleable.CursorAdapter_BindItem_as);
+            if (asType == null) {
+                throw new IllegalArgumentException("A bind item in " +
+                    resources.getResourceEntryName(mId) + " does not have an 'as' attribute");
+            }
+    
+            mFrom.add(fromName);
+            mTo.add(toName);
+            mBinders.put(fromName, findBinder(asType));
+    
+            a.recycle();
+        }
+    
+        private CursorBinder findBinder(String type) throws IOException, XmlPullParserException {
+            final XmlPullParser parser = mParser;
+            final Context context = mContext;
+            CursorTransformation transformation = mIdentity;
+    
+            int tagType;
+            int depth = parser.getDepth();
+            
+            final boolean isDrawable = ADAPTER_CURSOR_AS_DRAWABLE.equals(type);            
+    
+            while (((tagType = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
+                    && tagType != XmlPullParser.END_DOCUMENT) {
+    
+                if (tagType != XmlPullParser.START_TAG) {
+                    continue;
+                }
+
+                String name = parser.getName();
+                
+                if (ADAPTER_CURSOR_TRANSFORM.equals(name)) {
+                    transformation = findTransformation();
+                } else if (ADAPTER_CURSOR_MAP.equals(name)) {
+                    if (!(transformation instanceof MapTransformation)) {
+                        transformation = new MapTransformation(context);
+                    }
+                    findMap(((MapTransformation) transformation), isDrawable);
+                } else {
+                    throw new RuntimeException("Unknown tag name " + parser.getName() + " in " +
+                        context.getResources().getResourceEntryName(mId));
+                }
+            }
+            
+            if (ADAPTER_CURSOR_AS_STRING.equals(type)) {
+                return new StringBinder(context, transformation);
+            } else if (ADAPTER_CURSOR_AS_IMAGE.equals(type)) {
+                return new ImageBinder(context, transformation);            
+            } else if (ADAPTER_CURSOR_AS_IMAGE_URI.equals(type)) {
+                return new ImageUriBinder(context, transformation);
+            } else if (isDrawable) {
+                return new DrawableBinder(context, transformation);
+            } else {
+                return createBinder(type, transformation);
+            }
+        }
+
+        private CursorBinder createBinder(String type, CursorTransformation transformation) {
+            if (mContext.isRestricted()) return null;
+
+            try {
+                final Class<?> klass = Class.forName(type, true, mContext.getClassLoader());
+                if (CursorBinder.class.isAssignableFrom(klass)) {
+                    final Constructor<?> c = klass.getDeclaredConstructor(
+                            Context.class, CursorTransformation.class);
+                    return (CursorBinder) c.newInstance(mContext, transformation);
+                }
+            } catch (ClassNotFoundException e) {
+                throw new IllegalArgumentException("Cannot instanciate binder type in " +
+                    mContext.getResources().getResourceEntryName(mId) + ": " + type, e);
+            } catch (NoSuchMethodException e) {
+                throw new IllegalArgumentException("Cannot instanciate binder type in " +
+                    mContext.getResources().getResourceEntryName(mId) + ": " + type, e);
+            } catch (InvocationTargetException e) {
+                throw new IllegalArgumentException("Cannot instanciate binder type in " +
+                    mContext.getResources().getResourceEntryName(mId) + ": " + type, e);
+            } catch (InstantiationException e) {
+                throw new IllegalArgumentException("Cannot instanciate binder type in " +
+                    mContext.getResources().getResourceEntryName(mId) + ": " + type, e);
+            } catch (IllegalAccessException e) {
+                throw new IllegalArgumentException("Cannot instanciate binder type in " +
+                    mContext.getResources().getResourceEntryName(mId) + ": " + type, e);
+            }
+
+            return null;
+        }
+
+        private void findMap(MapTransformation transformation, boolean drawable) {
+            Resources resources = mResources;
+
+            TypedArray a = resources.obtainAttributes(mAttrs, styleable.CursorAdapter_MapItem);
+
+            String from = a.getString(styleable.CursorAdapter_MapItem_fromValue);
+            if (from == null) {
+                throw new IllegalArgumentException("A map item in " +
+                    resources.getResourceEntryName(mId) + " does not have a 'fromValue' attribute");
+            }
+
+            if (!drawable) {
+                String to = a.getString(styleable.CursorAdapter_MapItem_toValue);
+                if (to == null) {
+                    throw new IllegalArgumentException("A map item in " +
+                        resources.getResourceEntryName(mId) + " does not have a 'toValue' attribute");
+                }
+                transformation.addStringMapping(from, to);
+            } else {
+                int to = a.getResourceId(styleable.CursorAdapter_MapItem_toValue, 0);
+                if (to == 0) {
+                    throw new IllegalArgumentException("A map item in " +
+                        resources.getResourceEntryName(mId) + " does not have a 'toValue' attribute");
+                }
+                transformation.addResourceMapping(from, to);
+            }
+
+            a.recycle();
+        }
+
+        private CursorTransformation findTransformation() {
+            Resources resources = mResources;
+            CursorTransformation transformation = null;
+            TypedArray a = resources.obtainAttributes(mAttrs, styleable.CursorAdapter_TransformItem);
+            
+            String className = a.getString(styleable.CursorAdapter_TransformItem_withClass);
+            if (className == null) {
+                String expression = a.getString(
+                        styleable.CursorAdapter_TransformItem_withExpression);
+                transformation = createExpressionTransformation(expression);
+            } else if (!mContext.isRestricted()) {
+                try {
+                    final Class<?> klass = Class.forName(className, true, mContext.getClassLoader());
+                    if (CursorTransformation.class.isAssignableFrom(klass)) {
+                        final Constructor<?> c = klass.getDeclaredConstructor(Context.class);
+                        transformation = (CursorTransformation) c.newInstance(mContext);
+                    }
+                } catch (ClassNotFoundException e) {
+                    throw new IllegalArgumentException("Cannot instanciate transform type in " +
+                        mContext.getResources().getResourceEntryName(mId) + ": " + className, e);
+                } catch (NoSuchMethodException e) {
+                    throw new IllegalArgumentException("Cannot instanciate transform type in " +
+                        mContext.getResources().getResourceEntryName(mId) + ": " + className, e);
+                } catch (InvocationTargetException e) {
+                    throw new IllegalArgumentException("Cannot instanciate transform type in " +
+                        mContext.getResources().getResourceEntryName(mId) + ": " + className, e);
+                } catch (InstantiationException e) {
+                    throw new IllegalArgumentException("Cannot instanciate transform type in " +
+                        mContext.getResources().getResourceEntryName(mId) + ": " + className, e);
+                } catch (IllegalAccessException e) {
+                    throw new IllegalArgumentException("Cannot instanciate transform type in " +
+                        mContext.getResources().getResourceEntryName(mId) + ": " + className, e);
+                }
+            }
+
+            a.recycle();
+            
+            if (transformation == null) {
+                throw new IllegalArgumentException("A transform item in " +
+                    resources.getResourceEntryName(mId) + " must have a 'withClass' or " +
+                    "'withExpression' attribute");
+            }
+
+            return transformation;
+        }
+
+        private CursorTransformation createExpressionTransformation(String expression) {
+            return new ExpressionTransformation(mContext, expression);
+        }
+    }
+
+    /**
+     * Interface used by adapters that require to be loaded after creation.
+     */
+    private static interface ManagedAdapter {
+        /**
+         * Loads the content of the adapter, asynchronously.
+         */
+        void load();
+    }
+
+    /**
+     * Implementation of a Cursor adapter defined in XML. This class is a thin wrapper
+     * of a SimpleCursorAdapter. The main difference is the ability to handle CursorBinders.
+     */
+    private static class XmlCursorAdapter extends SimpleCursorAdapter implements ManagedAdapter {
+        private final Context mContext;
+        private String mUri;
+        private final String mSelection;
+        private final String[] mSelectionArgs;
+        private final String mSortOrder;
+        private final String[] mColumns;
+        private final CursorBinder[] mBinders;
+        private AsyncTask<Void,Void,Cursor> mLoadTask;
+
+        XmlCursorAdapter(Context context, int layout, String uri, String[] from, int[] to,
+                String selection, String[] selectionArgs, String sortOrder,
+                HashMap<String, CursorBinder> binders) {
+
+            super(context, layout, null, from, to);
+            mContext = context;
+            mUri = uri;
+            mSelection = selection;
+            mSelectionArgs = selectionArgs;
+            mSortOrder = sortOrder;
+            mColumns = new String[from.length + 1];
+            // This is mandatory in CursorAdapter
+            mColumns[0] = "_id";
+            System.arraycopy(from, 0, mColumns, 1, from.length);
+
+            CursorBinder basic = new StringBinder(context, new IdentityTransformation(context));
+            final int count = from.length;
+            mBinders = new CursorBinder[count];
+
+            for (int i = 0; i < count; i++) {
+                CursorBinder binder = binders.get(from[i]);
+                if (binder == null) binder = basic;
+                mBinders[i] = binder;
+            }
+        }
+        
+        @Override
+        public void bindView(View view, Context context, Cursor cursor) {
+            final int count = mTo.length;
+            final int[] from = mFrom;
+            final int[] to = mTo;
+            final CursorBinder[] binders = mBinders;
+    
+            for (int i = 0; i < count; i++) {
+                final View v = view.findViewById(to[i]);
+                if (v != null) {
+                    binders[i].bind(v, cursor, from[i]);
+                }
+            }
+        }
+        
+        public void load() {
+            if (mUri != null) {
+                mLoadTask = new QueryTask().execute();
+            }
+        }
+
+        void setUri(String uri) {
+            mUri = uri;
+        }
+
+        @Override
+        public void changeCursor(Cursor c) {
+            if (mLoadTask != null && mLoadTask.getStatus() != QueryTask.Status.FINISHED) {
+                mLoadTask.cancel(true);
+                mLoadTask = null;
+            }
+            super.changeCursor(c);
+        }
+
+        class QueryTask extends AsyncTask<Void, Void, Cursor> {
+            @Override
+            protected Cursor doInBackground(Void... params) {
+                if (mContext instanceof Activity) {
+                    return ((Activity) mContext).managedQuery(
+                            Uri.parse(mUri), mColumns, mSelection, mSelectionArgs, mSortOrder);
+                } else {
+                    return mContext.getContentResolver().query(
+                            Uri.parse(mUri), mColumns, mSelection, mSelectionArgs, mSortOrder);
+                }
+            }
+
+            @Override
+            protected void onPostExecute(Cursor cursor) {
+                if (!isCancelled()) {
+                    XmlCursorAdapter.super.changeCursor(cursor);
+                }
+            }
+        }
+    }
+
+    /**
+     * Identity transformation, returns the content of the specified column as a String,
+     * without performing any manipulation. This is used when no transformation is specified.
+     */
+    private static class IdentityTransformation extends CursorTransformation {
+        public IdentityTransformation(Context context) {
+            super(context);
+        }
+
+        @Override
+        public String transform(Cursor cursor, int columnIndex) {
+            return cursor.getString(columnIndex);
+        }
+    }
+
+    /**
+     * An expression transformation is a simple template based replacement utility.
+     * In an expression, each segment of the form <code>{(^[}]+)}</code> is replaced
+     * with the value of the column of name $1.
+     */
+    private static class ExpressionTransformation extends CursorTransformation {
+        private final ExpressionNode mFirstNode = new ConstantExpressionNode("");
+        private final StringBuilder mBuilder = new StringBuilder();
+        
+        public ExpressionTransformation(Context context, String expression) {
+            super(context);
+            
+            parse(expression);
+        }
+
+        private void parse(String expression) {
+            ExpressionNode node = mFirstNode;
+            int segmentStart;
+            int count = expression.length();
+
+            for (int i = 0; i < count; i++) {
+                char c = expression.charAt(i);
+                // Start a column name segment
+                segmentStart = i;
+                if (c == '{') {
+                    while (i < count && (c = expression.charAt(i)) != '}') {
+                        i++;
+                    }
+                    // We've reached the end, but the expression didn't close
+                    if (c != '}') {
+                        throw new IllegalStateException("The transform expression contains a " +
+                                "non-closed column name: " +
+                                expression.substring(segmentStart + 1, i));
+                    }
+                    node.next = new ColumnExpressionNode(expression.substring(segmentStart + 1, i));
+                } else {
+                    while (i < count && (c = expression.charAt(i)) != '{') {
+                        i++;
+                    }
+                    node.next = new ConstantExpressionNode(expression.substring(segmentStart, i));
+                    // Rewind if we've reached a column expression
+                    if (c == '{') i--;
+                }
+                node = node.next;
+            }
+        }
+
+        @Override
+        public String transform(Cursor cursor, int columnIndex) {
+            final StringBuilder builder = mBuilder;
+            builder.delete(0, builder.length());
+            
+            ExpressionNode node = mFirstNode;
+            // Skip the first node
+            while ((node = node.next) != null) {
+                builder.append(node.asString(cursor));
+            }
+
+            return builder.toString();
+        }
+        
+        static abstract class ExpressionNode {
+            public ExpressionNode next;
+
+            public abstract String asString(Cursor cursor);
+        }
+        
+        static class ConstantExpressionNode extends ExpressionNode {
+            private final String mConstant;
+
+            ConstantExpressionNode(String constant) {
+                mConstant = constant;
+            }
+
+            @Override
+            public String asString(Cursor cursor) {
+                return mConstant;
+            }
+        }
+        
+        static class ColumnExpressionNode extends ExpressionNode {
+            private final String mColumnName;
+            private Cursor mSignature;
+            private int mColumnIndex = -1;
+
+            ColumnExpressionNode(String columnName) {
+                mColumnName = columnName;
+            }
+
+            @Override
+            public String asString(Cursor cursor) {
+                if (cursor != mSignature || mColumnIndex == -1) {
+                    mColumnIndex = cursor.getColumnIndex(mColumnName);
+                    mSignature = cursor;
+                }
+
+                return cursor.getString(mColumnIndex);
+            }
+        }
+    }
+
+    /**
+     * A map transformation offers a simple mapping between specified String values
+     * to Strings or integers.
+     */
+    private static class MapTransformation extends CursorTransformation {
+        private final HashMap<String, String> mStringMappings;
+        private final HashMap<String, Integer> mResourceMappings;
+
+        public MapTransformation(Context context) {
+            super(context);
+            mStringMappings = new HashMap<String, String>();
+            mResourceMappings = new HashMap<String, Integer>();
+        }
+
+        void addStringMapping(String from, String to) {
+            mStringMappings.put(from, to);
+        }
+
+        void addResourceMapping(String from, int to) {
+            mResourceMappings.put(from, to);
+        }
+
+        @Override
+        public String transform(Cursor cursor, int columnIndex) {
+            final String value = cursor.getString(columnIndex);
+            final String transformed = mStringMappings.get(value);
+            return transformed == null ? value : transformed;
+        }
+
+        @Override
+        public int transformToResource(Cursor cursor, int columnIndex) {
+            final String value = cursor.getString(columnIndex);
+            final Integer transformed = mResourceMappings.get(value);
+            try {
+                return transformed == null ? Integer.parseInt(value) : transformed;
+            } catch (NumberFormatException e) {
+                return 0;
+            }
+        }
+    }
+
+    /**
+     * Binds a String to a TextView.
+     */
+    private static class StringBinder extends CursorBinder {
+        public StringBinder(Context context, CursorTransformation transformation) {
+            super(context, transformation);
+        }
+
+        @Override
+        public boolean bind(View view, Cursor cursor, int columnIndex) {
+            ((TextView) view).setText(mTransformation.transform(cursor, columnIndex));
+            return true;
+        }
+    }
+
+    /**
+     * Binds an image blob to an ImageView.
+     */
+    private static class ImageBinder extends CursorBinder {
+        public ImageBinder(Context context, CursorTransformation transformation) {
+            super(context, transformation);
+        }
+
+        @Override
+        public boolean bind(View view, Cursor cursor, int columnIndex) {
+            final byte[] data = cursor.getBlob(columnIndex);
+            ((ImageView) view).setImageBitmap(BitmapFactory.decodeByteArray(data, 0, data.length));
+            return true;
+        }
+    }
+
+    /**
+     * Binds an image URI to an ImageView.
+     */
+    private static class ImageUriBinder extends CursorBinder {
+        public ImageUriBinder(Context context, CursorTransformation transformation) {
+            super(context, transformation);
+        }
+
+        @Override
+        public boolean bind(View view, Cursor cursor, int columnIndex) {
+            ((ImageView) view).setImageURI(Uri.parse(
+                    mTransformation.transform(cursor, columnIndex)));
+            return true;
+        }
+    }
+
+    /**
+     * Binds a drawable resource identifier to an ImageView.
+     */
+    private static class DrawableBinder extends CursorBinder {
+        public DrawableBinder(Context context, CursorTransformation transformation) {
+            super(context, transformation);
+        }
+
+        @Override
+        public boolean bind(View view, Cursor cursor, int columnIndex) {
+            final int resource = mTransformation.transformToResource(cursor, columnIndex);
+            if (resource == 0) return false;
+
+            ((ImageView) view).setImageResource(resource);
+            return true;
+        }
+    }
+}
diff --git a/core/java/android/widget/AutoCompleteTextView.java b/core/java/android/widget/AutoCompleteTextView.java
index e15a520..8611901 100644
--- a/core/java/android/widget/AutoCompleteTextView.java
+++ b/core/java/android/widget/AutoCompleteTextView.java
@@ -913,10 +913,10 @@
 
             if (mItemClickListener != null) {
                 final DropDownListView list = mDropDownList;
-                // Note that we don't have a View here, so we will need to
-                // supply null.  Hopefully no existing apps crash...
-                mItemClickListener.onItemClick(list, null, completion.getPosition(),
-                        completion.getId());
+                final int position = completion.getPosition();
+                mItemClickListener.onItemClick(list,
+                        list.getChildAt(position - list.getFirstVisiblePosition()),
+                        position, completion.getId());
             }
         }
     }
diff --git a/core/java/android/widget/CursorAdapter.java b/core/java/android/widget/CursorAdapter.java
index baa6833..4cf8785 100644
--- a/core/java/android/widget/CursorAdapter.java
+++ b/core/java/android/widget/CursorAdapter.java
@@ -80,6 +80,18 @@
     protected FilterQueryProvider mFilterQueryProvider;
 
     /**
+     * If set the adapter will call requery() on the cursor whenever a content change
+     * notification is delivered. Implies {@link #FLAG_REGISTER_CONTENT_OBSERVER}
+     */
+    public static final int FLAG_AUTO_REQUERY = 0x01;
+
+    /**
+     * If set the adapter will register a content observer on the cursor and will call
+     * {@link #onContentChanged()} when a notification comes in.
+     */
+    public static final int FLAG_REGISTER_CONTENT_OBSERVER = 0x02;
+
+    /**
      * Constructor. The adapter will call requery() on the cursor whenever
      * it changes so that the most recent data is always displayed.
      *
@@ -87,7 +99,7 @@
      * @param context The context
      */
     public CursorAdapter(Context context, Cursor c) {
-        init(context, c, true);
+        init(context, c, FLAG_AUTO_REQUERY);
     }
 
     /**
@@ -99,19 +111,43 @@
      *                    data is always displayed.
      */
     public CursorAdapter(Context context, Cursor c, boolean autoRequery) {
-        init(context, c, autoRequery);
+        init(context, c, autoRequery ? FLAG_AUTO_REQUERY : FLAG_REGISTER_CONTENT_OBSERVER);
+    }
+
+    /**
+     * Constructor
+     * @param c The cursor from which to get the data.
+     * @param context The context
+     * @param flags flags used to determine the behavior of the adapter
+     */
+    public CursorAdapter(Context context, Cursor c, int flags) {
+        init(context, c, flags);
     }
 
     protected void init(Context context, Cursor c, boolean autoRequery) {
+        init(context, c, autoRequery ? FLAG_AUTO_REQUERY : FLAG_REGISTER_CONTENT_OBSERVER);
+    }
+
+    protected void init(Context context, Cursor c, int flags) {
+        if ((flags & FLAG_AUTO_REQUERY) == FLAG_AUTO_REQUERY) {
+            flags |= FLAG_REGISTER_CONTENT_OBSERVER;
+            mAutoRequery = true;
+        } else {
+            mAutoRequery = false;
+        }
         boolean cursorPresent = c != null;
-        mAutoRequery = autoRequery;
         mCursor = c;
         mDataValid = cursorPresent;
         mContext = context;
         mRowIDColumn = cursorPresent ? c.getColumnIndexOrThrow("_id") : -1;
-        mChangeObserver = new ChangeObserver();
+        if ((flags & FLAG_REGISTER_CONTENT_OBSERVER) == FLAG_REGISTER_CONTENT_OBSERVER) {
+            mChangeObserver = new ChangeObserver();
+        } else {
+            mChangeObserver = null;
+        }
+
         if (cursorPresent) {
-            c.registerContentObserver(mChangeObserver);
+            if (mChangeObserver != null) c.registerContentObserver(mChangeObserver);
             c.registerDataSetObserver(mDataSetObserver);
         }
     }
@@ -246,13 +282,13 @@
             return;
         }
         if (mCursor != null) {
-            mCursor.unregisterContentObserver(mChangeObserver);
+            if (mChangeObserver != null) mCursor.unregisterContentObserver(mChangeObserver);
             mCursor.unregisterDataSetObserver(mDataSetObserver);
             mCursor.close();
         }
         mCursor = cursor;
         if (cursor != null) {
-            cursor.registerContentObserver(mChangeObserver);
+            if (mChangeObserver != null) cursor.registerContentObserver(mChangeObserver);
             cursor.registerDataSetObserver(mDataSetObserver);
             mRowIDColumn = cursor.getColumnIndexOrThrow("_id");
             mDataValid = true;
diff --git a/core/java/android/widget/Gallery.java b/core/java/android/widget/Gallery.java
index f34823c..6775a94 100644
--- a/core/java/android/widget/Gallery.java
+++ b/core/java/android/widget/Gallery.java
@@ -1207,7 +1207,7 @@
 
         // We unfocus the old child down here so the above hasFocus check
         // returns true
-        if (oldSelectedChild != null) {
+        if (oldSelectedChild != null && oldSelectedChild != child) {
 
             // Make sure its drawable state doesn't contain 'selected'
             oldSelectedChild.setSelected(false);
@@ -1263,6 +1263,7 @@
          */
         if (gainFocus && mSelectedChild != null) {
             mSelectedChild.requestFocus(direction);
+            mSelectedChild.setSelected(true);
         }
 
     }
diff --git a/core/java/android/widget/QuickContactBadge.java b/core/java/android/widget/QuickContactBadge.java
index 07c3e4b..50fbb6b 100644
--- a/core/java/android/widget/QuickContactBadge.java
+++ b/core/java/android/widget/QuickContactBadge.java
@@ -48,6 +48,7 @@
     private QueryHandler mQueryHandler;
     private Drawable mBadgeBackground;
     private Drawable mNoBadgeBackground;
+    private Drawable mDefaultAvatar;
 
     protected String[] mExcludeMimes = null;
 
@@ -117,6 +118,16 @@
     public void setMode(int size) {
         mMode = size;
     }
+    
+    /**
+     * Resets the contact photo to the default state.
+     */
+    public void setImageToDefault() {
+        if (mDefaultAvatar == null) {
+            mDefaultAvatar = getResources().getDrawable(R.drawable.ic_contact_picture);
+        }
+        setImageDrawable(mDefaultAvatar);
+    }
 
     /**
      * Assign the contact uri that this QuickContactBadge should be associated
diff --git a/core/java/android/widget/SimpleCursorAdapter.java b/core/java/android/widget/SimpleCursorAdapter.java
index 7d3459e..d1c2270 100644
--- a/core/java/android/widget/SimpleCursorAdapter.java
+++ b/core/java/android/widget/SimpleCursorAdapter.java
@@ -62,7 +62,8 @@
     private int mStringConversionColumn = -1;
     private CursorToStringConverter mCursorToStringConverter;
     private ViewBinder mViewBinder;
-    private String[] mOriginalFrom;
+
+    String[] mOriginalFrom;
 
     /**
      * Constructor.
diff --git a/core/java/android/widget/ZoomButtonsController.java b/core/java/android/widget/ZoomButtonsController.java
index 3df419a..450c966 100644
--- a/core/java/android/widget/ZoomButtonsController.java
+++ b/core/java/android/widget/ZoomButtonsController.java
@@ -66,8 +66,9 @@
  * {@link #setZoomInEnabled(boolean)} and {@link #setZoomOutEnabled(boolean)}.
  * <p>
  * If you are using this with a custom View, please call
- * {@link #setVisible(boolean) setVisible(false)} from the
- * {@link View#onDetachedFromWindow}.
+ * {@link #setVisible(boolean) setVisible(false)} from
+ * {@link View#onDetachedFromWindow} and from {@link View#onVisibilityChanged}
+ * when <code>visibility != View.VISIBLE</code>.
  *
  */
 public class ZoomButtonsController implements View.OnTouchListener {
diff --git a/core/java/com/android/internal/util/XmlUtils.java b/core/java/com/android/internal/util/XmlUtils.java
index 8d8df16..e00a853 100644
--- a/core/java/com/android/internal/util/XmlUtils.java
+++ b/core/java/com/android/internal/util/XmlUtils.java
@@ -26,6 +26,7 @@
 import java.io.OutputStream;
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
@@ -284,6 +285,26 @@
 
         out.endTag(null, "list");
     }
+    
+    public static final void writeSetXml(Set val, String name, XmlSerializer out)
+            throws XmlPullParserException, java.io.IOException {
+        if (val == null) {
+            out.startTag(null, "null");
+            out.endTag(null, "null");
+            return;
+        }
+        
+        out.startTag(null, "set");
+        if (name != null) {
+            out.attribute(null, "name", name);
+        }
+        
+        for (Object v : val) {
+            writeValueXml(v, null, out);
+        }
+        
+        out.endTag(null, "set");
+    }
 
     /**
      * Flatten a byte[] into an XmlSerializer.  The list can later be read back
@@ -426,6 +447,9 @@
         } else if (v instanceof List) {
             writeListXml((List)v, name, out);
             return;
+        } else if (v instanceof Set) {
+            writeSetXml((Set)v, name, out);
+            return;
         } else if (v instanceof CharSequence) {
             // XXX This is to allow us to at least write something if
             // we encounter styled text...  but it means we will drop all
@@ -476,7 +500,7 @@
      *
      * @param in The InputStream from which to read.
      *
-     * @return HashMap The resulting list.
+     * @return ArrayList The resulting list.
      *
      * @see #readMapXml
      * @see #readValueXml
@@ -490,6 +514,29 @@
         parser.setInput(in, null);
         return (ArrayList)readValueXml(parser, new String[1]);
     }
+    
+    
+    /**
+     * Read a HashSet from an InputStream containing XML. The stream can
+     * previously have been written by writeSetXml().
+     * 
+     * @param in The InputStream from which to read.
+     * 
+     * @return HashSet The resulting set.
+     * 
+     * @throws XmlPullParserException
+     * @throws java.io.IOException
+     * 
+     * @see #readValueXml
+     * @see #readThisSetXml
+     * @see #writeSetXml
+     */
+    public static final HashSet readSetXml(InputStream in)
+            throws XmlPullParserException, java.io.IOException {
+        XmlPullParser parser = Xml.newPullParser();
+        parser.setInput(in, null);
+        return (HashSet) readValueXml(parser, new String[1]);
+    }
 
     /**
      * Read a HashMap object from an XmlPullParser.  The XML data could
@@ -573,6 +620,47 @@
         throw new XmlPullParserException(
             "Document ended before " + endTag + " end tag");
     }
+    
+    /**
+     * Read a HashSet object from an XmlPullParser. The XML data could previously
+     * have been generated by writeSetXml(). The XmlPullParser must be positioned
+     * <em>after</em> the tag that begins the set.
+     * 
+     * @param parser The XmlPullParser from which to read the set data.
+     * @param endTag Name of the tag that will end the set, usually "set".
+     * @param name An array of one string, used to return the name attribute
+     *             of the set's tag.
+     *
+     * @return HashSet The newly generated set.
+     * 
+     * @throws XmlPullParserException
+     * @throws java.io.IOException
+     * 
+     * @see #readSetXml
+     */
+    public static final HashSet readThisSetXml(XmlPullParser parser, String endTag, String[] name)
+            throws XmlPullParserException, java.io.IOException {
+        HashSet set = new HashSet();
+        
+        int eventType = parser.getEventType();
+        do {
+            if (eventType == parser.START_TAG) {
+                Object val = readThisValueXml(parser, name);
+                set.add(val);
+                //System.out.println("Adding to set: " + val);
+            } else if (eventType == parser.END_TAG) {
+                if (parser.getName().equals(endTag)) {
+                    return set;
+                }
+                throw new XmlPullParserException(
+                        "Expected " + endTag + " end tag at: " + parser.getName());
+            }
+            eventType = parser.next();
+        } while (eventType != parser.END_DOCUMENT);
+        
+        throw new XmlPullParserException(
+                "Document ended before " + endTag + " end tag");
+    }
 
     /**
      * Read an int[] object from an XmlPullParser.  The XML data could
@@ -740,6 +828,12 @@
             name[0] = valueName;
             //System.out.println("Returning value for " + valueName + ": " + res);
             return res;
+        } else if (tagName.equals("set")) {
+            parser.next();
+            res = readThisSetXml(parser, "set", name);
+            name[0] = valueName;
+            //System.out.println("Returning value for " + valueName + ": " + res);
+            return res;
         } else {
             throw new XmlPullParserException(
                 "Unknown tag: " + tagName);
diff --git a/core/java/com/android/internal/widget/ContactHeaderWidget.java b/core/java/com/android/internal/widget/ContactHeaderWidget.java
index f421466..a514089 100644
--- a/core/java/com/android/internal/widget/ContactHeaderWidget.java
+++ b/core/java/com/android/internal/widget/ContactHeaderWidget.java
@@ -55,7 +55,7 @@
 /**
  * Header used across system for displaying a title bar with contact info. You
  * can bind specific values on the header, or use helper methods like
- * {@link #bindFromContactId(long)} to populate asynchronously.
+ * {@link #bindFromContactLookupUri(Uri)} to populate asynchronously.
  * <p>
  * The parent must request the {@link Manifest.permission#READ_CONTACTS}
  * permission to access contact data.
@@ -257,7 +257,7 @@
                         if (photoBitmap == null) {
                             photoBitmap = loadPlaceholderPhoto(null);
                         }
-                        mPhotoView.setImageBitmap(photoBitmap);
+                        setPhoto(photoBitmap);
                         if (cookie != null && cookie instanceof Uri) {
                             mPhotoView.assignContactUri((Uri) cookie);
                         }
@@ -267,21 +267,13 @@
                     case TOKEN_CONTACT_INFO: {
                         if (cursor != null && cursor.moveToFirst()) {
                             bindContactInfo(cursor);
-                            Uri lookupUri = Contacts.getLookupUri(cursor.getLong(ContactQuery._ID),
+                            final Uri lookupUri = Contacts.getLookupUri(
+                                    cursor.getLong(ContactQuery._ID),
                                     cursor.getString(ContactQuery.LOOKUP_KEY));
 
                             final long photoId = cursor.getLong(ContactQuery.PHOTO_ID);
 
-                            if (photoId == 0) {
-                                mPhotoView.setImageBitmap(loadPlaceholderPhoto(null));
-                                if (cookie != null && cookie instanceof Uri) {
-                                    mPhotoView.assignContactUri((Uri) cookie);
-                                }
-                                invalidate();
-                            } else {
-                                startPhotoQuery(photoId, lookupUri,
-                                        false /* don't reset query handler */);
-                            }
+                            setPhotoId(photoId, lookupUri);
                         } else {
                             // shouldn't really happen
                             setDisplayName(null, null);
@@ -361,18 +353,40 @@
     }
 
     /**
-     * Manually set the contact uri
+     * Manually set the presence. If presence is null, it is hidden.
+     * This doesn't change the underlying {@link Contacts} value, only the UI state.
+     * @hide
+     */
+    public void setPresence(Integer presence) {
+        if (presence == null) {
+            showPresence(false);
+        } else {
+            showPresence(true);
+            setPresence(presence.intValue());
+        }
+    }
+
+    /**
+     * Turn on/off showing the presence.
+     * @hide this is here for consistency with setStared/showStar and should be public
+     */
+    public void showPresence(boolean showPresence) {
+        mPresenceView.setVisibility(showPresence ? View.VISIBLE : View.GONE);
+    }
+
+    /**
+     * Manually set the contact uri without loading any data
      */
     public void setContactUri(Uri uri) {
         setContactUri(uri, true);
     }
 
     /**
-     * Manually set the contact uri
+     * Manually set the contact uri without loading any data
      */
-    public void setContactUri(Uri uri, boolean sendToFastrack) {
+    public void setContactUri(Uri uri, boolean sendToQuickContact) {
         mContactUri = uri;
-        if (sendToFastrack) {
+        if (sendToQuickContact) {
             mPhotoView.assignContactUri(uri);
         }
     }
@@ -386,6 +400,22 @@
     }
 
     /**
+     * Manually set the photo given its id. If the id is 0, a placeholder picture will
+     * be loaded. For any other Id, an async query is started
+     * @hide
+     */
+    public void setPhotoId(final long photoId, final Uri lookupUri) {
+        if (photoId == 0) {
+            setPhoto(loadPlaceholderPhoto(null));
+            mPhotoView.assignContactUri(lookupUri);
+            invalidate();
+        } else {
+            startPhotoQuery(photoId, lookupUri,
+                    false /* don't reset query handler */);
+        }
+    }
+
+    /**
      * Manually set the display name and phonetic name to show in the header.
      * This doesn't change the underlying {@link Contacts}, only the UI state.
      */
@@ -400,7 +430,8 @@
     }
 
     /**
-     * Manually set the social snippet text to display in the header.
+     * Manually set the social snippet text to display in the header. This doesn't change the
+     * underlying {@link Contacts}, only the UI state.
      */
     public void setSocialSnippet(CharSequence snippet) {
         if (snippet == null) {
@@ -413,6 +444,20 @@
     }
 
     /**
+     * Manually set the status attribution text to display in the header.
+     * This doesn't change the underlying {@link Contacts}, only the UI state.
+     * @hide
+     */
+    public void setStatusAttribution(CharSequence attribution) {
+        if (attribution != null) {
+            mStatusAttributionView.setText(attribution);
+            mStatusAttributionView.setVisibility(View.VISIBLE);
+        } else {
+            mStatusAttributionView.setVisibility(View.GONE);
+        }
+    }
+
+    /**
      * Set a list of specific MIME-types to exclude and not display. For
      * example, this can be used to hide the {@link Contacts#CONTENT_ITEM_TYPE}
      * profile icon.
@@ -423,6 +468,88 @@
     }
 
     /**
+     * Manually set all the status values to display in the header.
+     * This doesn't change the underlying {@link Contacts}, only the UI state.
+     * @hide
+     * @param status             The status of the contact. If this is either null or empty,
+     *                           the status is cleared and the other parameters are ignored.
+     * @param statusTimestamp    The timestamp (retrieved via a call to
+     *                           {@link System#currentTimeMillis()}) of the last status update.
+     *                           This value can be null if it is not known.
+     * @param statusLabel        The id of a resource string that specifies the current
+     *                           status. This value can be null if no Label should be used.
+     * @param statusResPackage   The name of the resource package containing the resource string
+     *                           referenced in the parameter statusLabel.
+     */
+    public void setStatus(final String status, final Long statusTimestamp,
+            final Integer statusLabel, final String statusResPackage) {
+        if (TextUtils.isEmpty(status)) {
+            setSocialSnippet(null);
+            return;
+        }
+
+        setSocialSnippet(status);
+
+        final CharSequence timestampDisplayValue;
+
+        if (statusTimestamp != null) {
+            // Set the date/time field by mixing relative and absolute
+            // times.
+            int flags = DateUtils.FORMAT_ABBREV_RELATIVE;
+
+            timestampDisplayValue = DateUtils.getRelativeTimeSpanString(
+                    statusTimestamp.longValue(), System.currentTimeMillis(),
+                    DateUtils.MINUTE_IN_MILLIS, flags);
+        } else {
+            timestampDisplayValue = null;
+        }
+
+
+        String labelDisplayValue = null;
+
+        if (statusLabel != null) {
+            Resources resources;
+            if (TextUtils.isEmpty(statusResPackage)) {
+                resources = getResources();
+            } else {
+                PackageManager pm = getContext().getPackageManager();
+                try {
+                    resources = pm.getResourcesForApplication(statusResPackage);
+                } catch (NameNotFoundException e) {
+                    Log.w(TAG, "Contact status update resource package not found: "
+                            + statusResPackage);
+                    resources = null;
+                }
+            }
+
+            if (resources != null) {
+                try {
+                    labelDisplayValue = resources.getString(statusLabel.intValue());
+                } catch (NotFoundException e) {
+                    Log.w(TAG, "Contact status update resource not found: " + statusResPackage + "@"
+                            + statusLabel.intValue());
+                }
+            }
+        }
+
+        final CharSequence attribution;
+        if (timestampDisplayValue != null && labelDisplayValue != null) {
+            attribution = getContext().getString(
+                    R.string.contact_status_update_attribution_with_date,
+                    timestampDisplayValue, labelDisplayValue);
+        } else if (timestampDisplayValue == null && labelDisplayValue != null) {
+            attribution = getContext().getString(
+                    R.string.contact_status_update_attribution,
+                    labelDisplayValue);
+        } else if (timestampDisplayValue != null) {
+            attribution = timestampDisplayValue;
+        } else {
+            attribution = null;
+        }
+        setStatusAttribution(attribution);
+    }
+
+    /**
      * Convenience method for binding all available data from an existing
      * contact.
      *
@@ -543,89 +670,28 @@
         this.setDisplayName(displayName, phoneticName);
 
         final boolean starred = c.getInt(ContactQuery.STARRED) != 0;
-        mStarredView.setChecked(starred);
+        setStared(starred);
 
         //Set the presence status
         if (!c.isNull(ContactQuery.CONTACT_PRESENCE_STATUS)) {
             int presence = c.getInt(ContactQuery.CONTACT_PRESENCE_STATUS);
-            mPresenceView.setImageResource(StatusUpdates.getPresenceIconResourceId(presence));
-            mPresenceView.setVisibility(View.VISIBLE);
+            setPresence(presence);
+            showPresence(true);
         } else {
-            mPresenceView.setVisibility(View.GONE);
+            showPresence(false);
         }
 
         //Set the status update
-        String status = c.getString(ContactQuery.CONTACT_STATUS);
-        if (!TextUtils.isEmpty(status)) {
-            mStatusView.setText(status);
-            mStatusView.setVisibility(View.VISIBLE);
+        final String status = c.getString(ContactQuery.CONTACT_STATUS);
+        final Long statusTimestamp = c.isNull(ContactQuery.CONTACT_STATUS_TIMESTAMP)
+                ? null
+                : c.getLong(ContactQuery.CONTACT_STATUS_TIMESTAMP);
+        final Integer statusLabel = c.isNull(ContactQuery.CONTACT_STATUS_LABEL)
+                ? null
+                : c.getInt(ContactQuery.CONTACT_STATUS_LABEL);
+        final String statusResPackage = c.getString(ContactQuery.CONTACT_STATUS_RES_PACKAGE);
 
-            CharSequence timestamp = null;
-
-            if (!c.isNull(ContactQuery.CONTACT_STATUS_TIMESTAMP)) {
-                long date = c.getLong(ContactQuery.CONTACT_STATUS_TIMESTAMP);
-
-                // Set the date/time field by mixing relative and absolute
-                // times.
-                int flags = DateUtils.FORMAT_ABBREV_RELATIVE;
-
-                timestamp = DateUtils.getRelativeTimeSpanString(date,
-                        System.currentTimeMillis(), DateUtils.MINUTE_IN_MILLIS, flags);
-            }
-
-            String label = null;
-
-            if (!c.isNull(ContactQuery.CONTACT_STATUS_LABEL)) {
-                String resPackage = c.getString(ContactQuery.CONTACT_STATUS_RES_PACKAGE);
-                int labelResource = c.getInt(ContactQuery.CONTACT_STATUS_LABEL);
-                Resources resources;
-                if (TextUtils.isEmpty(resPackage)) {
-                    resources = getResources();
-                } else {
-                    PackageManager pm = getContext().getPackageManager();
-                    try {
-                        resources = pm.getResourcesForApplication(resPackage);
-                    } catch (NameNotFoundException e) {
-                        Log.w(TAG, "Contact status update resource package not found: "
-                                + resPackage);
-                        resources = null;
-                    }
-                }
-
-                if (resources != null) {
-                    try {
-                        label = resources.getString(labelResource);
-                    } catch (NotFoundException e) {
-                        Log.w(TAG, "Contact status update resource not found: " + resPackage + "@"
-                                + labelResource);
-                    }
-                }
-            }
-
-            CharSequence attribution;
-            if (timestamp != null && label != null) {
-                attribution = getContext().getString(
-                        R.string.contact_status_update_attribution_with_date,
-                        timestamp, label);
-            } else if (timestamp == null && label != null) {
-                attribution = getContext().getString(
-                        R.string.contact_status_update_attribution,
-                        label);
-            } else if (timestamp != null) {
-                attribution = timestamp;
-            } else {
-                attribution = null;
-            }
-            if (attribution != null) {
-                mStatusAttributionView.setText(attribution);
-                mStatusAttributionView.setVisibility(View.VISIBLE);
-            } else {
-                mStatusAttributionView.setVisibility(View.GONE);
-            }
-        } else {
-            mStatusView.setVisibility(View.GONE);
-            mStatusAttributionView.setVisibility(View.GONE);
-        }
+        setStatus(status, statusTimestamp, statusLabel, statusResPackage);
     }
 
     public void onClick(View view) {
diff --git a/core/java/com/android/internal/widget/DigitalClock.java b/core/java/com/android/internal/widget/DigitalClock.java
index fa47ff6..23e2277 100644
--- a/core/java/com/android/internal/widget/DigitalClock.java
+++ b/core/java/com/android/internal/widget/DigitalClock.java
@@ -30,7 +30,7 @@
 import android.text.format.DateFormat;
 import android.util.AttributeSet;
 import android.view.View;
-import android.widget.LinearLayout;
+import android.widget.RelativeLayout;
 import android.widget.TextView;
 
 import java.text.DateFormatSymbols;
@@ -39,7 +39,7 @@
 /**
  * Displays the time
  */
-public class DigitalClock extends LinearLayout {
+public class DigitalClock extends RelativeLayout {
 
     private final static String M12 = "h:mm";
     private final static String M24 = "kk:mm";
diff --git a/core/res/assets/images/combobox-disabled.png b/core/res/assets/images/combobox-disabled.png
deleted file mode 100644
index fe220e4..0000000
--- a/core/res/assets/images/combobox-disabled.png
+++ /dev/null
Binary files differ
diff --git a/core/res/assets/images/combobox-noHighlight.png b/core/res/assets/images/combobox-noHighlight.png
deleted file mode 100644
index abcdf72..0000000
--- a/core/res/assets/images/combobox-noHighlight.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-hdpi/combobox_disabled.png b/core/res/res/drawable-hdpi/combobox_disabled.png
new file mode 100644
index 0000000..50eb45e
--- /dev/null
+++ b/core/res/res/drawable-hdpi/combobox_disabled.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/combobox_nohighlight.png b/core/res/res/drawable-hdpi/combobox_nohighlight.png
new file mode 100644
index 0000000..9d60301
--- /dev/null
+++ b/core/res/res/drawable-hdpi/combobox_nohighlight.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/quickcontact_nobadge.9.png b/core/res/res/drawable-hdpi/quickcontact_nobadge.9.png
new file mode 100644
index 0000000..68d43c4
--- /dev/null
+++ b/core/res/res/drawable-hdpi/quickcontact_nobadge.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/sym_action_call.png b/core/res/res/drawable-hdpi/sym_action_call.png
index 105f7d0..da8afee 100644
--- a/core/res/res/drawable-hdpi/sym_action_call.png
+++ b/core/res/res/drawable-hdpi/sym_action_call.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/combobox_disabled.png b/core/res/res/drawable-mdpi/combobox_disabled.png
new file mode 100644
index 0000000..c32db7e
--- /dev/null
+++ b/core/res/res/drawable-mdpi/combobox_disabled.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/combobox_nohighlight.png b/core/res/res/drawable-mdpi/combobox_nohighlight.png
new file mode 100644
index 0000000..1963316
--- /dev/null
+++ b/core/res/res/drawable-mdpi/combobox_nohighlight.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/quickcontact_nobadge.9.png b/core/res/res/drawable-mdpi/quickcontact_nobadge.9.png
new file mode 100644
index 0000000..ebe747b
--- /dev/null
+++ b/core/res/res/drawable-mdpi/quickcontact_nobadge.9.png
Binary files differ
diff --git a/core/res/res/drawable-nodpi/loading_tile_android.png b/core/res/res/drawable-nodpi/loading_tile_android.png
new file mode 100644
index 0000000..8fde46f
--- /dev/null
+++ b/core/res/res/drawable-nodpi/loading_tile_android.png
Binary files differ
diff --git a/core/res/res/drawable-nodpi/no_tile_256.png b/core/res/res/drawable-nodpi/no_tile_256.png
new file mode 100644
index 0000000..388234e
--- /dev/null
+++ b/core/res/res/drawable-nodpi/no_tile_256.png
Binary files differ
diff --git a/core/res/res/drawable/ic_btn_back.png b/core/res/res/drawable/ic_btn_back.png
new file mode 100644
index 0000000..c9bff4c
--- /dev/null
+++ b/core/res/res/drawable/ic_btn_back.png
Binary files differ
diff --git a/core/res/res/drawable/ic_btn_next.png b/core/res/res/drawable/ic_btn_next.png
new file mode 100755
index 0000000..c6cf436
--- /dev/null
+++ b/core/res/res/drawable/ic_btn_next.png
Binary files differ
diff --git a/core/res/res/drawable/quickcontact_nobadge.xml b/core/res/res/drawable/quickcontact_nobadge.xml
deleted file mode 100644
index 922fa0e..0000000
--- a/core/res/res/drawable/quickcontact_nobadge.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/* bubble_with_chats.xml
-**
-** Copyright 2009, Google Inc.
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_pressed="true" android:drawable="@drawable/quickcontact_nobadge_pressed" />
-    <item android:state_selected="true" android:drawable="@drawable/quickcontact_nobadge_highlight" />
-    <item android:state_focused="true" android:drawable="@drawable/quickcontact_nobadge_highlight" />
-    <item android:state_enabled="false" android:drawable="@drawable/quickcontact_nobadge_normal" />
-    <item android:drawable="@drawable/quickcontact_nobadge_normal" />
-</selector>
diff --git a/core/res/res/drawable/quickcontact_nobadge_highlight.9.png b/core/res/res/drawable/quickcontact_nobadge_highlight.9.png
deleted file mode 100644
index f0f50b3..0000000
--- a/core/res/res/drawable/quickcontact_nobadge_highlight.9.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable/quickcontact_nobadge_normal.9.png b/core/res/res/drawable/quickcontact_nobadge_normal.9.png
deleted file mode 100644
index 01cc9dc..0000000
--- a/core/res/res/drawable/quickcontact_nobadge_normal.9.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable/quickcontact_nobadge_pressed.9.png b/core/res/res/drawable/quickcontact_nobadge_pressed.9.png
deleted file mode 100644
index 6e22c87..0000000
--- a/core/res/res/drawable/quickcontact_nobadge_pressed.9.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/layout/keyguard_screen_unlock_landscape.xml b/core/res/res/layout/keyguard_screen_unlock_landscape.xml
index c1b406f..83381a1 100644
--- a/core/res/res/layout/keyguard_screen_unlock_landscape.xml
+++ b/core/res/res/layout/keyguard_screen_unlock_landscape.xml
@@ -58,18 +58,19 @@
             android:ellipsize="marquee"
             android:gravity="right|bottom"
             />
+
         <com.android.internal.widget.DigitalClock android:id="@+id/time"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:layout_alignParentTop="true"
             android:layout_alignParentLeft="true"
             android:layout_marginTop="8dip"
+            android:layout_marginBottom="8dip"
             >
 
             <TextView android:id="@+id/timeDisplay"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
-                android:gravity="bottom"
                 android:singleLine="true"
                 android:ellipsize="none"
                 android:textSize="72sp"
@@ -84,8 +85,9 @@
 
             <TextView android:id="@+id/am_pm"
                 android:layout_width="wrap_content"
-                android:layout_height="match_parent"
-                android:gravity="bottom"
+                android:layout_height="wrap_content"
+                android:layout_toRightOf="@id/timeDisplay"
+                android:layout_alignBaseline="@id/timeDisplay"
                 android:singleLine="true"
                 android:ellipsize="none"
                 android:textSize="22sp"
diff --git a/core/res/res/layout/keyguard_screen_unlock_portrait.xml b/core/res/res/layout/keyguard_screen_unlock_portrait.xml
index 74a0eee..8dacfaf 100644
--- a/core/res/res/layout/keyguard_screen_unlock_portrait.xml
+++ b/core/res/res/layout/keyguard_screen_unlock_portrait.xml
@@ -55,12 +55,12 @@
             android:layout_alignParentTop="true"
             android:layout_marginTop="15dip"
             android:layout_marginLeft="20dip"
+            android:layout_marginBottom="8dip"
             >
 
             <TextView android:id="@+id/timeDisplay"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
-                android:gravity="bottom"
                 android:singleLine="true"
                 android:ellipsize="none"
                 android:textSize="56sp"
@@ -74,8 +74,9 @@
 
             <TextView android:id="@+id/am_pm"
                 android:layout_width="wrap_content"
-                android:layout_height="match_parent"
-                android:gravity="bottom"
+                android:layout_height="wrap_content"
+                android:layout_toRightOf="@id/timeDisplay"
+                android:layout_alignBaseline="@id/timeDisplay"
                 android:singleLine="true"
                 android:ellipsize="none"
                 android:textSize="18sp"
diff --git a/core/res/res/layout/preference_list_content.xml b/core/res/res/layout/preference_list_content.xml
index 8f86981..844d338 100644
--- a/core/res/res/layout/preference_list_content.xml
+++ b/core/res/res/layout/preference_list_content.xml
@@ -4,22 +4,58 @@
 **
 ** Copyright 2006, 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 
+** 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 
+**     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 
+** 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.
 */
 -->
-<ListView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@android:id/list"
-    android:layout_width="match_parent" 
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
     android:layout_height="match_parent"
-    android:drawSelectorOnTop="false"
-    android:scrollbarAlwaysDrawVerticalTrack="true"
+    android:layout_width="match_parent">
+
+    <ListView android:id="@android:id/list"
+        android:layout_width="fill_parent"
+        android:layout_height="fill_parent"
+        android:layout_weight="1"
+        android:drawSelectorOnTop="false"
+        android:scrollbarAlwaysDrawVerticalTrack="true"
     />
+
+    <RelativeLayout android:id="@+id/button_bar"
+        android:layout_height="wrap_content"
+        android:layout_width="fill_parent"
+        android:layout_weight="0"
+        android:background="@android:drawable/bottom_bar"
+        android:visibility="gone">
+
+        <Button android:id="@+id/back_button"
+            android:layout_width="150dip"
+            android:layout_height="wrap_content"
+            android:layout_margin="5dip"
+            android:layout_alignParentLeft="true"
+            android:drawableLeft="@drawable/ic_btn_back"
+            android:drawablePadding="3dip"
+            android:text="@string/back_button_label"
+        />
+
+        <Button android:id="@+id/next_button"
+            android:layout_width="150dip"
+            android:layout_height="wrap_content"
+            android:layout_margin="5dip"
+            android:layout_alignParentRight="true"
+            android:drawableRight="@drawable/ic_btn_next"
+            android:drawablePadding="3dip"
+            android:text="@string/next_button_label"
+        />
+    </RelativeLayout>
+</LinearLayout>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index a3ccaf7..fd00330 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -1518,6 +1518,8 @@
              will use only the number of items in the adapter and the number of items visible
              on screen to determine the scrollbar's properties. -->
         <attr name="smoothScrollbar" format="boolean" />
+        <!-- A reference to an XML description of the adapter to attach to the list. -->
+        <attr name="adapter" format="reference" />
     </declare-styleable>
     <declare-styleable name="AbsSpinner">
         <!-- Reference to an array resource that will populate the Spinner.  For static content,
@@ -2504,6 +2506,10 @@
         <attr name="drawable" />
     </declare-styleable>
 
+    <declare-styleable name="MipmapDrawableItem">
+        <attr name="drawable" />
+    </declare-styleable>
+
     <declare-styleable name="RotateDrawable">
         <attr name="visible" />
         <attr name="fromDegrees" format="float" />
@@ -3337,6 +3343,16 @@
         <attr name="entryValues" format="reference" />
     </declare-styleable>
 
+    <declare-styleable name="MultiSelectListPreference">
+        <!-- The human-readable array to present as a list. Each entry must have a corresponding
+             index in entryValues. -->
+        <attr name="entries" />
+        <!-- The array to find the value to save for a preference when an entry from
+             entries is selected. If a user clicks the second item in entries, the
+             second item in this array will be saved to the preference. -->
+        <attr name="entryValues" />
+    </declare-styleable>
+
     <!-- Base attributes available to RingtonePreference. -->
     <declare-styleable name="RingtonePreference">
         <!-- Which ringtone type(s) to show in the picker. -->
@@ -3614,6 +3630,9 @@
         <attr name="detailColumn" format="string" />
         <!-- Flag indicating that detail should be built from SocialProvider. -->
         <attr name="detailSocialSummary" format="boolean" />
+        <!-- Resource representing the term "All Contacts" (e.g. "All Friends" or
+        "All connections"). Optional (Default is "All Contacts"). -->
+        <attr name="allContactsName" format="string" />
     </declare-styleable>
 
     <!-- =============================== -->
@@ -3644,5 +3663,68 @@
     <declare-styleable name="RecognitionService">
         <attr name="settingsActivity" />
     </declare-styleable>
+    
+    <!-- =============================== -->
+    <!-- Adapters attributes             -->
+    <!-- =============================== -->
+    <eat-comment />
+    
+    <!-- Adapter used to bind cursors. -->
+    <declare-styleable name="CursorAdapter">
+        <!-- URI to get the cursor from. Optional. -->
+        <attr name="uri" format="string" />
+        <!-- Selection statement for the query. Optional. -->
+        <attr name="selection" format="string" />
+        <!-- Sort order statement for the query. Optional. -->
+        <attr name="sortOrder" format="string" />
+        <!-- Layout resource used to display each row from the cursor. Mandatory. -->
+        <attr name="layout" />
+    </declare-styleable>
+    
+    <!-- Attributes used in bind items for XML cursor adapters. -->
+    <declare-styleable name="CursorAdapter_BindItem">
+        <!-- The name of the column to bind from. Mandatory. -->
+        <attr name="from" format="string" />
+        <!-- The resource id of the view to bind to. Mandatory. -->
+        <attr name="to" format="reference" />
+        <!-- The type of binding. If this value is not specified, the type will be
+             inferred from the type of the "to" target view. Mandatory.
+             
+             The type can be one of:
+             <ul>
+             <li>string, The content of the column is interpreted as a string.</li>
+             <li>image, The content of the column is interpreted as a blob describing an image.</li>
+             <li>image-uri, The content of the column is interpreted as a URI to an image.</li>
+             <li>drawable, The content of the column is interpreted as a resource id to a drawable.</li>
+             <li>A fully qualified class name, corresponding to an implementation of
+                 android.widget.Adapters.CursorBinder.</li>
+             </ul>
+         -->
+        <attr name="as" format="string" />
+    </declare-styleable>
+    
+    <!-- Attributes used in select items for XML cursor adapters. -->
+    <declare-styleable name="CursorAdapter_SelectItem">
+        <!-- The name of the column to select. Mandatory. -->
+        <attr name="column" format="string" />
+    </declare-styleable>
+
+    <!-- Attributes used to map values to new values in XML cursor adapters' bind items. -->
+    <declare-styleable name="CursorAdapter_MapItem">
+        <!-- The original value from the column. Mandatory. -->
+        <attr name="fromValue" format="string" />
+        <!-- The new value from the column. Mandatory. -->
+        <attr name="toValue" format="string" />
+    </declare-styleable>
+    
+    <!-- Attributes used to map values to new values in XML cursor adapters' bind items. -->
+    <declare-styleable name="CursorAdapter_TransformItem">
+        <!-- The transformation expression. Mandatory if "withClass" is not specified. -->
+        <attr name="withExpression" format="string" />
+        <!-- The transformation class, an implementation of
+             android.widget.Adapters.CursorTransformation. Mandatory if "withExpression"
+             is not specified. -->
+        <attr name="withClass" format="string" />
+    </declare-styleable>
 
 </resources>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 5d18e9e..fe27174 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -1238,7 +1238,7 @@
   <public type="id" name="custom" id="0x0102002b" />
     
   <public type="anim" name="cycle_interpolator" id="0x010a000c" />
-
+    
 <!-- ===============================================================
      Resources introduced in kraken.
      =============================================================== -->
@@ -1258,4 +1258,22 @@
   <public-padding type="color" name="kraken_resource_pad" end="0x01060020" />
   <public-padding type="array" name="kraken_resource_pad" end="0x01070010" />
 
+<!-- ===============================================================
+     Resources proposed for Gingerbread.
+     =============================================================== -->
+  <eat-comment />
+  <public type="attr" name="adapter" />
+  <public type="attr" name="selection" />
+  <public type="attr" name="sortOrder" />
+  <public type="attr" name="uri" />
+  <public type="attr" name="from" />
+  <public type="attr" name="to" />
+  <public type="attr" name="as" />
+  <public type="attr" name="fromValue" />
+  <public type="attr" name="toValue" />
+  <public type="attr" name="column" />
+  <public type="attr" name="withExpression" />
+  <public type="attr" name="withClass" />
+  <public type="attr" name="allContactsName" />
+
 </resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index a76c70c..9accda8 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1615,7 +1615,7 @@
 
     <!-- Do not translate.  WebView User Agent string -->
     <string name="web_user_agent" translatable="false"><xliff:g id="x">Mozilla/5.0 (Linux; U; Android %s)
-        AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1</xliff:g></string>
+        AppleWebKit/533.6 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.6</xliff:g></string>
 
     <!-- Title for a JavaScript dialog. "The page at <url of current page> says:" -->
     <string name="js_dialog_title">The page at \'<xliff:g id="title">%s</xliff:g>\' says:</string>
@@ -2260,6 +2260,10 @@
     <string name="tethered_notification_title">Tethering active</string>
     <string name="tethered_notification_message">Touch to configure</string>
 
+    <!--  Strings for possible PreferenceActivity Back/Next buttons -->
+    <string name="back_button_label">Back</string>
+    <string name="next_button_label">Next</string>
+
     <!-- Strings for throttling notification -->
     <!-- Shown when the user is in danger of being throttled -->
     <string name="throttle_warning_notification_title">High mobile data use</string>
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index b5fff96..f9b0667 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -552,6 +552,7 @@
         <item name="android:background">@android:drawable/quickcontact_badge</item>
         <item name="android:clickable">true</item>
         <item name="android:scaleType">fitCenter</item>
+        <item name="android:src">@android:drawable/ic_contact_picture</item>
     </style>
     
     <style name="Widget.QuickContactBadgeSmall">
diff --git a/core/tests/coretests/src/android/accessibilityservice/AccessibilityTestService.java b/core/tests/coretests/src/android/accessibilityservice/AccessibilityTestService.java
deleted file mode 100644
index 2a51eea..0000000
--- a/core/tests/coretests/src/android/accessibilityservice/AccessibilityTestService.java
+++ /dev/null
@@ -1,167 +0,0 @@
-/*
- * Copyright (C) 2009 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.accessibilityservice;
-
-import android.accessibilityservice.AccessibilityService;
-import android.accessibilityservice.AccessibilityServiceInfo;
-import android.app.Notification;
-import android.util.Log;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityManager;
-
-import java.util.Timer;
-import java.util.TimerTask;
-
-/**
- * This class text the accessibility framework end to end.
- * <p>
- * Note: Since accessibility is provided by {@link AccessibilityService}s we create one,
- * and it generates an event and an interruption dispatching them through the
- * {@link AccessibilityManager}. We verify the received result. To trigger the test
- * go to Settings->Accessibility and select the enable accessibility check and then
- * select the check for this service (same name as the class).
- */
-public class AccessibilityTestService extends AccessibilityService {
-
-    private static final String LOG_TAG = "AccessibilityTestService";
-
-    private static final String CLASS_NAME = "foo.bar.baz.Test";
-    private static final String PACKAGE_NAME = "foo.bar.baz";
-    private static final String TEXT = "Some stuff";
-    private static final String BEFORE_TEXT = "Some other stuff";
-
-    private static final String CONTENT_DESCRIPTION = "Content description";
-
-    private static final int ITEM_COUNT = 10;
-    private static final int CURRENT_ITEM_INDEX = 1;
-    private static final int INTERRUPT_INVOCATION_TYPE = 0x00000200;
-
-    private static final int FROM_INDEX = 1;
-    private static final int ADDED_COUNT = 2;
-    private static final int REMOVED_COUNT = 1;
-
-    private static final int NOTIFICATION_TIMEOUT_MILLIS = 80;
-
-    private int mReceivedResult;
-
-    private Timer mTimer = new Timer();
-
-    @Override
-    public void onServiceConnected() {
-        AccessibilityServiceInfo info = new AccessibilityServiceInfo();
-        info.eventTypes = AccessibilityEvent.TYPES_ALL_MASK;
-        info.feedbackType = AccessibilityServiceInfo.FEEDBACK_AUDIBLE;
-        info.notificationTimeout = NOTIFICATION_TIMEOUT_MILLIS;
-        info.flags &= AccessibilityServiceInfo.DEFAULT;
-        setServiceInfo(info);
-
-        // we need to wait until the system picks our configuration
-        // otherwise it will not notify us
-        mTimer.schedule(new TimerTask() {
-            @Override
-            public void run() {
-                try {
-                    testAccessibilityEventDispatching();
-                    testInterrupt();
-                } catch (Exception e) {
-                    Log.e(LOG_TAG, "Error in testing Accessibility feature", e);
-                }
-            }
-        }, 1000);
-    }
-
-    /**
-     * Check here if the event we received is actually the one we sent.
-     */
-    @Override
-    public void onAccessibilityEvent(AccessibilityEvent event) {
-        assert(AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED == event.getEventType());
-        assert(event != null);
-        assert(event.getEventTime() > 0);
-        assert(CLASS_NAME.equals(event.getClassName()));
-        assert(PACKAGE_NAME.equals(event.getPackageName()));
-        assert(1 == event.getText().size());
-        assert(TEXT.equals(event.getText().get(0)));
-        assert(BEFORE_TEXT.equals(event.getBeforeText()));
-        assert(event.isChecked());
-        assert(CONTENT_DESCRIPTION.equals(event.getContentDescription()));
-        assert(ITEM_COUNT == event.getItemCount());
-        assert(CURRENT_ITEM_INDEX == event.getCurrentItemIndex());
-        assert(event.isEnabled());
-        assert(event.isPassword());
-        assert(FROM_INDEX == event.getFromIndex());
-        assert(ADDED_COUNT == event.getAddedCount());
-        assert(REMOVED_COUNT == event.getRemovedCount());
-        assert(event.getParcelableData() != null);
-        assert(1 == ((Notification) event.getParcelableData()).icon);
-
-        // set the type of the receved request
-        mReceivedResult = AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED;
-    }
-
-    /**
-     * Set a flag that we received the interrupt request.
-     */
-    @Override
-    public void onInterrupt() {
-
-        // set the type of the receved request
-        mReceivedResult = INTERRUPT_INVOCATION_TYPE;
-    }
-
-    /**
-     * If an {@link AccessibilityEvent} is sent and received correctly.
-     */
-   public void testAccessibilityEventDispatching() throws Exception {
-       AccessibilityEvent event =
-           AccessibilityEvent.obtain(AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED);
-
-       assert(event != null);
-       event.setClassName(CLASS_NAME);
-       event.setPackageName(PACKAGE_NAME);
-       event.getText().add(TEXT);
-       event.setBeforeText(BEFORE_TEXT);
-       event.setChecked(true);
-       event.setContentDescription(CONTENT_DESCRIPTION);
-       event.setItemCount(ITEM_COUNT);
-       event.setCurrentItemIndex(CURRENT_ITEM_INDEX);
-       event.setEnabled(true);
-       event.setPassword(true);
-       event.setFromIndex(FROM_INDEX);
-       event.setAddedCount(ADDED_COUNT);
-       event.setRemovedCount(REMOVED_COUNT);
-       event.setParcelableData(new Notification(1, "Foo", 1234));
-
-       AccessibilityManager.getInstance(this).sendAccessibilityEvent(event);
-
-       assert(mReceivedResult == event.getEventType());
-
-       Log.i(LOG_TAG, "AccessibilityTestService#testAccessibilityEventDispatching: Success");
-   }
-
-   /**
-    * If accessibility feedback interruption is triggered and received correctly.
-    */
-   public void testInterrupt() throws Exception {
-       AccessibilityManager.getInstance(this).interrupt();
-
-       assert(INTERRUPT_INVOCATION_TYPE == mReceivedResult);
-
-       Log.i(LOG_TAG, "AccessibilityTestService#testInterrupt: Success");
-   }
-}
-
diff --git a/core/tests/coretests/src/android/database/DatabaseGeneralTest.java b/core/tests/coretests/src/android/database/DatabaseGeneralTest.java
index 656029d..34434d2 100644
--- a/core/tests/coretests/src/android/database/DatabaseGeneralTest.java
+++ b/core/tests/coretests/src/android/database/DatabaseGeneralTest.java
@@ -33,6 +33,7 @@
 import junit.framework.Assert;
 
 import java.io.File;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Locale;
 
@@ -1023,6 +1024,34 @@
         }
     }
 
+    @MediumTest
+    public void testUnionsWithBindArgs() {
+        /* make sure unions with bindargs work http://b/issue?id=1061291 */
+        mDatabase.execSQL("CREATE TABLE A (i int);");
+        mDatabase.execSQL("create table B (k int);");
+        mDatabase.execSQL("create table C (n int);");
+        mDatabase.execSQL("insert into A values(1);");
+        mDatabase.execSQL("insert into A values(2);");
+        mDatabase.execSQL("insert into A values(3);");
+        mDatabase.execSQL("insert into B values(201);");
+        mDatabase.execSQL("insert into B values(202);");
+        mDatabase.execSQL("insert into B values(203);");
+        mDatabase.execSQL("insert into C values(901);");
+        mDatabase.execSQL("insert into C values(902);");
+        String s = "select i from A where i > 2 " +
+                "UNION select k from B where k > 201 " +
+                "UNION select n from C where n !=900;";
+        Cursor c = mDatabase.rawQuery(s, null);
+        int n = c.getCount();
+        c.close();
+        String s1 = "select i from A where i > ? " +
+                "UNION select k from B where k > ? " +
+                "UNION select n from C where n != ?;";
+        Cursor c1 = mDatabase.rawQuery(s1, new String[]{"2", "201", "900"});
+        assertEquals(n, c1.getCount());
+        c1.close();
+    }
+
     /**
      * This test is available only when the platform has a locale with the language "ja".
      * It finishes without failure when it is not available.  
@@ -1108,5 +1137,44 @@
                 }
             }
         }
-    }    
+    }
+
+    @SmallTest
+    public void testLruCachingOfSqliteCompiledSqlObjs() {
+        mDatabase.execSQL("CREATE TABLE test (i int, j int);");
+        mDatabase.execSQL("insert into test values(1,1);");
+        // set cache size
+        int N = SQLiteDatabase.MAX_SQL_CACHE_SIZE;
+        mDatabase.setMaxSqlCacheSize(N);
+
+        // do N+1 queries - and when the 0th entry is removed from LRU cache due to the
+        // insertion of (N+1)th entry, make sure 0th entry is closed
+        ArrayList<SQLiteStatement> stmtObjs = new ArrayList<SQLiteStatement>();
+        for (int i = 0; i < N+1; i++) {
+            SQLiteStatement c = mDatabase.compileStatement("select * from test where i = " + i);
+            c.close();
+            stmtObjs.add(i, c);
+        }
+
+        assertEquals(0, stmtObjs.get(0).getUniqueId());
+        for (int i = 1; i < N+1; i++) {
+            assertTrue(stmtObjs.get(i).getUniqueId() > 0);
+        }
+    }
+
+    @SmallTest
+    public void testSetMaxCahesize() {
+        mDatabase.execSQL("CREATE TABLE test (i int, j int);");
+        mDatabase.execSQL("insert into test values(1,1);");
+        // set cache size
+        int N = SQLiteDatabase.MAX_SQL_CACHE_SIZE;
+        mDatabase.setMaxSqlCacheSize(N);
+
+        // try reduce cachesize
+        try {
+            mDatabase.setMaxSqlCacheSize(1);
+        } catch (IllegalStateException e) {
+            assertTrue(e.getMessage().contains("cannot set cacheSize to a value less than"));
+        }
+    }
 }
diff --git a/core/tests/coretests/src/android/pim/vcard/VCardExporterTests.java b/core/tests/coretests/src/android/pim/vcard/VCardExporterTests.java
index 004a197..0820a68 100644
--- a/core/tests/coretests/src/android/pim/vcard/VCardExporterTests.java
+++ b/core/tests/coretests/src/android/pim/vcard/VCardExporterTests.java
@@ -17,7 +17,9 @@
 package android.pim.vcard;
 
 import android.content.ContentValues;
-import android.pim.vcard.VCardConfig;
+import android.pim.vcard.test_utils.ContactEntry;
+import android.pim.vcard.test_utils.PropertyNodesVerifierElem;
+import android.pim.vcard.test_utils.PropertyNodesVerifierElem.TypeSet;
 import android.provider.ContactsContract.CommonDataKinds.Email;
 import android.provider.ContactsContract.CommonDataKinds.Event;
 import android.provider.ContactsContract.CommonDataKinds.Im;
@@ -31,8 +33,6 @@
 import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
 import android.provider.ContactsContract.CommonDataKinds.Website;
 
-import android.pim.vcard.PropertyNodesVerifierElem.TypeSet;
-
 import java.util.Arrays;
 
 /**
@@ -275,6 +275,16 @@
         testPhoneBasicCommon(V30);
     }
 
+    public void testPhoneRefrainFormatting() {
+        mVerifier.initForExportTest(V21 | VCardConfig.FLAG_REFRAIN_PHONE_NUMBER_FORMATTING);
+        mVerifier.addInputEntry().addContentValues(Phone.CONTENT_ITEM_TYPE)
+                .put(Phone.NUMBER, "1234567890(abcdefghijklmnopqrstuvwxyz)")
+                .put(Phone.TYPE, Phone.TYPE_HOME);
+        mVerifier.addPropertyNodesVerifierElemWithEmptyName()
+                .addExpectedNode("TEL", "1234567890(abcdefghijklmnopqrstuvwxyz)",
+                        new TypeSet("HOME"));
+    }
+
     /**
      * Tests that vCard composer emits corresponding type param which we expect.
      */
diff --git a/core/tests/coretests/src/android/pim/vcard/VCardImporterTests.java b/core/tests/coretests/src/android/pim/vcard/VCardImporterTests.java
index 21f2254..ea2ac6a 100644
--- a/core/tests/coretests/src/android/pim/vcard/VCardImporterTests.java
+++ b/core/tests/coretests/src/android/pim/vcard/VCardImporterTests.java
@@ -16,7 +16,9 @@
 package android.pim.vcard;
 
 import android.content.ContentValues;
-import android.pim.vcard.VCardConfig;
+import android.pim.vcard.test_utils.ContentValuesVerifier;
+import android.pim.vcard.test_utils.ContentValuesVerifierElem;
+import android.pim.vcard.test_utils.PropertyNodesVerifierElem.TypeSet;
 import android.provider.ContactsContract.Data;
 import android.provider.ContactsContract.CommonDataKinds.Email;
 import android.provider.ContactsContract.CommonDataKinds.Event;
@@ -29,7 +31,6 @@
 import android.provider.ContactsContract.CommonDataKinds.Website;
 
 import com.android.frameworks.coretests.R;
-import android.pim.vcard.PropertyNodesVerifierElem.TypeSet;
 
 import java.util.Arrays;
 
@@ -410,7 +411,7 @@
     }
 
     public void testV21SimpleCase1_Type_Generic() {
-        mVerifier.initForImportTest(VCardConfig.VCARD_TYPE_V21_GENERIC_UTF8, R.raw.v21_simple_1);
+        mVerifier.initForImportTest(VCardConfig.VCARD_TYPE_V21_GENERIC, R.raw.v21_simple_1);
         mVerifier.addContentValuesVerifierElem()
                 .addExpected(StructuredName.CONTENT_ITEM_TYPE)
                         .put(StructuredName.FAMILY_NAME, "Ando")
@@ -419,7 +420,7 @@
     }
 
     public void testV21SimpleCase1_Type_Japanese() {
-        mVerifier.initForImportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE_SJIS, R.raw.v21_simple_1);
+        mVerifier.initForImportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE, R.raw.v21_simple_1);
         mVerifier.addContentValuesVerifierElem()
                 .addExpected(StructuredName.CONTENT_ITEM_TYPE)
                         .put(StructuredName.FAMILY_NAME, "Ando")
@@ -431,7 +432,7 @@
     }
 
     public void testV21SimpleCase2() {
-        mVerifier.initForImportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE_SJIS, R.raw.v21_simple_2);
+        mVerifier.initForImportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE, R.raw.v21_simple_2);
         mVerifier.addContentValuesVerifierElem()
                 .addExpected(StructuredName.CONTENT_ITEM_TYPE)
                         .put(StructuredName.DISPLAY_NAME, "Ando Roid");
@@ -717,8 +718,7 @@
         // Though Japanese careers append ";;;;" at the end of the value of "SOUND",
         // vCard 2.1/3.0 specification does not allow multiple values.
         // Do not need to handle it as multiple values.
-        mVerifier.initForImportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE_SJIS,
-                R.raw.v21_japanese_1);
+        mVerifier.initForImportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE, R.raw.v21_japanese_1);
         mVerifier.addPropertyNodesVerifierElem()
                 .addExpectedNodeWithOrder("VERSION", "2.1", null, null, null, null, null)
                 .addExpectedNodeWithOrder("N", "\u5B89\u85E4\u30ED\u30A4\u30C9;;;;",
@@ -752,35 +752,34 @@
 
     /**
      * Verifies vCard with Japanese can be parsed correctly with
-     * {@link android.pim.vcard.VCardConfig#VCARD_TYPE_V21_GENERIC_UTF8}.
+     * {@link android.pim.vcard.VCardConfig#VCARD_TYPE_V21_GENERIC}.
      */
     public void testV21Japanese1_Type_Generic_Utf8() {
         testV21Japanese1Common(
-                R.raw.v21_japanese_1, VCardConfig.VCARD_TYPE_V21_GENERIC_UTF8, false);
+                R.raw.v21_japanese_1, VCardConfig.VCARD_TYPE_V21_GENERIC, false);
     }
 
     /**
      * Verifies vCard with Japanese can be parsed correctly with
-     * {@link android.pim.vcard.VCardConfig#VCARD_TYPE_V21_JAPANESE_SJIS}.
+     * {@link android.pim.vcard.VCardConfig#VCARD_TYPE_V21_JAPANESE}.
      */
     public void testV21Japanese1_Type_Japanese_Sjis() {
         testV21Japanese1Common(
-                R.raw.v21_japanese_1, VCardConfig.VCARD_TYPE_V21_JAPANESE_SJIS, true);
+                R.raw.v21_japanese_1, VCardConfig.VCARD_TYPE_V21_JAPANESE, true);
     }
 
     /**
      * Verifies vCard with Japanese can be parsed correctly with
-     * {@link android.pim.vcard.VCardConfig#VCARD_TYPE_V21_JAPANESE_UTF8}.
+     * {@link android.pim.vcard.VCardConfig#VCARD_TYPE_V21_JAPANESE}.
      * since vCard 2.1 specifies the charset of each line if it contains non-Ascii.
      */
     public void testV21Japanese1_Type_Japanese_Utf8() {
         testV21Japanese1Common(
-                R.raw.v21_japanese_1, VCardConfig.VCARD_TYPE_V21_JAPANESE_UTF8, true);
+                R.raw.v21_japanese_1, VCardConfig.VCARD_TYPE_V21_JAPANESE, true);
     }
 
     public void testV21Japanese2_Parsing() {
-        mVerifier.initForImportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE_SJIS,
-                R.raw.v21_japanese_2);
+        mVerifier.initForImportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE, R.raw.v21_japanese_2);
         mVerifier.addPropertyNodesVerifierElem()
                 .addExpectedNodeWithOrder("VERSION", "2.1")
                 .addExpectedNodeWithOrder("N", "\u5B89\u85E4;\u30ED\u30A4\u30C9\u0031;;;",
@@ -838,8 +837,7 @@
     }
 
     public void testV21MultipleEntryCase_Parse() {
-        mVerifier.initForImportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE_SJIS,
-                R.raw.v21_multiple_entry);
+        mVerifier.initForImportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE, R.raw.v21_multiple_entry);
         mVerifier.addPropertyNodesVerifierElem()
                 .addExpectedNodeWithOrder("VERSION", "2.1")
                 .addExpectedNodeWithOrder("N", "\u5B89\u85E4\u30ED\u30A4\u30C9\u0033;;;;",
@@ -882,8 +880,7 @@
     }
 
     public void testV21MultipleEntryCase() {
-        mVerifier.initForImportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE_SJIS,
-                R.raw.v21_multiple_entry);
+        mVerifier.initForImportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE, R.raw.v21_multiple_entry);
         ContentValuesVerifierElem elem = mVerifier.addContentValuesVerifierElem();
         elem.addExpected(StructuredName.CONTENT_ITEM_TYPE)
                 .put(StructuredName.FAMILY_NAME, "\u5B89\u85E4\u30ED\u30A4\u30C9\u0033")
diff --git a/core/tests/coretests/src/android/pim/vcard/VCardJapanizationTests.java b/core/tests/coretests/src/android/pim/vcard/VCardJapanizationTests.java
index 5b60342..17ee322 100644
--- a/core/tests/coretests/src/android/pim/vcard/VCardJapanizationTests.java
+++ b/core/tests/coretests/src/android/pim/vcard/VCardJapanizationTests.java
@@ -17,15 +17,16 @@
 package android.pim.vcard;
 
 import android.content.ContentValues;
-import android.pim.vcard.VCardConfig;
+import android.pim.vcard.test_utils.ContactEntry;
+import android.pim.vcard.test_utils.ContentValuesBuilder;
+import android.pim.vcard.test_utils.PropertyNodesVerifierElem;
+import android.pim.vcard.test_utils.PropertyNodesVerifierElem.TypeSet;
 import android.provider.ContactsContract.CommonDataKinds.Nickname;
 import android.provider.ContactsContract.CommonDataKinds.Note;
 import android.provider.ContactsContract.CommonDataKinds.Phone;
 import android.provider.ContactsContract.CommonDataKinds.StructuredName;
 import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
 
-import android.pim.vcard.PropertyNodesVerifierElem.TypeSet;
-
 import java.util.Arrays;
 
 public class VCardJapanizationTests extends VCardTestsBase {
@@ -50,15 +51,15 @@
     }
 
     public void testNameUtf8V21() {
-        testNameUtf8Common(VCardConfig.VCARD_TYPE_V21_JAPANESE_UTF8);
+        testNameUtf8Common(VCardConfig.VCARD_TYPE_V21_JAPANESE);
     }
 
     public void testNameUtf8V30() {
-        testNameUtf8Common(VCardConfig.VCARD_TYPE_V30_JAPANESE_UTF8);
+        testNameUtf8Common(VCardConfig.VCARD_TYPE_V30_JAPANESE);
     }
 
     public void testNameShiftJis() {
-        mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_V30_JAPANESE_SJIS);
+        mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_V30_JAPANESE, "Shift_JIS");
         ContactEntry entry = mVerifier.addInputEntry();
         entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
                 .put(StructuredName.FAMILY_NAME, "\u3075\u308B\u3069")
@@ -80,7 +81,7 @@
      * DoCoMo phones require all name elements should be in "family name" field.
      */
     public void testNameDoCoMo() {
-        mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO);
+        mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO, "Shift_JIS");
         ContactEntry entry = mVerifier.addInputEntry();
         entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
                 .put(StructuredName.FAMILY_NAME, "\u3075\u308B\u3069")
@@ -105,8 +106,8 @@
                 .addExpectedNode("X-DCM-HMN-MODE", "");
     }
 
-    private void testPhoneticNameCommon(int vcardType) {
-        mVerifier.initForExportTest(vcardType);
+    private void testPhoneticNameCommon(int vcardType, String charset) {
+        mVerifier.initForExportTest(vcardType, charset);
         ContactEntry entry = mVerifier.addInputEntry();
         entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
                 .put(StructuredName.PHONETIC_FAMILY_NAME, "\u3084\u307E\u3060")
@@ -114,7 +115,7 @@
                 .put(StructuredName.PHONETIC_GIVEN_NAME, "\u305F\u308D\u3046");
 
         final ContentValues contentValues =
-            (VCardConfig.usesShiftJis(vcardType) ?
+            ("SHIFT_JIS".equalsIgnoreCase(charset) ?
                     (VCardConfig.isV30(vcardType) ? mContentValuesForSJis :
                             mContentValuesForQPAndSJis) :
                     (VCardConfig.isV30(vcardType) ? null : mContentValuesForQPAndUtf8));
@@ -142,23 +143,23 @@
     }
 
     public void testPhoneticNameForJapaneseV21Utf8() {
-        testPhoneticNameCommon(VCardConfig.VCARD_TYPE_V21_JAPANESE_UTF8);
+        testPhoneticNameCommon(VCardConfig.VCARD_TYPE_V21_JAPANESE, null);
     }
 
     public void testPhoneticNameForJapaneseV21Sjis() {
-        testPhoneticNameCommon(VCardConfig.VCARD_TYPE_V21_JAPANESE_SJIS);
+        testPhoneticNameCommon(VCardConfig.VCARD_TYPE_V21_JAPANESE, "Shift_JIS");
     }
 
     public void testPhoneticNameForJapaneseV30Utf8() {
-        testPhoneticNameCommon(VCardConfig.VCARD_TYPE_V30_JAPANESE_UTF8);
+        testPhoneticNameCommon(VCardConfig.VCARD_TYPE_V30_JAPANESE, null);
     }
 
     public void testPhoneticNameForJapaneseV30SJis() {
-        testPhoneticNameCommon(VCardConfig.VCARD_TYPE_V30_JAPANESE_SJIS);
+        testPhoneticNameCommon(VCardConfig.VCARD_TYPE_V30_JAPANESE, "Shift_JIS");
     }
 
     public void testPhoneticNameForMobileV21_1() {
-        mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE_MOBILE);
+        mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE_MOBILE, "Shift_JIS");
         ContactEntry entry = mVerifier.addInputEntry();
         entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
                 .put(StructuredName.PHONETIC_FAMILY_NAME, "\u3084\u307E\u3060")
@@ -182,7 +183,7 @@
     }
 
     public void testPhoneticNameForMobileV21_2() {
-        mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE_MOBILE);
+        mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE_MOBILE, "Shift_JIS");
         ContactEntry entry = mVerifier.addInputEntry();
         entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
                 .put(StructuredName.PHONETIC_FAMILY_NAME, "\u3084\u307E\u3060")
@@ -198,8 +199,8 @@
                 .put(StructuredName.DISPLAY_NAME, "\uFF94\uFF8F\uFF80\uFF9E \uFF80\uFF9B\uFF73");
     }
 
-    private void testPostalAddressWithJapaneseCommon(int vcardType) {
-        mVerifier.initForExportTest(vcardType);
+    private void testPostalAddressWithJapaneseCommon(int vcardType, String charset) {
+        mVerifier.initForExportTest(vcardType, charset);
         ContactEntry entry = mVerifier.addInputEntry();
         entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
                 .put(StructuredPostal.POBOX, "\u79C1\u66F8\u7BB107")
@@ -214,7 +215,7 @@
                 .put(StructuredPostal.TYPE, StructuredPostal.TYPE_CUSTOM)
                 .put(StructuredPostal.LABEL, "\u304A\u3082\u3061\u304B\u3048\u308A");
 
-        ContentValues contentValues = (VCardConfig.usesShiftJis(vcardType) ?
+        ContentValues contentValues = ("UTF-8".equalsIgnoreCase(charset) ?
                 (VCardConfig.isV30(vcardType) ? mContentValuesForSJis :
                     mContentValuesForQPAndSJis) :
                 (VCardConfig.isV30(vcardType) ? mContentValuesForUtf8 :
@@ -240,7 +241,7 @@
                 .put(StructuredPostal.TYPE, StructuredPostal.TYPE_HOME);
     }
     public void testPostalAddresswithJapaneseV21() {
-        testPostalAddressWithJapaneseCommon(VCardConfig.VCARD_TYPE_V21_JAPANESE_SJIS);
+        testPostalAddressWithJapaneseCommon(VCardConfig.VCARD_TYPE_V21_JAPANESE, "Shift_JIS");
     }
 
     /**
@@ -248,7 +249,7 @@
      * Prefered type must (should?) be: HOME > WORK > OTHER > CUSTOM
      */
     public void testPostalAdrressForDoCoMo_1() {
-        mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO);
+        mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO, "Shift_JIS");
         ContactEntry entry = mVerifier.addInputEntry();
         entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
                 .put(StructuredPostal.TYPE, StructuredPostal.TYPE_WORK)
@@ -276,7 +277,7 @@
     }
 
     public void testPostalAdrressForDoCoMo_2() {
-        mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO);
+        mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO, "Shift_JIS");
         ContactEntry entry = mVerifier.addInputEntry();
         entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
                 .put(StructuredPostal.TYPE, StructuredPostal.TYPE_OTHER)
@@ -301,7 +302,7 @@
     }
 
     public void testPostalAdrressForDoCoMo_3() {
-        mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO);
+        mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO, "Shift_JIS");
         ContactEntry entry = mVerifier.addInputEntry();
         entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
                 .put(StructuredPostal.TYPE, StructuredPostal.TYPE_CUSTOM)
@@ -329,7 +330,7 @@
      * Verifies the vCard exporter tolerates null TYPE.
      */
     public void testPostalAdrressForDoCoMo_4() {
-        mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO);
+        mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO, "Shift_JIS");
         ContactEntry entry = mVerifier.addInputEntry();
         entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
                 .put(StructuredPostal.POBOX, "1");
@@ -371,15 +372,15 @@
     }
 
     public void testJapanesePhoneNumberV21_1() {
-        testJapanesePhoneNumberCommon(VCardConfig.VCARD_TYPE_V21_JAPANESE_UTF8);
+        testJapanesePhoneNumberCommon(VCardConfig.VCARD_TYPE_V21_JAPANESE);
     }
 
     public void testJapanesePhoneNumberV30() {
-        testJapanesePhoneNumberCommon(VCardConfig.VCARD_TYPE_V30_JAPANESE_UTF8);
+        testJapanesePhoneNumberCommon(VCardConfig.VCARD_TYPE_V30_JAPANESE);
     }
 
     public void testJapanesePhoneNumberDoCoMo() {
-        mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO);
+        mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO, "Shift_JIS");
         ContactEntry entry = mVerifier.addInputEntry();
         entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
                 .put(Phone.NUMBER, "0312341234")
@@ -399,7 +400,7 @@
     }
 
     public void testNoteDoCoMo() {
-        mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO);
+        mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO, "Shift_JIS");
         ContactEntry entry = mVerifier.addInputEntry();
         entry.addContentValues(Note.CONTENT_ITEM_TYPE)
                 .put(Note.NOTE, "note1");
@@ -421,7 +422,7 @@
     }
 
     public void testAndroidCustomV21() {
-        mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_V21_GENERIC_UTF8);
+        mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_V21_GENERIC);
         mVerifier.addInputEntry().addContentValues(Nickname.CONTENT_ITEM_TYPE)
                 .put(Nickname.NAME, "\u304D\u3083\u30FC\u30A8\u30C3\u30C1\u30FC");
         mVerifier.addPropertyNodesVerifierElemWithEmptyName()
diff --git a/core/tests/coretests/src/android/pim/vcard/VCardTestsBase.java b/core/tests/coretests/src/android/pim/vcard/VCardTestsBase.java
index 0857e0c..383a9af 100644
--- a/core/tests/coretests/src/android/pim/vcard/VCardTestsBase.java
+++ b/core/tests/coretests/src/android/pim/vcard/VCardTestsBase.java
@@ -16,103 +16,17 @@
 
 package android.pim.vcard;
 
-import android.content.ContentProvider;
-import android.content.ContentProviderOperation;
-import android.content.ContentProviderResult;
 import android.content.ContentValues;
-import android.content.EntityIterator;
-import android.content.res.AssetFileDescriptor;
-import android.database.Cursor;
-import android.database.CursorWindow;
-import android.database.IBulkCursor;
-import android.database.IContentObserver;
-import android.net.Uri;
-import android.os.IBinder;
-import android.os.ParcelFileDescriptor;
-import android.os.RemoteException;
-import android.pim.vcard.VCardConfig;
+import android.pim.vcard.test_utils.VCardVerifier;
 import android.test.AndroidTestCase;
-import android.util.Log;
-
-import java.util.ArrayList;
-
-/**
- * Almost a dead copy of android.test.mock.MockContentProvider, but different in that this
- * class extends ContentProvider, not implementing IContentProvider,
- * so that MockContentResolver is able to accept this class :(
- */
-class MockContentProvider extends ContentProvider {
-    @Override
-    public boolean onCreate() {
-        return true;
-    }
-
-    @Override
-    public int bulkInsert(Uri url, ContentValues[] initialValues) {
-        throw new UnsupportedOperationException("unimplemented mock method");
-    }
-
-    @SuppressWarnings("unused")
-    public IBulkCursor bulkQuery(Uri url, String[] projection, String selection,
-            String[] selectionArgs, String sortOrder, IContentObserver observer,
-            CursorWindow window) throws RemoteException {
-        throw new UnsupportedOperationException("unimplemented mock method");
-    }
-
-    @Override
-    @SuppressWarnings("unused")
-    public int delete(Uri url, String selection, String[] selectionArgs) {
-        throw new UnsupportedOperationException("unimplemented mock method");
-    }
-
-    @Override
-    public String getType(Uri url) {
-        throw new UnsupportedOperationException("unimplemented mock method");
-    }
-
-    @Override
-    public Uri insert(Uri url, ContentValues initialValues) {
-        throw new UnsupportedOperationException("unimplemented mock method");
-    }
-
-    @Override
-    public ParcelFileDescriptor openFile(Uri url, String mode) {
-        throw new UnsupportedOperationException("unimplemented mock method");
-    }
-
-    @Override
-    public AssetFileDescriptor openAssetFile(Uri uri, String mode) {
-        throw new UnsupportedOperationException("unimplemented mock method");
-    }
-
-    @Override
-    public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations) {
-        throw new UnsupportedOperationException("unimplemented mock method");
-    }
-
-    @Override
-    public Cursor query(Uri url, String[] projection, String selection, String[] selectionArgs,
-            String sortOrder) {
-        throw new UnsupportedOperationException("unimplemented mock method");
-    }
-
-    @Override
-    public int update(Uri url, ContentValues values, String selection, String[] selectionArgs) {
-        throw new UnsupportedOperationException("unimplemented mock method");
-    }
-
-    public IBinder asBinder() {
-        throw new UnsupportedOperationException("unimplemented mock method");
-    }
-}
 
 /**
  * BaseClass for vCard unit tests with utility classes.
  * Please do not add each unit test here.
  */
 /* package */ class VCardTestsBase extends AndroidTestCase {
-    public static final int V21 = VCardConfig.VCARD_TYPE_V21_GENERIC_UTF8;
-    public static final int V30 = VCardConfig.VCARD_TYPE_V30_GENERIC_UTF8;
+    public static final int V21 = VCardConfig.VCARD_TYPE_V21_GENERIC;
+    public static final int V30 = VCardConfig.VCARD_TYPE_V30_GENERIC;
 
     // Do not modify these during tests.
     protected final ContentValues mContentValuesForQP;
@@ -128,6 +42,7 @@
 
     public VCardTestsBase() {
         super();
+        // Not using constants in vCard code since it may be wrong.
         mContentValuesForQP = new ContentValues();
         mContentValuesForQP.put("ENCODING", "QUOTED-PRINTABLE");
         mContentValuesForSJis = new ContentValues();
diff --git a/core/tests/coretests/src/android/pim/vcard/test_utils/ContactEntry.java b/core/tests/coretests/src/android/pim/vcard/test_utils/ContactEntry.java
new file mode 100644
index 0000000..843750e
--- /dev/null
+++ b/core/tests/coretests/src/android/pim/vcard/test_utils/ContactEntry.java
@@ -0,0 +1,43 @@
+/*
+ * 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.pim.vcard.test_utils;
+
+import android.content.ContentValues;
+import android.provider.ContactsContract.Data;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * <p>
+ * The class representing one contact, which should contain multiple ContentValues like
+ * StructuredName, Email, etc.
+ * </p>
+ */
+public final class ContactEntry {
+    private final List<ContentValues> mContentValuesList = new ArrayList<ContentValues>();
+
+    public ContentValuesBuilder addContentValues(String mimeType) {
+        ContentValues contentValues = new ContentValues();
+        contentValues.put(Data.MIMETYPE, mimeType);
+        mContentValuesList.add(contentValues);
+        return new ContentValuesBuilder(contentValues);
+    }
+
+    public List<ContentValues> getList() {
+        return mContentValuesList;
+    }
+}
\ No newline at end of file
diff --git a/core/tests/coretests/src/android/pim/vcard/ContentValuesBuilder.java b/core/tests/coretests/src/android/pim/vcard/test_utils/ContentValuesBuilder.java
similarity index 96%
rename from core/tests/coretests/src/android/pim/vcard/ContentValuesBuilder.java
rename to core/tests/coretests/src/android/pim/vcard/test_utils/ContentValuesBuilder.java
index b3c0773..5c24186 100644
--- a/core/tests/coretests/src/android/pim/vcard/ContentValuesBuilder.java
+++ b/core/tests/coretests/src/android/pim/vcard/test_utils/ContentValuesBuilder.java
@@ -13,8 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
-package android.pim.vcard;
+package android.pim.vcard.test_utils;
 
 import android.content.ContentValues;
 
@@ -22,7 +21,7 @@
  * ContentValues-like class which enables users to chain put() methods and restricts
  * the other methods.
  */
-/* package */ class ContentValuesBuilder {
+public class ContentValuesBuilder {
     private final ContentValues mContentValues;
 
     public ContentValuesBuilder(final ContentValues contentValues) {
diff --git a/core/tests/coretests/src/android/pim/vcard/ContentValuesVerifier.java b/core/tests/coretests/src/android/pim/vcard/test_utils/ContentValuesVerifier.java
similarity index 92%
rename from core/tests/coretests/src/android/pim/vcard/ContentValuesVerifier.java
rename to core/tests/coretests/src/android/pim/vcard/test_utils/ContentValuesVerifier.java
index b9e9875..f2516ed 100644
--- a/core/tests/coretests/src/android/pim/vcard/ContentValuesVerifier.java
+++ b/core/tests/coretests/src/android/pim/vcard/test_utils/ContentValuesVerifier.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package android.pim.vcard;
+package android.pim.vcard.test_utils;
 
 import android.pim.vcard.VCardConfig;
 import android.pim.vcard.VCardEntry;
@@ -30,7 +30,7 @@
 import java.util.ArrayList;
 import java.util.List;
 
-/* package */ class ContentValuesVerifier implements VCardEntryHandler {
+public class ContentValuesVerifier implements VCardEntryHandler {
     private AndroidTestCase mTestCase;
     private List<ContentValuesVerifierElem> mContentValuesVerifierElemList =
         new ArrayList<ContentValuesVerifierElem>();
@@ -56,7 +56,7 @@
     public void verify(InputStream is, int vCardType) throws IOException, VCardException {
         final VCardParser vCardParser;
         if (VCardConfig.isV30(vCardType)) {
-            vCardParser = new VCardParser_V30(true);  // use StrictParsing
+            vCardParser = new VCardParser_V30();
         } else {
             vCardParser = new VCardParser_V21();
         }
@@ -66,7 +66,7 @@
     public void verify(InputStream is, int vCardType, final VCardParser vCardParser)
             throws IOException, VCardException {
         VCardEntryConstructor builder =
-            new VCardEntryConstructor(null, null, false, vCardType, null);
+            new VCardEntryConstructor(vCardType, null, null, false);
         builder.addEntryHandler(this);
         try {
             vCardParser.parse(is, builder);
diff --git a/core/tests/coretests/src/android/pim/vcard/ContentValuesVerifierElem.java b/core/tests/coretests/src/android/pim/vcard/test_utils/ContentValuesVerifierElem.java
similarity index 92%
rename from core/tests/coretests/src/android/pim/vcard/ContentValuesVerifierElem.java
rename to core/tests/coretests/src/android/pim/vcard/test_utils/ContentValuesVerifierElem.java
index 2edbb36..66d69b3 100644
--- a/core/tests/coretests/src/android/pim/vcard/ContentValuesVerifierElem.java
+++ b/core/tests/coretests/src/android/pim/vcard/test_utils/ContentValuesVerifierElem.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package android.pim.vcard;
+package android.pim.vcard.test_utils;
 
 import android.content.ContentValues;
 import android.pim.vcard.VCardConfig;
@@ -31,7 +31,7 @@
 import java.io.IOException;
 import java.io.InputStream;
 
-/* package */ class ContentValuesVerifierElem {
+public class ContentValuesVerifierElem {
     private final AndroidTestCase mTestCase;
     private final ImportTestResolver mResolver;
     private final VCardEntryHandler mHandler;
@@ -57,12 +57,12 @@
     public void verify(InputStream is, int vCardType) throws IOException, VCardException {
         final VCardParser vCardParser;
         if (VCardConfig.isV30(vCardType)) {
-            vCardParser = new VCardParser_V30(true);  // use StrictParsing
+            vCardParser = new VCardParser_V30();
         } else {
             vCardParser = new VCardParser_V21();
         }
         VCardEntryConstructor builder =
-                new VCardEntryConstructor(null, null, false, vCardType, null);
+                new VCardEntryConstructor(vCardType, null, null, false);
         builder.addEntryHandler(mHandler);
         try {
             vCardParser.parse(is, builder);
diff --git a/core/tests/coretests/src/android/pim/vcard/ExportTestResolver.java b/core/tests/coretests/src/android/pim/vcard/test_utils/ExportTestProvider.java
similarity index 63%
rename from core/tests/coretests/src/android/pim/vcard/ExportTestResolver.java
rename to core/tests/coretests/src/android/pim/vcard/test_utils/ExportTestProvider.java
index 5968e83..0fbd9bb 100644
--- a/core/tests/coretests/src/android/pim/vcard/ExportTestResolver.java
+++ b/core/tests/coretests/src/android/pim/vcard/test_utils/ExportTestProvider.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2009 The Android Open Source Project
+ * 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
@@ -13,7 +13,7 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package android.pim.vcard;
+package android.pim.vcard.test_utils;
 
 import android.content.ContentResolver;
 import android.content.ContentValues;
@@ -21,10 +21,11 @@
 import android.content.EntityIterator;
 import android.database.Cursor;
 import android.net.Uri;
+import android.pim.vcard.VCardComposer;
 import android.provider.ContactsContract.Contacts;
 import android.provider.ContactsContract.Data;
 import android.provider.ContactsContract.RawContacts;
-import android.test.mock.MockContentResolver;
+import android.test.mock.MockContentProvider;
 import android.test.mock.MockCursor;
 
 import junit.framework.TestCase;
@@ -33,76 +34,44 @@
 import java.util.Iterator;
 import java.util.List;
 
-/* package */ public class ExportTestResolver extends MockContentResolver {
-    ExportTestProvider mProvider;
-    public ExportTestResolver(TestCase testCase) {
-        mProvider = new ExportTestProvider(testCase);
-        addProvider(VCardComposer.VCARD_TEST_AUTHORITY, mProvider);
-        addProvider(RawContacts.CONTENT_URI.getAuthority(), mProvider);
-    }
-
-    public ContactEntry addInputContactEntry() {
-        return mProvider.buildInputEntry();
-    }
-}
-
-/* package */ class MockEntityIterator implements EntityIterator {
-    List<Entity> mEntityList;
-    Iterator<Entity> mIterator;
-
-    public MockEntityIterator(List<ContentValues> contentValuesList) {
-        mEntityList = new ArrayList<Entity>();
-        Entity entity = new Entity(new ContentValues());
-        for (ContentValues contentValues : contentValuesList) {
-                entity.addSubValue(Data.CONTENT_URI, contentValues);
-        }
-        mEntityList.add(entity);
-        mIterator = mEntityList.iterator();
-    }
-
-    public boolean hasNext() {
-        return mIterator.hasNext();
-    }
-
-    public Entity next() {
-        return mIterator.next();
-    }
-
-    public void remove() {
-        throw new UnsupportedOperationException("remove not supported");
-    }
-
-    public void reset() {
-        mIterator = mEntityList.iterator();
-    }
-
-    public void close() {
-    }
-}
-
-/**
- * Represents one contact, which should contain multiple ContentValues like
- * StructuredName, Email, etc.
- */
-/* package */ class ContactEntry {
-    private final List<ContentValues> mContentValuesList = new ArrayList<ContentValues>();
-
-    public ContentValuesBuilder addContentValues(String mimeType) {
-        ContentValues contentValues = new ContentValues();
-        contentValues.put(Data.MIMETYPE, mimeType);
-        mContentValuesList.add(contentValues);
-        return new ContentValuesBuilder(contentValues);
-    }
-
-    public List<ContentValues> getList() {
-        return mContentValuesList;
-    }
-}
-
 /* package */ class ExportTestProvider extends MockContentProvider {
     final private TestCase mTestCase;
     final private ArrayList<ContactEntry> mContactEntryList = new ArrayList<ContactEntry>();
 
+    private static class MockEntityIterator implements EntityIterator {
+        List<Entity> mEntityList;
+        Iterator<Entity> mIterator;
+
+        public MockEntityIterator(List<ContentValues> contentValuesList) {
+            mEntityList = new ArrayList<Entity>();
+            Entity entity = new Entity(new ContentValues());
+            for (ContentValues contentValues : contentValuesList) {
+                    entity.addSubValue(Data.CONTENT_URI, contentValues);
+            }
+            mEntityList.add(entity);
+            mIterator = mEntityList.iterator();
+        }
+
+        public boolean hasNext() {
+            return mIterator.hasNext();
+        }
+
+        public Entity next() {
+            return mIterator.next();
+        }
+
+        public void remove() {
+            throw new UnsupportedOperationException("remove not supported");
+        }
+
+        public void reset() {
+            mIterator = mEntityList.iterator();
+        }
+
+        public void close() {
+        }
+    }
+
     public ExportTestProvider(TestCase testCase) {
         mTestCase = testCase;
     }
@@ -121,16 +90,6 @@
      * We still keep using this method since we don't have a propeer way to know
      * which value in the ContentValue corresponds to the entry in Contacts database.
      * </p>
-     * <p>
-     * Detail:
-     * There's an easy way to know which index "family name" corresponds to, via
-     * {@link android.provider.ContactsContract}.
-     * FAMILY_NAME equals DATA3, so the corresponding index
-     * for "family name" should be 2 (note that index is 0-origin).
-     * However, we cannot know what the index 2 corresponds to; it may be "family name",
-     * "label" for now, but may be the other some column in the future. We don't have
-     * convenient way to know the original data structure.
-     * </p>
      */
     public EntityIterator queryEntities(Uri uri,
             String selection, String[] selectionArgs, String sortOrder) {
@@ -213,4 +172,4 @@
             }
         };
     }
-}
+}
\ No newline at end of file
diff --git a/core/tests/coretests/src/android/pim/vcard/test_utils/ExportTestResolver.java b/core/tests/coretests/src/android/pim/vcard/test_utils/ExportTestResolver.java
new file mode 100644
index 0000000..97e1e38
--- /dev/null
+++ b/core/tests/coretests/src/android/pim/vcard/test_utils/ExportTestResolver.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2009 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.pim.vcard.test_utils;
+
+import android.pim.vcard.VCardComposer;
+import android.provider.ContactsContract.RawContacts;
+import android.test.mock.MockContentResolver;
+
+import junit.framework.TestCase;
+
+/* package */ class ExportTestResolver extends MockContentResolver {
+    private final ExportTestProvider mProvider;
+    public ExportTestResolver(TestCase testCase) {
+        mProvider = new ExportTestProvider(testCase);
+        addProvider(VCardComposer.VCARD_TEST_AUTHORITY, mProvider);
+        addProvider(RawContacts.CONTENT_URI.getAuthority(), mProvider);
+    }
+
+    public ContactEntry addInputContactEntry() {
+        return mProvider.buildInputEntry();
+    }
+
+    public ExportTestProvider getProvider() {
+        return mProvider;
+    }
+}
diff --git a/core/tests/coretests/src/android/pim/vcard/ImportTestResolver.java b/core/tests/coretests/src/android/pim/vcard/test_utils/ImportTestProvider.java
similarity index 91%
rename from core/tests/coretests/src/android/pim/vcard/ImportTestResolver.java
rename to core/tests/coretests/src/android/pim/vcard/test_utils/ImportTestProvider.java
index c3f6f79..ffbf95d 100644
--- a/core/tests/coretests/src/android/pim/vcard/ImportTestResolver.java
+++ b/core/tests/coretests/src/android/pim/vcard/test_utils/ImportTestProvider.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2009 The Android Open Source Project
+ * 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.
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package android.pim.vcard;
+package android.pim.vcard.test_utils;
 
 import android.content.ContentProviderOperation;
 import android.content.ContentProviderResult;
@@ -34,7 +34,7 @@
 import android.provider.ContactsContract.CommonDataKinds.StructuredName;
 import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
 import android.provider.ContactsContract.CommonDataKinds.Website;
-import android.test.mock.MockContentResolver;
+import android.test.mock.MockContentProvider;
 import android.text.TextUtils;
 
 import junit.framework.TestCase;
@@ -50,37 +50,6 @@
 import java.util.TreeMap;
 import java.util.Map.Entry;
 
-/* package */ class ImportTestResolver extends MockContentResolver {
-    final ImportTestProvider mProvider;
-
-    public ImportTestResolver(TestCase testCase) {
-        mProvider = new ImportTestProvider(testCase);
-    }
-
-    @Override
-    public ContentProviderResult[] applyBatch(String authority,
-            ArrayList<ContentProviderOperation> operations) {
-        equalsString(authority, RawContacts.CONTENT_URI.toString());
-        return mProvider.applyBatch(operations);
-    }
-
-    public void addExpectedContentValues(ContentValues expectedContentValues) {
-        mProvider.addExpectedContentValues(expectedContentValues);
-    }
-
-    public void verify() {
-        mProvider.verify();
-    }
-
-    private static boolean equalsString(String a, String b) {
-        if (a == null || a.length() == 0) {
-            return b == null || b.length() == 0;
-        } else {
-            return a.equals(b);
-        }
-    }
-}
-
 /* package */ class ImportTestProvider extends MockContentProvider {
     private static final Set<String> sKnownMimeTypeSet =
         new HashSet<String>(Arrays.asList(StructuredName.CONTENT_ITEM_TYPE,
@@ -296,4 +265,4 @@
         }
         return true;
     }
-}
+}
\ No newline at end of file
diff --git a/core/tests/coretests/src/android/pim/vcard/test_utils/ImportTestResolver.java b/core/tests/coretests/src/android/pim/vcard/test_utils/ImportTestResolver.java
new file mode 100644
index 0000000..29738c6
--- /dev/null
+++ b/core/tests/coretests/src/android/pim/vcard/test_utils/ImportTestResolver.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2009 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.pim.vcard.test_utils;
+
+import android.content.ContentProviderOperation;
+import android.content.ContentProviderResult;
+import android.content.ContentValues;
+import android.provider.ContactsContract.RawContacts;
+import android.test.mock.MockContentResolver;
+
+import junit.framework.TestCase;
+
+import java.util.ArrayList;
+
+/* package */ class ImportTestResolver extends MockContentResolver {
+    private final ImportTestProvider mProvider;
+
+    public ImportTestResolver(TestCase testCase) {
+        mProvider = new ImportTestProvider(testCase);
+    }
+
+    @Override
+    public ContentProviderResult[] applyBatch(String authority,
+            ArrayList<ContentProviderOperation> operations) {
+        equalsString(authority, RawContacts.CONTENT_URI.toString());
+        return mProvider.applyBatch(operations);
+    }
+
+    public void addExpectedContentValues(ContentValues expectedContentValues) {
+        mProvider.addExpectedContentValues(expectedContentValues);
+    }
+
+    public void verify() {
+        mProvider.verify();
+    }
+
+    private static boolean equalsString(String a, String b) {
+        if (a == null || a.length() == 0) {
+            return b == null || b.length() == 0;
+        } else {
+            return a.equals(b);
+        }
+    }
+}
diff --git a/core/tests/coretests/src/android/pim/vcard/LineVerifier.java b/core/tests/coretests/src/android/pim/vcard/test_utils/LineVerifier.java
similarity index 94%
rename from core/tests/coretests/src/android/pim/vcard/LineVerifier.java
rename to core/tests/coretests/src/android/pim/vcard/test_utils/LineVerifier.java
index cef15fd..3edec67 100644
--- a/core/tests/coretests/src/android/pim/vcard/LineVerifier.java
+++ b/core/tests/coretests/src/android/pim/vcard/test_utils/LineVerifier.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package android.pim.vcard;
+package android.pim.vcard.test_utils;
 
 import android.content.Context;
 import android.pim.vcard.VCardComposer;
@@ -22,7 +22,7 @@
 
 import java.util.ArrayList;
 
-class LineVerifier implements VCardComposer.OneEntryHandler {
+public class LineVerifier implements VCardComposer.OneEntryHandler {
     private final TestCase mTestCase;
     private final ArrayList<LineVerifierElem> mLineVerifierElemList;
     private int mVCardType;
diff --git a/core/tests/coretests/src/android/pim/vcard/LineVerifierElem.java b/core/tests/coretests/src/android/pim/vcard/test_utils/LineVerifierElem.java
similarity index 97%
rename from core/tests/coretests/src/android/pim/vcard/LineVerifierElem.java
rename to core/tests/coretests/src/android/pim/vcard/test_utils/LineVerifierElem.java
index b23b29b..4f7a9cf 100644
--- a/core/tests/coretests/src/android/pim/vcard/LineVerifierElem.java
+++ b/core/tests/coretests/src/android/pim/vcard/test_utils/LineVerifierElem.java
@@ -13,7 +13,7 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package android.pim.vcard;
+package android.pim.vcard.test_utils;
 
 import android.pim.vcard.VCardConfig;
 import android.text.TextUtils;
@@ -23,7 +23,7 @@
 import java.util.ArrayList;
 import java.util.List;
 
-class LineVerifierElem {
+public class LineVerifierElem {
     private final TestCase mTestCase;
     private final List<String> mExpectedLineList = new ArrayList<String>();
     private final boolean mIsV30;
diff --git a/core/tests/coretests/src/android/pim/vcard/PropertyNode.java b/core/tests/coretests/src/android/pim/vcard/test_utils/PropertyNode.java
similarity index 95%
rename from core/tests/coretests/src/android/pim/vcard/PropertyNode.java
rename to core/tests/coretests/src/android/pim/vcard/test_utils/PropertyNode.java
index 2c1f6d2..de7ad8e 100644
--- a/core/tests/coretests/src/android/pim/vcard/PropertyNode.java
+++ b/core/tests/coretests/src/android/pim/vcard/test_utils/PropertyNode.java
@@ -13,11 +13,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package android.pim.vcard;
+package android.pim.vcard.test_utils;
 
 import android.content.ContentValues;
 import android.pim.vcard.VCardEntry;
-import android.util.Log;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -26,12 +25,18 @@
 import java.util.Set;
 
 /**
+ * <p>
+ * The class representing one property (e.g. "N;ENCODING=UTF-8:family:given:middle:prefix:suffix").
+ * </p>
+ * <p>
  * Previously used in main vCard handling code but now exists only for testing.
- *
+ * </p>
+ * <p>
  * Especially useful for testing parser code (VCardParser), since all properties can be
  * checked via this class unlike {@link VCardEntry}, which only emits the result of
  * interpretation of the content of each vCard. We cannot know whether vCard parser or
- * ContactStruct is wrong withouth this class.
+ * {@link VCardEntry} is wrong without this class.
+ * </p>
  */
 public class PropertyNode {
     public String propName;
@@ -43,7 +48,8 @@
      */
     public byte[] propValue_bytes;
 
-    /** param store: key=paramType, value=paramValue
+    /**
+     * param store: key=paramType, value=paramValue
      * Note that currently PropertyNode class does not support multiple param-values
      * defined in vCard 3.0 (See also RFC 2426). multiple-values are stored as
      * one String value like "A,B", not ["A", "B"]...
diff --git a/core/tests/coretests/src/android/pim/vcard/test_utils/PropertyNodesVerifier.java b/core/tests/coretests/src/android/pim/vcard/test_utils/PropertyNodesVerifier.java
new file mode 100644
index 0000000..5c8215f
--- /dev/null
+++ b/core/tests/coretests/src/android/pim/vcard/test_utils/PropertyNodesVerifier.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2009 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.pim.vcard.test_utils;
+
+import android.pim.vcard.VCardConfig;
+import android.pim.vcard.VCardParser;
+import android.pim.vcard.VCardParser_V21;
+import android.pim.vcard.VCardParser_V30;
+import android.pim.vcard.exception.VCardException;
+import android.test.AndroidTestCase;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+public class PropertyNodesVerifier extends VNodeBuilder {
+    private final List<PropertyNodesVerifierElem> mPropertyNodesVerifierElemList;
+    private final AndroidTestCase mAndroidTestCase;
+    private int mIndex;
+
+    public PropertyNodesVerifier(AndroidTestCase testCase) {
+        super();
+        mPropertyNodesVerifierElemList = new ArrayList<PropertyNodesVerifierElem>();
+        mAndroidTestCase = testCase;
+    }
+
+    public PropertyNodesVerifierElem addPropertyNodesVerifierElem() {
+        PropertyNodesVerifierElem elem = new PropertyNodesVerifierElem(mAndroidTestCase);
+        mPropertyNodesVerifierElemList.add(elem);
+        return elem;
+    }
+
+    public void verify(int resId, int vCardType)
+            throws IOException, VCardException {
+        verify(mAndroidTestCase.getContext().getResources().openRawResource(resId), vCardType);
+    }
+
+    public void verify(int resId, int vCardType, final VCardParser vCardParser)
+            throws IOException, VCardException {
+        verify(mAndroidTestCase.getContext().getResources().openRawResource(resId),
+                vCardType, vCardParser);
+    }
+
+    public void verify(InputStream is, int vCardType) throws IOException, VCardException {
+        final VCardParser vCardParser;
+        if (VCardConfig.isV30(vCardType)) {
+            vCardParser = new VCardParser_V30();
+        } else {
+            vCardParser = new VCardParser_V21();
+        }
+        verify(is, vCardType, vCardParser);
+    }
+
+    public void verify(InputStream is, int vCardType, final VCardParser vCardParser)
+            throws IOException, VCardException {
+        try {
+            vCardParser.parse(is, this);
+        } finally {
+            if (is != null) {
+                try {
+                    is.close();
+                } catch (IOException e) {
+                }
+            }
+        }
+    }
+
+    @Override
+    public void endEntry() {
+        super.endEntry();
+        mAndroidTestCase.assertTrue(mIndex < mPropertyNodesVerifierElemList.size());
+        mAndroidTestCase.assertTrue(mIndex < vNodeList.size());
+        mPropertyNodesVerifierElemList.get(mIndex).verify(vNodeList.get(mIndex));
+        mIndex++;
+    }
+}
diff --git a/core/tests/coretests/src/android/pim/vcard/PropertyNodesVerifier.java b/core/tests/coretests/src/android/pim/vcard/test_utils/PropertyNodesVerifierElem.java
similarity index 82%
rename from core/tests/coretests/src/android/pim/vcard/PropertyNodesVerifier.java
rename to core/tests/coretests/src/android/pim/vcard/test_utils/PropertyNodesVerifierElem.java
index cfdd074..8c6c734 100644
--- a/core/tests/coretests/src/android/pim/vcard/PropertyNodesVerifier.java
+++ b/core/tests/coretests/src/android/pim/vcard/test_utils/PropertyNodesVerifierElem.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2009 The Android Open Source Project
+ * 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.
@@ -13,87 +13,18 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package android.pim.vcard;
+package android.pim.vcard.test_utils;
 
 import android.content.ContentValues;
-import android.pim.vcard.VCardConfig;
-import android.pim.vcard.VCardParser;
-import android.pim.vcard.VCardParser_V21;
-import android.pim.vcard.VCardParser_V30;
-import android.pim.vcard.exception.VCardException;
-import android.test.AndroidTestCase;
 
 import junit.framework.TestCase;
 
-import java.io.IOException;
-import java.io.InputStream;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 
-/* package */ class PropertyNodesVerifier extends VNodeBuilder {
-    private final List<PropertyNodesVerifierElem> mPropertyNodesVerifierElemList;
-    private final AndroidTestCase mAndroidTestCase;
-    private int mIndex;
-
-    public PropertyNodesVerifier(AndroidTestCase testCase) {
-        mPropertyNodesVerifierElemList = new ArrayList<PropertyNodesVerifierElem>();
-        mAndroidTestCase = testCase;
-    }
-
-    public PropertyNodesVerifierElem addPropertyNodesVerifierElem() {
-        PropertyNodesVerifierElem elem = new PropertyNodesVerifierElem(mAndroidTestCase);
-        mPropertyNodesVerifierElemList.add(elem);
-        return elem;
-    }
-
-    public void verify(int resId, int vCardType)
-            throws IOException, VCardException {
-        verify(mAndroidTestCase.getContext().getResources().openRawResource(resId), vCardType);
-    }
-
-    public void verify(int resId, int vCardType, final VCardParser vCardParser)
-            throws IOException, VCardException {
-        verify(mAndroidTestCase.getContext().getResources().openRawResource(resId),
-                vCardType, vCardParser);
-    }
-
-    public void verify(InputStream is, int vCardType) throws IOException, VCardException {
-        final VCardParser vCardParser;
-        if (VCardConfig.isV30(vCardType)) {
-            vCardParser = new VCardParser_V30(true);  // Use StrictParsing.
-        } else {
-            vCardParser = new VCardParser_V21();
-        }
-        verify(is, vCardType, vCardParser);
-    }
-
-    public void verify(InputStream is, int vCardType, final VCardParser vCardParser)
-            throws IOException, VCardException {
-        try {
-            vCardParser.parse(is, this);
-        } finally {
-            if (is != null) {
-                try {
-                    is.close();
-                } catch (IOException e) {
-                }
-            }
-        }
-    }
-
-    @Override
-    public void endEntry() {
-        super.endEntry();
-        mAndroidTestCase.assertTrue(mIndex < mPropertyNodesVerifierElemList.size());
-        mAndroidTestCase.assertTrue(mIndex < vNodeList.size());
-        mPropertyNodesVerifierElemList.get(mIndex).verify(vNodeList.get(mIndex));
-        mIndex++;
-    }
-}
-
 /**
  * Utility class which verifies input VNode.
  *
@@ -102,7 +33,7 @@
  * If the node does not exist in the "ordered list", the class refers to
  * "unorderd expected property set" and checks the node is expected somewhere.
  */
-/* package */ class PropertyNodesVerifierElem {
+public class PropertyNodesVerifierElem {
     public static class TypeSet extends HashSet<String> {
         public TypeSet(String ... array) {
             super(Arrays.asList(array));
diff --git a/core/tests/coretests/src/android/pim/vcard/VCardVerifier.java b/core/tests/coretests/src/android/pim/vcard/test_utils/VCardVerifier.java
similarity index 79%
rename from core/tests/coretests/src/android/pim/vcard/VCardVerifier.java
rename to core/tests/coretests/src/android/pim/vcard/test_utils/VCardVerifier.java
index bfc3158..5fb2942 100644
--- a/core/tests/coretests/src/android/pim/vcard/VCardVerifier.java
+++ b/core/tests/coretests/src/android/pim/vcard/test_utils/VCardVerifier.java
@@ -13,9 +13,8 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package android.pim.vcard;
+package android.pim.vcard.test_utils;
 
-import android.content.ContentProvider;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.EntityIterator;
@@ -31,6 +30,8 @@
 import android.pim.vcard.exception.VCardException;
 import android.test.AndroidTestCase;
 import android.test.mock.MockContext;
+import android.text.TextUtils;
+import android.util.Log;
 
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
@@ -38,19 +39,32 @@
 import java.lang.reflect.Method;
 import java.util.Arrays;
 
-/* package */ class CustomMockContext extends MockContext {
-    final ContentResolver mResolver;
-    public CustomMockContext(ContentResolver resolver) {
-        mResolver = resolver;
+/**
+ * <p>
+ * The class lets users checks that given expected vCard data are same as given actual vCard data.
+ * Able to verify both vCard importer/exporter.
+ * </p>
+ * <p>
+ * First a user has to initialize the object by calling either
+ * {@link #initForImportTest(int, int)} or {@link #initForExportTest(int)}.
+ * "Round trip test" (import -> export -> import, or export -> import -> export) is not supported.
+ * </p>
+ */
+public class VCardVerifier {
+    private static final String LOG_TAG = "VCardVerifier";
+
+    private static class CustomMockContext extends MockContext {
+        final ContentResolver mResolver;
+        public CustomMockContext(ContentResolver resolver) {
+            mResolver = resolver;
+        }
+
+        @Override
+        public ContentResolver getContentResolver() {
+            return mResolver;
+        }
     }
 
-    @Override
-    public ContentResolver getContentResolver() {
-        return mResolver;
-    }
-}
-
-/* package */ class VCardVerifier {
     private class VCardVerifierInternal implements VCardComposer.OneEntryHandler {
         public boolean onInit(Context context) {
             return true;
@@ -80,9 +94,11 @@
     private ContentValuesVerifier mContentValuesVerifier;
     private boolean mInitialized;
     private boolean mVerified = false;
+    private String mCharset;
 
-    public VCardVerifier(AndroidTestCase androidTestCase) {
-        mTestCase = androidTestCase;
+    // Called by VCardTestsBase
+    public VCardVerifier(AndroidTestCase testCase) {
+        mTestCase = testCase;
         mVCardVerifierInternal = new VCardVerifierInternal();
         mExportTestResolver = null;
         mInputStream = null;
@@ -90,17 +106,7 @@
         mVerified = false;
     }
 
-    public void initForExportTest(int vcardType) {
-        if (mInitialized) {
-            mTestCase.fail("Already initialized");
-        }
-        mExportTestResolver = new ExportTestResolver(mTestCase);
-        mVCardType = vcardType;
-        mIsV30 = VCardConfig.isV30(vcardType);
-        mIsDoCoMo = VCardConfig.isDoCoMo(vcardType);
-        mInitialized = true;
-    }
-
+    // Should be called at the beginning of each import test.
     public void initForImportTest(int vcardType, int resId) {
         if (mInitialized) {
             mTestCase.fail("Already initialized");
@@ -112,6 +118,27 @@
         mInitialized = true;
     }
 
+    // Should be called at the beginning of each export test.
+    public void initForExportTest(int vcardType) {
+        initForExportTest(vcardType, "UTF-8");
+    }
+
+    public void initForExportTest(int vcardType, String charset) {
+        if (mInitialized) {
+            mTestCase.fail("Already initialized");
+        }
+        mExportTestResolver = new ExportTestResolver(mTestCase);
+        mVCardType = vcardType;
+        mIsV30 = VCardConfig.isV30(vcardType);
+        mIsDoCoMo = VCardConfig.isDoCoMo(vcardType);
+        mInitialized = true;
+        if (TextUtils.isEmpty(charset)) {
+            mCharset = "UTF-8";
+        } else {
+            mCharset = charset;
+        }
+    }
+
     private void setInputResourceId(int resId) {
         InputStream inputStream = mTestCase.getContext().getResources().openRawResource(resId);
         if (inputStream == null) {
@@ -188,7 +215,7 @@
     }
 
     private void verifyOneVCard(final String vcard) {
-        // Log.d("@@@", vcard);
+        Log.d(LOG_TAG, vcard);
         final VCardInterpreter builder;
         if (mContentValuesVerifier != null) {
             final VNodeBuilder vnodeBuilder = mPropertyNodesVerifier;
@@ -209,14 +236,15 @@
             }
         }
 
-        final VCardParser parser =
-                (mIsV30 ? new VCardParser_V30(true) : new VCardParser_V21());
         InputStream is = null;
         try {
-            String charset =
-                (VCardConfig.usesShiftJis(mVCardType) ? "SHIFT_JIS" : "UTF-8");
-            is = new ByteArrayInputStream(vcard.getBytes(charset));
-            mTestCase.assertEquals(true, parser.parse(is, null, builder));
+            // Note: we must not specify charset toward vCard parsers. This code checks whether
+            // those parsers are able to encode given binary without any extra information for
+            // charset.
+            final VCardParser parser = (mIsV30 ?
+                    new VCardParser_V30(mVCardType) : new VCardParser_V21(mVCardType));
+            is = new ByteArrayInputStream(vcard.getBytes(mCharset));
+            parser.parse(is, builder);
         } catch (IOException e) {
             mTestCase.fail("Unexpected IOException: " + e.getMessage());
         } catch (VCardException e) {
@@ -226,6 +254,7 @@
                 try {
                     is.close();
                 } catch (IOException e) {
+                    mTestCase.fail("Unexpected IOException: " + e.getMessage());
                 }
             }
         }
@@ -267,10 +296,13 @@
             final ContentResolver resolver,
             final Uri uri, final String selection,
             final String[] selectionArgs, final String sortOrder) {
-        final ContentProvider provider =
-            resolver.acquireContentProviderClient(uri).getLocalContentProvider();
-        return ((ExportTestProvider)provider).queryEntities(
-                uri, selection, selectionArgs, sortOrder);
+        if (ExportTestResolver.class.equals(resolver.getClass())) {
+            return ((ExportTestResolver)resolver).getProvider().queryEntities(
+                    uri, selection, selectionArgs, sortOrder);
+        }
+
+        Log.e(LOG_TAG, "Unexpected provider given.");
+        return null;
     }
 
     private Method getMockGetEntityIteratorMethod()
@@ -281,7 +313,7 @@
 
     private void verifyForExportTest() {
        final VCardComposer composer =
-            new VCardComposer(new CustomMockContext(mExportTestResolver), mVCardType);
+            new VCardComposer(new CustomMockContext(mExportTestResolver), mVCardType, mCharset);
         composer.addHandler(mLineVerifier);
         composer.addHandler(mVCardVerifierInternal);
         if (!composer.init(VCardComposer.CONTACTS_TEST_CONTENT_URI, null, null, null)) {
@@ -292,8 +324,8 @@
             while (!composer.isAfterLast()) {
                 try {
                     final Method mockGetEntityIteratorMethod = getMockGetEntityIteratorMethod();
-                    mTestCase.assertTrue(
-                            composer.createOneEntry(getMockGetEntityIteratorMethod()));
+                    mTestCase.assertNotNull(mockGetEntityIteratorMethod);
+                    mTestCase.assertTrue(composer.createOneEntry(mockGetEntityIteratorMethod));
                 } catch (Exception e) {
                     e.printStackTrace();
                     mTestCase.fail();
diff --git a/core/tests/coretests/src/android/pim/vcard/VNode.java b/core/tests/coretests/src/android/pim/vcard/test_utils/VNode.java
similarity index 95%
rename from core/tests/coretests/src/android/pim/vcard/VNode.java
rename to core/tests/coretests/src/android/pim/vcard/test_utils/VNode.java
index 79f10dc..b890e2c 100644
--- a/core/tests/coretests/src/android/pim/vcard/VNode.java
+++ b/core/tests/coretests/src/android/pim/vcard/test_utils/VNode.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package android.pim.vcard;
+package android.pim.vcard.test_utils;
 
 import java.util.ArrayList;
 
diff --git a/core/tests/coretests/src/android/pim/vcard/VNodeBuilder.java b/core/tests/coretests/src/android/pim/vcard/test_utils/VNodeBuilder.java
similarity index 66%
rename from core/tests/coretests/src/android/pim/vcard/VNodeBuilder.java
rename to core/tests/coretests/src/android/pim/vcard/test_utils/VNodeBuilder.java
index 0e6c325..25fb6ac 100644
--- a/core/tests/coretests/src/android/pim/vcard/VNodeBuilder.java
+++ b/core/tests/coretests/src/android/pim/vcard/test_utils/VNodeBuilder.java
@@ -13,17 +13,16 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package android.pim.vcard;
+package android.pim.vcard.test_utils;
 
 import android.content.ContentValues;
-import android.pim.vcard.VCardInterpreter;
 import android.pim.vcard.VCardConfig;
+import android.pim.vcard.VCardInterpreter;
+import android.pim.vcard.VCardUtils;
 import android.util.CharsetUtils;
 import android.util.Log;
 
-import org.apache.commons.codec.DecoderException;
 import org.apache.commons.codec.binary.Base64;
-import org.apache.commons.codec.net.QuotedPrintableCodec;
 
 import java.io.UnsupportedEncodingException;
 import java.nio.ByteBuffer;
@@ -32,34 +31,29 @@
 import java.util.List;
 
 /**
- * Store the parse result to custom datastruct: VNode, PropertyNode
+ * <p>
+ * The class storing the parse result to custom datastruct:
+ * {@link VNode}, and {@link PropertyNode}.
  * Maybe several vcard instance, so use vNodeList to store.
- * VNode: standy by a vcard instance.
- * PropertyNode: standy by a property line of a card.
- *
- * Previously used in main vCard handling code but now exists only for testing.
+ * </p>
+ * <p>
+ * This is called VNode, not VCardNode, since it was used for expressing vCalendar (iCal).
+ * </p>
  */
-public class VNodeBuilder implements VCardInterpreter {
+/* package */ class VNodeBuilder implements VCardInterpreter {
     static private String LOG_TAG = "VNodeBuilder"; 
     
-    /**
-     * If there's no other information available, this class uses this charset for encoding
-     * byte arrays.
-     */
-    static public String TARGET_CHARSET = "UTF-8"; 
-    
-    /** type=VNode */
     public List<VNode> vNodeList = new ArrayList<VNode>();
     private int mNodeListPos = 0;
     private VNode mCurrentVNode;
     private PropertyNode mCurrentPropNode;
     private String mCurrentParamType;
-    
+
     /**
      * The charset using which VParser parses the text.
      */
     private String mSourceCharset;
-    
+
     /**
      * The charset with which byte array is encoded to String.
      */
@@ -68,11 +62,11 @@
     private boolean mStrictLineBreakParsing;
     
     public VNodeBuilder() {
-        this(VCardConfig.DEFAULT_CHARSET, TARGET_CHARSET, false);
+        this(VCardConfig.DEFAULT_INTERMEDIATE_CHARSET, VCardConfig.DEFAULT_IMPORT_CHARSET, false);
     }
 
-    public VNodeBuilder(String charset, boolean strictLineBreakParsing) {
-        this(null, charset, strictLineBreakParsing);
+    public VNodeBuilder(String targetCharset, boolean strictLineBreakParsing) {
+        this(null, targetCharset, strictLineBreakParsing);
     }
     
     /**
@@ -83,12 +77,12 @@
         if (sourceCharset != null) {
             mSourceCharset = sourceCharset;
         } else {
-            mSourceCharset = VCardConfig.DEFAULT_CHARSET;
+            mSourceCharset = VCardConfig.DEFAULT_INTERMEDIATE_CHARSET;
         }
         if (targetCharset != null) {
             mTargetCharset = targetCharset;
         } else {
-            mTargetCharset = TARGET_CHARSET;
+            mTargetCharset = VCardConfig.DEFAULT_IMPORT_CHARSET;
         }
         mStrictLineBreakParsing = strictLineBreakParsing;
     }
@@ -149,7 +143,6 @@
         mCurrentPropNode.propName = name;
     }
 
-    // Used only in VCard.
     public void propertyGroup(String group) {
         mCurrentPropNode.propGroupSet.add(group);
     }
@@ -196,67 +189,8 @@
                     Base64.decodeBase64(value.getBytes());
                 return value;
             } else if (encoding.equals("QUOTED-PRINTABLE")) {
-                String quotedPrintable = value
-                .replaceAll("= ", " ").replaceAll("=\t", "\t");
-                String[] lines;
-                if (mStrictLineBreakParsing) {
-                    lines = quotedPrintable.split("\r\n");
-                } else {
-                    StringBuilder builder = new StringBuilder();
-                    int length = quotedPrintable.length();
-                    ArrayList<String> list = new ArrayList<String>();
-                    for (int i = 0; i < length; i++) {
-                        char ch = quotedPrintable.charAt(i);
-                        if (ch == '\n') {
-                            list.add(builder.toString());
-                            builder = new StringBuilder();
-                        } else if (ch == '\r') {
-                            list.add(builder.toString());
-                            builder = new StringBuilder();
-                            if (i < length - 1) {
-                                char nextCh = quotedPrintable.charAt(i + 1);
-                                if (nextCh == '\n') {
-                                    i++;
-                                }
-                            }
-                        } else {
-                            builder.append(ch);
-                        }
-                    }
-                    String finalLine = builder.toString();
-                    if (finalLine.length() > 0) {
-                        list.add(finalLine);
-                    }
-                    lines = list.toArray(new String[0]);
-                }
-                StringBuilder builder = new StringBuilder();
-                for (String line : lines) {
-                    if (line.endsWith("=")) {
-                        line = line.substring(0, line.length() - 1);
-                    }
-                    builder.append(line);
-                }
-                byte[] bytes;
-                try {
-                    bytes = builder.toString().getBytes(mSourceCharset);
-                } catch (UnsupportedEncodingException e1) {
-                    Log.e(LOG_TAG, "Failed to encode: charset=" + mSourceCharset);
-                    bytes = builder.toString().getBytes();
-                }
-                
-                try {
-                    bytes = QuotedPrintableCodec.decodeQuotedPrintable(bytes);
-                } catch (DecoderException e) {
-                    Log.e(LOG_TAG, "Failed to decode quoted-printable: " + e);
-                    return "";
-                }
-
-                try {
-                    return new String(bytes, targetCharset);
-                } catch (UnsupportedEncodingException e) {
-                    Log.e(LOG_TAG, "Failed to encode: charset=" + targetCharset);
-                    return new String(bytes);
-                }
+                return VCardUtils.parseQuotedPrintable(
+                        value, mStrictLineBreakParsing, mSourceCharset, targetCharset);
             }
             // Unknown encoding. Fall back to default.
         }
@@ -309,6 +243,6 @@
     }
     
     public String getResult(){
-        return null;
+        throw new RuntimeException("Not supported");
     }
 }
diff --git a/core/tests/coretests/src/android/provider/SettingsProviderTest.java b/core/tests/coretests/src/android/provider/SettingsProviderTest.java
index 370ae78..b82e698 100644
--- a/core/tests/coretests/src/android/provider/SettingsProviderTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsProviderTest.java
@@ -144,4 +144,51 @@
 
         assertEquals(null, Settings.Bookmarks.getIntentForShortcut(r, '*'));
     }
+
+    @MediumTest
+    public void testParseProviderList() {
+        ContentResolver r = getContext().getContentResolver();
+
+        // Make sure we get out what we put in.
+        Settings.Secure.putString(r, Settings.Secure.LOCATION_PROVIDERS_ALLOWED,
+                "test1,test2,test3");
+        assertEquals(Settings.Secure.getString(r, Settings.Secure.LOCATION_PROVIDERS_ALLOWED),
+                "test1,test2,test3");
+
+        // Test adding a value
+        Settings.Secure.putString(r, Settings.Secure.LOCATION_PROVIDERS_ALLOWED,
+                "");
+        Settings.Secure.putString(r, Settings.Secure.LOCATION_PROVIDERS_ALLOWED, "+test1");
+        assertEquals("test1",
+                Settings.Secure.getString(r, Settings.Secure.LOCATION_PROVIDERS_ALLOWED));
+
+        // Test adding a second value
+        Settings.Secure.putString(r, Settings.Secure.LOCATION_PROVIDERS_ALLOWED, "+test2");
+        assertEquals("test1,test2",
+                Settings.Secure.getString(r, Settings.Secure.LOCATION_PROVIDERS_ALLOWED));
+
+        // Test adding a third value
+        Settings.Secure.putString(r, Settings.Secure.LOCATION_PROVIDERS_ALLOWED, "+test3");
+        assertEquals("test1,test2,test3",
+                Settings.Secure.getString(r, Settings.Secure.LOCATION_PROVIDERS_ALLOWED));
+
+        // Test deleting the first value in a 3 item list
+        Settings.Secure.putString(r, Settings.Secure.LOCATION_PROVIDERS_ALLOWED, "-test1");
+        assertEquals("test2,test3",
+                Settings.Secure.getString(r, Settings.Secure.LOCATION_PROVIDERS_ALLOWED));
+
+        // Test deleting the middle value in a 3 item list
+        Settings.Secure.putString(r, Settings.Secure.LOCATION_PROVIDERS_ALLOWED,
+                "test1,test2,test3");
+        Settings.Secure.putString(r, Settings.Secure.LOCATION_PROVIDERS_ALLOWED, "-test2");
+        assertEquals("test1,test3",
+                Settings.Secure.getString(r, Settings.Secure.LOCATION_PROVIDERS_ALLOWED));
+
+        // Test deleting the last value in a 3 item list
+        Settings.Secure.putString(r, Settings.Secure.LOCATION_PROVIDERS_ALLOWED,
+                "test1,test2,test3");
+        Settings.Secure.putString(r, Settings.Secure.LOCATION_PROVIDERS_ALLOWED, "-test3");
+        assertEquals("test1,test2",
+                Settings.Secure.getString(r, Settings.Secure.LOCATION_PROVIDERS_ALLOWED));
+     }
 }
diff --git a/core/tests/coretests/src/android/text/StaticLayoutBidiTest.java b/core/tests/coretests/src/android/text/StaticLayoutBidiTest.java
index 8e7e63e..fb0f0c1 100644
--- a/core/tests/coretests/src/android/text/StaticLayoutBidiTest.java
+++ b/core/tests/coretests/src/android/text/StaticLayoutBidiTest.java
@@ -22,7 +22,7 @@
 import junit.framework.TestCase;
 
 /**
- * Tests StaticLayout bidi implementation.
+ * Quick check of native bidi implementation.
  */
 public class StaticLayoutBidiTest extends TestCase {
     
@@ -41,73 +41,47 @@
     
     //@SmallTest
     public void testAllLtr() {
-        expectBidi(REQ_DL, "a test", "000000", L);
+        expectNativeBidi(REQ_DL, "a test", "000000", L);
     }
     
     //@SmallTest
     public void testLtrRtl() {
-        expectBidi(REQ_DL, "abc " + ALEF + BET + GIMEL, "0000111", L);
+        expectNativeBidi(REQ_DL, "abc " + ALEF + BET + GIMEL, "0000111", L);
     }
     
     //@SmallTest
     public void testAllRtl() {
-        expectBidi(REQ_DL, ALEF + SP + ALEF + BET + GIMEL + DALET, "111111", R);
+        expectNativeBidi(REQ_DL, ALEF + SP + ALEF + BET + GIMEL + DALET, "111111", R);
     }
     
     //@SmallTest
     public void testRtlLtr() {
-        expectBidi(REQ_DL,  ALEF + BET + GIMEL + " abc", "1111000", R);
+        expectNativeBidi(REQ_DL,  ALEF + BET + GIMEL + " abc", "1111222", R);
     }
     
     //@SmallTest
     public void testRAllLtr() {
-        expectBidi(REQ_R, "a test", "000000", R);
+        expectNativeBidi(REQ_R, "a test", "222222", R);
     }
     
     //@SmallTest
     public void testRLtrRtl() {
-        expectBidi(REQ_R, "abc " + ALEF + BET + GIMEL, "0001111", R);
+        expectNativeBidi(REQ_R, "abc " + ALEF + BET + GIMEL, "2221111", R);
     }
     
     //@SmallTest
     public void testLAllRtl() {
-        expectBidi(REQ_L, ALEF + SP + ALEF + BET + GIMEL + DALET, "111111", L);
+        expectNativeBidi(REQ_L, ALEF + SP + ALEF + BET + GIMEL + DALET, "111111", L);
     }
     
     //@SmallTest
     public void testLRtlLtr() {
-        expectBidi(REQ_L,  ALEF + BET + GIMEL + " abc", "1110000", L);
-    }
-    
-    private void expectBidi(int dir, String text, 
-            String expectedLevels, int expectedDir) {
-        char[] chs = text.toCharArray();
-        int n = chs.length;
-        byte[] chInfo = new byte[n];
-        
-        int resultDir = StaticLayout.bidi(dir, chs, chInfo, n, false);
-        
-        {
-            StringBuilder sb = new StringBuilder("info:");
-            for (int i = 0; i < n; ++i) {
-                sb.append(" ").append(String.valueOf(chInfo[i]));
-            }
-            Log.i("BIDI", sb.toString());
-        }
-        
-        char[] resultLevelChars = new char[n];
-        for (int i = 0; i < n; ++i) {
-            resultLevelChars[i] = (char)('0' + chInfo[i]);
-        }
-        String resultLevels = new String(resultLevelChars);
-        assertEquals("direction", expectedDir, resultDir);
-        assertEquals("levels", expectedLevels, resultLevels);
+        expectNativeBidi(REQ_DL,  ALEF + BET + GIMEL + " abc", "1111222", R);
     }
     
     //@SmallTest
     public void testNativeBidi() {
-        // native bidi returns levels, not simply directions
-        expectNativeBidi(REQ_DL,  ALEF + BET + GIMEL + " abc", "1111222", R);
+        expectNativeBidi(REQ_L,  ALEF + BET + GIMEL + " abc", "1110000", L);
     }
     
     private void expectNativeBidi(int dir, String text, 
diff --git a/core/tests/coretests/src/android/text/StaticLayoutDirectionsTest.java b/core/tests/coretests/src/android/text/StaticLayoutDirectionsTest.java
new file mode 100644
index 0000000..4fde849
--- /dev/null
+++ b/core/tests/coretests/src/android/text/StaticLayoutDirectionsTest.java
@@ -0,0 +1,243 @@
+/*
+ * 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.text;
+
+import android.text.Layout.Directions;
+import android.text.StaticLayoutTest.LayoutBuilder;
+
+import java.util.Arrays;
+import java.util.Formatter;
+
+import junit.framework.TestCase;
+
+public class StaticLayoutDirectionsTest extends TestCase {
+    private static final char ALEF = '\u05d0';
+
+    private static Directions dirs(int ... dirs) {
+        return new Directions(dirs);
+    }
+
+    // constants from Layout that are package-protected
+    private static final int RUN_LENGTH_MASK = 0x03ffffff;
+    private static final int RUN_LEVEL_SHIFT = 26;
+    private static final int RUN_LEVEL_MASK = 0x3f;
+    private static final int RUN_RTL_FLAG = 1 << RUN_LEVEL_SHIFT;
+
+    private static final Directions DIRS_ALL_LEFT_TO_RIGHT =
+        new Directions(new int[] { 0, RUN_LENGTH_MASK });
+    private static final Directions DIRS_ALL_RIGHT_TO_LEFT =
+        new Directions(new int[] { 0, RUN_LENGTH_MASK | RUN_RTL_FLAG });
+
+    private static final int LVL1_1 = 1 | (1 << RUN_LEVEL_SHIFT);
+    private static final int LVL2_1 = 1 | (2 << RUN_LEVEL_SHIFT);
+    private static final int LVL2_2 = 2 | (2 << RUN_LEVEL_SHIFT);
+
+    private static String[] texts = {
+        "",
+        " ",
+        "a",
+        "a1",
+        "aA",
+        "a1b",
+        "a1A",
+        "aA1",
+        "aAb",
+        "aA1B",
+        "aA1B2",
+
+        // rtl
+        "A",
+        "A1",
+        "Aa",
+        "A1B",
+        "A1a",
+        "Aa1",
+        "AaB"
+    };
+
+    // Expected directions are an array of start/length+level pairs,
+    // in visual order from the leading margin.
+    private static Directions[] expected = {
+        DIRS_ALL_LEFT_TO_RIGHT,
+        DIRS_ALL_LEFT_TO_RIGHT,
+        DIRS_ALL_LEFT_TO_RIGHT,
+        DIRS_ALL_LEFT_TO_RIGHT,
+        dirs(0, 1, 1, LVL1_1),
+        DIRS_ALL_LEFT_TO_RIGHT,
+        dirs(0, 2, 2, LVL1_1),
+        dirs(0, 1, 2, LVL2_1, 1, LVL1_1),
+        dirs(0, 1, 1, LVL1_1, 2, 1),
+        dirs(0, 1, 3, LVL1_1, 2, LVL2_1, 1, LVL1_1),
+        dirs(0, 1, 4, LVL2_1, 3, LVL1_1, 2, LVL2_1, 1, LVL1_1),
+
+        // rtl
+        DIRS_ALL_RIGHT_TO_LEFT,
+        dirs(0, LVL1_1, 1, LVL2_1),
+        dirs(0, LVL1_1, 1, LVL2_1),
+        dirs(0, LVL1_1, 1, LVL2_1, 2, LVL1_1),
+        dirs(0, LVL1_1, 1, LVL2_2),
+        dirs(0, LVL1_1, 1, LVL2_2),
+        dirs(0, LVL1_1, 1, LVL2_1, 2, LVL1_1),
+    };
+
+    private static String pseudoBidiToReal(String src) {
+        char[] chars = src.toCharArray();
+        for (int j = 0; j < chars.length; ++j) {
+            char c = chars[j];
+            if (c >= 'A' && c <= 'D') {
+                chars[j] = (char)(ALEF + c - 'A');
+            }
+        }
+
+        return new String(chars, 0, chars.length);
+    }
+
+    // @SmallTest
+    public void testDirections() {
+        StringBuilder buf = new StringBuilder("\n");
+        Formatter f = new Formatter(buf);
+
+        LayoutBuilder b = StaticLayoutTest.builder();
+        for (int i = 0; i < texts.length; ++i) {
+            b.setText(pseudoBidiToReal(texts[i]));
+            checkDirections(b.build(), i, b.text, expected, f);
+        }
+        if (buf.length() > 1) {
+            fail(buf.toString());
+        }
+    }
+
+    // @SmallTest
+    public void testTrailingWhitespace() {
+        LayoutBuilder b = StaticLayoutTest.builder();
+        b.setText(pseudoBidiToReal("Ab   c"));
+        float width = b.paint.measureText(b.text, 0, 5);  // exclude 'c'
+        b.setWidth(Math.round(width));
+        Layout l = b.build();
+        if (l.getLineCount() != 2) {
+            throw new RuntimeException("expected 2 lines, got: " + l.getLineCount());
+        }
+        Directions result = l.getLineDirections(0);
+        Directions expected = dirs(0, LVL1_1, 1, LVL2_1, 2, 3 | (1 << Layout.RUN_LEVEL_SHIFT));
+        expectDirections("split line", expected, result);
+    }
+
+    public void testNextToRightOf() {
+        LayoutBuilder b = StaticLayoutTest.builder();
+        b.setText(pseudoBidiToReal("aA1B2"));
+        // visual a2B1A positions 04321
+        // 0: |a2B1A, strong is sol, after -> 0
+        // 1: a|2B1A, strong is a, after ->, 1
+        // 2: a2|B1A, strong is B, after -> 4
+        // 3: a2B|1A, strong is B, before -> 3
+        // 4: a2B1|A, strong is A, after -> 2
+        // 5: a2B1A|, strong is eol, before -> 5
+        int[] expected = { 0, 1, 4, 3, 2, 5 };
+        Layout l = b.build();
+        int n = 0;
+        for (int i = 1; i < expected.length; ++i) {
+            int t = l.getOffsetToRightOf(n);
+            if (t != expected[i]) {
+                fail("offset[" + i + "] to right of: " + n + " expected: " +
+                        expected[i] + " got: " + t);
+            }
+            n = t;
+        }
+    }
+
+    public void testNextToLeftOf() {
+        LayoutBuilder b = StaticLayoutTest.builder();
+        b.setText(pseudoBidiToReal("aA1B2"));
+        int[] expected = { 0, 1, 4, 3, 2, 5 };
+        Layout l = b.build();
+        int n = 5;
+        for (int i = expected.length - 1; --i >= 0;) {
+            int t = l.getOffsetToLeftOf(n);
+            if (t != expected[i]) {
+                fail("offset[" + i + "] to left of: " + n + " expected: " +
+                        expected[i] + " got: " + t);
+            }
+            n = t;
+        }
+    }
+
+    // utility, not really a test
+    /*
+    public void testMeasureText1() {
+        LayoutBuilder b = StaticLayoutTest.builder();
+        String text = "ABC"; // "abAB"
+        b.setText(pseudoBidiToReal(text));
+        Layout l = b.build();
+        Directions directions = l.getLineDirections(0);
+
+        TextPaint workPaint = new TextPaint();
+
+        int dir = -1; // LEFT_TO_RIGHT
+        boolean trailing = true;
+        boolean alt = true;
+        do {
+            dir = -dir;
+            do {
+                trailing = !trailing;
+                for (int offset = 0, end = b.text.length(); offset <= end; ++offset) {
+                    float width = Layout.measureText(b.paint,
+                            workPaint,
+                            b.text,
+                            0, offset, end,
+                            dir, directions,
+                            trailing, false,
+                            null);
+                    Log.i("BIDI", "dir: " + dir + " trail: " + trailing +
+                            " offset: " + offset + " width: " + width);
+                }
+            } while (!trailing);
+        } while (dir > 0);
+    }
+    */
+
+    // utility for displaying arrays in hex
+    private static String hexArray(int[] array) {
+        StringBuilder sb = new StringBuilder();
+        sb.append('{');
+        for (int i : array) {
+            if (sb.length() > 1) {
+                sb.append(", ");
+            }
+            sb.append(Integer.toHexString(i));
+        }
+        sb.append('}');
+        return sb.toString();
+    }
+
+    private void checkDirections(Layout l, int i, String text,
+            Directions[] expectedDirs, Formatter f) {
+        Directions expected = expectedDirs[i];
+        Directions result = l.getLineDirections(0);
+        if (!Arrays.equals(expected.mDirections, result.mDirections)) {
+            f.format("%n[%2d] '%s', %s != %s", i, text,
+                    hexArray(expected.mDirections),
+                    hexArray(result.mDirections));
+        }
+    }
+
+    private void expectDirections(String msg, Directions expected, Directions result) {
+        if (!Arrays.equals(expected.mDirections, result.mDirections)) {
+            fail("expected: " + hexArray(expected.mDirections) +
+                    " got: " + hexArray(result.mDirections));
+        }
+    }
+}
diff --git a/core/tests/coretests/src/android/text/StaticLayoutTest.java b/core/tests/coretests/src/android/text/StaticLayoutTest.java
index 1f58a2c..d554a50 100644
--- a/core/tests/coretests/src/android/text/StaticLayoutTest.java
+++ b/core/tests/coretests/src/android/text/StaticLayoutTest.java
@@ -228,11 +228,11 @@
         }
     }
 
-    private static LayoutBuilder builder() {
+    /* package */ static LayoutBuilder builder() {
         return new LayoutBuilder();
     }
 
-    private static class LayoutBuilder {
+    /* package */ static class LayoutBuilder {
         String text = "This is a test";
         TextPaint paint = new TextPaint(); // default
         int width = 100;
diff --git a/core/tests/coretests/src/android/util/ExpandableListScenario.java b/core/tests/coretests/src/android/util/ExpandableListScenario.java
deleted file mode 100644
index 4a12b0d..0000000
--- a/core/tests/coretests/src/android/util/ExpandableListScenario.java
+++ /dev/null
@@ -1,386 +0,0 @@
-/*
- * Copyright (C) 2007 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.util.ArrayList;
-import java.util.List;
-
-import android.view.Gravity;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.AbsListView;
-import android.widget.BaseExpandableListAdapter;
-import android.widget.ExpandableListAdapter;
-import android.widget.ExpandableListView;
-import android.widget.ListView;
-import android.widget.TextView;
-
-/**
- * Utility base class for creating various Expandable List scenarios.
- * <p>
- * WARNING: A lot of the features are mixed between ListView's expected position
- * (flat list position) and an ExpandableListView's expected position.  You must add/change
- * features as you need them.
- * 
- * @see ListScenario
- */
-public abstract class ExpandableListScenario extends ListScenario {
-    protected ExpandableListAdapter mAdapter; 
-    protected List<MyGroup> mGroups;
-    
-    @Override
-    protected ListView createListView() {
-        return new ExpandableListView(this);
-    }
-
-    @Override
-    protected Params createParams() {
-        return new ExpandableParams();
-    }
-
-    @Override
-    protected void setAdapter(ListView listView) {
-        ((ExpandableListView) listView).setAdapter(mAdapter = createAdapter());
-    }
-    
-    protected ExpandableListAdapter createAdapter() {
-        return new MyAdapter();
-    }
-    
-    @Override
-    protected void readAndValidateParams(Params params) {
-        ExpandableParams expandableParams = (ExpandableParams) params;
-        
-        int[] numChildren = expandableParams.mNumChildren;
-        
-        mGroups = new ArrayList<MyGroup>(numChildren.length);
-        for (int i = 0; i < numChildren.length; i++) {
-            mGroups.add(new MyGroup(numChildren[i]));
-        }
-        
-        expandableParams.superSetNumItems();
-        
-        super.readAndValidateParams(params);
-    }
-
-    /**
-     * Get the ExpandableListView widget.
-     * @return The main widget.
-     */
-    public ExpandableListView getExpandableListView() {
-        return (ExpandableListView) super.getListView();
-    }
-
-    public static class ExpandableParams extends Params {
-        private int[] mNumChildren;
-        
-        /**
-         * Sets the number of children per group.
-         *  
-         * @param numChildrenPerGroup The number of children per group.
-         */
-        public ExpandableParams setNumChildren(int[] numChildren) {
-            mNumChildren = numChildren;
-            return this;
-        }
-
-        /**
-         * Sets the number of items on the superclass based on the number of
-         * groups and children per group.
-         */
-        private ExpandableParams superSetNumItems() {
-            int numItems = 0;
-            
-            if (mNumChildren != null) {
-                for (int i = mNumChildren.length - 1; i >= 0; i--) {
-                    numItems += mNumChildren[i];
-                }
-            }
-            
-            super.setNumItems(numItems);
-            
-            return this;
-        }
-        
-        @Override
-        public Params setNumItems(int numItems) {
-            throw new IllegalStateException("Use setNumGroups and setNumChildren instead.");
-        }
-
-        @Override
-        public ExpandableParams setFadingEdgeScreenSizeFactor(double fadingEdgeScreenSizeFactor) {
-            return (ExpandableParams) super.setFadingEdgeScreenSizeFactor(fadingEdgeScreenSizeFactor);
-        }
-
-        @Override
-        public ExpandableParams setItemScreenSizeFactor(double itemScreenSizeFactor) {
-            return (ExpandableParams) super.setItemScreenSizeFactor(itemScreenSizeFactor);
-        }
-
-        @Override
-        public ExpandableParams setItemsFocusable(boolean itemsFocusable) {
-            return (ExpandableParams) super.setItemsFocusable(itemsFocusable);
-        }
-
-        @Override
-        public ExpandableParams setMustFillScreen(boolean fillScreen) {
-            return (ExpandableParams) super.setMustFillScreen(fillScreen);
-        }
-
-        @Override
-        public ExpandableParams setPositionScreenSizeFactorOverride(int position, double itemScreenSizeFactor) {
-            return (ExpandableParams) super.setPositionScreenSizeFactorOverride(position, itemScreenSizeFactor);
-        }
-
-        @Override
-        public ExpandableParams setPositionUnselectable(int position) {
-            return (ExpandableParams) super.setPositionUnselectable(position);
-        }
-
-        @Override
-        public ExpandableParams setStackFromBottom(boolean stackFromBottom) {
-            return (ExpandableParams) super.setStackFromBottom(stackFromBottom);
-        }
-
-        @Override
-        public ExpandableParams setStartingSelectionPosition(int startingSelectionPosition) {
-            return (ExpandableParams) super.setStartingSelectionPosition(startingSelectionPosition);
-        }
-
-        @Override
-        public ExpandableParams setConnectAdapter(boolean connectAdapter) {
-            return (ExpandableParams) super.setConnectAdapter(connectAdapter);
-        }
-    }
-
-    /**
-     * Gets a string for the value of some item.
-     * @param packedPosition The position of the item.
-     * @return The string.
-     */
-    public final String getValueAtPosition(long packedPosition) {
-        final int type = ExpandableListView.getPackedPositionType(packedPosition);
-        
-        if (type == ExpandableListView.PACKED_POSITION_TYPE_CHILD) {
-            return mGroups.get(ExpandableListView.getPackedPositionGroup(packedPosition))
-                    .children.get(ExpandableListView.getPackedPositionChild(packedPosition))
-                    .name;
-        } else if (type == ExpandableListView.PACKED_POSITION_TYPE_GROUP) {
-            return mGroups.get(ExpandableListView.getPackedPositionGroup(packedPosition))
-                    .name;
-        } else {
-            throw new IllegalStateException("packedPosition is not a valid position.");
-        }
-    }
-
-    /**
-     * Whether a particular position is out of bounds.
-     * 
-     * @param packedPosition The packed position.
-     * @return Whether it's out of bounds.
-     */
-    private boolean isOutOfBounds(long packedPosition) {
-        final int type = ExpandableListView.getPackedPositionType(packedPosition);
-        
-        if (type == ExpandableListView.PACKED_POSITION_TYPE_NULL) {
-            throw new IllegalStateException("packedPosition is not a valid position.");
-        }
-
-        final int group = ExpandableListView.getPackedPositionGroup(packedPosition); 
-        if (group >= mGroups.size() || group < 0) {
-            return true;
-        }
-        
-        if (type == ExpandableListView.PACKED_POSITION_TYPE_CHILD) {
-            final int child = ExpandableListView.getPackedPositionChild(packedPosition); 
-            if (child >= mGroups.get(group).children.size() || child < 0) {
-                return true;
-            }
-        }
-        
-        return false;
-    }
-    
-    /**
-     * Gets a view for the packed position, possibly reusing the convertView.
-     * 
-     * @param packedPosition The position to get a view for.
-     * @param convertView Optional view to convert.
-     * @param parent The future parent.
-     * @return A view.
-     */
-    private View getView(long packedPosition, View convertView, ViewGroup parent) {
-        if (isOutOfBounds(packedPosition)) {
-            throw new IllegalStateException("position out of range for adapter!");
-        }
-        
-        final ExpandableListView elv = getExpandableListView();
-        final int flPos = elv.getFlatListPosition(packedPosition); 
-        
-        if (convertView != null) {
-            ((TextView) convertView).setText(getValueAtPosition(packedPosition));
-            convertView.setId(flPos);
-            return convertView;
-        }
-
-        int desiredHeight = getHeightForPosition(flPos);
-        return createView(packedPosition, flPos, parent, desiredHeight);
-    }
-    
-    /**
-     * Create a view for a group or child position.
-     * 
-     * @param packedPosition The packed position (has type, group pos, and optionally child pos).
-     * @param flPos The flat list position (the position that the ListView goes by).
-     * @param parent The parent view.
-     * @param desiredHeight The desired height.
-     * @return A view.
-     */
-    protected View createView(long packedPosition, int flPos, ViewGroup parent, int desiredHeight) {
-        TextView result = new TextView(parent.getContext());
-        result.setHeight(desiredHeight);
-        result.setText(getValueAtPosition(packedPosition));
-        final ViewGroup.LayoutParams lp = new AbsListView.LayoutParams(
-                ViewGroup.LayoutParams.MATCH_PARENT,
-                ViewGroup.LayoutParams.WRAP_CONTENT);
-        result.setLayoutParams(lp);
-        result.setGravity(Gravity.CENTER_VERTICAL);
-        result.setPadding(36, 0, 0, 0);
-        result.setId(flPos);
-        return result;
-    }
-    
-    /**
-     * Returns a group index containing either the number of children or at
-     * least one child.
-     * 
-     * @param numChildren The group must have this amount, or -1 if using
-     *            atLeastOneChild.
-     * @param atLeastOneChild The group must have at least one child, or false
-     *            if using numChildren.
-     * @return A group index with the requirements.
-     */
-    public int findGroupWithNumChildren(int numChildren, boolean atLeastOneChild) {
-        final ExpandableListAdapter adapter = mAdapter;
-        
-        for (int i = adapter.getGroupCount() - 1; i >= 0; i--) {
-            final int curNumChildren = adapter.getChildrenCount(i);
-            
-            if (numChildren == curNumChildren || atLeastOneChild && curNumChildren > 0) {
-                return i;
-            }
-        }
-        
-        return -1;
-    }
-    
-    public List<MyGroup> getGroups() {
-        return mGroups;
-    }
-    
-    public ExpandableListAdapter getAdapter() {
-        return mAdapter;
-    }
-
-    /**
-     * Simple expandable list adapter.
-     */
-    protected class MyAdapter extends BaseExpandableListAdapter {
-        public Object getChild(int groupPosition, int childPosition) {
-            return getValueAtPosition(ExpandableListView.getPackedPositionForChild(groupPosition,
-                    childPosition));
-        }
-
-        public long getChildId(int groupPosition, int childPosition) {
-            return mGroups.get(groupPosition).children.get(childPosition).id;
-        }
-
-        public int getChildrenCount(int groupPosition) {
-            return mGroups.get(groupPosition).children.size();
-        }
-
-        public View getChildView(int groupPosition, int childPosition, boolean isLastChild,
-                View convertView, ViewGroup parent) {
-            return getView(ExpandableListView.getPackedPositionForChild(groupPosition,
-                    childPosition), convertView, parent);
-        }
-
-        public Object getGroup(int groupPosition) {
-            return getValueAtPosition(ExpandableListView.getPackedPositionForGroup(groupPosition));
-        }
-
-        public int getGroupCount() {
-            return mGroups.size();
-        }
-
-        public long getGroupId(int groupPosition) {
-            return mGroups.get(groupPosition).id;
-        }
-
-        public View getGroupView(int groupPosition, boolean isExpanded, View convertView,
-                ViewGroup parent) {
-            return getView(ExpandableListView.getPackedPositionForGroup(groupPosition),
-                    convertView, parent);
-        }
-
-        public boolean isChildSelectable(int groupPosition, int childPosition) {
-            return true;
-        }
-
-        public boolean hasStableIds() {
-            return true;
-        }
-        
-    }
-
-    public static class MyGroup {
-        private static long mNextId = 1000;
-        
-        String name;
-        long id = mNextId++;
-        List<MyChild> children;
-        
-        public MyGroup(int numChildren) {
-            name = "Group " + id;
-            children = new ArrayList<MyChild>(numChildren);
-            for (int i = 0; i < numChildren; i++) {
-                children.add(new MyChild());
-            }
-        }
-    }
-    
-    public static class MyChild {
-        private static long mNextId = 2000;
-        
-        String name;
-        long id = mNextId++;
-        
-        public MyChild() {
-            name = "Child " + id;
-        }
-    }
-    
-    @Override
-    protected final void init(Params params) {
-        init((ExpandableParams) params);
-    }
-
-    /**
-     * @see ListScenario#init
-     */
-    protected abstract void init(ExpandableParams params);
-}
diff --git a/core/tests/coretests/src/android/widget/expandablelistview/ExpandableListBasicTest.java b/core/tests/coretests/src/android/widget/expandablelistview/ExpandableListBasicTest.java
deleted file mode 100644
index e23b516..0000000
--- a/core/tests/coretests/src/android/widget/expandablelistview/ExpandableListBasicTest.java
+++ /dev/null
@@ -1,144 +0,0 @@
-/*
- * Copyright (C) 2007 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.widget.expandablelistview;
-
-import android.test.ActivityInstrumentationTestCase2;
-import android.test.suitebuilder.annotation.MediumTest;
-import android.util.ExpandableListScenario;
-import android.util.ListUtil;
-import android.util.ExpandableListScenario.MyGroup;
-import android.view.KeyEvent;
-import android.widget.BaseExpandableListAdapter;
-import android.widget.ExpandableListAdapter;
-import android.widget.ExpandableListView;
-
-import java.util.List;
-
-public class ExpandableListBasicTest extends ActivityInstrumentationTestCase2<ExpandableListSimple> {
-    private ExpandableListScenario mActivity;
-    private ExpandableListView mExpandableListView;
-    private ExpandableListAdapter mAdapter;
-    private ListUtil mListUtil;
-    
-    public ExpandableListBasicTest() {
-        super(ExpandableListSimple.class);
-    }
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        
-        mActivity = getActivity();
-        mExpandableListView = mActivity.getExpandableListView();
-        mAdapter = mExpandableListView.getExpandableListAdapter();
-        mListUtil = new ListUtil(mExpandableListView, getInstrumentation());
-    }
-    
-    @MediumTest
-    public void testPreconditions() {
-        assertNotNull(mActivity);
-        assertNotNull(mExpandableListView);
-    }
-    
-    private int expandGroup(int numChildren, boolean atLeastOneChild) {
-        final int groupPos = mActivity.findGroupWithNumChildren(numChildren, atLeastOneChild);
-        assertTrue("Could not find group to expand", groupPos >= 0);
-
-        assertFalse("Group is already expanded", mExpandableListView.isGroupExpanded(groupPos));
-        mListUtil.arrowScrollToSelectedPosition(groupPos);
-        getInstrumentation().waitForIdleSync();
-        sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
-        getInstrumentation().waitForIdleSync();
-        assertTrue("Group did not expand", mExpandableListView.isGroupExpanded(groupPos));
-
-        return groupPos;
-    }
-
-    @MediumTest
-    public void testExpandGroup() {
-        expandGroup(-1, true);
-    }
-    
-    @MediumTest
-    public void testCollapseGroup() {
-        final int groupPos = expandGroup(-1, true);
-        
-        sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
-        getInstrumentation().waitForIdleSync();
-        assertFalse("Group did not collapse", mExpandableListView.isGroupExpanded(groupPos));
-    }
-    
-    @MediumTest
-    public void testExpandedGroupMovement() {
-        // Expand the first group
-        mListUtil.arrowScrollToSelectedPosition(0);
-        sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
-        getInstrumentation().waitForIdleSync();
-
-        // Ensure it expanded
-        assertTrue("Group did not expand", mExpandableListView.isGroupExpanded(0));
-        
-        // Wait until that's all good
-        getInstrumentation().waitForIdleSync();
-        
-        // Make sure it expanded
-        assertTrue("Group did not expand", mExpandableListView.isGroupExpanded(0));
-        
-        // Insert a collapsed group in front of the one just expanded
-        List<MyGroup> groups = mActivity.getGroups();
-        MyGroup insertedGroup = new MyGroup(1);
-        groups.add(0, insertedGroup);
-        
-        // Notify data change
-        assertTrue("Adapter is not an instance of the base adapter",
-                mAdapter instanceof BaseExpandableListAdapter);
-        final BaseExpandableListAdapter adapter = (BaseExpandableListAdapter) mAdapter;
-     
-        mActivity.runOnUiThread(new Runnable() {
-            public void run() {
-                adapter.notifyDataSetChanged();
-            }
-        });
-        getInstrumentation().waitForIdleSync();
-        
-        // Make sure the right group is expanded
-        assertTrue("The expanded state didn't stay with the proper group",
-                mExpandableListView.isGroupExpanded(1));
-        assertFalse("The expanded state was given to the inserted group",
-                mExpandableListView.isGroupExpanded(0));
-    }
-
-    @MediumTest
-    public void testContextMenus() {
-        ExpandableListTester tester = new ExpandableListTester(mExpandableListView, this);
-        tester.testContextMenus();
-    }
-
-    @MediumTest
-    public void testConvertionBetweenFlatAndPacked() {
-        ExpandableListTester tester = new ExpandableListTester(mExpandableListView, this);
-        tester.testConvertionBetweenFlatAndPackedOnGroups();
-        tester.testConvertionBetweenFlatAndPackedOnChildren();
-    }
-
-    @MediumTest
-    public void testSelectedPosition() {
-        ExpandableListTester tester = new ExpandableListTester(mExpandableListView, this);
-        tester.testSelectedPositionOnGroups();
-        tester.testSelectedPositionOnChildren();
-    }
-}
diff --git a/core/tests/coretests/src/android/widget/expandablelistview/ExpandableListSimple.java b/core/tests/coretests/src/android/widget/expandablelistview/ExpandableListSimple.java
deleted file mode 100644
index 78db28c..0000000
--- a/core/tests/coretests/src/android/widget/expandablelistview/ExpandableListSimple.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (C) 2007 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.widget.expandablelistview;
-
-import android.view.Menu;
-import android.view.MenuItem;
-import android.view.MenuItem.OnMenuItemClickListener;
-import android.widget.BaseExpandableListAdapter;
-
-import android.util.ExpandableListScenario;
-
-public class ExpandableListSimple extends ExpandableListScenario {
-    private static final int[] NUM_CHILDREN = {4, 3, 2, 1, 0};
-
-    @Override
-    protected void init(ExpandableParams params) {
-        params.setNumChildren(NUM_CHILDREN)
-                .setItemScreenSizeFactor(0.14);
-    }
-
-    @Override
-    public boolean onCreateOptionsMenu(Menu menu) {
-
-        menu.add("Add item").setOnMenuItemClickListener(new OnMenuItemClickListener() {
-            public boolean onMenuItemClick(MenuItem item) {
-                mGroups.add(0, new MyGroup(2));
-                ((BaseExpandableListAdapter) mAdapter).notifyDataSetChanged();
-                return true;
-            }
-        });
-        
-        return true;
-    }
-    
-}
diff --git a/core/tests/coretests/src/android/widget/expandablelistview/ExpandableListTester.java b/core/tests/coretests/src/android/widget/expandablelistview/ExpandableListTester.java
deleted file mode 100644
index dfb10fb..0000000
--- a/core/tests/coretests/src/android/widget/expandablelistview/ExpandableListTester.java
+++ /dev/null
@@ -1,241 +0,0 @@
-/*
- * Copyright (C) 2007 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.widget.expandablelistview;
-
-import android.app.Instrumentation;
-import android.test.ActivityInstrumentationTestCase2;
-import android.util.ExpandableListScenario;
-import android.util.ListUtil;
-import android.view.KeyEvent;
-import android.view.View;
-import android.widget.ExpandableListAdapter;
-import android.widget.ExpandableListView;
-
-import junit.framework.Assert;
-
-public class ExpandableListTester {
-    private final ExpandableListView mExpandableListView;
-    private final ExpandableListAdapter mAdapter;
-    private final ListUtil mListUtil;
-
-    private final ActivityInstrumentationTestCase2<? extends ExpandableListScenario>
-        mActivityInstrumentation;
-
-    Instrumentation mInstrumentation;
-
-    public ExpandableListTester(
-            ExpandableListView expandableListView,
-            ActivityInstrumentationTestCase2<? extends ExpandableListScenario>
-            activityInstrumentation) {
-        mExpandableListView = expandableListView;
-        Instrumentation instrumentation = activityInstrumentation.getInstrumentation();
-        mListUtil = new ListUtil(mExpandableListView, instrumentation);
-        mAdapter = mExpandableListView.getExpandableListAdapter();
-        mActivityInstrumentation = activityInstrumentation;
-        mInstrumentation = mActivityInstrumentation.getInstrumentation();
-    }
-
-    private void expandGroup(final int groupIndex, int flatPosition) {
-        Assert.assertFalse("Group is already expanded", mExpandableListView
-                .isGroupExpanded(groupIndex));
-        mListUtil.arrowScrollToSelectedPosition(flatPosition);
-        mInstrumentation.waitForIdleSync();
-        mActivityInstrumentation.sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
-        mActivityInstrumentation.getInstrumentation().waitForIdleSync();
-        Assert.assertTrue("Group did not expand " + groupIndex, 
-                mExpandableListView.isGroupExpanded(groupIndex));
-    }
-
-    void testContextMenus() {
-        // Add a position tester ContextMenu listener to the ExpandableListView
-        PositionTesterContextMenuListener menuListener = new PositionTesterContextMenuListener();
-        mExpandableListView.setOnCreateContextMenuListener(menuListener);
-
-        int index = 0;
-
-        // Scrolling on header elements should trigger an AdapterContextMenu
-        for (int i=0; i<mExpandableListView.getHeaderViewsCount(); i++) {
-            // Check group index in context menu
-            menuListener.expectAdapterContextMenu(i);
-            // Make sure the group is visible so that getChild finds it
-            mListUtil.arrowScrollToSelectedPosition(index);
-            View headerChild = mExpandableListView.getChildAt(index
-                    - mExpandableListView.getFirstVisiblePosition());
-            mExpandableListView.showContextMenuForChild(headerChild);
-            mInstrumentation.waitForIdleSync();
-            Assert.assertNull(menuListener.getErrorMessage(), menuListener.getErrorMessage());
-            index++;
-        }
-
-        int groupCount = mAdapter.getGroupCount();
-        for (int groupIndex = 0; groupIndex < groupCount; groupIndex++) {
-
-            // Expand group
-            expandGroup(groupIndex, index);
-
-            // Check group index in context menu
-            menuListener.expectGroupContextMenu(groupIndex);
-            // Make sure the group is visible so that getChild finds it
-            mListUtil.arrowScrollToSelectedPosition(index);
-            View groupChild = mExpandableListView.getChildAt(index
-                    - mExpandableListView.getFirstVisiblePosition());
-            mExpandableListView.showContextMenuForChild(groupChild);
-            mInstrumentation.waitForIdleSync();
-            Assert.assertNull(menuListener.getErrorMessage(), menuListener.getErrorMessage());
-            index++;
-
-            final int childrenCount = mAdapter.getChildrenCount(groupIndex);
-            for (int childIndex = 0; childIndex < childrenCount; childIndex++) {
-                // Check child index in context menu
-                mListUtil.arrowScrollToSelectedPosition(index);
-                menuListener.expectChildContextMenu(groupIndex, childIndex);
-                View child = mExpandableListView.getChildAt(index
-                        - mExpandableListView.getFirstVisiblePosition());
-                mExpandableListView.showContextMenuForChild(child);
-                mInstrumentation.waitForIdleSync();
-                Assert.assertNull(menuListener.getErrorMessage(), menuListener.getErrorMessage());
-                index++;
-            }
-        }
-
-        // Scrolling on footer elements should trigger an AdapterContextMenu
-        for (int i=0; i<mExpandableListView.getFooterViewsCount(); i++) {
-            // Check group index in context menu
-            menuListener.expectAdapterContextMenu(index);
-            // Make sure the group is visible so that getChild finds it
-            mListUtil.arrowScrollToSelectedPosition(index);
-            View footerChild = mExpandableListView.getChildAt(index
-                    - mExpandableListView.getFirstVisiblePosition());
-            mExpandableListView.showContextMenuForChild(footerChild);
-            mInstrumentation.waitForIdleSync();
-            Assert.assertNull(menuListener.getErrorMessage(), menuListener.getErrorMessage());
-            index++;
-        }
-
-        // Cleanup: remove the listener we added.
-        mExpandableListView.setOnCreateContextMenuListener(null);
-    }
-
-    private int expandAGroup() {
-        final int groupIndex = 2;
-        final int headerCount = mExpandableListView.getHeaderViewsCount();
-        Assert.assertTrue("Not enough groups", groupIndex < mAdapter.getGroupCount());
-        expandGroup(groupIndex, groupIndex + headerCount);
-        return groupIndex;
-    }
-
-    // This method assumes that NO group is expanded when called
-    void testConvertionBetweenFlatAndPackedOnGroups() {
-        final int headerCount = mExpandableListView.getHeaderViewsCount();
-
-        for (int i=0; i<headerCount; i++) {
-            Assert.assertEquals("Non NULL position for header item",
-                    ExpandableListView.PACKED_POSITION_VALUE_NULL,
-                    mExpandableListView.getExpandableListPosition(i));
-        }
-
-        // Test all (non expanded) groups
-        final int groupCount = mAdapter.getGroupCount();
-        for (int groupIndex = 0; groupIndex < groupCount; groupIndex++) {
-            int expectedFlatPosition = headerCount + groupIndex;
-            long packedPositionForGroup = ExpandableListView.getPackedPositionForGroup(groupIndex);
-            Assert.assertEquals("Group not found at flat position " + expectedFlatPosition,
-                    packedPositionForGroup,
-                    mExpandableListView.getExpandableListPosition(expectedFlatPosition));
-
-            Assert.assertEquals("Wrong flat position for group " + groupIndex,
-                    expectedFlatPosition,
-                    mExpandableListView.getFlatListPosition(packedPositionForGroup));
-        }
-
-        for (int i=0; i<mExpandableListView.getFooterViewsCount(); i++) {
-            Assert.assertEquals("Non NULL position for header item",
-                    ExpandableListView.PACKED_POSITION_VALUE_NULL,
-                    mExpandableListView.getExpandableListPosition(headerCount + groupCount + i));
-        }
-    }
-
-    // This method assumes that NO group is expanded when called
-    void testConvertionBetweenFlatAndPackedOnChildren() {
-        // Test with an expanded group
-        final int headerCount = mExpandableListView.getHeaderViewsCount();
-        final int groupIndex = expandAGroup();
-
-        final int childrenCount = mAdapter.getChildrenCount(groupIndex);
-        for (int childIndex = 0; childIndex < childrenCount; childIndex++) {
-            int expectedFlatPosition = headerCount + groupIndex + 1 + childIndex;
-            long childPos = ExpandableListView.getPackedPositionForChild(groupIndex, childIndex);
-
-            Assert.assertEquals("Wrong flat position for child ",
-                    childPos,
-                    mExpandableListView.getExpandableListPosition(expectedFlatPosition));
-
-            Assert.assertEquals("Wrong flat position for child ",
-                    expectedFlatPosition,
-                    mExpandableListView.getFlatListPosition(childPos));
-        }
-    }
-
-    // This method assumes that NO group is expanded when called
-    void testSelectedPositionOnGroups() {
-        int index = 0;
-
-        // Scrolling on header elements should not give a valid selected position.
-        for (int i=0; i<mExpandableListView.getHeaderViewsCount(); i++) {
-            mListUtil.arrowScrollToSelectedPosition(index);
-            Assert.assertEquals("Header item is selected",
-                    ExpandableListView.PACKED_POSITION_VALUE_NULL,
-                    mExpandableListView.getSelectedPosition());
-            index++;
-        }
-
-        // Check selection on group items
-        final int groupCount = mAdapter.getGroupCount();
-        for (int groupIndex = 0; groupIndex < groupCount; groupIndex++) {
-            mListUtil.arrowScrollToSelectedPosition(index);
-            Assert.assertEquals("Group item is not selected",
-                    ExpandableListView.getPackedPositionForGroup(groupIndex),
-                    mExpandableListView.getSelectedPosition());
-            index++;
-        }
-
-        // Scrolling on footer elements should not give a valid selected position.
-        for (int i=0; i<mExpandableListView.getFooterViewsCount(); i++) {
-            mListUtil.arrowScrollToSelectedPosition(index);
-            Assert.assertEquals("Footer item is selected",
-                    ExpandableListView.PACKED_POSITION_VALUE_NULL,
-                    mExpandableListView.getSelectedPosition());
-            index++;
-        }
-    }
-
-    // This method assumes that NO group is expanded when called
-    void testSelectedPositionOnChildren() {
-        // Test with an expanded group
-        final int headerCount = mExpandableListView.getHeaderViewsCount();
-        final int groupIndex = expandAGroup();
-
-        final int childrenCount = mAdapter.getChildrenCount(groupIndex);
-        for (int childIndex = 0; childIndex < childrenCount; childIndex++) {
-            int childFlatPosition = headerCount + groupIndex + 1 + childIndex;
-            mListUtil.arrowScrollToSelectedPosition(childFlatPosition);
-            Assert.assertEquals("Group item is not selected",
-                    ExpandableListView.getPackedPositionForChild(groupIndex, childIndex),
-                    mExpandableListView.getSelectedPosition());
-        }
-    }
-}
diff --git a/core/tests/coretests/src/android/widget/expandablelistview/ExpandableListWithHeaders.java b/core/tests/coretests/src/android/widget/expandablelistview/ExpandableListWithHeaders.java
deleted file mode 100644
index 2251c1d..0000000
--- a/core/tests/coretests/src/android/widget/expandablelistview/ExpandableListWithHeaders.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2007 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.widget.expandablelistview;
-
-import android.os.Bundle;
-import android.util.ExpandableListScenario;
-import android.widget.Button;
-import android.widget.ExpandableListView;
-
-public class ExpandableListWithHeaders extends ExpandableListScenario {
-    private static final int[] sNumChildren = {1, 4, 3, 2, 6};
-    private static final int sNumOfHeadersAndFooters = 12;
-    
-    @Override
-    protected void init(ExpandableParams params) {
-        params.setStackFromBottom(false)
-                .setStartingSelectionPosition(-1)
-                .setNumChildren(sNumChildren)
-                .setItemScreenSizeFactor(0.14)
-                .setConnectAdapter(false);
-    }
-
-    @Override
-    protected void onCreate(Bundle icicle) {
-        super.onCreate(icicle);
-
-        final ExpandableListView expandableListView = getExpandableListView();
-        expandableListView.setItemsCanFocus(true);
-
-        for (int i = 0; i < sNumOfHeadersAndFooters; i++) {
-            Button header = new Button(this);
-            header.setText("Header View " + i);
-            expandableListView.addHeaderView(header);
-        }
-
-        for (int i = 0; i < sNumOfHeadersAndFooters; i++) {
-            Button footer = new Button(this);
-            footer.setText("Footer View " + i);
-            expandableListView.addFooterView(footer);
-        }
-        
-        // Set adapter here AFTER we set header and footer views
-        setAdapter(expandableListView);
-    }
-    
-    /**
-     * @return The number of headers (and the same number of footers)
-     */
-    public int getNumOfHeadersAndFooters() {
-        return sNumOfHeadersAndFooters;
-    }
-
-}
diff --git a/core/tests/coretests/src/android/widget/expandablelistview/ExpandableListWithHeadersTest.java b/core/tests/coretests/src/android/widget/expandablelistview/ExpandableListWithHeadersTest.java
deleted file mode 100644
index 64a0fff..0000000
--- a/core/tests/coretests/src/android/widget/expandablelistview/ExpandableListWithHeadersTest.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Copyright (C) 2007 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.widget.expandablelistview;
-
-import android.test.ActivityInstrumentationTestCase2;
-import android.test.suitebuilder.annotation.LargeTest;
-import android.test.suitebuilder.annotation.MediumTest;
-import android.util.ListUtil;
-import android.view.KeyEvent;
-import android.widget.ExpandableListView;
-
-public class ExpandableListWithHeadersTest extends
-        ActivityInstrumentationTestCase2<ExpandableListWithHeaders> {
-    private ExpandableListView mExpandableListView;
-    private ListUtil mListUtil;
-    
-    public ExpandableListWithHeadersTest() {
-        super(ExpandableListWithHeaders.class);
-    }
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        
-        mExpandableListView = getActivity().getExpandableListView();
-        mListUtil = new ListUtil(mExpandableListView, getInstrumentation());
-    }
-    
-    @MediumTest
-    public void testPreconditions() {
-        assertNotNull(mExpandableListView);
-    }
-    
-    @MediumTest
-    public void testExpandOnFirstPosition() {
-        // Should be a header, and hence the first group should NOT have expanded
-        mListUtil.arrowScrollToSelectedPosition(0);
-        sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
-        getInstrumentation().waitForIdleSync();
-        assertFalse(mExpandableListView.isGroupExpanded(0));
-    }
-
-    @LargeTest
-    public void testExpandOnFirstGroup() {
-        mListUtil.arrowScrollToSelectedPosition(getActivity().getNumOfHeadersAndFooters());
-        sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
-        getInstrumentation().waitForIdleSync();
-        assertTrue(mExpandableListView.isGroupExpanded(0));
-    }
-
-    @MediumTest
-    public void testContextMenus() {
-        ExpandableListTester tester = new ExpandableListTester(mExpandableListView, this);
-        tester.testContextMenus();
-    }
-
-    @MediumTest
-    public void testConvertionBetweenFlatAndPacked() {
-        ExpandableListTester tester = new ExpandableListTester(mExpandableListView, this);
-        tester.testConvertionBetweenFlatAndPackedOnGroups();
-        tester.testConvertionBetweenFlatAndPackedOnChildren();
-    }
-
-    @MediumTest
-    public void testSelectedPosition() {
-        ExpandableListTester tester = new ExpandableListTester(mExpandableListView, this);
-        tester.testSelectedPositionOnGroups();
-        tester.testSelectedPositionOnChildren();
-    }
-}
diff --git a/core/tests/coretests/src/android/widget/expandablelistview/InflatedExpandableListView.java b/core/tests/coretests/src/android/widget/expandablelistview/InflatedExpandableListView.java
deleted file mode 100644
index f4c9d56..0000000
--- a/core/tests/coretests/src/android/widget/expandablelistview/InflatedExpandableListView.java
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * Copyright (C) 2007 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.widget.expandablelistview;
-
-import com.android.frameworks.coretests.R;
-
-import android.app.Activity;
-import android.os.Bundle;
-import android.view.Gravity;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.AbsListView;
-import android.widget.BaseExpandableListAdapter;
-import android.widget.ExpandableListView;
-import android.widget.TextView;
-
-public class InflatedExpandableListView extends Activity {
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        
-        setContentView(R.layout.inflated_expandablelistview);
-        
-        ExpandableListView elv = (ExpandableListView) findViewById(R.id.elv);
-        elv.setAdapter(new MyExpandableListAdapter());
-    }
-
-    public class MyExpandableListAdapter extends BaseExpandableListAdapter {
-        // Sample data set.  children[i] contains the children (String[]) for groups[i].
-        private String[] groups = { "People Names", "Dog Names", "Cat Names", "Fish Names" };
-        private String[][] children = {
-                { "Arnold", "Barry", "Chuck", "David" },
-                { "Ace", "Bandit", "Cha-Cha", "Deuce" },
-                { "Fluffy", "Snuggles" },
-                { "Goldy", "Bubbles" }
-        };
-        
-        public Object getChild(int groupPosition, int childPosition) {
-            return children[groupPosition][childPosition];
-        }
-
-        public long getChildId(int groupPosition, int childPosition) {
-            return childPosition;
-        }
-
-        public int getChildrenCount(int groupPosition) {
-            return children[groupPosition].length;
-        }
-
-        public TextView getGenericView() {
-            // Layout parameters for the ExpandableListView
-            AbsListView.LayoutParams lp = new AbsListView.LayoutParams(
-                    ViewGroup.LayoutParams.MATCH_PARENT, 64);
-
-            TextView textView = new TextView(InflatedExpandableListView.this);
-            textView.setLayoutParams(lp);
-            // Center the text vertically
-            textView.setGravity(Gravity.CENTER_VERTICAL | Gravity.LEFT);
-            // Set the text starting position
-            textView.setPadding(36, 0, 0, 0);
-            return textView;
-        }
-        
-        public View getChildView(int groupPosition, int childPosition, boolean isLastChild,
-                View convertView, ViewGroup parent) {
-            TextView textView = getGenericView();
-            textView.setText(getChild(groupPosition, childPosition).toString());
-            return textView;
-        }
-
-        public Object getGroup(int groupPosition) {
-            return groups[groupPosition];
-        }
-
-        public int getGroupCount() {
-            return groups.length;
-        }
-
-        public long getGroupId(int groupPosition) {
-            return groupPosition;
-        }
-
-        public View getGroupView(int groupPosition, boolean isExpanded, View convertView,
-                ViewGroup parent) {
-            TextView textView = getGenericView();
-            textView.setText(getGroup(groupPosition).toString());
-            return textView;
-        }
-
-        public boolean isChildSelectable(int groupPosition, int childPosition) {
-            return true;
-        }
-
-        public boolean hasStableIds() {
-            return true;
-        }
-
-    }
-    
-}
diff --git a/core/tests/coretests/src/android/widget/expandablelistview/PositionTesterContextMenuListener.java b/core/tests/coretests/src/android/widget/expandablelistview/PositionTesterContextMenuListener.java
deleted file mode 100644
index 2dbdff8..0000000
--- a/core/tests/coretests/src/android/widget/expandablelistview/PositionTesterContextMenuListener.java
+++ /dev/null
@@ -1,111 +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.
- */
-
-package android.widget.expandablelistview;
-
-import android.view.ContextMenu;
-import android.view.View;
-import android.view.ContextMenu.ContextMenuInfo;
-import android.view.View.OnCreateContextMenuListener;
-import android.widget.ExpandableListView;
-import android.widget.AdapterView.AdapterContextMenuInfo;
-
-public class PositionTesterContextMenuListener implements OnCreateContextMenuListener {
-
-    private int groupPosition, childPosition;
-
-    // Fake constant to store in testType a test type specific to headers and footers
-    private static final int ADAPTER_TYPE = -1;
-    private int testType; // as returned by getPackedPositionType
-
-    // Will be set to null by each call to onCreateContextMenu, unless an error occurred. 
-    private String errorMessage;
-
-    public void expectGroupContextMenu(int groupPosition) {
-        this.groupPosition = groupPosition;
-        testType = ExpandableListView.PACKED_POSITION_TYPE_GROUP;
-    }
-
-    public void expectChildContextMenu(int groupPosition, int childPosition) {
-        this.groupPosition = groupPosition;
-        this.childPosition = childPosition;
-        testType = ExpandableListView.PACKED_POSITION_TYPE_CHILD;
-    }
-
-    public void expectAdapterContextMenu(int flatPosition) {
-        this.groupPosition = flatPosition;
-        testType = ADAPTER_TYPE;
-    }
-
-    public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
-        errorMessage = null;
-        if (testType == ADAPTER_TYPE) {
-            if (!isTrue("MenuInfo is not an AdapterContextMenuInfo",
-                    menuInfo instanceof AdapterContextMenuInfo)) {
-                return;
-            }
-            AdapterContextMenuInfo adapterContextMenuInfo = (AdapterContextMenuInfo) menuInfo;
-            if (!areEqual("Wrong flat position", groupPosition, adapterContextMenuInfo.position)) {
-                return;
-            }
-        } else {
-            if (!isTrue("MenuInfo is not an ExpandableListContextMenuInfo",
-                    menuInfo instanceof ExpandableListView.ExpandableListContextMenuInfo)) {
-                return;
-            }
-            ExpandableListView.ExpandableListContextMenuInfo elvMenuInfo =
-                (ExpandableListView.ExpandableListContextMenuInfo) menuInfo;
-            long packedPosition = elvMenuInfo.packedPosition;
-
-            int packedPositionType = ExpandableListView.getPackedPositionType(packedPosition);
-            if (!areEqual("Wrong packed position type", testType, packedPositionType)) {
-                return;
-            }
-
-            int packedPositionGroup = ExpandableListView.getPackedPositionGroup(packedPosition);
-            if (!areEqual("Wrong group position", groupPosition, packedPositionGroup)) {
-                return;
-            }
-
-            if (testType == ExpandableListView.PACKED_POSITION_TYPE_CHILD) {
-                int packedPositionChild = ExpandableListView.getPackedPositionChild(packedPosition);
-                if (!areEqual("Wrong child position", childPosition, packedPositionChild)) {
-                    return;
-                }
-            }
-        }
-    }
-
-    private boolean areEqual(String message, int expected, int actual) {
-        if (expected != actual) {
-            errorMessage = String.format(message + " (%d vs %d", expected, actual);
-            return false;
-        }
-        return true;
-    }
-
-    private boolean isTrue(String message, boolean value) {
-        if (!value) {
-            errorMessage = message;
-            return false;
-        }
-        return true;
-    }
-
-    public String getErrorMessage() {
-        return errorMessage;
-    }
-}
diff --git a/docs/html/images/testing/eclipse_new_android_project_complete_callouts.png b/docs/html/images/testing/eclipse_new_android_project_complete_callouts.png
new file mode 100644
index 0000000..2e15232
--- /dev/null
+++ b/docs/html/images/testing/eclipse_new_android_project_complete_callouts.png
Binary files differ
diff --git a/docs/html/images/testing/eclipse_new_android_testproject_complete_callouts.png b/docs/html/images/testing/eclipse_new_android_testproject_complete_callouts.png
new file mode 100644
index 0000000..b287c65
--- /dev/null
+++ b/docs/html/images/testing/eclipse_new_android_testproject_complete_callouts.png
Binary files differ
diff --git a/docs/html/images/testing/hwtest_create_test_class_menu_callouts.png b/docs/html/images/testing/hwtest_create_test_class_menu_callouts.png
new file mode 100644
index 0000000..cf7de04
--- /dev/null
+++ b/docs/html/images/testing/hwtest_create_test_class_menu_callouts.png
Binary files differ
diff --git a/docs/html/images/testing/hwtest_junit_success.png b/docs/html/images/testing/hwtest_junit_success.png
new file mode 100644
index 0000000..7872dfc
--- /dev/null
+++ b/docs/html/images/testing/hwtest_junit_success.png
Binary files differ
diff --git a/docs/html/images/testing/hwtest_new_test_class_dialog_complete_callouts.png b/docs/html/images/testing/hwtest_new_test_class_dialog_complete_callouts.png
new file mode 100644
index 0000000..312c5ad
--- /dev/null
+++ b/docs/html/images/testing/hwtest_new_test_class_dialog_complete_callouts.png
Binary files differ
diff --git a/docs/html/images/testing/hwtest_new_test_project_dialog_complete_callouts.png b/docs/html/images/testing/hwtest_new_test_project_dialog_complete_callouts.png
new file mode 100644
index 0000000..2f9c617
--- /dev/null
+++ b/docs/html/images/testing/hwtest_new_test_project_dialog_complete_callouts.png
Binary files differ
diff --git a/docs/html/images/testing/hwtest_new_test_project_menu.png b/docs/html/images/testing/hwtest_new_test_project_menu.png
new file mode 100644
index 0000000..3f9a6db
--- /dev/null
+++ b/docs/html/images/testing/hwtest_new_test_project_menu.png
Binary files differ
diff --git a/docs/html/images/testing/hwtest_runas_menu_callouts.png b/docs/html/images/testing/hwtest_runas_menu_callouts.png
new file mode 100644
index 0000000..78e4fbf
--- /dev/null
+++ b/docs/html/images/testing/hwtest_runas_menu_callouts.png
Binary files differ
diff --git a/docs/html/images/testing/spinner_create_test_class_callouts.png b/docs/html/images/testing/spinner_create_test_class_callouts.png
new file mode 100644
index 0000000..ff8247a
--- /dev/null
+++ b/docs/html/images/testing/spinner_create_test_class_callouts.png
Binary files differ
diff --git a/docs/html/images/testing/spinnertest_junit_panel.png b/docs/html/images/testing/spinnertest_junit_panel.png
new file mode 100644
index 0000000..d6af368
--- /dev/null
+++ b/docs/html/images/testing/spinnertest_junit_panel.png
Binary files differ
diff --git a/docs/html/images/testing/spinnertest_junit_panel_fail_callouts.png b/docs/html/images/testing/spinnertest_junit_panel_fail_callouts.png
new file mode 100644
index 0000000..ee32099
--- /dev/null
+++ b/docs/html/images/testing/spinnertest_junit_panel_fail_callouts.png
Binary files differ
diff --git a/docs/html/images/testing/spinnertest_junit_panel_failtrace_callouts.png b/docs/html/images/testing/spinnertest_junit_panel_failtrace_callouts.png
new file mode 100644
index 0000000..b20f62f
--- /dev/null
+++ b/docs/html/images/testing/spinnertest_junit_panel_failtrace_callouts.png
Binary files differ
diff --git a/docs/html/images/testing/spinnertest_junit_success.png b/docs/html/images/testing/spinnertest_junit_success.png
new file mode 100644
index 0000000..4760224
--- /dev/null
+++ b/docs/html/images/testing/spinnertest_junit_success.png
Binary files differ
diff --git a/docs/html/images/testing/spinnertest_new_class_callouts.png b/docs/html/images/testing/spinnertest_new_class_callouts.png
new file mode 100644
index 0000000..bf0f718
--- /dev/null
+++ b/docs/html/images/testing/spinnertest_new_class_callouts.png
Binary files differ
diff --git a/docs/html/images/testing/spinnertest_runas_menu_callouts.png b/docs/html/images/testing/spinnertest_runas_menu_callouts.png
new file mode 100644
index 0000000..ac387d0
--- /dev/null
+++ b/docs/html/images/testing/spinnertest_runas_menu_callouts.png
Binary files differ
diff --git a/docs/html/resources/index.jd b/docs/html/resources/index.jd
index 5174dee..1668721 100644
--- a/docs/html/resources/index.jd
+++ b/docs/html/resources/index.jd
@@ -29,7 +29,7 @@
 <dd>Links to the Android discussion groups and information about other ways to
 collaborate with other developers. </dd>
 
-<dt><b>More</b></dt> 
+<dt><b>More</b></dt>
 <dd>Quick development tips, troubleshooting information, and frequently asked
 questions (FAQs). </dd>
 </dl>
diff --git a/docs/html/resources/resources_toc.cs b/docs/html/resources/resources_toc.cs
index 43ab562..c9d92d4 100644
--- a/docs/html/resources/resources_toc.cs
+++ b/docs/html/resources/resources_toc.cs
@@ -159,9 +159,17 @@
       <li><a href="<?cs var:toroot ?>resources/tutorials/localization/index.html">
             <span class="en">Hello Localization</span>
           </a></li>
+      <li><a href="<?cs var:toroot ?>resources/tutorials/testing/helloandroid_test.html">
+            <span class="en">Hello Testing</span></a>
+            <span class="new">new!</span>
+      </li>
       <li><a href="<?cs var:toroot ?>resources/tutorials/notepad/index.html">
             <span class="en">Notepad Tutorial</span>
           </a></li>
+      <li><a href="<?cs var:toroot ?>resources/tutorials/testing/activity_test.html">
+            <span class="en">Activity Testing</span></a>
+            <span class="new">new!</span>
+      </li>
     </ul>
   </li>
 
@@ -230,6 +238,12 @@
           <li><a href="<?cs var:toroot ?>resources/samples/SoftKeyboard/index.html">
                 <span class="en">Soft Keyboard</span>
               </a></li>
+          <li><a href="<?cs var:toroot ?>resources/samples/Spinner/index.html">
+                <span class="en">Spinner</span>
+                </a> <span class="new">new!</span></li>
+          <li><a href="<?cs var:toroot ?>resources/samples/SpinnerTest/index.html">
+                <span class="en">SpinnerTest</span>
+                </a> <span class="new">new!</span></li>
           <li><a href="<?cs var:toroot ?>resources/samples/Wiktionary/index.html">
                 <span class="en">Wiktionary</span>
               </a></li>
diff --git a/docs/html/resources/samples/images/SpinnerTest1.png b/docs/html/resources/samples/images/SpinnerTest1.png
new file mode 100644
index 0000000..21442f2
--- /dev/null
+++ b/docs/html/resources/samples/images/SpinnerTest1.png
Binary files differ
diff --git a/docs/html/resources/samples/images/SpinnerTest2.png b/docs/html/resources/samples/images/SpinnerTest2.png
new file mode 100644
index 0000000..79ffeb6
--- /dev/null
+++ b/docs/html/resources/samples/images/SpinnerTest2.png
Binary files differ
diff --git a/docs/html/resources/samples/index.jd b/docs/html/resources/samples/index.jd
index 2718d0c..e917495 100644
--- a/docs/html/resources/samples/index.jd
+++ b/docs/html/resources/samples/index.jd
@@ -79,6 +79,19 @@
  <dt><a href="SoftKeyboard/index.html">Soft Keyboard</a></dt>
   <dd>An example of writing an input method for a software keyboard.</dd>
 
+ <dt><a href="Spinner/index.html">Spinner</a></dt>
+ <dd>
+    A simple application that serves as an application-under-test for the 
+    SpinnerTest sample application. 
+ </dd>
+ <dt><a href="SpinnerTest/index.html">SpinnerTest</a></dt>
+ <dd>
+    An example test application that contains test cases run against the 
+    Spinner sample application. 
+    To learn more about the application and how to run it, 
+    please read the 
+    <a href="{@docRoot}resources/tutorials/testing/activity_test.html">Activity Testing</a> tutorial.
+ </dd>
  <dt><a href="Wiktionary/index.html">Wiktionary</a></dt>
   <dd>An example of creating interactive widgets for display on the Android
   home screen.</dd>
diff --git a/docs/html/resources/tutorials/hello-world.jd b/docs/html/resources/tutorials/hello-world.jd
index 431a6cd..1079fb5 100644
--- a/docs/html/resources/tutorials/hello-world.jd
+++ b/docs/html/resources/tutorials/hello-world.jd
@@ -201,7 +201,7 @@
 The bold items are lines that have been added.</p>
 
 <pre>
-package com.android.helloandroid;
+package com.example.helloandroid;
 
 import android.app.Activity;
 import android.os.Bundle;
@@ -297,6 +297,7 @@
 
 <pre>&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
 &lt;TextView xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
+  android:id=&quot;@id+/textview&quot;
   android:layout_width=&quot;fill_parent&quot;
   android:layout_height=&quot;fill_parent&quot;
   android:text=&quot;@string/hello&quot;/&gt;</pre>
@@ -312,7 +313,7 @@
 application (its UI) from the application logic used to fetch and fill in data.</p>
 
 <p>In the above XML example, there's just one View element: the <code>TextView</code>, 
-which has four XML attributes.  Here's a summary of what they mean:</p>
+which has five XML attributes.  Here's a summary of what they mean:</p>
 
 <table>
     <tbody>
@@ -334,11 +335,21 @@
         </tr>
         <tr>
             <td>
+                <code>android:id</code>
+            </td>
+            <td>
+                This attribute assigns a unique identifier to the <code>TextView</code> element.
+                You can use the assigned ID to reference this View from your source code or from other 
+                XML resource declarations.
+            </td>
+        </tr>
+        <tr>
+            <td>
                 <code>android:layout_width</code>
             </td>
             <td>
                 This attribute defines how much of the available width on the screen this View should consume. 
-In this case, it's the only View so you want it to take up the entire screen, which is what a value of "fill_parent" means.<br>
+                In this case, it's the only View so you want it to take up the entire screen, which is what a value of "fill_parent" means.<br>
             </td>
         </tr>
         <tr>
@@ -397,6 +408,7 @@
 
 <pre>&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
 &lt;TextView xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
+  android:id=&quot;@id+/textview&quot;
   android:layout_width=&quot;fill_parent&quot;
   android:layout_height=&quot;fill_parent&quot;
   android:text=&quot;@string/hello&quot;/&gt;</pre>
@@ -478,6 +490,9 @@
     public static final class drawable {
         public static final int icon=0x7f020000;
     }
+    public static final class id {
+        public static final int textview=0x7f050000;
+    }
     public static final class layout {
         public static final int main=0x7f030000;
     }
@@ -512,7 +527,7 @@
 your code. Change your HelloAndroid source code to look like this:</p>
 
 <pre>
-package com.android.helloandroid;
+package com.example.helloandroid;
 
 import android.app.Activity;
 import android.os.Bundle;
@@ -568,7 +583,7 @@
   
   <pre>
 android create project \
-    --package com.android.helloandroid \
+    --package com.example.helloandroid \
     --activity HelloAndroid \ 
     --target 2 \
     --path <em>&lt;path-to-your-project></em>/HelloAndroid 
diff --git a/docs/html/resources/tutorials/testing/activity_test.jd b/docs/html/resources/tutorials/testing/activity_test.jd
new file mode 100644
index 0000000..ae4b6f3
--- /dev/null
+++ b/docs/html/resources/tutorials/testing/activity_test.jd
@@ -0,0 +1,1378 @@
+page.title=Activity Testing
+@jd:body
+ <div id="qv-wrapper">
+  <div id="qv">
+  <h2>In this document</h2>
+  <ol>
+    <li>
+      <a href="#Prerequisites">Prerequisites</a>
+    </li>
+    <li>
+      <a href="#DownloadCode">Installing the Tutorial Sample Code</a>
+    </li>
+    <li>
+        <a href="#SetupEmulator">Setting Up the Emulator</a>
+    </li>
+    <li>
+        <a href="#SetupProjects">Setting Up the Projects</a>
+    </li>
+    <li>
+      <a href="#CreateTestCaseClass">Creating the Test Case Class</a>
+      <ol>
+        <li>
+          <a href="#AddTestCaseClass">Adding the test case class file</a>
+        </li>
+        <li>
+          <a href="#AddConstructor">Adding the test case constructor</a>
+        </li>
+        <li>
+            <a href="#AddSetupMethod">Adding the setup method</a>
+        </li>
+        <li>
+            <a href="#AddPreConditionsTest">Adding an initial conditions test</a>
+        </li>
+        <li>
+            <a href="#AddUITest">Adding a UI test</a>
+        </li>
+        <li>
+            <a href="#StateManagementTests">Adding state management tests</a>
+        </li>
+      </ol>
+    </li>
+    <li>
+        <a href="#RunTests">Running the Tests and Seeing the Results</a>
+    </li>
+    <li>
+        <a href="#TestFailure">Forcing Some Tests to Fail</a>
+    </li>
+    <li>
+        <a href="#NextSteps">Next Steps</a>
+    </li>
+</ol>
+<h2 id="#Appendix">Appendix</h2>
+<ol>
+    <li>
+        <a href="#InstallCompletedTestApp">Installing the Completed Test Application Java File</a>
+    </li>
+    <li>
+        <a href="#EditorCommandLine">For Users Not Developing In Eclipse</a>
+    </li>
+</ol>
+<h2>Related Tutorials</h2>
+<ol>
+    <li>
+        <a href="{@docRoot}resources/tutorials/testing/helloandroid_test.html">Hello, Testing</a>
+    </li>
+</ol>
+<h2>See Also</h2>
+<ol>
+    <li>
+        <a href="{@docRoot}guide/topics/testing/testing_android.html">Testing Android Applications</a>
+    </li>
+    <li>
+        {@link android.test.ActivityInstrumentationTestCase2}
+    </li>
+    <li>
+        {@link junit.framework.Assert}
+    </li>
+    <li>
+        {@link android.test.InstrumentationTestRunner}
+    </li>
+</ol>
+</div>
+</div>
+<p>
+  Android includes powerful tools for testing applications. The tools extend JUnit with additional features, provide convenience classes for mock Android system objects, and use
+  instrumentation to give you control over your main application while you are testing it. The entire Android testing environment is discussed in the document
+  <a href="{@docRoot}guide/topics/testing/testing_android.html">Testing Android Applications</a>.
+</p>
+<p>
+  This tutorial demonstrates the Android testing tools by presenting a simple Android application and then leading you step-by-step through the creation of a test application for it.
+  The test application demonstrates these key points:
+</p>
+  <ul>
+    <li>
+      An Android test is itself an Android application that is linked to the application under test by entries in its <code>AndroidManifest.xml</code> file.
+    </li>
+    <li>
+      Instead of Android components, an Android test application contains one or more test cases. Each of these is a separate class definition.
+    </li>
+    <li>
+      Android test case classes extend the JUnit {@link junit.framework.TestCase} class.
+    </li>
+    <li>
+      Android test case classes for activities extend JUnit and also connect you to the application under test with instrumentation. You can send keystroke or touch events directly to the UI.
+    </li>
+    <li>
+      You choose an Android test case class based on the type of component (application, activity, content provider, or service) you are testing.
+    </li>
+    <li>
+      Additional test tools in Eclipse/ADT provide integrated support for creating test applications, running them, and viewing the results.
+    </li>
+  </ul>
+<p>
+  The test application contains methods that perform the following tests:
+</p>
+  <ul>
+    <li>
+      Initial conditions test. Tests that the application under test initializes correctly. This is also a unit test of the application's
+      {@link android.app.Activity#onCreate(android.os.Bundle) onCreate()} method. Testing initial conditions also provides a confidence measure for subsequent tests.
+    </li>
+    <li>
+      UI test. Tests that the main UI operation works correctly. This test demonstrates the instrumentation features available in activity testing.
+      It shows that you can automate UI tests by sending key events from the test application to the main application.
+    </li>
+    <li>
+      State management tests. Test the application's code for saving state. This test demonstrates the instrumentation features of the test runner, which
+      are available for testing any component.
+    </li>
+  </ul>
+<h2 id="Prerequisites">Prerequisites</h2>
+<p>
+  The instructions and code in this tutorial depend on the following:
+</p>
+  <ul>
+    <li>
+      Basic knowledge of Android programming. If you haven't yet written an Android application, do the
+      <a href="{@docRoot}resources/tutorials/hello-world.html">Hello, World</a> tutorial. If you
+      want to learn more about Spinner, the application under test, then you might want to visit the
+      <a href="{@docRoot}resources/tutorials/views/hello-spinner.html">Hello Views &gt; Spinner</a> example.
+    </li>
+    <li>
+      Some familiarity with the Android testing framework and concepts. If you haven't explored
+      Android testing yet, start by reading the Developer Guide topic <a href="{@docRoot}guide/topics/testing/testing_android.html">Testing Android Applications</a>
+      or following the <a href="{@docRoot}resources/tutorials/testing/helloandroid_test.html">
+      Hello, Testing</a> tutorial.
+    </li>
+    <li>
+        Eclipse with ADT. This tutorial describes how to set up and run a test application using
+        Eclipse with ADT. If you haven't yet installed Eclipse and the ADT plugin,
+        follow the steps in <a href="{@docRoot}sdk/installing.html">Installing the SDK</a>
+        to install them before continuing. If you are not developing in Eclipse, you will
+        find instructions for setting up and running the test application in the
+        <a href="#EditorCommandLine">appendix</a> of this document.
+    </li>
+    <li>
+        Android 1.5 platform (API Level 3) or higher. You must have the Android 1.5 platform
+        (API Level 3) or higher installed in your SDK, because this tutorial uses APIs that
+        were introduced in that version.
+        <p>
+            If you are not sure which platforms are installed in your SDK,
+            open the Android SDK and AVD Manager and check in the
+            <strong>Installed Packages</strong> panel.
+            If aren't sure how to download a platform into your SDK,
+            read <a href="{@docRoot}sdk/adding-components.html">Adding SDK Components</a>.
+        </p>
+    </li>
+  </ul>
+<h2 id="DownloadCode">Installing the Tutorial Sample Code</h2>
+<p>
+    During this tutorial, you will be working with sample code that is provided as part
+    of the downloadable Samples component of the SDK. Specifically, you will be working
+    with a pair of related sample applications &mdash; an application under test and a test
+    application:
+</p>
+    <ul>
+        <li>
+            Spinner is the application under test. This tutorial focuses on the
+            common situation of writing tests for an application that already exists, so the main
+            application is provided to you.
+        </li>
+        <li>
+             SpinnerTest is the test application. In the tutorial, you create this application
+             step-by-step. If you want to run quickly through the tutorial,
+             you can install the completed SpinnerTest application first, and then follow the
+             text. You may get more from the tutorial, however, if you create the test application
+             as you go. The instructions for installing the completed test application are in the
+             section <a href="#InstallCompletedTestApp">Installing the Completed Test Application Java File</a>.
+        </li>
+    </ul>
+<p>
+    The sample applications are provided in the SDK component named
+    "Samples for SDK API 8" and in later versions of the Samples.
+</p>
+<p>
+    To get started with the tutorial, first use the Android SDK and AVD manager to install an
+    appropriate version of the Samples:
+</p>
+<ol>
+    <li>
+        In Eclipse, select <strong>Window</strong> &gt; <strong>Android SDK and AVD Manager</strong>.
+    </li>
+    <li>
+        Open the <strong>Installed Packages</strong> panel and check whether
+        &quot;Samples for SDK API 8&quot; (or higher version) is listed.
+        If so, skip to the next section,
+        <a href="#SetupProjects">Setting Up the Projects</a>, to get started with the tutorial.
+        Otherwise, continue with the next step.
+     </li>
+     <li>
+        Open the <strong>Available Packages</strong> panel.
+     </li>
+     <li>
+        Select the &quot;Samples for SDK API 8&quot; component and click <strong>Install Selected</strong>.
+     </li>
+     <li>
+        Verify and accept the component and then click <strong>Install Accepted</strong>.
+        The Samples component will now be installed into your SDK.
+     </li>
+</ol>
+<p>
+    When the installation is complete, the applications in the
+    Samples component are stored at this location on your computer:
+</p>
+<p style="margin-left:2em">
+    <code>&lt;<em>sdk</em>&gt;/samples/android-8/</code>
+</p>
+<p>
+    For general information about the Samples, see
+    <a href="{@docRoot}resources/samples/get.html">Getting the Samples</a>
+</p>
+<p class="note">
+    <strong>Note:</strong> Although the sample code for this tutorial is provided in the
+    &quot;Samples for SDK API 8&quot; component, that does not imply that you need to build or
+    run the application against the corresponding platform (Android 2.2).
+    The API level referenced in the Samples component name indicates only the origin branch from
+    which the samples were built.
+</p>
+<h2 id="SetupEmulator">Setting Up the Emulator</h2>
+<p>
+  In this tutorial, you will use the Android emulator to run applications. The emulator needs
+  an Android Virtual Device (AVD) with an API level equal to or higher than the one you set for the projects in the previous step.
+  To find out how to check this and create the right AVD if necessary, see <a href="{@docRoot}guide/developing/eclipse-adt.html#AVD">Creating an AVD</a>.
+</p>
+<p>
+    As a test of the AVD and emulator, run the SpinnerActivity application in Eclipse with ADT. When it starts,
+    click the large downward-pointing arrow to the right of the spinner text. You see the spinner expand and display the title &quot;Select a planet&quot; at the top.
+    Click one of the other planets. The spinner closes, and your selection appears below it on the screen.
+</p>
+<h2 id="SetupProjects">Setting Up the Projects</h2>
+<p>
+    When you are ready to get started with the tutorial, begin by setting up Eclipse projects for
+    both Spinner (the application under test) and SpinnerTest (the test application).
+</p>
+<p>
+    You'll be using the Spinner application as-is, without modification, so you'll be loading it
+    into Eclipse as a new Android project from existing source. In the process, you'll be
+    creating a new test project associated with Spinner that will contain the SpinnerTest
+    application. The SpinnerTest application will be completely new and you'll be
+    using the code examples in this tutorial to add test classes and tests to it.
+</p>
+<p>
+    To install the Spinner app in a new Android project from existing source, following these steps:
+</p>
+<ol>
+    <li>
+        In Eclipse, select <strong>File</strong>&nbsp;&gt;&nbsp;<strong>New</strong>&nbsp;&gt;&nbsp;<strong>Project</strong>&nbsp;&gt;&nbsp;<strong>Android</strong>&nbsp;&gt;&nbsp;<strong>Android Project</strong>,
+        then click Next. The <strong>New Android Project</strong> dialog appears.
+    </li>
+    <li>
+        In the <em>Project name</em> text box, enter &quot;SpinnerActivity&quot;. The <em>Properties</em> area is filled in automatically.
+    </li>
+    <li>
+        In the <em>Contents</em> area, set &quot;Create project from existing source&quot;.
+    </li>
+    <li>
+        For <em>Location</em>, click <strong>Browse</strong>, navigate to the directory <code>&lt;SDK_path&gt;/samples/android-8/Spinner</code>,
+        then click Open. The directory name <code>&lt;SDK_path&gt;/samples/android-8/Spinner</code> now appears in the <em>Location</em> text box.
+    </li>
+    <li>
+        In the <em>Build Target</em> area, set a API level of 3 or higher. If you are already developing with a particular target, and it is API level 3 or higher, then use that target.
+    </li>
+    <li>
+        In the <em>Properties</em> area, in the <em>Min SDK Version:</em>, enter &quot;3&quot;.
+    </li>
+    <li>
+        You should now see these values:
+        <ul>
+            <li><em>Project Name:</em> &quot;SpinnerActivity&quot;</li>
+            <li><em>Create project from existing source:</em> set</li>
+            <li><em>Location:</em> &quot;<code>&lt;SDK_path&gt;/samples/android-8/Spinner</code>&quot;</li>
+            <li><em>Build Target:</em> &quot;API level of 3 or higher&quot; (<em>Target Name</em> &quot;Android 1.5 or higher&quot;)</li>
+            <li><em>Package name:</em> (disabled, set to &quot;<code>com.android.example.spinner</code>&quot;)</li>
+            <li><em>Create Activity:</em> (disabled, set to &quot;.SpinnerActivity&quot;)</li>
+            <li><em>Min SDK Version:</em> &quot;3&quot;</li>
+        </ul>
+        <p>
+            The following screenshot summarizes these values:
+        </p>
+            <a href="{@docRoot}images/testing/eclipse_new_android_project_complete_callouts.png">
+                <img src="{@docRoot}images/testing/eclipse_new_android_project_complete_callouts.png" alt="New Android Project dialog with filled-in values" style="height:230px"/>
+            </a>
+
+    </li>
+</ol>
+<p>
+    To create a new test project for the SpinnerTest application, follow these steps:
+</p>
+<ol>
+    <li>
+        Click Next. The <strong>New Android Test Project</strong> dialog appears.
+    </li>
+    <li>
+        Set &quot;Create a Test Project&quot;.
+    </li>
+    <li>
+        Leave the other values unchanged. The result should be:
+        <ul>
+            <li><em>Create a Test Project:</em> checked</li>
+            <li><em>Test Project Name:</em> &quot;SpinnerActivityTest&quot;</li>
+            <li><em>Use default location:</em> checked (this should contain the directory name &quot;<code>workspace/SpinnerActivityTest</code>&quot;).</li>
+            <li><em>Build Target:</em> Use the same API level you used in the previous step.</li>
+            <li><em>Application name:</em> &quot;SpinnerActivityTest&quot;</li>
+            <li><em>Package name:</em> &quot;<code>com.android.example.spinner.test</code>&quot;</li>
+            <li><em>Min SDK Version:</em> &quot;3&quot;</li>
+        </ul>
+        <p>
+            The following screenshot summarizes these values:
+        </p>
+            <a href="{@docRoot}images/testing/eclipse_new_android_testproject_complete_callouts.png">
+            <img src="{@docRoot}images/testing/eclipse_new_android_testproject_complete_callouts.png" alt="New Android Test Project dialog with filled-in values" style="height:230px"/>
+            </a>
+    </li>
+    <li>
+        Click Finish. Entries for SpinnerActivity and SpinnerActivityTest should appear in the
+        <strong>Package Explorer</strong>.
+        <p class="note">
+            <strong>Note:</strong> If you set <em>Build Target</em> to an API level higher than &quot;3&quot;, you will see the warning
+            &quot;The API level for the selected SDK target does not match the Min SDK version&quot;. You do not need to change the API level or the Min SDK version.
+            The message tells you that you are building the projects with one particular API level, but specifying that a lower API level is required. This may
+            occur if you have chosen not to install the optional earlier API levels.
+        </p>
+        <p>
+            If you see errors listed in the <strong>Problems</strong> pane at the bottom of the Eclipse window, or if a red error marker appears next to
+            the entry for SpinnerActivity in the Package Explorer, highlight the SpinnerActivity entry and then select
+            <strong>Project</strong>&nbsp;&gt;&nbsp;<strong>Clean</strong>. This should fix any errors.
+        </p>
+    </li>
+</ol>
+<p>
+    You now have the application under test in the SpinnerActivity project,
+    and an empty test project in SpinnerActivityTest. You may
+    notice that the two projects are in different directories, but Eclipse with
+    ADT handles this automatically. You should have no problem in either building or running them.
+</p>
+<p>
+    Notice that Eclipse and ADT have already done some initial setup for your test application.
+    Expand the SpinnerActivityTest project, and notice that it already has an
+    Android manifest file <code>AndroidManifest.xml</code>.
+    Eclipse with ADT created this when you added the test project.
+    Also, the test application is already set up to use instrumentation. You can see this
+    by examining <code>AndroidManifest.xml</code>.
+    Open it, then at the bottom of the center pane click <strong>AndroidManifest.xml</strong>
+    to display the XML contents:
+</p>
+<pre>
+&lt;?xml version="1.0" encoding="utf-8"?&gt;
+&lt;manifest xmlns:android="http://schemas.android.com/apk/res/android"
+      package="com.android.example.spinner.test"
+      android:versionCode="1"
+      android:versionName="1.0"&gt;
+    &lt;application android:icon="@drawable/icon" android:label="@string/app_name"&gt;
+
+    &lt;uses-library android:name="android.test.runner" /&gt;
+    &lt;/application&gt;
+    &lt;uses-sdk android:minSdkVersion="3" /&gt;
+    &lt;instrumentation
+        android:targetPackage="com.android.example.spinner"
+        android:name="android.test.InstrumentationTestRunner" /&gt;
+&lt;/manifest&gt;
+</pre>
+<p>
+    Notice the <code>&lt;instrumentation&gt;</code> element. The attribute
+    <code>android:targetPackage="com.android.example.spinner"</code> tells Android that the
+    application under test is defined in the Android package
+    <code>com.android.example.spinner</code>. Android now knows to use that
+    package's <code>AndroidManifest.xml</code> file to launch the application under test.
+    The <code>&lt;instrumentation&gt;</code> element also contains the attribute
+    <code>android:name="android.test.InstrumentationTestRunner"</code>, which tells Android
+    instrumentation to run the test application with Android's instrumentation-enabled test runner.
+</p>
+<h2 id="CreateTestCaseClass">Creating the Test Case Class</h2>
+
+<p>
+    You now have a test project SpinnerActivityTest, and the basic structure of a test
+    application also called SpinnerActivityTest. The basic structure includes all the files and
+    directories you need to build and run a test application, except for the class that
+    contains your tests (the test case class).
+</p>
+<p>
+    The next step is to define the test case class. In this tutorial, you'll be creating a
+    test case class that includes:
+</p>
+<ul>
+    <li>
+        Test setup. This use of the JUnit {@link junit.framework.TestCase#setUp() setUp()}
+        method demonstrates some of the tasks you might perform before running an Android test.
+    </li>
+    <li>
+        Testing initial conditions. This test demonstrates a good testing technique.
+        It also demonstrates that with Android instrumentation you can look at the application
+        under test <em>before</em> the main activity starts. The test checks that the application's
+        important objects have been initialized.
+        If the test fails, you then know that any other tests against the application are
+        unreliable, since the application was running in an incorrect state.
+        <p class="note">
+            <strong>Note:</strong> The purpose of testing initial conditions is not the same as
+            using <code>setUp()</code>. The JUnit {@link junit.framework.TestCase#setUp()} runs once
+            before <strong>each test method</strong>, and its purpose is to create a clean test
+            environment. The initial conditions test runs once, and its purpose is to verify that the
+            application under test is ready to be tested.
+        </p>
+    </li>
+    <li>
+        Testing the UI. This test shows how to control the main application's UI
+        with instrumentation, a powerful automation feature of Android testing.
+    </li>
+    <li>
+        Testing state management. This test shows some techniques for testing how
+        well the application maintains state in the Android environment. Remember that to
+        provide a satisfactory user experience, your application must never lose its current state,
+        even if it's interrupted by a phone call or destroyed because of memory constraints.
+        The Android activity lifecycle provides ways to maintain state, and the
+        <code>SpinnerActivity</code> application uses them. The test shows the techniques for
+        verifying that they work.
+    </li>
+</ul>
+<p>
+  Android tests are contained in a special type of Android application that contains one or more test class definitions. Each of these contains
+  one or more test methods that do the actual tests. In this tutorial, you will first add a test case class, and then add tests to it.
+</p>
+<p>
+ You first choose an Android test case class to extend. You choose from the base test case classes according to the Android component you are testing and the types of tests you are doing.
+ In this tutorial, the application under test has a single simple activity, so the test case class will be for an Activity component. Android offers several, but the one that tests in
+ the most realistic environment is {@link android.test.ActivityInstrumentationTestCase2}, so you will use it as the base class. Like all activity test case classes,
+ <code>ActivityInstrumentationTestCase2</code> offers convenience methods for interacting directly with the UI of the application under test.
+</p>
+<h3 id="AddTestCaseClass">Adding the test case class file</h3>
+<p>
+    To add <code>ActivityInstrumentationTestCase2</code> as the base test case class, follow these steps:
+</p>
+<ol>
+  <li>
+    In the Package Explorer, expand the test project SpinnerActivityTest if it is not open already.
+  </li>
+  <li>
+    Within SpinnerActivityTest, expand the <code>src/</code> folder and then the package marker for
+    <code>com.android.example.spinner.test</code>. Right-click on the package name and select <strong>New</strong> &gt; <strong>Class</strong>:<br/>
+    <a href="{@docRoot}images/testing/spinner_create_test_class_callouts.png">
+      <img alt="Menu for creating a new class in the test application" src="{@docRoot}images/testing/spinner_create_test_class_callouts.png" style="height:230px"/>
+    </a>
+    <p>
+      The <strong>New Java Class</strong> wizard appears:
+    </p>
+    <a href="{@docRoot}images/testing/spinnertest_new_class_callouts.png">
+      <img alt="New Java Class wizard dialog" src="{@docRoot}images/testing/spinnertest_new_class_callouts.png" style="height:230px"/>
+    </a>
+  </li>
+  <li>
+    In the wizard, enter the following:
+    <ul>
+      <li>
+        <em>Name:</em> &quot;SpinnerActivityTest&quot;. This becomes the name of your test class.
+      </li>
+      <li>
+        <em>Superclass:</em> &quot;<code>android.test.ActivityInstrumentationTestCase2&lt;SpinnerActivity&gt;</code>&quot;. The superclass is parameterized, so
+        you have to provide it your main application's class name.
+      </li>
+    </ul>
+    <p>
+      Do not change any of the other settings. Click Finish.
+    </p>
+  </li>
+  <li>
+    You now have a new file <code>SpinnerActivityTest.java</code> in the project.
+  </li>
+  <li>
+    To resolve the reference to SpinnerActivity, add the following import:
+<pre>
+import com.android.example.spinner.SpinnerActivity;
+</pre>
+  </li>
+</ol>
+<h3 id="AddConstructor">Adding the test case constructor</h3>
+  <p>
+    To ensure that the test application is instantiated correctly, you must set up a constructor that the test
+    runner will call when it instantiates your test class. This constructor has no parameters, and its sole
+    purpose is to pass information to the superclass's default constructor. To set up this constructor, enter the
+    following code in the class:
+  </p>
+<pre>
+  public SpinnerActivityTest() {
+    super("com.android.example.spinner", SpinnerActivity.class);
+  } // end of SpinnerActivityTest constructor definition
+</pre>
+<p>
+  This calls the superclass constructor with the Android package name (<code>com.android.example.spinner</code>)and main activity's class
+  (<code>SpinnerActivity.class</code>) for the application under test. Android uses this information to find the application and activity to test.
+</p>
+<p>
+  You are now ready to add tests, by adding test methods to the class.
+</p>
+<h3 id="AddSetupMethod">Adding the setup method</h3>
+<p>
+    The <code>setUp()</code> method is invoked before every test. You use it to initialize variables and clean up from previous tests. You can also use
+    the JUnit {@link junit.framework.TestCase#tearDown() tearDown()} method, which runs <strong>after</strong> every test method. The tutorial does not use it.
+</p>
+<p>
+    The method you are going to add does the following:
+</p>
+<ul>
+  <li>
+    <code>super.setUp()</code>. Invokes the superclass constructor for <code>setUp()</code>, which is required by JUnit.
+  </li>
+  <li>
+    Calls {@link android.test.ActivityInstrumentationTestCase2#setActivityInitialTouchMode(boolean) setActivityInitialTouchMode(false)}.
+    This turns off <strong>touch mode</strong> in the device or emulator. If any of your test methods send key events to the application,
+    you must turn off touch mode <em>before</em> you start any activities; otherwise, the call is ignored.
+  </li>
+  <li>
+    Stores references to system objects. Retrieves and stores a reference to the activity under test, the <code>Spinner</code>
+    widget used by the activity, the <code>SpinnerAdapter</code> that backs the widget, and the string value of the selection that is
+    set when the application is first installed. These objects are used in the state management test. The methods invoked are:
+    <ul>
+      <li>
+        {@link android.test.ActivityInstrumentationTestCase2#getActivity()}. Gets a reference to the activity under test (<code>SpinnerActivity</code>).
+        This call also starts the activity if it is not already running.
+      </li>
+      <li>
+        {@link android.app.Activity#findViewById(int)}. Gets a reference to the <code>Spinner</code> widget of the application under test.
+      </li>
+      <li>
+        {@link android.widget.AbsSpinner#getAdapter()}. Gets a reference to the adapter (an array of strings) backing the spinner.
+      </li>
+    </ul>
+  </li>
+</ul>
+<p>
+    Add this code to the definition of <code>SpinnerActivityTest</code>, after the constructor definition:
+</p>
+<pre>
+  &#64;Override
+  protected void setUp() throws Exception {
+    super.setUp();
+
+    setActivityInitialTouchMode(false);
+
+    mActivity = getActivity();
+
+    mSpinner =
+      (Spinner) mActivity.findViewById(
+        com.android.example.spinner.R.id.Spinner01
+      );
+
+      mPlanetData = mSpinner.getAdapter();
+
+  } // end of setUp() method definition
+</pre>
+<p>
+    Add these members to the test case class:
+</p>
+<pre>
+  private SpinnerActivity mActivity;
+  private Spinner mSpinner;
+  private SpinnerAdapter mPlanetData;
+</pre>
+<p>
+  Add these imports:
+</p>
+<pre>
+import android.widget.Spinner;
+import android.widget.SpinnerAdapter;
+</pre>
+<p>
+    You now have the the complete <code>setUp()</code> method.
+</p>
+<h3 id="AddPreConditionsTest">Adding an initial conditions test</h3>
+<p>
+  The initial conditions test verifies that the application under test is initialized correctly. It is an illustration of the types of tests you can run, so it is not comprehensive.
+  It verifies the following:
+</p>
+<ul>
+  <li>
+    The item select listener is initialized. This listener is called when a selection is made from the spinner.
+  </li>
+  <li>
+    The adapter that provides values to the spinner is initialized.
+  </li>
+  <li>
+    The adapter contains the right number of entries.
+  </li>
+</ul>
+<p>
+  The actual initialization of the application under test is done in <code>setUp()</code>, which the test runner calls automatically before every test. The verifications are
+  done with JUnit {@link junit.framework.Assert} calls. As a useful convention, the method name is <code>testPreConditions()</code>:
+</p>
+<pre>
+  public void testPreConditions() {
+    assertTrue(mSpinner.getOnItemSelectedListener() != null);
+    assertTrue(mPlanetData != null);
+    assertEquals(mPlanetData.getCount(),ADAPTER_COUNT);
+  } // end of testPreConditions() method definition
+</pre>
+<p>
+  Add this member:
+</p>
+<pre>
+  public static final int ADAPTER_COUNT = 9;
+</pre>
+<h3 id="AddUITest">Adding a UI test</h3>
+<p>
+  Now create a UI test that selects an item from the <code>Spinner</code> widget. The test sends key events to the UI with key events.
+  The test confirms that the selection matches the result you expect.
+</p>
+<p>
+  This test demonstrates the power of using instrumentation in Android testing. Only an instrumentation-based test class allows you to send key events (or touch events)
+  to the application under test. With instrumentation, you can test your UI without having to take screenshots, record the screen, or do human-controlled testing.
+</p>
+<p>
+  To work with the spinner, the test has to request focus for it and then set it to a known position. The test uses {@link android.view.View#requestFocus() requestFocus()} and
+  {@link android.widget.AbsSpinner#setSelection(int) setSelection()} to do this. Both of these methods interact with a View in the application under test, so you have to call them
+  in a special way.
+</p>
+<p>
+  Code in a test application that interacts with a View of the application under test must run in the main application's thread, also
+  known as the <em>UI thread</em>. To do this, you use the {@link android.app.Activity#runOnUiThread(java.lang.Runnable) Activity.runOnUiThread()}
+  method. You pass the code to <code>runOnUiThread()</code>in an anonymous {@link java.lang.Runnable Runnable} object. To set
+  the Java statements in the <code>Runnable</code> object, you override the object's {@link java.lang.Runnable#run()} method.
+</p>
+<p>
+  To send key events to the UI of the application under test, you use the <a href="{@docRoot}reference/android/test/InstrumentationTestCase.html#sendKeys(int...)">sendKeys</a>() method.
+  This method does not have to run on the UI thread, since Android uses instrumentation to pass the key events to the application under test.
+</p>
+<p>
+  The last part of the test compares the selection made by sending the key events to a pre-determined value. This tests that the spinner is working as intended.
+</p>
+<p>
+    The following sections show you how to add the code for this test.
+</p>
+<ol>
+    <li>
+        Get focus and set selection. Create a new method <code>public void testSpinnerUI()</code>. Add
+        code to to request focus for the spinner and set its position to default or initial position, "Earth". This code is run on the UI thread of
+        the application under test:
+<pre>
+  public void testSpinnerUI() {
+
+    mActivity.runOnUiThread(
+      new Runnable() {
+        public void run() {
+          mSpinner.requestFocus();
+          mSpinner.setSelection(INITIAL_POSITION);
+        } // end of run() method definition
+      } // end of anonymous Runnable object instantiation
+    ); // end of invocation of runOnUiThread
+</pre>
+        <p>
+          Add the following member to the test case class.
+        </p>
+<pre>
+  public static final int INITIAL_POSITION = 0;
+</pre>
+    </li>
+    <li>
+      Make a selection. Send key events to the spinner to select one of the items. To do this, open the spinner by
+      "clicking" the center keypad button (sending a DPAD_CENTER key event) and then clicking (sending) the down arrow keypad button five times. Finally,
+      click the center keypad button again to highlight the desired item. Add the following code:
+<pre>
+    this.sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
+    for (int i = 1; i &lt;= TEST_POSITION; i++) {
+      this.sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+    } // end of for loop
+
+    this.sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
+</pre>
+    <p>
+      Add the following member to the test case class:
+    </p>
+<pre>
+  public static final int TEST_POSITION = 5;
+</pre>
+    <p>
+      This sets the final position of the spinner to "Saturn" (the spinner's backing adapter is 0-based).
+    </p>
+  </li>
+  <li>
+    Check the result. Query the current state of the spinner, and compare its current selection to the expected value.
+    Call the method {@link android.widget.AdapterView#getSelectedItemPosition() getSelectedItemPosition()} to find out the current selection position, and then
+    {@link android.widget.AdapterView#getItemAtPosition(int) getItemAtPosition()} to get the object corresponding to that position (casting it to a String). Assert that
+    this string value matches the expected value of "Saturn":
+<pre>
+    mPos = mSpinner.getSelectedItemPosition();
+    mSelection = (String)mSpinner.getItemAtPosition(mPos);
+    TextView resultView =
+      (TextView) mActivity.findViewById(
+        com.android.example.spinner.R.id.SpinnerResult
+      );
+
+    String resultText = (String) resultView.getText();
+
+    assertEquals(resultText,mSelection);
+
+  } // end of testSpinnerUI() method definition
+</pre>
+<p>
+  Add the following members to the test case class:
+</p>
+<pre>
+  private String mSelection;
+  private int mPos;
+</pre>
+  <p>
+    Add the following imports to the test case class:
+  </p>
+<pre>
+  import android.view.KeyEvent;
+  import android.widget.TextView;
+</pre>
+  </li>
+</ol>
+<p>
+  Pause here to run the tests you have. The procedure for running a test application is different
+  from running a regular Android application. You run a test application as an Android JUnit
+  application. To see how to do this, see <a href="#RunTests">Running the Tests and Seeing the Results</a>.
+</p>
+<p>
+    Eventually, you will see the <code>SpinnerActivity</code> application start, and the test
+    application controlling it by sending it key events. You will also see a new
+    <strong>JUnit</strong> view in the Explorer pane, showing the results of the
+    test. The JUnit view is documented in a following section,
+    <a href="#RunTests">Running the Test and Seeing the Results</a>.
+</p>
+<h3 id="StateManagementTests">Adding state management tests</h3>
+<p>
+  You now write two tests that verify that SpinnerActivity maintains its state when it is paused or terminated.
+  The state, in this case, is the current selection in the spinner. When users make a selection,
+  pause or terminate the application, and then resume or restart it, they should see
+  the same selection.
+</p>
+<p>
+  Maintaining state is an important feature of an application. Users may switch from the current
+  application temporarily to answer the phone, and then switch back. Android may decide to
+  terminate and restart an activity to change the screen orientation, or terminate an unused
+  activity to regain storage. In each case, users are best served by having the UI return to its
+  previous state (except where the logic of the application dictates otherwise).
+</p>
+<p>
+  SpinnerActivity manages its state in these ways:
+</p>
+  <ul>
+    <li>
+      Activity is hidden. When the spinner screen (the activity) is running but hidden by some other screen, it
+      stores the spinner's position and value in a form that persists while the application is running.
+    </li>
+    <li>
+      Application is terminated. When the activity is terminated, it stores the spinner's position and value in
+      a permanent form. The activity can read the position and value when it restarts, and restore the spinner to its previous state.
+    </li>
+    <li>
+      Activity re-appears. When the user returns to the spinner screen, the previous selection is restored.
+    </li>
+    <li>
+      Application is restarted. When the user starts the application again, the previous selection is restored.
+    </li>
+  </ul>
+<p class="note">
+    <strong>Note:</strong> An application can manage its state in other ways as well, but these are
+    not covered in this tutorial.
+</p>
+<p>
+  When an activity is hidden, it is <strong>paused</strong>. When it re-appears, it
+  <strong>resumes</strong>. Recognizing that these are key points in an activity's life cycle,
+  the Activity class provides two callback methods {@link android.app.Activity#onPause()} and
+  {@link android.app.Activity#onResume()} for handling pauses and resumes.
+  SpinnerActivity uses them for code that saves and restores state.
+</p>
+<p>
+  <strong>Note:</strong> If you would like to learn more about the difference between losing
+  focus/pausing and killing an application,
+  refer to the <a href="{@docRoot}guide/topics/fundamentals.html#actlife">Activity Lifecycle</a>
+  section.
+</p>
+<p>
+  The first test verifies that the spinner selection is maintained after the entire application is shut down and then restarted. The test uses instrumentation to
+  set the spinner's variables outside of the UI. It then terminates the activity by calling {@link android.app.Activity#finish() Activity.finish()}, and restarts it
+  using the instrumentation method {@link android.test.ActivityInstrumentationTestCase2#getActivity()}. The test then asserts that the current spinner state matches
+  the test values.
+</p>
+<p>
+  The second test verifies that the spinner selection is maintained after the activity is paused and then resumed. The test uses instrumentation to
+  set the spinner's variables outside of the UI and then force calls to the <code>onPause()</code> and <code>onResume()</code> methods. The test then
+  asserts that the current spinner state matches the test values.
+</p>
+<p>
+  Notice that these tests make limited assumptions about the mechanism by which the activity manages state. The tests use the activity's getters and
+  setters to control the spinner. The first test also knows that hiding an activity calls <code>onPause()</code>, and bringing it back to the foreground
+  calls <code>onResume()</code>. Other than this, the tests treat the activity as a "black box".
+</p>
+<p>
+    To add the code for testing state management across shutdown and restart, follow these steps:
+</p>
+ <ol>
+    <li>
+      Add the test method <code>testStateDestroy()</code>, then
+      set the spinner selection to a test value:
+<pre>
+  public void testStateDestroy() {
+    mActivity.setSpinnerPosition(TEST_STATE_DESTROY_POSITION);
+    mActivity.setSpinnerSelection(TEST_STATE_DESTROY_SELECTION);
+</pre>
+    </li>
+    <li>
+      Terminate the activity and restart it:
+<pre>
+    mActivity.finish();
+    mActivity = this.getActivity();
+</pre>
+    </li>
+    <li>
+      Get the current spinner settings from the activity:
+<pre>
+    int currentPosition = mActivity.getSpinnerPosition();
+    String currentSelection = mActivity.getSpinnerSelection();
+</pre>
+    </li>
+    <li>
+      Test the current settings against the test values:
+<pre>
+    assertEquals(TEST_STATE_DESTROY_POSITION, currentPosition);
+    assertEquals(TEST_STATE_DESTROY_SELECTION, currentSelection);
+  } // end of testStateDestroy() method definition
+</pre>
+<p>
+  Add the following members to the test case class:
+<pre>
+  public static final int TEST_STATE_DESTROY_POSITION = 2;
+  public static final String TEST_STATE_DESTROY_SELECTION = "Earth";
+</pre>
+    </li>
+ </ol>
+<p>
+    To add the code for testing state management across a pause and resume, follow these steps:
+</p>
+<ol>
+    <li>
+      Add the test method <code>testStatePause()</code>:
+<pre>
+    &#64;UiThreadTest
+    public void testStatePause() {
+</pre>
+    <p>
+      The <code>@UiThreadTest</code> annotation tells Android to build this method so that it runs
+      on the UI thread. This allows the method to change the state of the spinner widget in the
+      application under test. This use of <code>@UiThreadTest</code> shows that, if necessary, you
+      can run an entire method on the UI thread.
+    </p>
+    </li>
+   <li>
+    Set up instrumentation. Get the instrumentation object
+    that is controlling the application under test. This is used later to
+    invoke the <code>onPause()</code> and <code>onResume()</code> methods:
+<pre>
+    Instrumentation mInstr = this.getInstrumentation();
+</pre>
+  </li>
+  <li>
+    Set the spinner selection to a test value:
+<pre>
+    mActivity.setSpinnerPosition(TEST_STATE_PAUSE_POSITION);
+    mActivity.setSpinnerSelection(TEST_STATE_PAUSE_SELECTION);
+</pre>
+  </li>
+  <li>
+    Use instrumentation to call the Activity's <code>onPause()</code>:
+<pre>
+    mInstr.callActivityOnPause(mActivity);
+</pre>
+    <p>
+      Under test, the activity is waiting for input. The invocation of
+      {@link android.app.Instrumentation#callActivityOnPause(android.app.Activity)}
+      performs a call directly to the activity's <code>onPause()</code> instead
+      of manipulating the activity's UI to force it into a paused state.
+    </p>
+  </li>
+  <li>
+    Force the spinner to a different selection:
+<pre>
+    mActivity.setSpinnerPosition(0);
+    mActivity.setSpinnerSelection("");
+</pre>
+    <p>
+      This ensures that resuming the activity actually restores the
+      spinner's state rather than simply leaving it as it was.
+    </p>
+  </li>
+  <li>
+    Use instrumentation to call the Activity's <code>onResume()</code>:
+<pre>
+    mInstr.callActivityOnResume(mActivity);
+</pre>
+    <p>
+      Invoking {@link android.app.Instrumentation#callActivityOnResume(android.app.Activity)}
+      affects the activity in a way similar to <code>callActivityOnPause</code>. The
+      activity's <code>onResume()</code> method is invoked instead of manipulating the
+      activity's UI to force it to resume.
+    </p>
+  </li>
+  <li>
+    Get the current state of the spinner:
+<pre>
+    int currentPosition = mActivity.getSpinnerPosition();
+    String currentSelection = mActivity.getSpinnerSelection();
+</pre>
+  </li>
+  <li>
+    Test the current spinner state against the test values:
+<pre>
+    assertEquals(TEST_STATE_PAUSE_POSITION,currentPosition);
+    assertEquals(TEST_STATE_PAUSE_SELECTION,currentSelection);
+  } // end of testStatePause() method definition
+</pre>
+    <p>
+      Add the following members to the test case class:
+    </p>
+<pre>
+  public static final int TEST_STATE_PAUSE_POSITION = 4;
+  public static final String TEST_STATE_PAUSE_SELECTION = "Jupiter";
+</pre>
+  </li>
+  <li>
+    Add the following imports:
+<pre>
+  import android.app.Instrumentation;
+  import android.test.UiThreadTest;
+</pre>
+  </li>
+</ol>
+<h2 id="RunTests">Running the Tests and Seeing the Results</h2>
+ <p>
+    The most simple way to run the <code>SpinnerActivityTest</code> test case is to run it directly from the Package Explorer.
+ </p>
+ <p>
+    To run the <code>SpinnerActivityTest</code> test, follow these steps:
+</p>
+ <ol>
+    <li>
+      In the Package Explorer, right-click the project SpinnerActivityTest at the top level, and then
+      select <strong>Run As</strong> &gt; <strong>Android JUnit Test</strong>:<br/>
+      <a href="{@docRoot}images/testing/spinnertest_runas_menu_callouts.png">
+        <img alt="Menu to run a test as an Android JUnit test" src="{@docRoot}images/testing/spinnertest_runas_menu_callouts.png" style="height:230px">
+      </a>
+    </li>
+    <li>
+        You will see the emulator start. When the unlock option is displayed (its appearance depends on the API level you specified for the AVD),
+        unlock the home screen.
+    </li>
+    <li>
+      The test application starts. You see a new tab for the <strong>JUnit</strong> view, next to the Package Explorer tab:<br/>
+      <a href="{@docRoot}images/testing/spinnertest_junit_panel.png">
+        <img alt="The JUnit window" src="{@docRoot}images/testing/spinnertest_junit_panel.png" style="height:230px">
+      </a>
+    </li>
+</ol>
+<p>
+    This view contains two sub-panes. The top pane summarizes the tests that were run, and the bottom pane shows failure traces for
+    highlighted tests.
+</p>
+<p>
+   At the conclusion of a successful test run, this is the view's appearance:<br/>
+   <a href="{@docRoot}images/testing/spinnertest_junit_success.png">
+    <img src="{@docRoot}images/testing/spinnertest_junit_success.png" alt="JUnit test run success" style="height:230px"/>
+   </a>
+</p>
+<p>
+    The upper pane summarizes the test:
+</p>
+    <ul>
+        <li>
+            Total time elapsed for the test application(labeled <em>Finished after &lt;x&gt; seconds</em>).
+        </li>
+        <li>
+            Number of runs (<em>Runs:</em>) - the number of tests in the entire test class.
+        </li>
+        <li>
+            Number of errors (<em>Errors:</em>) - the number of program errors and exceptions encountered during
+            the test run.
+        </li>
+        <li>
+            Number of failures (<em>Failures:</em>) - the number of test failures encountered during the test
+            run. This is the number of assertion failures. A test can fail even if the program does not encounter an error.
+        </li>
+        <li>
+            A progress bar. The progress bar extends from left to right as the tests run.
+            <p>
+               If all the tests succeed, the bar remains green. If a test fails, the bar turns from green to red.
+            </p>
+        </li>
+        <li>
+            A test method summary. Below the bar, you see a line for each class in the test application. To look at the results for the individual
+            methods in a test, click the arrow at the left to expand the line. You see the name of each test method. To the
+            right of the name, you see the time taken by the test. You can look at the test's code
+            by double-clicking its name.
+        </li>
+    </ul>
+<p>
+    The lower pane contains the failure trace. If all the tests are successful, this pane is empty. If some tests fail,
+    then if you highlight a failed test in the upper pane, the lower view contains a stack trace for the test. This is
+    demonstrated in the next section.
+</p>
+<p class="note">
+    <strong>Note:</strong> If you run the test application and nothing seems to happen, look for
+    the JUnit view. If you do not see it, you may have run the test application
+    as a regular Android application.
+    Remember that you need to run it as an Android <strong>JUnit</strong>
+    application.
+</p>
+<h2 id="TestFailure">Forcing Some Tests to Fail</h2>
+<p>
+  A test is as useful when it fails as when it succeeds. This section shows what happens in Eclipse with ADT when a test fails. You
+  can quickly see that a test class has failed, find the method or methods that failed, and then use a failure trace to find
+  the exact problem.
+</p>
+<p>
+  The example application SpinnerActivity that you downloaded passes all the tests in the test application SpinnerActivityTest.
+  To force the test to fail, you must modify the example application. You change a line of setup code in the application under test. This
+  causes the <code>testPreConditions()</code> and <code>testTextView()</code> test methods to fail.
+</p>
+<p>
+    To force the tests to fail, follow these steps:
+</p>
+<ol>
+  <li>
+    In Eclipse with ADT, go to the SpinnerActivity project and open the file <code>SpinnerActivity.java</code>.
+  </li>
+  <li>
+    At the top of <code>SpinnerActivity.java</code>, at the end of the <code>onCreate()</code> method, find the following line:
+<pre>
+    // mySpinner.setOnItemSelectedListener(null);
+</pre>
+    <p>Remove the forward slash characters at the beginning of the line to
+    uncomment the line. This sets the listener callback to null:
+    </p>
+<pre>
+    mySpinner.setOnItemSelectedListener(null);
+</pre>
+  </li>
+  <li>
+    The <code>testPreConditions()</code> method in <code>SpinnerActivityTest</code> contains the following test:
+    <code>assertTrue(mSpinner.getOnItemSelectedListener() != null);</code>. This test asserts that the listener callback is <em>not</em> null.
+    Since you have modified the application under test, this assertion now fails.
+  </li>
+  <li>
+    Run the test, as described in the previous section <a href="#RunTests">Running the Tests and Seeing the Results</a>.
+  </li>
+</ol>
+<p>
+    The JUnit view is either created or updated with the results of the test. Now, however, the progress bar is red,
+    the number of failures is 2, and small "x" icons appear in the list icons next to the testPreConditions and
+    TestSpinnerUI tests. This indicates that the tests have failed. The display is similar to this:<br/>
+    <a href="{@docRoot}images/testing/spinnertest_junit_panel_fail_callouts.png">
+      <img src="{@docRoot}images/testing/spinnertest_junit_panel_fail_callouts.png" alt="The JUnit Failure window" style="height:230px"/>
+    </a>
+</p>
+<p>
+  You now want to look at the failures to see exactly where they occurred.
+</p>
+<p>
+    To examine the failures, follow these steps:
+</p>
+<ol>
+  <li>
+    Click the testPreconditions entry. In the lower pane entitled <strong>Failure Trace</strong>,
+    you see a stack trace of the calls that led to the failure. This trace is similar to the following screenshot:<br/>
+    <a href="{@docRoot}images/testing/spinnertest_junit_panel_failtrace_callouts.png">
+      <img src="{@docRoot}images/testing/spinnertest_junit_panel_failtrace_callouts.png" alt="The JUnit failure trace" style="height:230px"/>
+    </a>
+  </li>
+  <li>
+      The first line of the trace tells you the error. In this case, a JUnit assertion failed. To look at the
+      assertion in the test code, double-click the next line (the first line of the trace). In the center pane
+      a new tabbed window opens, containing the code for the test application <code>SpinnerActivityTest</code>. The failed assertion
+      is highlighted in the middle of the window.
+  </li>
+</ol>
+<p>
+    The assertion failed because you modified the main application to set the <code>getOnItemSelectedListener</code> callback to <code>null</code>.
+</p>
+<p>
+    You can look at the failure in <code>testTextView</code> if you want. Remember, though, that <code>testPreConditions</code> is meant to verify the
+    initial setup of the application under test. If testPreConditions() fails, then succeeding tests can't be trusted. The best strategy to follow is to
+    fix the problem and re-run all the tests.
+</p>
+<p>
+    Remember to go back to <code>SpinnerActivity.java</code> and re-comment the line you uncommented in an earlier step.
+</p>
+<p>
+  You have now completed the tutorial.
+</p>
+<h2 id="NextSteps">Next Steps</h2>
+<p>
+    This example test application has shown you how to create a test project and link it to
+    the application you want to test, how to choose and add a test case class, how to write
+    UI and state management tests, and how to run the tests against the application under
+    test. Now that you are familiar with the basics of testing Android applications, here
+    are some suggested next steps:
+</p>
+<p>
+    <strong>Learn more about testing on Android</strong>
+</p>
+<ul>
+    <li>
+        If you haven't done so already, read the
+        <a href="{@docRoot}guide/topics/testing/testing_android.html">Testing Android Applications</a>
+        document in the <em>Dev Guide</em>. It provides an overview of how testing on Android
+        works. If you are just getting started with Android testing, reading that document will
+        help you understand the tools available to you, so that you can develop effective
+        tests.
+    </li>
+</ul>
+<p>
+    <strong>Review the main Android test case classes</strong>
+</p>
+<ul>
+    <li>
+        {@link android.test.ActivityInstrumentationTestCase2}
+    </li>
+    <li>
+        {@link android.test.ActivityUnitTestCase}
+    </li>
+    <li>
+        {@link android.test.ProviderTestCase2}
+    </li>
+    <li>
+        {@link android.test.ServiceTestCase}
+    </li>
+</ul>
+<p>
+    <strong>Learn more about the assert and utility classes</strong>
+</p>
+<ul>
+    <li>
+        {@link junit.framework.Assert}, the JUnit Assert class.
+    </li>
+    <li>
+        {@link android.test.MoreAsserts}, additional Android assert methods.
+    </li>
+    <li>
+        {@link android.test.ViewAsserts}, useful assertion methods for testing Views.
+    </li>
+    <li>
+        {@link android.test.TouchUtils}, utility methods for simulating touch events in an Activity.
+    </li>
+</ul>
+<p>
+    <strong>Learn about instrumentation and the instrumented test runner</strong>
+</p>
+<ul>
+    <li>
+        {@link android.app.Instrumentation}, the base instrumentation class.
+    </li>
+    <li>
+        {@link android.test.InstrumentationTestCase}, the base instrumentation test case.
+    </li>
+    <li>
+        {@link android.test.InstrumentationTestRunner}, the standard Android test runner.
+    </li>
+</ul>
+<h2 id="Appendix">Appendix</h2>
+<h3 id="InstallCompletedTestApp">Installing the Completed Test Application Java File</h3>
+<p>
+    The recommended approach to this tutorial is to follow the instructions step-by-step and
+    write the test code as you go. However, if you want to do this tutorial quickly,
+    you can install the entire Java file for the test application into the test project.
+</p>
+<p>
+    To do this, you first create a test project with the necessary structure and files by using
+    the automated tools in Eclipse. Then you exit Eclipse and copy the test application's Java file
+    from the SpinnerTest sample project into your test project. The SpinnerTest sample project is
+    part of the Samples component of the SDK.
+</p>
+<p>
+    The result is a complete test application, ready to run against the Spinner sample application.
+</p>
+<p>
+    To install the test application Java file, follow these steps:
+</p>
+<ol>
+    <li>
+        Set up the projects for the application under test and the test application, as described
+        in the section section <a href="#SetupProjects">Setting Up the Projects</a>.
+    </li>
+    <li>
+        Set up the emulator, as described in the section <a href="#SetupEmulator">Setting Up the Emulator</a>.
+    </li>
+    <li>
+        Add the test case class, as described in the section <a href="#AddTestCaseClass">Adding the test case class file</a>.
+    </li>
+    <li>
+        Close Eclipse with ADT.
+    </li>
+    <li>
+        Copy the file <code>&lt;SDK_path&gt;/samples/android-8/SpinnerTest/src/com/android/examples/spinner/test/SpinnerActivityTest.java</code>
+        to the directory <code>workspace/SpinnerActivityTest/src/com/android/examples/spinner/test/</code>.
+    </li>
+    <li>
+        Restart Eclipse with ADT.
+    </li>
+    <li>
+        In Eclipse with ADT, re-build the project <code>SpinnerActivityTest</code> by selecting it in the Package Explorer, right-clicking,
+        and selecting <em>Project</em>&nbsp;&gt;&nbsp;<em>Clean</em>.
+    </li>
+    <li>
+        The complete, working test application should now be in the <code>SpinnerActivityTest</code> project.
+    </li>
+</ol>
+<p>
+    You can now continue with the tutorial, starting at the section <a href="#AddConstructor">Adding the test case constructor</a> and
+    following along in the text.
+</p>
+<h3 id="EditorCommandLine">For Users Not Developing In Eclipse</h3>
+<p>
+    If you are not developing in Eclipse, you can still do this tutorial. Android provides tools for
+    creating test applications using a code editor and command-line tools. You use the following tools:
+</p>
+<ul>
+  <li>
+   <a href="{@docRoot}guide/developing/tools/adb.html">adb</a> - Installs and uninstalls applications and test applications to a device or the emulator. You
+   also use this tool to run the test application from the command line.
+  </li>
+  <li>
+    <a href="{@docRoot}guide/developing/tools/othertools.html#android">android</a> - Manages projects and test projects. This tool also manages AVDs and Android platforms.
+  </li>
+</ul>
+  <p>
+    You use the <code>emulator</code> tool to run the emulator from the command line.
+  </p>
+  <p>
+    Here are the general steps for doing this tutorial using an editor and the command line:
+  </p>
+<ol>
+  <li>
+    As described in the section <a href="#DownloadCode">Installing the Tutorial Sample Code</a>, get the sample code. You will then
+    have a directory <code>&lt;SDK_path&gt;/samples/android-8</code>, containing (among others) the directories <code>Spinner</code>
+    and <code>SpinnerTest</code>:
+    <ul>
+        <li>
+            <code>Spinner</code> contains the main application, also known as the <strong>application under test</strong>. This tutorial focuses on the
+            common situation of writing tests for an application that already exists, so the main application is provided to you.
+        </li>
+        <li>
+            <code>SpinnerTest</code> contains all the code for the test application. If you want to run quickly through the tutorial, you can
+            install the test code and then follow the text. You may get more from the tutorial, however, if you write the code as you go. The instructions
+            for installing the test code are in the section <a href="#InstallCompletedTestApp">Appendix: Installing the Completed Test Application</a>.
+        </li>
+        </ul>
+  </li>
+  <li>
+    Navigate to the directory <code>&lt;SDK_path&gt;/samples/android-8</code>.
+  </li>
+  <li>
+    Create a new Android application project using <code>android create project</code>:
+<pre>
+$ android create project -t &lt;APItarget&gt; -k com.android.example.spinner -a SpinnerActivity -n SpinnerActivity -p Spinner
+</pre>
+    <p>
+        The value of <code>&lt;APItarget&gt;</code> should be &quot;3&quot; (API level 3) or higher. If you are already developing with a particular API level, and it is
+        higher than 3, then use that API level.
+    </p>
+    <p>
+        This a new Android project <code>SpinnerActivity</code> in the existing <code>Spinner</code> directory. The existing source and
+        resource files are not touched, but the <code>android</code> tool adds the necessary build files.
+    </p>
+  </li>
+  <li>
+    Create a new Android test project using <code>android create test-project</code>:
+<pre>
+$ android create test-project -m ../Spinner -n SpinnerActivityTest -p SpinnerActivityTest
+</pre>
+    <p>
+        This will create a new Android test project in the <em>new</em> directory <code>SpinnerActivityTest</code>. You do this
+        so that the solution to the tutorial that is in <code>SpinnerTest</code> is left untouched. If you want to use the solution
+        code instead of entering it as you read through the tutorial, refer to the section
+        <a href="#InstallCompletedTestApp">Appendix: Installing the Completed Test Application</a>.
+    </p>
+    <p class="Note">
+      <strong>Note:</strong> Running <code>android create test-project</code> will automatically create
+      the file <code>AndroidManifest.xml</code> with the correct <code>&lt;instrumentation&gt;</code> element.
+    </p>
+  </li>
+  <li>
+    Build the sample application. If you are building with Ant, then it is easiest to use the command <code>ant debug</code> to build a debug version, since the SDK comes
+    with a debug signing key. The result will be the file <code>Spinner/bin/SpinnerActivity-debug.apk</code>.
+    You can install this to your device or emulator. Attach your device or start the emulator if you haven't already, and run the command:
+<pre>
+$ adb install Spinner/bin/SpinnerActivity-debug.apk
+</pre>
+  </li>
+  <li>
+    To create the test application, create a file <code>SpinnerActivityTest.java</code> in the directory
+    <code>SpinnerActivityTest/src/com/android/example/spinner/test/</code>.
+  </li>
+  <li>
+    Follow the tutorial, starting with the section <a href="#CreateTestCaseClass">Creating the Test Case Class</a>. When you are prompted to
+    run the sample application, go the the Launcher screen in your device or emulator and select SpinnerActivity.
+    When you are prompted to run the test application, return here to continue with the following instructions.
+  </li>
+  <li>
+    Build the test application. If you are building with Ant, then it is easiest to use the command <code>ant debug</code> to build a
+    debug version, since the SDK comes with a debug signing key. The result will be the Android file
+    <code>SpinnerActivityTest/bin/SpinnerActivityTest-debug.apk</code>. You can install this to your device or emulator.
+    Attach your device or start the emulator if you haven't already, and run the command:
+<pre>
+$ adb install SpinnerActivityTest/bin/SpinnerActivityTest-debug.apk
+</pre>
+  </li>
+  <li>
+    In your device or emulator, check that both the main application <code>SpinnerActivity</code> and the test application
+    <code>SpinnerActivityTest</code> are installed.
+  </li>
+  <li>
+    To run the test application, enter the following at the command line:
+<pre>
+$ adb shell am instrument -w com.android.example.spinner.test/android.test.InstrumentationTestRunner
+ </pre>
+  </li>
+</ol>
+<p>
+    The result of a successful test looks like this:
+</p>
+<pre>
+com.android.example.spinner.test.SpinnerActivityTest:....
+Test results for InstrumentationTestRunner=....
+Time: 10.098
+OK (4 tests)
+</pre>
+<p>
+    If you force the test to fail, as described in the previous section <a href="#TestFailure">Forcing Some Tests to Fail</a>, then
+    the output looks like this:
+</p>
+<pre>
+com.android.example.spinner.test.SpinnerActivityTest:
+Failure in testPreConditions:
+junit.framework.AssertionFailedError
+  at com.android.example.spinner.test.SpinnerActivityTest.testPreConditions(SpinnerActivityTest.java:104)
+  at java.lang.reflect.Method.invokeNative(Native Method)
+  at android.test.InstrumentationTestCase.runMethod(InstrumentationTestCase.java:205)
+  at android.test.InstrumentationTestCase.runTest(InstrumentationTestCase.java:195)
+  at android.test.ActivityInstrumentationTestCase2.runTest(ActivityInstrumentationTestCase2.java:175)
+  at android.test.AndroidTestRunner.runTest(AndroidTestRunner.java:169)
+  at android.test.AndroidTestRunner.runTest(AndroidTestRunner.java:154)
+  at android.test.InstrumentationTestRunner.onStart(InstrumentationTestRunner.java:430)
+  at android.app.Instrumentation$InstrumentationThread.run(Instrumentation.java:1447)
+Failure in testSpinnerUI:
+junit.framework.ComparisonFailure: expected:&lt;Result&gt; but was:&lt;Saturn&gt;
+  at com.android.example.spinner.test.SpinnerActivityTest.testSpinnerUI(SpinnerActivityTest.java:153)
+  at java.lang.reflect.Method.invokeNative(Native Method)
+  at android.test.InstrumentationTestCase.runMethod(InstrumentationTestCase.java:205)
+  at android.test.InstrumentationTestCase.runTest(InstrumentationTestCase.java:195)
+  at android.test.ActivityInstrumentationTestCase2.runTest(ActivityInstrumentationTestCase2.java:175)
+  at android.test.AndroidTestRunner.runTest(AndroidTestRunner.java:169)
+  at android.test.AndroidTestRunner.runTest(AndroidTestRunner.java:154)
+  at android.test.InstrumentationTestRunner.onStart(InstrumentationTestRunner.java:430)
+  at android.app.Instrumentation$InstrumentationThread.run(Instrumentation.java:1447)
+..
+Test results for InstrumentationTestRunner=.F.F..
+Time: 9.377
+FAILURES!!!
+Tests run: 4,  Failures: 2,  Errors: 0
+</pre>
diff --git a/docs/html/resources/tutorials/testing/helloandroid_test.jd b/docs/html/resources/tutorials/testing/helloandroid_test.jd
new file mode 100644
index 0000000..f06d5af
--- /dev/null
+++ b/docs/html/resources/tutorials/testing/helloandroid_test.jd
@@ -0,0 +1,506 @@
+page.title=Hello, Testing
+@jd:body
+ <div id="qv-wrapper">
+  <div id="qv">
+  <h2>In this document</h2>
+  <ol>
+    <li>
+        <a href="#CreateTestProject">Creating the Test Project</a>
+    </li>
+    <li>
+        <a href="#CreateTestClass">Creating the Test Case Class</a>
+        <ol>
+            <li>
+                <a href="#CreateTestCaseClassFile">Adding the test case class file</a>
+            </li>
+            <li>
+                <a href="#CreateConstructor">Adding the test case constructor</a>
+            </li>
+            <li>
+                <a href="#CreateSetUp">Adding a setup method</a>
+            </li>
+            <li>
+                <a href="#CreatePreConditions">Adding a preconditions test</a>
+            </li>
+            <li>
+                <a href="#CreateText">Adding a unit test</a>
+            </li>
+            <li>
+                <a href="#CompleteTest">The finished test case class</a>
+            </li>
+        </ol>
+    </li>
+    <li>
+        <a href="#RunTest">Running the Tests and Seeing the Results</a>
+    </li>
+    <li>
+        <a href="#NextSteps">Next Steps</a>
+    </li>
+  </ol>
+<h2>Related Tutorials</h2>
+<ol>
+    <li>
+        <a href="{@docRoot}resources/tutorials/hello-world.html">Hello, World</a>
+    </li>
+    <li>
+        <a href="{@docRoot}resources/tutorials/testing/activity_test.html">Activity Testing</a>
+    </li>
+</ol>
+<h2>See Also</h2>
+<ol>
+    <li>
+        <a href="{@docRoot}guide/topics/testing/testing_android.html">Testing Android Applications</a>
+    </li>
+    <li>
+        {@link android.test.ActivityInstrumentationTestCase2}
+    </li>
+    <li>
+        {@link android.test.InstrumentationTestRunner}
+    </li>
+</ol>
+
+</div>
+</div>
+<p>
+    Android offers a powerful but easy-to-use testing framework that is well integrated with the SDK tools. Because writing
+    tests is an important part of any development effort, this tutorial introduces the basics of testing and helps you get started testing quickly.
+
+    To keep things simple, this tutorial builds on the <a href="{@docRoot}resources/tutorials/hello-world.html">Hello World</a> tutorial, which you may have already completed.
+    It guides you through the process of setting up a test project, adding a test, and running the test against the Hello World application, all from inside the Eclipse environment.
+    Of course, when you are done with this tutorial, you will want to create a test project for your own app and add various types of tests to it.
+</p>
+<p>
+    If you'd like to read an overview of the test and instrumentation framework and the core test case classes available, look at
+    the <a href="{@docRoot}guide/topics/testing/testing_android.html">Testing Android Applications</a> topic.
+    If you prefer a more advanced testing tutorial, try the
+    <a href="{@docRoot}resources/tutorials/testing/activity_test.html">Activity Testing</a> tutorial.
+</p>
+<h2 id="Prerequisites">Prerequisites</h2>
+    <p>
+        This tutorial and its code depend on the Hello World tutorial. If you haven't completed that tutorial already,
+        do so now. You will learn the fundamentals of Android application development, and you will
+        have an Android application that is ready to be tested. The tutorial guides you through the
+        setup of an Android test project using the ADT Plugin for Eclipse and other SDK tools.
+        You will need an SDK development platform that is version 1.5
+        (API level 3) or higher.
+    </p>
+    <p>
+        If you aren't developing in Eclipse with ADT or you would like to run tests directly from the
+        command line, please see the topic <a href="{@docRoot}guide/developing/testing_otheride.html">Testing in Other IDEs</a>
+        for instructions.
+    </p>
+<h2 id="CreateTestProject">Creating the Test Project</h2>
+<p>
+    In the Hello World tutorial you created Android application project called
+    HelloAndroid. A test of an Android application is also an Android
+    application, and you create it within an Eclipse project. The Eclipse with ADT
+    <strong>New Android Test Project</strong> dialog creates a new test project and the
+    framework of a new test application at the same time.
+</p>
+<p>
+    To create the test project and test application framework in Eclipse with ADT, follow these steps
+</p>
+    <ol>
+        <li>
+            In Eclipse, select <strong>New</strong> &gt; <strong>Project</strong> &gt; <strong>Android</strong> &gt; <strong>Android Test Project</strong>.
+            <p>
+              <a href="{@docRoot}images/testing/hwtest_new_test_project_menu.png">
+                  <img alt="New Android Test Project menu" src="{@docRoot}images/testing/hwtest_new_test_project_menu.png" style="height:230px"/>
+              </a>
+            </p>
+            <p>
+              The New Android Test Project dialog appears.
+            </p>
+        </li>
+        <li>
+            Set the following values:
+            <ul>
+                <li>
+                    <em>Test Project Name:</em> &quot;HelloAndroidTest&quot;
+                </li>
+                <li>
+                    <em>Test Target:</em> Set &quot;An existing Android project&quot;, click Browse, and then
+                    select &quot;HelloAndroid&quot; from the list of projects.
+                </li>
+                <li>
+                    <em>Build Target:</em> Set a target whose platform is Android 1.5 or above.
+                </li>
+                <li>
+                    <em>Application name:</em> &quot;HelloAndroidTest&quot;
+                </li>
+                <li>
+                    <em>Package name:</em> &quot;<code>com.example.helloandroid.test</code>&quot;
+                </li>
+            </ul>
+            <p>
+                The dialog should now look like this:
+            </p>
+            <a href="{@docRoot}images/testing/hwtest_new_test_project_dialog_complete_callouts.png">
+                <img alt="New Android Test Project dialog with entries" src="{@docRoot}images/testing/hwtest_new_test_project_dialog_complete_callouts.png" style="height:230px"/>
+            </a>
+        </li>
+        <li>
+            Click Finish. The new project appears in the Package Explorer.
+        </li>
+    </ol>
+<h2 id="CreateTestClass">Creating the Test Case Class</h2>
+<p>
+    You now have a test project HelloAndroidTest, and the basic structure of a test application
+    also called HelloAndroidTest. The basic structure includes all the files and directories you
+    need to build and run a test application, <em>except for</em> the class that contains
+    your tests (the <strong>test case class</strong>).
+</p>
+<p>
+    The next step is to define the test case class. In this tutorial, you define a test case class
+    that extends one of Android's test case classes designed for Activities. The class contains
+    definitions for four methods:
+</p>
+    <ol>
+        <li>
+            <code>HelloAndroidTest</code>: This defines the constructor for the class. It is
+            required by the Android testing framework.
+        </li>
+        <li>
+            <code>setUp()</code>: This overrides the JUnit <code>setUp()</code> method. You use
+            it to initialize the environment before each test runs.
+        </li>
+        <li>
+            <code>testPreconditions()</code>: This defines a small test that ensures the Hello, Android
+            application starts up correctly.
+        </li>
+        <li>
+            <code>testText()</code>: This tests that what is displayed on the screen is the
+            same as what is contained in the application's string resources. It is an example of
+            a real unit test you would perform against an application's UI.
+        </li>
+    </ol>
+<p>
+    The following sections contain the code for the test case class and its methods.
+</p>
+
+<h3 id="CreateTestCaseClassFile">Adding the test case class file</h3>
+<p>
+  To add the Java file for the test case class, follow these steps
+</p>
+    <ol>
+        <li>
+            In Eclipse, open the HelloAndroidTest project if it is not already open.
+        </li>
+        <li>
+            Within HelloAndroidTest, expand the <code>src/</code> folder and
+            then find the package icon for <code>com.example.helloandroid.test</code>.
+            Right-click on the package icon and select <strong>New</strong> &gt; <strong>Class</strong>:
+            <p>
+              <a href="{@docRoot}images/testing/hwtest_create_test_class_menu_callouts.png">
+                  <img alt="Menu for creating a new class in the test application" src="{@docRoot}images/testing/hwtest_create_test_class_menu_callouts.png" style="height:230px"/>
+              </a>
+            </p>
+            <p>
+                The New Java Class dialog appears.
+            </p>
+        </li>
+        <li>
+            In the dialog, enter the following:
+            <ul>
+                <li>
+                    <em>Name:</em> &quot;HelloAndroidTest&quot;. This becomes the name of your test class.
+                </li>
+                <li>
+                    <em>Superclass:</em> &quot;<code>android.test.ActivityInstrumentationTestCase2&lt;HelloAndroid&gt;</code>&quot;.
+                    The superclass is parameterized by an Activity class name.
+                    <p>
+                        The dialog should now look like this:
+                    </p>
+                    <a href="{@docRoot}images/testing/hwtest_new_test_class_dialog_complete_callouts.png">
+                        <img alt="New Java Class dialog with entries" src="{@docRoot}images/testing/hwtest_new_test_class_dialog_complete_callouts.png" style="height:230px"/>
+                    </a>
+                </li>
+            </ul>
+            <p>
+                Do not change any of the other settings. Click Finish.
+            </p>
+        </li>
+        <li>
+            You now have a new file <code>HelloAndroidTest.java</code> in the project.
+            This file contains the class <code>HelloAndroidTest</code>,
+            which extends the Activity test case class
+            <code>ActivityInstrumentationTestCase2&lt;T&gt;</code>. You parameterize the
+            class with <code>HelloAndroid</code>, which is the class name of the activity under test.
+        </li>
+        <li>
+            Open <code>HelloAndroidTest.java</code>. It should look like this:
+<pre class="prettyprint">
+package com.example.helloandroid.test;
+
+import android.test.ActivityInstrumentationTestCase2;
+
+public class HelloAndroidTest extends ActivityInstrumentationTestCase2&lt;HelloAndroid&gt; {
+}
+</pre>
+        </li>
+        <li>
+            The test case class depends on the <code>HelloAndroid</code> class, which is not
+            yet imported. To import the class, add the following line just before the current
+            <code>import</code> statement:
+<pre class="prettyprint">
+import com.example.helloandroid.HelloAndroid;
+</pre>
+        </li>
+    </ol>
+<h3 id="CreateConstructor">Adding the test case constructor</h3>
+<p>
+    The test case class constructor is used by the Android testing framework when you run the test.
+    It calls the super constructor with parameters that tell the framework what Android application
+    should be tested.
+</p>
+<p>
+    Add the following constructor method immediately after the class definition:
+</p>
+<pre class="prettyprint">
+    public HelloAndroidTest() {
+      super("com.example.helloandroid", HelloAndroid.class);
+    }
+</pre>
+<p>
+    Save the file <code>HelloAndroidTest.java</code>.
+</p>
+<h3 id="CreateSetUp">Adding a setup method</h3>
+<p>
+    The <code>setUp()</code> method overrides the JUnit {@link junit.framework.TestCase#setUp() setUp()}
+    method, which the Android testing framework calls prior to running each test method. You use
+    <code>setUp()</code> to initialize variables and prepare the test environment. For this
+    test case, the <code>setUp()</code> method starts the Hello, Android application,
+    retrieves the text being displayed on the screen, and retrieves the text string in the
+    resource file.
+</p>
+<p>
+    First, add the following code immediately after the constructor method:
+</p>
+<pre class="prettyprint">
+    &#064;Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mActivity = this.getActivity();
+        mView = (TextView) mActivity.findViewById(com.example.helloandroid.R.id.textview);
+        resourceString = mActivity.getString(com.example.helloandroid.R.string.hello);
+    }
+</pre>
+<p>
+    For this code to work, you must also add some class members and another import statement. To
+    add the class members, add the following code immediately after the class definition:
+</p>
+<pre class="prettyprint">
+    private HelloAndroid mActivity;
+    private TextView mView;
+    private String resourceString;
+</pre>
+<p>
+    To add the import statement, add the following statement just after the import for
+    <code>android.test.ActivityInstrumentationTestCase2</code>:
+</p>
+<pre class="prettyprint">
+  import android.widget.TextView;
+</pre>
+<h3 id="CreatePreConditions">Adding a preconditions test</h3>
+<p>
+    A preconditions test checks the initial application conditions prior to executing other tests.
+    It's similar to <code>setUp()</code>, but with less overhead, since it only runs once.
+</p>
+<p>
+    Although a preconditions test can check for a variety of different conditions,
+    in this application it only needs to check whether the application under test is
+    initialized properly and the target TextView exists.
+    To do this, it calls the inherited
+    {@link junit.framework.Assert#assertNotNull(Object) assertNotNull()}
+    method, passing a reference to the TextView.
+    The test succeeds only if the object reference is not null.
+</p>
+<pre class="prettyprint">
+    public void testPreconditions() {
+      assertNotNull(mView);
+    }
+</pre>
+<h3 id="CreateText">Adding a unit test</h3>
+<p>
+    Now add a simple unit test to the test case class.
+    The method <code>testText()</code> will call a
+    {@link junit.framework.Assert JUnit Assert}
+    method to check whether the target TextView is displaying the expected text.
+</p>
+<p>
+    For this example, the test expects that the TextView is
+    displaying the string resource that was originally declared for it in HelloAndroid's
+    <code>main.xml</code> file, referred to by the resource ID <code>hello</code>.
+    The call to
+    {@link junit.framework.Assert#assertEquals(String, String) assertEquals(String,String)}
+    compares the expected value, read directly from the <code>hello</code>string resource,
+    to the text displayed by the TextView, obtained from the
+    TextView's <code>getText()</code> method. The test succeeds only if the strings match.
+</p>
+<p>
+    To add this test, add the following code
+    immediately after the <code>testPreconditions()</code> method:
+</p>
+<pre class="prettyprint">
+    public void testText() {
+      assertEquals(resourceString,(String)mView.getText());
+    }
+</pre>
+<h3 id="CompleteTest">The finished test case class</h3>
+<p>
+    You have now finished writing the test. This is what the complete test case class
+    should look like:
+</p>
+<pre class="prettyprint">
+package com.example.helloandroid.test;
+
+import com.example.helloandroid.HelloAndroid;
+import android.test.ActivityInstrumentationTestCase2;
+import android.widget.TextView;
+
+public class HelloAndroidTest extends ActivityInstrumentationTestCase2&lt;HelloAndroid&gt; {
+    private HelloAndroid mActivity;  // the activity under test
+    private TextView mView;          // the activity's TextView (the only view)
+    private String resourceString;
+
+    public HelloAndroidTest() {
+      super("com.example.helloandroid", HelloAndroid.class);
+    }
+    &#064;Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mActivity = this.getActivity();
+        mView = (TextView) mActivity.findViewById(com.example.helloandroid.R.id.textview);
+        resourceString = mActivity.getString(com.example.helloandroid.R.string.hello);
+    }
+    public void testPreconditions() {
+      assertNotNull(mView);
+    }
+    public void testText() {
+      assertEquals(resourceString,(String)mView.getText());
+    }
+}
+</pre>
+<h2 id="RunTest">Running the Tests and Seeing the Results</h2>
+<p>
+    You can now run the tests you've created against the Hello, Android application. In Eclipse with
+    ADT, you run a test application as an <strong>Android JUnit test</strong> rather than a regular
+    Android application.
+</p>
+<p>
+    To run the test application as an Android JUnit test, in the Package Explorer right-click
+    the HelloAndroidTest project and select <strong>Run As</strong> &gt; <strong>Android JUnit Test</strong>
+</p>
+    <a href="{@docRoot}images/testing/hwtest_runas_menu_callouts.png">
+        <img alt="Menu to run Hello, World as an Android JUnit test"
+            src="{@docRoot}images/testing/hwtest_runas_menu_callouts.png" style="height:230px">
+    </a>
+<p>
+    The ADT plugin then launches the test application and the application
+    under test on a the target emulator or device. When both applications are running,
+    the testing framework runs the tests and reports the results in the JUnit view of Eclipse,
+    which appears by default as a tab next to the Package Explorer.
+</p>
+<p>
+    As shown below, the JUnit view shows test results in two separate panes:
+    an upper pane summarizes the tests that were run and a lower pane reports the failure traces
+    for the tests. In this case, the tests in this example have run successfully, so there is no
+    failure reported in the view:
+</p>
+    <a href="{@docRoot}images/testing/hwtest_junit_success.png">
+        <img src="{@docRoot}images/testing/hwtest_junit_success.png"
+            alt="JUnit test run success" style="height:230px"/>
+    </a>
+<p>
+    The upper pane summarizes the test:
+</p>
+    <ul>
+        <li>
+            &quot;Finished after <em>x</em> seconds&quot;: How long the test took to run.
+        </li>
+        <li>
+            &quot;Runs&quot;: The number of tests run.
+        </li>
+        <li>
+            &quot;Errors:&quot;: The number of program errors and exceptions encountered during
+            the test run.
+        </li>
+        <li>
+            &quot;Failures:&quot;: The number of assertion failures encountered during the
+            test run.
+        </li>
+        <li>
+            A progress bar. The progress bar extends from left to right as the tests run.
+            <p>
+              If all the tests succeed, the bar remains green.
+              If a test fails, the bar turns from green to red.
+            </p>
+        </li>
+        <li>
+            A test method summary. Below the bar, you see a line for each class in the
+            test application, labeled by its fully-qualified class name.
+            To look at the results for the individual methods in a test case class,
+            click the arrow at the left of the class to expand the line.
+            You see the name of each test method. To the right of the method name, you see the
+            time needed to run that method. You can look at the method's code by
+            double-clicking its name.
+        </li>
+     </ul>
+     <p>
+        The lower pane contains the failure trace. If all the tests are successful,
+        this pane is empty. If some tests fail, then if you select a failed test in the
+        upper pane, the lower view contains a stack trace for the test.
+     </p>
+<h2 id="NextSteps">Next Steps</h2>
+<p>
+    This simple example test application has shown you how to create a test project,
+    create a test class and test cases, and then run the tests against a target application.
+    Now that you are familiar with these fundamentals, here are some suggested next steps:
+</p>
+<p>
+    <strong>Learn more about testing on Android</strong>
+</p>
+<ul>
+    <li>
+        The
+      <a href="{@docRoot}guide/topics/testing/testing_android.html">Testing Android Applications</a>
+        document in the <em>Dev Guide</em> provides an overview of how testing on Android works.
+        If you are just getting started with Android testing, reading that document will
+        help you understand the tools available to you, so that you can develop effective
+        tests.
+    </li>
+</ul>
+<p>
+    <strong>Learn more about the testing classes available in Android</strong>
+</p>
+<ul>
+    <li>
+        For an overview of the types of testing classes you can use,
+        browse through the reference documentation for
+        {@link android.test.ActivityInstrumentationTestCase2},
+        {@link android.test.ProviderTestCase2},
+        {@link android.test.ServiceTestCase}, and
+        {@link junit.framework.Assert}.
+    </li>
+</ul>
+<p>
+    <strong>Explore the Android instrumentation framework</strong>
+</p>
+<ul>
+    <li>
+        The {@link android.test.InstrumentationTestRunner} class contains the code that Android uses
+        to run tests against an application. The {@link android.test.InstrumentationTestCase} class
+        is the base class for test case classes that use additional instrumentation features.
+    </li>
+</ul>
+<p>
+    <strong>Follow the Activity Testing tutorial</strong>
+</p>
+<ul>
+    <li>
+        The <a href="{@docRoot}resources/tutorials/testing/activity_test.html">Activity Testing</a>
+        tutorial is an excellent follow-up to this tutorial.
+        It guides you through a more complex testing scenario that you develop against a
+        more realistic application.
+    </li>
+</ul>
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index 3e3f87b..183c896 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -93,7 +93,9 @@
         /**
          * Geometry and text drawn with this style will be both filled and
          * stroked at the same time, respecting the stroke-related fields on
-         * the paint.
+         * the paint. This mode can give unexpected results if the geometry
+         * is oriented counter-clockwise. This restriction does not apply to
+         * either FILL or STROKE.
          */
         FILL_AND_STROKE (2);
         
diff --git a/graphics/java/android/graphics/PorterDuff.java b/graphics/java/android/graphics/PorterDuff.java
index 3904234..0e405c2 100644
--- a/graphics/java/android/graphics/PorterDuff.java
+++ b/graphics/java/android/graphics/PorterDuff.java
@@ -53,7 +53,10 @@
         /** [Sa * Da, Sc * Dc] */
         MULTIPLY    (14),
         /** [Sa + Da - Sa * Da, Sc + Dc - Sc * Dc] */
-        SCREEN      (15);
+        SCREEN      (15),
+        /** Saturate(S + D) */
+        ADD         (16),
+        OVERLAY     (17);
 
         Mode(int nativeInt) {
             this.nativeInt = nativeInt;
diff --git a/graphics/java/android/graphics/drawable/Drawable.java b/graphics/java/android/graphics/drawable/Drawable.java
index 6a7b2d1..4c1d243 100644
--- a/graphics/java/android/graphics/drawable/Drawable.java
+++ b/graphics/java/android/graphics/drawable/Drawable.java
@@ -16,21 +16,30 @@
 
 package android.graphics.drawable;
 
-import java.io.InputStream;
-import java.io.IOException;
-import java.util.Arrays;
-
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
 import android.content.res.Resources;
 import android.content.res.TypedArray;
-import android.graphics.*;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.NinePatch;
+import android.graphics.PixelFormat;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffColorFilter;
+import android.graphics.Rect;
+import android.graphics.Region;
 import android.util.AttributeSet;
 import android.util.DisplayMetrics;
 import android.util.StateSet;
-import android.util.Xml;
 import android.util.TypedValue;
+import android.util.Xml;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
 
 /**
  * A Drawable is a general abstraction for "something that can be drawn."  Most
@@ -645,6 +654,8 @@
      * Calling this method on a mutable Drawable will have no effect.
      *
      * @return This drawable.
+     * @see ConstantState
+     * @see #getConstantState()
      */
     public Drawable mutate() {
         return this;
@@ -750,6 +761,8 @@
             drawable = new StateListDrawable();
         } else if (name.equals("level-list")) {
             drawable = new LevelListDrawable();
+        } else if (name.equals("mipmap")) {
+            drawable = new MipmapDrawable();
         } else if (name.equals("layer-list")) {
             drawable = new LayerDrawable();
         } else if (name.equals("transition")) {
@@ -771,7 +784,7 @@
         } else if (name.equals("inset")) {
             drawable = new InsetDrawable();
         } else if (name.equals("bitmap")) {
-            drawable = new BitmapDrawable();
+            drawable = new BitmapDrawable(r);
             if (r != null) {
                ((BitmapDrawable) drawable).setTargetDensity(r.getDisplayMetrics());
             }
@@ -806,6 +819,9 @@
         return null;
     }
 
+    /**
+     * Inflate this Drawable from an XML resource.
+     */
     public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs)
             throws XmlPullParserException, IOException {
 
@@ -814,6 +830,12 @@
         a.recycle();
     }
 
+    /**
+     * Inflate a Drawable from an XML resource.
+     *
+     * @throws XmlPullParserException
+     * @throws IOException
+     */
     void inflateWithAttributes(Resources r, XmlPullParser parser,
             TypedArray attrs, int visibleAttr)
             throws XmlPullParserException, IOException {
@@ -821,12 +843,27 @@
         mVisible = attrs.getBoolean(visibleAttr, mVisible);
     }
 
+    /**
+     * This abstract class is used by {@link Drawable}s to store shared constant state and data
+     * between Drawables. {@link BitmapDrawable}s created from the same resource will for instance
+     * share a unique bitmap stored in their ConstantState.
+     *
+     * <p>
+     * {@link #newDrawable(Resources)} can be used as a factory to create new Drawable instances
+     * from this ConstantState.
+     * </p>
+     *
+     * Use {@link Drawable#getConstantState()} to retrieve the ConstantState of a Drawable. Calling
+     * {@link Drawable#mutate()} on a Drawable should typically create a new ConstantState for that
+     * Drawable.
+     */
     public static abstract class ConstantState {
         /**
          * Create a new drawable without supplying resources the caller
          * is running in.  Note that using this means the density-dependent
          * drawables (like bitmaps) will not be able to update their target
-         * density correctly.
+         * density correctly. One should use {@link #newDrawable(Resources)}
+         * instead to provide a resource.
          */
         public abstract Drawable newDrawable();
         /**
@@ -845,6 +882,13 @@
         public abstract int getChangingConfigurations();
     }
 
+    /**
+     * Return a {@link ConstantState} instance that holds the shared state of this Drawable.
+     *q
+     * @return The ConstantState associated to that Drawable.
+     * @see ConstantState
+     * @see Drawable#mutate()
+     */
     public ConstantState getConstantState() {
         return null;
     }
diff --git a/graphics/java/android/graphics/drawable/DrawableContainer.java b/graphics/java/android/graphics/drawable/DrawableContainer.java
index c6f57d4..124d907 100644
--- a/graphics/java/android/graphics/drawable/DrawableContainer.java
+++ b/graphics/java/android/graphics/drawable/DrawableContainer.java
@@ -17,8 +17,16 @@
 package android.graphics.drawable;
 
 import android.content.res.Resources;
-import android.graphics.*;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
 
+/**
+ * A helper class that contains several {@link Drawable}s and selects which one to use.
+ *
+ * You can subclass it to create your own DrawableContainers or directly use one its child classes.
+ */
 public class DrawableContainer extends Drawable implements Drawable.Callback {
 
     /**
@@ -196,8 +204,7 @@
                 mDrawableContainerState.getOpacity();
     }
 
-    public boolean selectDrawable(int idx)
-    {
+    public boolean selectDrawable(int idx) {
         if (idx == mCurIndex) {
             return false;
         }
@@ -255,6 +262,12 @@
         return this;
     }
 
+    /**
+     * A ConstantState that can contain several {@link Drawable}s.
+     *
+     * This class was made public to enable testing, and its visibility may change in a future
+     * release.
+     */
     public abstract static class DrawableContainerState extends ConstantState {
         final DrawableContainer mOwner;
 
@@ -443,12 +456,12 @@
             return mConstantMinimumHeight;
         }
 
-        private void computeConstantSize() {
+        protected void computeConstantSize() {
             mComputedConstantSize = true;
 
             final int N = getChildCount();
             final Drawable[] drawables = mDrawables;
-            mConstantWidth = mConstantHeight = 0;
+            mConstantWidth = mConstantHeight = -1;
             mConstantMinimumWidth = mConstantMinimumHeight = 0;
             for (int i = 0; i < N; i++) {
                 Drawable dr = drawables[i];
diff --git a/graphics/java/android/graphics/drawable/MipmapDrawable.java b/graphics/java/android/graphics/drawable/MipmapDrawable.java
new file mode 100644
index 0000000..75fdeed
--- /dev/null
+++ b/graphics/java/android/graphics/drawable/MipmapDrawable.java
@@ -0,0 +1,309 @@
+/*
+ * Copyright (C) 2006 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.graphics.drawable;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+
+import java.io.IOException;
+
+/**
+ * A resource that manages a number of alternate Drawables, and which actually draws the one which
+ * size matches the most closely the drawing bounds. Providing several pre-scaled version of the
+ * drawable helps minimizing the aliasing artifacts that can be introduced by the scaling.
+ *
+ * <p>
+ * Use {@link #addDrawable(Drawable)} to define the different Drawables that will represent the
+ * mipmap levels of this MipmapDrawable. The mipmap Drawable that will actually be used when this
+ * MipmapDrawable is drawn is the one which has the smallest intrinsic height greater or equal than
+ * the bounds' height. This selection ensures that the best available mipmap level is scaled down to
+ * draw this MipmapDrawable.
+ * </p>
+ *
+ * If the bounds' height is larger than the largest mipmap, the largest mipmap will be scaled up.
+ * Note that Drawables without intrinsic height (i.e. with a negative value, such as Color) will
+ * only be used if no other mipmap Drawable are provided. The Drawables' intrinsic heights should
+ * not be changed after the Drawable has been added to this MipmapDrawable.
+ *
+ * <p>
+ * The different mipmaps' parameters (opacity, padding, color filter, gravity...) should typically
+ * be similar to ensure a continuous visual appearance when the MipmapDrawable is scaled. The aspect
+ * ratio of the different mipmaps should especially be equal.
+ * </p>
+ *
+ * A typical example use of a MipmapDrawable would be for an image which is intended to be scaled at
+ * various sizes, and for which one wants to provide pre-scaled versions to precisely control its
+ * appearance.
+ *
+ * <p>
+ * The intrinsic size of a MipmapDrawable are inferred from those of the largest mipmap (in terms of
+ * {@link Drawable#getIntrinsicHeight()}). On the opposite, its minimum
+ * size is defined by the smallest provided mipmap.
+ * </p>
+
+ * It can be defined in an XML file with the <code>&lt;mipmap></code> element.
+ * Each mipmap Drawable is defined in a nested <code>&lt;item></code>. For example:
+ * <pre>
+ * &lt;mipmap xmlns:android="http://schemas.android.com/apk/res/android">
+ *  &lt;item android:drawable="@drawable/my_image_8" />
+ *  &lt;item android:drawable="@drawable/my_image_32" />
+ *  &lt;item android:drawable="@drawable/my_image_128" />
+ * &lt;/mipmap>
+ *</pre>
+ * <p>
+ * With this XML saved into the res/drawable/ folder of the project, it can be referenced as
+ * the drawable for an {@link android.widget.ImageView}. Assuming that the heights of the provided
+ * drawables are respectively 8, 32 and 128 pixels, the first one will be scaled down when the
+ * bounds' height is lower or equal than 8 pixels. The second drawable will then be used up to a
+ * height of 32 pixels and the largest drawable will be used for greater heights.
+ * </p>
+ * @attr ref android.R.styleable#MipmapDrawableItem_drawable
+ */
+public class MipmapDrawable extends DrawableContainer {
+    private final MipmapContainerState mMipmapContainerState;
+    private boolean mMutated;
+
+    public MipmapDrawable() {
+        this(null, null);
+    }
+
+    /**
+     * Adds a Drawable to the list of available mipmap Drawables. The Drawable actually used when
+     * this MipmapDrawable is drawn is determined from its bounds.
+     *
+     * This method has no effect if drawable is null.
+     *
+     * @param drawable The Drawable that will be added to list of available mipmap Drawables.
+     */
+
+    public void addDrawable(Drawable drawable) {
+        if (drawable != null) {
+            mMipmapContainerState.addDrawable(drawable);
+            onDrawableAdded();
+        }
+    }
+
+    private void onDrawableAdded() {
+        // selectDrawable assumes that the container content does not change.
+        // When a Drawable is added, the same index can correspond to a new Drawable, and since
+        // selectDrawable has a fast exit case when oldIndex==newIndex, the new drawable could end
+        // up not being used in place of the previous one if they happen to share the same index.
+        // This make sure the new computed index can actually replace the previous one.
+        selectDrawable(-1);
+        onBoundsChange(getBounds());
+    }
+
+    // overrides from Drawable
+
+    @Override
+    protected void onBoundsChange(Rect bounds) {
+        final int index = mMipmapContainerState.indexForBounds(bounds);
+
+        // Will call invalidateSelf() if needed
+        selectDrawable(index);
+
+        super.onBoundsChange(bounds);
+    }
+
+    @Override
+    public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs)
+    throws XmlPullParserException, IOException {
+
+        super.inflate(r, parser, attrs);
+
+        int type;
+
+        final int innerDepth = parser.getDepth() + 1;
+        int depth;
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && ((depth = parser.getDepth()) >= innerDepth
+                        || type != XmlPullParser.END_TAG)) {
+            if (type != XmlPullParser.START_TAG) {
+                continue;
+            }
+
+            if (depth > innerDepth || !parser.getName().equals("item")) {
+                continue;
+            }
+
+            TypedArray a = r.obtainAttributes(attrs,
+                    com.android.internal.R.styleable.MipmapDrawableItem);
+
+            int drawableRes = a.getResourceId(
+                    com.android.internal.R.styleable.MipmapDrawableItem_drawable, 0);
+
+            a.recycle();
+
+            Drawable dr;
+            if (drawableRes != 0) {
+                dr = r.getDrawable(drawableRes);
+            } else {
+                while ((type = parser.next()) == XmlPullParser.TEXT) {
+                }
+                if (type != XmlPullParser.START_TAG) {
+                    throw new XmlPullParserException(
+                            parser.getPositionDescription()
+                            + ": <item> tag requires a 'drawable' attribute or "
+                            + "child tag defining a drawable");
+                }
+                dr = Drawable.createFromXmlInner(r, parser, attrs);
+            }
+
+            mMipmapContainerState.addDrawable(dr);
+        }
+
+        onDrawableAdded();
+    }
+
+    @Override
+    public Drawable mutate() {
+        if (!mMutated && super.mutate() == this) {
+            mMipmapContainerState.mMipmapHeights = mMipmapContainerState.mMipmapHeights.clone();
+            mMutated = true;
+        }
+        return this;
+    }
+
+    private final static class MipmapContainerState extends DrawableContainerState {
+        private int[] mMipmapHeights;
+
+        MipmapContainerState(MipmapContainerState orig, MipmapDrawable owner, Resources res) {
+            super(orig, owner, res);
+
+            if (orig != null) {
+                mMipmapHeights = orig.mMipmapHeights;
+            } else {
+                mMipmapHeights = new int[getChildren().length];
+            }
+
+            // Change the default value
+            setConstantSize(true);
+        }
+
+        /**
+         * Returns the index of the child mipmap drawable that will best fit the provided bounds.
+         * This index is determined by comparing bounds' height and children intrinsic heights.
+         * The returned mipmap index is the smallest mipmap which height is greater or equal than
+         * the bounds' height. If the bounds' height is larger than the largest mipmap, the largest
+         * mipmap index is returned.
+         *
+         * @param bounds The bounds of the MipMapDrawable.
+         * @return The index of the child Drawable that will best fit these bounds, or -1 if there
+         * are no children mipmaps.
+         */
+        public int indexForBounds(Rect bounds) {
+            final int boundsHeight = bounds.height();
+            final int N = getChildCount();
+            for (int i = 0; i < N; i++) {
+                if (boundsHeight <= mMipmapHeights[i]) {
+                    return i;
+                }
+            }
+
+            // No mipmap larger than bounds found. Use largest one which will be scaled up.
+            if (N > 0) {
+                return N - 1;
+            }
+            // No Drawable mipmap at all
+            return -1;
+        }
+
+        /**
+         * Adds a Drawable to the list of available mipmap Drawables. This list can be retrieved
+         * using {@link DrawableContainer.DrawableContainerState#getChildren()} and this method
+         * ensures that it is always sorted by increasing {@link Drawable#getIntrinsicHeight()}.
+         *
+         * @param drawable The Drawable that will be added to children list
+         */
+        public void addDrawable(Drawable drawable) {
+            // Insert drawable in last position, correctly resetting cached values and
+            // especially mComputedConstantSize
+            int pos = addChild(drawable);
+
+            // Bubble sort the last drawable to restore the sort by intrinsic height
+            final int drawableHeight = drawable.getIntrinsicHeight();
+
+            while (pos > 0) {
+                final Drawable previousDrawable = mDrawables[pos-1];
+                final int previousIntrinsicHeight = previousDrawable.getIntrinsicHeight();
+
+                if (drawableHeight < previousIntrinsicHeight) {
+                    mDrawables[pos] = previousDrawable;
+                    mMipmapHeights[pos] = previousIntrinsicHeight;
+
+                    mDrawables[pos-1] = drawable;
+                    mMipmapHeights[pos-1] = drawableHeight;
+                    pos--;
+                } else {
+                    break;
+                }
+            }
+        }
+
+        /**
+         * Intrinsic sizes are those of the largest available mipmap.
+         * Minimum sizes are those of the smallest available mipmap.
+         */
+        @Override
+        protected void computeConstantSize() {
+            final int N = getChildCount();
+            if (N > 0) {
+                final Drawable smallestDrawable = mDrawables[0];
+                mConstantMinimumWidth = smallestDrawable.getMinimumWidth();
+                mConstantMinimumHeight = smallestDrawable.getMinimumHeight();
+
+                final Drawable largestDrawable = mDrawables[N-1];
+                mConstantWidth = largestDrawable.getIntrinsicWidth();
+                mConstantHeight = largestDrawable.getIntrinsicHeight();
+            } else {
+                mConstantWidth = mConstantHeight = -1;
+                mConstantMinimumWidth = mConstantMinimumHeight = 0;
+            }
+            mComputedConstantSize = true;
+        }
+
+        @Override
+        public Drawable newDrawable() {
+            return new MipmapDrawable(this, null);
+        }
+
+        @Override
+        public Drawable newDrawable(Resources res) {
+            return new MipmapDrawable(this, res);
+        }
+
+        @Override
+        public void growArray(int oldSize, int newSize) {
+            super.growArray(oldSize, newSize);
+            int[] newInts = new int[newSize];
+            System.arraycopy(mMipmapHeights, 0, newInts, 0, oldSize);
+            mMipmapHeights = newInts;
+        }
+    }
+
+    private MipmapDrawable(MipmapContainerState state, Resources res) {
+        MipmapContainerState as = new MipmapContainerState(state, this, res);
+        mMipmapContainerState = as;
+        setConstantState(as);
+        onDrawableAdded();
+    }
+}
diff --git a/graphics/java/android/renderscript/Allocation.java b/graphics/java/android/renderscript/Allocation.java
index 17c0778..d32a0b5 100644
--- a/graphics/java/android/renderscript/Allocation.java
+++ b/graphics/java/android/renderscript/Allocation.java
@@ -76,10 +76,30 @@
         subData1D(0, mType.getElementCount(), d);
     }
 
+    public void subData(int off, FieldPacker fp) {
+        int eSize = mType.mElement.getSizeBytes();
+        final byte[] data = fp.getData();
+
+        int count = data.length / eSize;
+        if ((eSize * count) != data.length) {
+            throw new IllegalArgumentException("Field packer length " + data.length +
+                                               " not divisible by element size " + eSize + ".");
+        }
+        data1DChecks(off, count, data.length, data.length);
+        mRS.nAllocationSubData1D(mID, off, count, data, data.length);
+    }
+
     private void data1DChecks(int off, int count, int len, int dataSize) {
         mRS.validate();
-        if((off < 0) || (count < 1) || ((off + count) > mType.getElementCount())) {
-            throw new IllegalArgumentException("Offset or Count out of bounds.");
+        if(off < 0) {
+            throw new IllegalArgumentException("Offset must be >= 0.");
+        }
+        if(count < 1) {
+            throw new IllegalArgumentException("Count must be >= 1.");
+        }
+        if((off + count) > mType.getElementCount()) {
+            throw new IllegalArgumentException("Overflow, Available count " + mType.getElementCount() +
+                                               ", got " + count + " at offset " + off + ".");
         }
         if((len) < dataSize) {
             throw new IllegalArgumentException("Array too small for allocation type.");
diff --git a/graphics/java/android/renderscript/Vector2f.java b/graphics/java/android/renderscript/Byte2.java
similarity index 89%
copy from graphics/java/android/renderscript/Vector2f.java
copy to graphics/java/android/renderscript/Byte2.java
index 567d57fa..95cf88c 100644
--- a/graphics/java/android/renderscript/Vector2f.java
+++ b/graphics/java/android/renderscript/Byte2.java
@@ -24,12 +24,12 @@
  * @hide
  *
  **/
-public class Vector2f {
-    public Vector2f() {
+public class Byte2 {
+    public Byte2() {
     }
 
-    public float x;
-    public float y;
+    public byte x;
+    public byte y;
 }
 
 
diff --git a/graphics/java/android/renderscript/Vector2f.java b/graphics/java/android/renderscript/Byte3.java
similarity index 87%
copy from graphics/java/android/renderscript/Vector2f.java
copy to graphics/java/android/renderscript/Byte3.java
index 567d57fa..a6c0ca9 100644
--- a/graphics/java/android/renderscript/Vector2f.java
+++ b/graphics/java/android/renderscript/Byte3.java
@@ -24,12 +24,13 @@
  * @hide
  *
  **/
-public class Vector2f {
-    public Vector2f() {
+public class Byte3 {
+    public Byte3() {
     }
 
-    public float x;
-    public float y;
+    public byte x;
+    public byte y;
+    public byte z;
 }
 
 
diff --git a/graphics/java/android/renderscript/Vector2f.java b/graphics/java/android/renderscript/Byte4.java
similarity index 85%
copy from graphics/java/android/renderscript/Vector2f.java
copy to graphics/java/android/renderscript/Byte4.java
index 567d57fa..a5bfc61 100644
--- a/graphics/java/android/renderscript/Vector2f.java
+++ b/graphics/java/android/renderscript/Byte4.java
@@ -24,14 +24,15 @@
  * @hide
  *
  **/
-public class Vector2f {
-    public Vector2f() {
+public class Byte4 {
+    public Byte4() {
     }
 
-    public float x;
-    public float y;
+    public byte x;
+    public byte y;
+    public byte z;
+    public byte w;
 }
 
 
 
-
diff --git a/graphics/java/android/renderscript/Element.java b/graphics/java/android/renderscript/Element.java
index 10ef05a..7b155fe 100644
--- a/graphics/java/android/renderscript/Element.java
+++ b/graphics/java/android/renderscript/Element.java
@@ -126,6 +126,77 @@
         return rs.mElement_USER_F32;
     }
 
+    public static Element USER_ELEMENT(RenderScript rs) {
+        if(rs.mElement_USER_ELEMENT == null) {
+            rs.mElement_USER_ELEMENT = createUser(rs, DataType.RS_ELEMENT);
+        }
+        return rs.mElement_USER_ELEMENT;
+    }
+
+    public static Element USER_TYPE(RenderScript rs) {
+        if(rs.mElement_USER_TYPE == null) {
+            rs.mElement_USER_TYPE = createUser(rs, DataType.RS_TYPE);
+        }
+        return rs.mElement_USER_TYPE;
+    }
+
+    public static Element USER_ALLOCATION(RenderScript rs) {
+        if(rs.mElement_USER_ALLOCATION == null) {
+            rs.mElement_USER_ALLOCATION = createUser(rs, DataType.RS_ALLOCATION);
+        }
+        return rs.mElement_USER_ALLOCATION;
+    }
+
+    public static Element USER_SAMPLER(RenderScript rs) {
+        if(rs.mElement_USER_SAMPLER == null) {
+            rs.mElement_USER_SAMPLER = createUser(rs, DataType.RS_SAMPLER);
+        }
+        return rs.mElement_USER_SAMPLER;
+    }
+
+    public static Element USER_SCRIPT(RenderScript rs) {
+        if(rs.mElement_USER_SCRIPT == null) {
+            rs.mElement_USER_SCRIPT = createUser(rs, DataType.RS_SCRIPT);
+        }
+        return rs.mElement_USER_SCRIPT;
+    }
+
+    public static Element USER_MESH(RenderScript rs) {
+        if(rs.mElement_USER_MESH == null) {
+            rs.mElement_USER_MESH = createUser(rs, DataType.RS_MESH);
+        }
+        return rs.mElement_USER_MESH;
+    }
+
+    public static Element USER_PROGRAM_FRAGMENT(RenderScript rs) {
+        if(rs.mElement_USER_PROGRAM_FRAGMENT == null) {
+            rs.mElement_USER_PROGRAM_FRAGMENT = createUser(rs, DataType.RS_PROGRAM_FRAGMENT);
+        }
+        return rs.mElement_USER_PROGRAM_FRAGMENT;
+    }
+
+    public static Element USER_PROGRAM_VERTEX(RenderScript rs) {
+        if(rs.mElement_USER_PROGRAM_VERTEX == null) {
+            rs.mElement_USER_PROGRAM_VERTEX = createUser(rs, DataType.RS_PROGRAM_VERTEX);
+        }
+        return rs.mElement_USER_PROGRAM_VERTEX;
+    }
+
+    public static Element USER_PROGRAM_RASTER(RenderScript rs) {
+        if(rs.mElement_USER_PROGRAM_RASTER == null) {
+            rs.mElement_USER_PROGRAM_RASTER = createUser(rs, DataType.RS_PROGRAM_RASTER);
+        }
+        return rs.mElement_USER_PROGRAM_RASTER;
+    }
+
+    public static Element USER_PROGRAM_STORE(RenderScript rs) {
+        if(rs.mElement_USER_PROGRAM_STORE == null) {
+            rs.mElement_USER_PROGRAM_STORE = createUser(rs, DataType.RS_PROGRAM_STORE);
+        }
+        return rs.mElement_USER_PROGRAM_STORE;
+    }
+
+
     public static Element A_8(RenderScript rs) {
         if(rs.mElement_A_8 == null) {
             rs.mElement_A_8 = createPixel(rs, DataType.UNSIGNED_8, DataKind.PIXEL_A);
diff --git a/graphics/java/android/renderscript/FieldPacker.java b/graphics/java/android/renderscript/FieldPacker.java
index b26e47d..6d55c7e 100644
--- a/graphics/java/android/renderscript/FieldPacker.java
+++ b/graphics/java/android/renderscript/FieldPacker.java
@@ -33,21 +33,24 @@
         }
     }
 
-    void reset() {
+    public void reset() {
         mPos = 0;
     }
+    public void reset(int i) {
+        mPos = i;
+    }
 
-    void addI8(byte v) {
+    public void addI8(byte v) {
         mData[mPos++] = v;
     }
 
-    void addI16(short v) {
+    public void addI16(short v) {
         align(2);
         mData[mPos++] = (byte)(v & 0xff);
         mData[mPos++] = (byte)(v >> 8);
     }
 
-    void addI32(int v) {
+    public void addI32(int v) {
         align(4);
         mData[mPos++] = (byte)(v & 0xff);
         mData[mPos++] = (byte)((v >> 8) & 0xff);
@@ -55,7 +58,7 @@
         mData[mPos++] = (byte)((v >> 24) & 0xff);
     }
 
-    void addI64(long v) {
+    public void addI64(long v) {
         align(8);
         mData[mPos++] = (byte)(v & 0xff);
         mData[mPos++] = (byte)((v >> 8) & 0xff);
@@ -67,14 +70,14 @@
         mData[mPos++] = (byte)((v >> 56) & 0xff);
     }
 
-    void addU8(short v) {
+    public void addU8(short v) {
         if ((v < 0) || (v > 0xff)) {
             throw new IllegalArgumentException("Saving value out of range for type");
         }
         mData[mPos++] = (byte)v;
     }
 
-    void addU16(int v) {
+    public void addU16(int v) {
         if ((v < 0) || (v > 0xffff)) {
             throw new IllegalArgumentException("Saving value out of range for type");
         }
@@ -83,7 +86,7 @@
         mData[mPos++] = (byte)(v >> 8);
     }
 
-    void addU32(long v) {
+    public void addU32(long v) {
         if ((v < 0) || (v > 0xffffffff)) {
             throw new IllegalArgumentException("Saving value out of range for type");
         }
@@ -94,7 +97,7 @@
         mData[mPos++] = (byte)((v >> 24) & 0xff);
     }
 
-    void addU64(long v) {
+    public void addU64(long v) {
         if (v < 0) {
             throw new IllegalArgumentException("Saving value out of range for type");
         }
@@ -109,15 +112,135 @@
         mData[mPos++] = (byte)((v >> 56) & 0xff);
     }
 
-    void addF32(float v) {
+    public void addF32(float v) {
         addI32(Float.floatToRawIntBits(v));
     }
 
-    void addF64(float v) {
+    public void addF64(float v) {
         addI64(Double.doubleToRawLongBits(v));
     }
 
-    final byte[] getData() {
+    public void addObj(BaseObj obj) {
+        if (obj != null) {
+            addI32(obj.getID());
+        } else {
+            addI32(0);
+        }
+    }
+
+    public void addF32(Float2 v) {
+        addF32(v.x);
+        addF32(v.y);
+    }
+    public void addF32(Float3 v) {
+        addF32(v.x);
+        addF32(v.y);
+        addF32(v.z);
+    }
+    public void addF32(Float4 v) {
+        addF32(v.x);
+        addF32(v.y);
+        addF32(v.z);
+        addF32(v.w);
+    }
+
+    public void addI8(Byte2 v) {
+        addI8(v.x);
+        addI8(v.y);
+    }
+    public void addI8(Byte3 v) {
+        addI8(v.x);
+        addI8(v.y);
+        addI8(v.z);
+    }
+    public void addI8(Byte4 v) {
+        addI8(v.x);
+        addI8(v.y);
+        addI8(v.z);
+        addI8(v.w);
+    }
+
+    public void addU8(Short2 v) {
+        addU8(v.x);
+        addU8(v.y);
+    }
+    public void addU8(Short3 v) {
+        addU8(v.x);
+        addU8(v.y);
+        addU8(v.z);
+    }
+    public void addU8(Short4 v) {
+        addU8(v.x);
+        addU8(v.y);
+        addU8(v.z);
+        addU8(v.w);
+    }
+
+    public void addI16(Short2 v) {
+        addI16(v.x);
+        addI16(v.y);
+    }
+    public void addI16(Short3 v) {
+        addI16(v.x);
+        addI16(v.y);
+        addI16(v.z);
+    }
+    public void addI16(Short4 v) {
+        addI16(v.x);
+        addI16(v.y);
+        addI16(v.z);
+        addI16(v.w);
+    }
+
+    public void addU16(Int2 v) {
+        addU16(v.x);
+        addU16(v.y);
+    }
+    public void addU16(Int3 v) {
+        addU16(v.x);
+        addU16(v.y);
+        addU16(v.z);
+    }
+    public void addU16(Int4 v) {
+        addU16(v.x);
+        addU16(v.y);
+        addU16(v.z);
+        addU16(v.w);
+    }
+
+    public void addI32(Int2 v) {
+        addI32(v.x);
+        addI32(v.y);
+    }
+    public void addI32(Int3 v) {
+        addI32(v.x);
+        addI32(v.y);
+        addI32(v.z);
+    }
+    public void addI32(Int4 v) {
+        addI32(v.x);
+        addI32(v.y);
+        addI32(v.z);
+        addI32(v.w);
+    }
+
+    public void addU32(Int2 v) {
+        addU32(v.x);
+        addU32(v.y);
+    }
+    public void addU32(Int3 v) {
+        addU32(v.x);
+        addU32(v.y);
+        addU32(v.z);
+    }
+    public void addU32(Int4 v) {
+        addU32(v.x);
+        addU32(v.y);
+        addU32(v.z);
+        addU32(v.w);
+    }
+
+    public final byte[] getData() {
         return mData;
     }
 
diff --git a/graphics/java/android/renderscript/Vector2f.java b/graphics/java/android/renderscript/Float2.java
similarity index 93%
rename from graphics/java/android/renderscript/Vector2f.java
rename to graphics/java/android/renderscript/Float2.java
index 567d57fa..8fea91f 100644
--- a/graphics/java/android/renderscript/Vector2f.java
+++ b/graphics/java/android/renderscript/Float2.java
@@ -24,8 +24,8 @@
  * @hide
  *
  **/
-public class Vector2f {
-    public Vector2f() {
+public class Float2 {
+    public Float2() {
     }
 
     public float x;
diff --git a/graphics/java/android/renderscript/Vector3f.java b/graphics/java/android/renderscript/Float3.java
similarity index 94%
rename from graphics/java/android/renderscript/Vector3f.java
rename to graphics/java/android/renderscript/Float3.java
index f2842f3..9d9e406 100644
--- a/graphics/java/android/renderscript/Vector3f.java
+++ b/graphics/java/android/renderscript/Float3.java
@@ -24,8 +24,8 @@
  * @hide
  *
  **/
-public class Vector3f {
-    public Vector3f() {
+public class Float3 {
+    public Float3() {
     }
 
     public float x;
diff --git a/graphics/java/android/renderscript/Vector4f.java b/graphics/java/android/renderscript/Float4.java
similarity index 94%
rename from graphics/java/android/renderscript/Vector4f.java
rename to graphics/java/android/renderscript/Float4.java
index fabd959..a703e80 100644
--- a/graphics/java/android/renderscript/Vector4f.java
+++ b/graphics/java/android/renderscript/Float4.java
@@ -24,8 +24,8 @@
  * @hide
  *
  **/
-public class Vector4f {
-    public Vector4f() {
+public class Float4 {
+    public Float4() {
     }
 
     public float x;
diff --git a/graphics/java/android/renderscript/Vector2f.java b/graphics/java/android/renderscript/Int2.java
similarity index 89%
copy from graphics/java/android/renderscript/Vector2f.java
copy to graphics/java/android/renderscript/Int2.java
index 567d57fa..56e2fe9 100644
--- a/graphics/java/android/renderscript/Vector2f.java
+++ b/graphics/java/android/renderscript/Int2.java
@@ -24,12 +24,12 @@
  * @hide
  *
  **/
-public class Vector2f {
-    public Vector2f() {
+public class Int2 {
+    public Int2() {
     }
 
-    public float x;
-    public float y;
+    public int x;
+    public int y;
 }
 
 
diff --git a/graphics/java/android/renderscript/Vector2f.java b/graphics/java/android/renderscript/Int3.java
similarity index 88%
copy from graphics/java/android/renderscript/Vector2f.java
copy to graphics/java/android/renderscript/Int3.java
index 567d57fa..1b27509 100644
--- a/graphics/java/android/renderscript/Vector2f.java
+++ b/graphics/java/android/renderscript/Int3.java
@@ -24,12 +24,13 @@
  * @hide
  *
  **/
-public class Vector2f {
-    public Vector2f() {
+public class Int3 {
+    public Int3() {
     }
 
-    public float x;
-    public float y;
+    public int x;
+    public int y;
+    public int z;
 }
 
 
diff --git a/graphics/java/android/renderscript/Vector2f.java b/graphics/java/android/renderscript/Int4.java
similarity index 86%
copy from graphics/java/android/renderscript/Vector2f.java
copy to graphics/java/android/renderscript/Int4.java
index 567d57fa..3d6f3f5 100644
--- a/graphics/java/android/renderscript/Vector2f.java
+++ b/graphics/java/android/renderscript/Int4.java
@@ -24,14 +24,15 @@
  * @hide
  *
  **/
-public class Vector2f {
-    public Vector2f() {
+public class Int4 {
+    public Int4() {
     }
 
-    public float x;
-    public float y;
+    public int x;
+    public int y;
+    public int z;
+    public int w;
 }
 
 
 
-
diff --git a/graphics/java/android/renderscript/Vector2f.java b/graphics/java/android/renderscript/Long2.java
similarity index 89%
copy from graphics/java/android/renderscript/Vector2f.java
copy to graphics/java/android/renderscript/Long2.java
index 567d57fa..11ead2f 100644
--- a/graphics/java/android/renderscript/Vector2f.java
+++ b/graphics/java/android/renderscript/Long2.java
@@ -24,12 +24,12 @@
  * @hide
  *
  **/
-public class Vector2f {
-    public Vector2f() {
+public class Long2 {
+    public Long2() {
     }
 
-    public float x;
-    public float y;
+    public long x;
+    public long y;
 }
 
 
diff --git a/graphics/java/android/renderscript/Vector2f.java b/graphics/java/android/renderscript/Long3.java
similarity index 87%
copy from graphics/java/android/renderscript/Vector2f.java
copy to graphics/java/android/renderscript/Long3.java
index 567d57fa..1604532 100644
--- a/graphics/java/android/renderscript/Vector2f.java
+++ b/graphics/java/android/renderscript/Long3.java
@@ -24,12 +24,13 @@
  * @hide
  *
  **/
-public class Vector2f {
-    public Vector2f() {
+public class Long3 {
+    public Long3() {
     }
 
-    public float x;
-    public float y;
+    public long x;
+    public long y;
+    public long z;
 }
 
 
diff --git a/graphics/java/android/renderscript/Vector2f.java b/graphics/java/android/renderscript/Long4.java
similarity index 85%
copy from graphics/java/android/renderscript/Vector2f.java
copy to graphics/java/android/renderscript/Long4.java
index 567d57fa..2fd2747 100644
--- a/graphics/java/android/renderscript/Vector2f.java
+++ b/graphics/java/android/renderscript/Long4.java
@@ -24,14 +24,15 @@
  * @hide
  *
  **/
-public class Vector2f {
-    public Vector2f() {
+public class Long4 {
+    public Long4() {
     }
 
-    public float x;
-    public float y;
+    public long x;
+    public long y;
+    public long z;
+    public long w;
 }
 
 
 
-
diff --git a/graphics/java/android/renderscript/RenderScript.java b/graphics/java/android/renderscript/RenderScript.java
index a935243..db2a3fd 100644
--- a/graphics/java/android/renderscript/RenderScript.java
+++ b/graphics/java/android/renderscript/RenderScript.java
@@ -203,6 +203,17 @@
     Element mElement_USER_I32;
     Element mElement_USER_F32;
 
+    Element mElement_USER_ELEMENT;
+    Element mElement_USER_TYPE;
+    Element mElement_USER_ALLOCATION;
+    Element mElement_USER_SAMPLER;
+    Element mElement_USER_SCRIPT;
+    Element mElement_USER_MESH;
+    Element mElement_USER_PROGRAM_FRAGMENT;
+    Element mElement_USER_PROGRAM_VERTEX;
+    Element mElement_USER_PROGRAM_RASTER;
+    Element mElement_USER_PROGRAM_STORE;
+
     Element mElement_A_8;
     Element mElement_RGB_565;
     Element mElement_RGB_888;
diff --git a/graphics/java/android/renderscript/Script.java b/graphics/java/android/renderscript/Script.java
index 57ccfa3..0d21368 100644
--- a/graphics/java/android/renderscript/Script.java
+++ b/graphics/java/android/renderscript/Script.java
@@ -42,6 +42,10 @@
         }
     }
 
+    protected void invoke(int slot) {
+        mRS.nScriptInvoke(mID, slot);
+    }
+
     Script(int id, RenderScript rs) {
         super(rs);
         mID = id;
@@ -145,5 +149,48 @@
 
     }
 
+
+    public static class FieldBase {
+        protected Element mElement;
+        protected Type mType;
+        protected Allocation mAllocation;
+
+        protected void init(RenderScript rs, int dimx) {
+            mAllocation = Allocation.createSized(rs, mElement, dimx);
+            mType = mAllocation.getType();
+        }
+
+        protected FieldBase() {
+        }
+
+        public Element getElement() {
+            return mElement;
+        }
+
+        public Type getType() {
+            return mType;
+        }
+
+        public Allocation getAllocation() {
+            return mAllocation;
+        }
+
+        //@Override
+        public void updateAllocation() {
+        }
+
+
+        //
+        /*
+        public class ScriptField_UserField
+            extends android.renderscript.Script.FieldBase {
+
+            protected
+
+        }
+
+        */
+
+    }
 }
 
diff --git a/graphics/java/android/renderscript/ScriptC.java b/graphics/java/android/renderscript/ScriptC.java
index bb99e23..f5d5b2f 100644
--- a/graphics/java/android/renderscript/ScriptC.java
+++ b/graphics/java/android/renderscript/ScriptC.java
@@ -37,6 +37,47 @@
         super(id, rs);
     }
 
+    protected ScriptC(RenderScript rs, Resources resources, int resourceID, boolean isRoot) {
+        super(0, rs);
+        mID = internalCreate(rs, resources, resourceID, isRoot);
+    }
+
+
+    private static synchronized int internalCreate(RenderScript rs, Resources resources, int resourceID, boolean isRoot) {
+        byte[] pgm;
+        int pgmLength;
+        InputStream is = resources.openRawResource(resourceID);
+        try {
+            try {
+                pgm = new byte[1024];
+                pgmLength = 0;
+                while(true) {
+                    int bytesLeft = pgm.length - pgmLength;
+                    if (bytesLeft == 0) {
+                        byte[] buf2 = new byte[pgm.length * 2];
+                        System.arraycopy(pgm, 0, buf2, 0, pgm.length);
+                        pgm = buf2;
+                        bytesLeft = pgm.length - pgmLength;
+                    }
+                    int bytesRead = is.read(pgm, pgmLength, bytesLeft);
+                    if (bytesRead <= 0) {
+                        break;
+                    }
+                    pgmLength += bytesRead;
+                }
+            } finally {
+                is.close();
+            }
+        } catch(IOException e) {
+            throw new Resources.NotFoundException();
+        }
+
+        rs.nScriptCBegin();
+        rs.nScriptCSetScript(pgm, 0, pgmLength);
+        rs.nScriptSetRoot(isRoot);
+        return rs.nScriptCCreate();
+    }
+
     public static class Builder extends Script.Builder {
         byte[] mProgram;
         int mProgramLength;
diff --git a/graphics/java/android/renderscript/Vector2f.java b/graphics/java/android/renderscript/Short2.java
similarity index 89%
copy from graphics/java/android/renderscript/Vector2f.java
copy to graphics/java/android/renderscript/Short2.java
index 567d57fa..426801f 100644
--- a/graphics/java/android/renderscript/Vector2f.java
+++ b/graphics/java/android/renderscript/Short2.java
@@ -24,12 +24,12 @@
  * @hide
  *
  **/
-public class Vector2f {
-    public Vector2f() {
+public class Short2 {
+    public Short2() {
     }
 
-    public float x;
-    public float y;
+    public short x;
+    public short y;
 }
 
 
diff --git a/graphics/java/android/renderscript/Vector2f.java b/graphics/java/android/renderscript/Short3.java
similarity index 87%
copy from graphics/java/android/renderscript/Vector2f.java
copy to graphics/java/android/renderscript/Short3.java
index 567d57fa..7b9c305 100644
--- a/graphics/java/android/renderscript/Vector2f.java
+++ b/graphics/java/android/renderscript/Short3.java
@@ -24,12 +24,13 @@
  * @hide
  *
  **/
-public class Vector2f {
-    public Vector2f() {
+public class Short3 {
+    public Short3() {
     }
 
-    public float x;
-    public float y;
+    public short x;
+    public short y;
+    public short z;
 }
 
 
diff --git a/graphics/java/android/renderscript/Vector2f.java b/graphics/java/android/renderscript/Short4.java
similarity index 85%
copy from graphics/java/android/renderscript/Vector2f.java
copy to graphics/java/android/renderscript/Short4.java
index 567d57fa..9a474e2 100644
--- a/graphics/java/android/renderscript/Vector2f.java
+++ b/graphics/java/android/renderscript/Short4.java
@@ -24,14 +24,15 @@
  * @hide
  *
  **/
-public class Vector2f {
-    public Vector2f() {
+public class Short4 {
+    public Short4() {
     }
 
-    public float x;
-    public float y;
+    public short x;
+    public short y;
+    public short z;
+    public short w;
 }
 
 
 
-
diff --git a/icu4j/java/android/icu/text/ArabicShaping.java b/icu4j/java/android/icu/text/ArabicShaping.java
new file mode 100644
index 0000000..13e2175
--- /dev/null
+++ b/icu4j/java/android/icu/text/ArabicShaping.java
@@ -0,0 +1,1947 @@
+/*
+*******************************************************************************
+*   Copyright (C) 2001-2009, International Business Machines
+*   Corporation and others.  All Rights Reserved.
+*******************************************************************************
+*/
+
+/*
+ * Ported with minor modifications from ICU4J 4.2's
+ * com.ibm.icu.text.ArabicShaping class.
+ */
+
+package android.icu.text;
+
+
+/**
+ * Shape Arabic text on a character basis.
+ *
+ * <p>ArabicShaping performs basic operations for "shaping" Arabic text. It is most
+ * useful for use with legacy data formats and legacy display technology
+ * (simple terminals). All operations are performed on Unicode characters.</p>
+ *
+ * <p>Text-based shaping means that some character code points in the text are
+ * replaced by others depending on the context. It transforms one kind of text
+ * into another. In comparison, modern displays for Arabic text select
+ * appropriate, context-dependent font glyphs for each text element, which means
+ * that they transform text into a glyph vector.</p>
+ *
+ * <p>Text transformations are necessary when modern display technology is not
+ * available or when text needs to be transformed to or from legacy formats that
+ * use "shaped" characters. Since the Arabic script is cursive, connecting
+ * adjacent letters to each other, computers select images for each letter based
+ * on the surrounding letters. This usually results in four images per Arabic
+ * letter: initial, middle, final, and isolated forms. In Unicode, on the other
+ * hand, letters are normally stored abstract, and a display system is expected
+ * to select the necessary glyphs. (This makes searching and other text
+ * processing easier because the same letter has only one code.) It is possible
+ * to mimic this with text transformations because there are characters in
+ * Unicode that are rendered as letters with a specific shape
+ * (or cursive connectivity). They were included for interoperability with
+ * legacy systems and codepages, and for unsophisticated display systems.</p>
+ *
+ * <p>A second kind of text transformations is supported for Arabic digits:
+ * For compatibility with legacy codepages that only include European digits,
+ * it is possible to replace one set of digits by another, changing the
+ * character code points. These operations can be performed for either
+ * Arabic-Indic Digits (U+0660...U+0669) or Eastern (Extended) Arabic-Indic
+ * digits (U+06f0...U+06f9).</p>
+ *
+ * <p>Some replacements may result in more or fewer characters (code points).
+ * By default, this means that the destination buffer may receive text with a
+ * length different from the source length. Some legacy systems rely on the
+ * length of the text to be constant. They expect extra spaces to be added
+ * or consumed either next to the affected character or at the end of the
+ * text.</p>
+ * @stable ICU 2.0
+ *
+ * @hide
+ */
+public class ArabicShaping {
+    private final int options;
+    private boolean isLogical; // convenience
+    private boolean spacesRelativeToTextBeginEnd;
+    private char tailChar;
+
+    public static final ArabicShaping SHAPER = new ArabicShaping(
+            ArabicShaping.TEXT_DIRECTION_LOGICAL |
+            ArabicShaping.LENGTH_FIXED_SPACES_NEAR |
+            ArabicShaping.LETTERS_SHAPE |
+            ArabicShaping.DIGITS_NOOP);
+
+    /**
+     * Convert a range of text in the source array, putting the result
+     * into a range of text in the destination array, and return the number
+     * of characters written.
+     *
+     * @param source An array containing the input text
+     * @param sourceStart The start of the range of text to convert
+     * @param sourceLength The length of the range of text to convert
+     * @param dest The destination array that will receive the result.
+     *   It may be <code>NULL</code> only if  <code>destSize</code> is 0.
+     * @param destStart The start of the range of the destination buffer to use.
+     * @param destSize The size (capacity) of the destination buffer.
+     *   If <code>destSize</code> is 0, then no output is produced,
+     *   but the necessary buffer size is returned ("preflighting").  This
+     *   does not validate the text against the options, for example,
+     *   if letters are being unshaped, and spaces are being consumed
+     *   following lamalef, this will not detect a lamalef without a
+     *   corresponding space.  An error will be thrown when the actual
+     *   conversion is attempted.
+     * @return The number of chars written to the destination buffer.
+     *   If an error occurs, then no output was written, or it may be
+     *   incomplete.
+     * @throws ArabicShapingException if the text cannot be converted according to the options.
+     * @stable ICU 2.0
+     */
+    public int shape(char[] source, int sourceStart, int sourceLength,
+                     char[] dest, int destStart, int destSize) throws ArabicShapingException {
+        if (source == null) {
+            throw new IllegalArgumentException("source can not be null");
+        }
+        if (sourceStart < 0 || sourceLength < 0 || sourceStart + sourceLength > source.length) {
+            throw new IllegalArgumentException("bad source start (" + sourceStart +
+                                               ") or length (" + sourceLength +
+                                               ") for buffer of length " + source.length);
+        }
+        if (dest == null && destSize != 0) {
+            throw new IllegalArgumentException("null dest requires destSize == 0");
+        }
+        if ((destSize != 0) &&
+            (destStart < 0 || destSize < 0 || destStart + destSize > dest.length)) {
+            throw new IllegalArgumentException("bad dest start (" + destStart +
+                                               ") or size (" + destSize +
+                                               ") for buffer of length " + dest.length);
+        }
+        /* Validate input options */
+        if ( ((options&TASHKEEL_MASK) > 0) &&
+             !(((options & TASHKEEL_MASK)==TASHKEEL_BEGIN)  ||
+               ((options & TASHKEEL_MASK)==TASHKEEL_END )   ||
+               ((options & TASHKEEL_MASK)==TASHKEEL_RESIZE )||
+               ((options & TASHKEEL_MASK)==TASHKEEL_REPLACE_BY_TATWEEL)) ){
+            throw new IllegalArgumentException("Wrong Tashkeel argument");
+        }
+
+       ///CLOVER:OFF
+       //According to Steven Loomis, the code is unreachable when you OR all the constants within the if statements
+       if(((options&LAMALEF_MASK) > 0)&&
+              !(((options & LAMALEF_MASK)==LAMALEF_BEGIN)  ||
+                ((options & LAMALEF_MASK)==LAMALEF_END )   ||
+                ((options & LAMALEF_MASK)==LAMALEF_RESIZE )||
+                 ((options & LAMALEF_MASK)==LAMALEF_AUTO)  ||
+                 ((options & LAMALEF_MASK)==LAMALEF_NEAR))){
+           throw new IllegalArgumentException("Wrong Lam Alef argument");
+       }
+       ///CLOVER:ON
+
+       /* Validate Tashkeel (Tashkeel replacement options should be enabled in shaping mode only)*/
+       if(((options&TASHKEEL_MASK) > 0) && (options&LETTERS_MASK) == LETTERS_UNSHAPE) {
+            throw new IllegalArgumentException("Tashkeel replacement should not be enabled in deshaping mode ");
+       }
+       return internalShape(source, sourceStart, sourceLength, dest, destStart, destSize);
+    }
+
+    /**
+     * Convert a range of text in place.  This may only be used if the Length option
+     * does not grow or shrink the text.
+     *
+     * @param source An array containing the input text
+     * @param start The start of the range of text to convert
+     * @param length The length of the range of text to convert
+     * @throws ArabicShapingException if the text cannot be converted according to the options.
+     * @stable ICU 2.0
+     */
+    public void shape(char[] source, int start, int length) throws ArabicShapingException {
+        if ((options & LAMALEF_MASK) == LAMALEF_RESIZE) {
+            throw new ArabicShapingException("Cannot shape in place with length option resize.");
+        }
+        shape(source, start, length, source, start, length);
+    }
+
+    /**
+     * Convert a string, returning the new string.
+     *
+     * @param text the string to convert
+     * @return the converted string
+     * @throws ArabicShapingException if the string cannot be converted according to the options.
+     * @stable ICU 2.0
+     */
+    public String shape(String text) throws ArabicShapingException {
+        char[] src = text.toCharArray();
+        char[] dest = src;
+        if (((options & LAMALEF_MASK) == LAMALEF_RESIZE) &&
+            ((options & LETTERS_MASK) == LETTERS_UNSHAPE)) {
+
+            dest = new char[src.length * 2]; // max
+        }
+        int len = shape(src, 0, src.length, dest, 0, dest.length);
+
+        return new String(dest, 0, len);
+    }
+
+    /**
+     * Construct ArabicShaping using the options flags.
+     * The flags are as follows:<br>
+     * 'LENGTH' flags control whether the text can change size, and if not,
+     * how to maintain the size of the text when LamAlef ligatures are
+     * formed or broken.<br>
+     * 'TEXT_DIRECTION' flags control whether the text is read and written
+     * in visual order or in logical order.<br>
+     * 'LETTERS_SHAPE' flags control whether conversion is to or from
+     * presentation forms.<br>
+     * 'DIGITS' flags control whether digits are shaped, and whether from
+     * European to Arabic-Indic or vice-versa.<br>
+     * 'DIGIT_TYPE' flags control whether standard or extended Arabic-Indic
+     * digits are used when performing digit conversion.
+     * @stable ICU 2.0
+     */
+    public ArabicShaping(int options) {
+        this.options = options;
+        if ((options & DIGITS_MASK) > 0x80) {
+            throw new IllegalArgumentException("bad DIGITS options");
+        }
+
+        isLogical = ( (options & TEXT_DIRECTION_MASK) == TEXT_DIRECTION_LOGICAL );
+        /* Validate options */
+        spacesRelativeToTextBeginEnd = ( (options & SPACES_RELATIVE_TO_TEXT_MASK) == SPACES_RELATIVE_TO_TEXT_BEGIN_END );
+        if ( (options&SHAPE_TAIL_TYPE_MASK) == SHAPE_TAIL_NEW_UNICODE){
+            tailChar = NEW_TAIL_CHAR;
+        } else {
+            tailChar = OLD_TAIL_CHAR;
+        }
+    }
+
+    /* Seen Tail options */
+    /**
+     * Memory option: the result must have the same length as the source.
+     * Shaping mode: The SEEN family character will expand into two characters using space near
+     *               the SEEN family character(i.e. the space after the character).
+     *               if there are no spaces found, ArabicShapingException will be thrown
+     *
+     * De-shaping mode: Any Seen character followed by Tail character will be
+     *                  replaced by one cell Seen and a space will replace the Tail.
+     * Affects: Seen options
+     */
+    public static final int SEEN_TWOCELL_NEAR = 0x200000;
+
+    /** Bit mask for Seen memory options. */
+    public static final int SEEN_MASK = 0x700000;
+
+    /* YehHamza options */
+    /**
+     * Memory option: the result must have the same length as the source.
+     * Shaping mode: The YEHHAMZA character will expand into two characters using space near it
+     *              (i.e. the space after the character)
+     *               if there are no spaces found, ArabicShapingException will be thrown
+     *
+     * De-shaping mode: Any Yeh (final or isolated) character followed by Hamza character will be
+     *                  replaced by one cell YehHamza and space will replace the Hamza.
+     * Affects: YehHamza options
+     */
+    public static final int YEHHAMZA_TWOCELL_NEAR  = 0x1000000;
+
+
+    /** Bit mask for YehHamza memory options. */
+    public static final int YEHHAMZA_MASK = 0x3800000;
+
+    /* New Tashkeel options */
+    /**
+     * Memory option: the result must have the same length as the source.
+     * Shaping mode: Tashkeel characters will be replaced by spaces.
+     *               Spaces will be placed at beginning of the buffer
+     *
+     * De-shaping mode: N/A
+     * Affects: Tashkeel options
+     */
+    public static final int TASHKEEL_BEGIN = 0x40000;
+
+    /**
+     * Memory option: the result must have the same length as the source.
+     * Shaping mode: Tashkeel characters will be replaced by spaces.
+     *               Spaces will be placed at end of the buffer
+     *
+     * De-shaping mode: N/A
+     * Affects: Tashkeel options
+     */
+    public static final int TASHKEEL_END = 0x60000;
+
+    /**
+     * Memory option: allow the result to have a different length than the source.
+     * Shaping mode: Tashkeel characters will be removed, buffer length will shrink.
+     * De-shaping mode: N/A
+     *
+     * Affects: Tashkeel options
+     */
+    public static final int TASHKEEL_RESIZE = 0x80000;
+
+    /**
+     * Memory option: the result must have the same length as the source.
+     * Shaping mode: Tashkeel characters will be replaced by Tatweel if it is connected to adjacent
+     *               characters (i.e. shaped on Tatweel) or replaced by space if it is not connected.
+     *
+     * De-shaping mode: N/A
+     * Affects: YehHamza options
+     */
+    public static final int TASHKEEL_REPLACE_BY_TATWEEL = 0xC0000;
+
+    /** Bit mask for Tashkeel replacement with Space or Tatweel memory options. */
+    public static final int TASHKEEL_MASK  = 0xE0000;
+
+    /* Space location Control options */
+    /**
+     * This option effects the meaning of BEGIN and END options. if this option is not used the default
+     * for BEGIN and END will be as following:
+     * The Default (for both Visual LTR, Visual RTL and Logical Text)
+     *           1. BEGIN always refers to the start address of physical memory.
+     *           2. END always refers to the end address of physical memory.
+     *
+     * If this option is used it will swap the meaning of BEGIN and END only for Visual LTR text.
+     *
+     * The affect on BEGIN and END Memory Options will be as following:
+     *    A. BEGIN For Visual LTR text: This will be the beginning (right side) of the visual text
+     *       (corresponding to the physical memory address end, same as END in default behavior)
+     *    B. BEGIN For Logical text: Same as BEGIN in default behavior.
+     *    C. END For Visual LTR text: This will be the end (left side) of the visual text. (corresponding to
+     *      the physical memory address beginning, same as BEGIN in default behavior)
+     *    D. END For Logical text: Same as END in default behavior.
+     * Affects: All LamAlef BEGIN, END and AUTO options.
+     */
+    public static final int SPACES_RELATIVE_TO_TEXT_BEGIN_END = 0x4000000;
+
+    /** Bit mask for swapping BEGIN and END for Visual LTR text */
+    public static final int SPACES_RELATIVE_TO_TEXT_MASK = 0x4000000;
+
+    /**
+     * If this option is used, shaping will use the new Unicode code point for TAIL (i.e. 0xFE73).
+     * If this option is not specified (Default), old unofficial Unicode TAIL code point is used (i.e. 0x200B)
+     * De-shaping will not use this option as it will always search for both the new Unicode code point for the
+     * TAIL (i.e. 0xFE73) or the old unofficial Unicode TAIL code point (i.e. 0x200B) and de-shape the
+     * Seen-Family letter accordingly.
+     *
+     * Shaping Mode: Only shaping.
+     * De-shaping Mode: N/A.
+     * Affects: All Seen options
+     */
+    public static final int SHAPE_TAIL_NEW_UNICODE = 0x8000000;
+
+    /** Bit mask for new Unicode Tail option */
+    public static final int SHAPE_TAIL_TYPE_MASK = 0x8000000;
+
+    /**
+     * Memory option: allow the result to have a different length than the source.
+     * @stable ICU 2.0
+     */
+    public static final int LENGTH_GROW_SHRINK = 0;
+
+    /**
+     * Memory option: allow the result to have a different length than the source.
+     * Affects: LamAlef options
+     * This option is an alias to LENGTH_GROW_SHRINK
+     */
+    public static final int LAMALEF_RESIZE   = 0;
+
+    /**
+     * Memory option: the result must have the same length as the source.
+     * If more room is necessary, then try to consume spaces next to modified characters.
+     * @stable ICU 2.0
+     */
+    public static final int LENGTH_FIXED_SPACES_NEAR = 1;
+
+    /**
+     * Memory option: the result must have the same length as the source.
+     * If more room is necessary, then try to consume spaces next to modified characters.
+     * Affects: LamAlef options
+     * This option is an alias to LENGTH_FIXED_SPACES_NEAR
+     */
+    public static final int LAMALEF_NEAR = 1 ;
+
+    /**
+     * Memory option: the result must have the same length as the source.
+     * If more room is necessary, then try to consume spaces at the end of the text.
+     * @stable ICU 2.0
+     */
+    public static final int LENGTH_FIXED_SPACES_AT_END = 2;
+
+
+    /**
+     * Memory option: the result must have the same length as the source.
+     * If more room is necessary, then try to consume spaces at the end of the text.
+     * Affects: LamAlef options
+     * This option is an alias to LENGTH_FIXED_SPACES_AT_END
+     */
+    public static final int LAMALEF_END = 2;
+
+    /**
+     * Memory option: the result must have the same length as the source.
+     * If more room is necessary, then try to consume spaces at the beginning of the text.
+     * @stable ICU 2.0
+     */
+    public static final int LENGTH_FIXED_SPACES_AT_BEGINNING = 3;
+
+    /**
+     * Memory option: the result must have the same length as the source.
+     * If more room is necessary, then try to consume spaces at the beginning of the text.
+     * Affects: LamAlef options
+     * This option is an alias to LENGTH_FIXED_SPACES_AT_BEGINNING
+     */
+    public static final int LAMALEF_BEGIN = 3;
+
+    /**
+     * Memory option: the result must have the same length as the source.
+     * Shaping Mode: For each LAMALEF character found, expand LAMALEF using space at end.
+     *               If there is no space at end, use spaces at beginning of the buffer. If there
+     *               is no space at beginning of the buffer, use spaces at the near (i.e. the space
+     *               after the LAMALEF character).
+     *
+     * Deshaping Mode: Perform the same function as the flag equals LAMALEF_END.
+     * Affects: LamAlef options
+     */
+    public static final int LAMALEF_AUTO  = 0x10000;
+
+    /**
+     * Bit mask for memory options.
+     * @stable ICU 2.0
+     */
+    public static final int LENGTH_MASK = 0x10003;
+
+    /** Bit mask for LamAlef memory options. */
+
+    public static final int LAMALEF_MASK  = 0x10003;
+
+    /**
+     * Direction indicator: the source is in logical (keyboard) order.
+     * @stable ICU 2.0
+     */
+    public static final int TEXT_DIRECTION_LOGICAL = 0;
+
+    /**
+     * Direction indicator:the source is in visual RTL order,
+     * the rightmost displayed character stored first.
+     * This option is an alias to U_SHAPE_TEXT_DIRECTION_LOGICAL
+     */
+    public static final int TEXT_DIRECTION_VISUAL_RTL = 0;
+
+    /**
+     * Direction indicator: the source is in visual (display) order, that is,
+     * the leftmost displayed character is stored first.
+     * @stable ICU 2.0
+     */
+    public static final int TEXT_DIRECTION_VISUAL_LTR = 4;
+
+    /**
+     * Bit mask for direction indicators.
+     * @stable ICU 2.0
+     */
+    public static final int TEXT_DIRECTION_MASK = 4;
+
+
+    /**
+     * Letter shaping option: do not perform letter shaping.
+     * @stable ICU 2.0
+     */
+    public static final int LETTERS_NOOP = 0;
+
+    /**
+     * Letter shaping option: replace normative letter characters in the U+0600 (Arabic) block,
+     * by shaped ones in the U+FE70 (Presentation Forms B) block. Performs Lam-Alef ligature
+     * substitution.
+     * @stable ICU 2.0
+     */
+    public static final int LETTERS_SHAPE = 8;
+
+    /**
+     * Letter shaping option: replace shaped letter characters in the U+FE70 (Presentation Forms B) block
+     * by normative ones in the U+0600 (Arabic) block.  Converts Lam-Alef ligatures to pairs of Lam and
+     * Alef characters, consuming spaces if required.
+     * @stable ICU 2.0
+     */
+    public static final int LETTERS_UNSHAPE = 0x10;
+
+    /**
+     * Letter shaping option: replace normative letter characters in the U+0600 (Arabic) block,
+     * except for the TASHKEEL characters at U+064B...U+0652, by shaped ones in the U+Fe70
+     * (Presentation Forms B) block.  The TASHKEEL characters will always be converted to
+     * the isolated forms rather than to their correct shape.
+     * @stable ICU 2.0
+     */
+    public static final int LETTERS_SHAPE_TASHKEEL_ISOLATED = 0x18;
+
+    /**
+     * Bit mask for letter shaping options.
+     * @stable ICU 2.0
+     */
+    public static final int LETTERS_MASK = 0x18;
+
+
+    /**
+     * Digit shaping option: do not perform digit shaping.
+     * @stable ICU 2.0
+     */
+    public static final int DIGITS_NOOP = 0;
+
+    /**
+     * Digit shaping option: Replace European digits (U+0030...U+0039) by Arabic-Indic digits.
+     * @stable ICU 2.0
+     */
+    public static final int DIGITS_EN2AN = 0x20;
+
+    /**
+     * Digit shaping option: Replace Arabic-Indic digits by European digits (U+0030...U+0039).
+     * @stable ICU 2.0
+     */
+    public static final int DIGITS_AN2EN = 0x40;
+
+    /**
+     * Digit shaping option:
+     * Replace European digits (U+0030...U+0039) by Arabic-Indic digits
+     * if the most recent strongly directional character
+     * is an Arabic letter (its Bidi direction value is RIGHT_TO_LEFT_ARABIC).
+     * The initial state at the start of the text is assumed to be not an Arabic,
+     * letter, so European digits at the start of the text will not change.
+     * Compare to DIGITS_ALEN2AN_INIT_AL.
+     * @stable ICU 2.0
+     */
+    public static final int DIGITS_EN2AN_INIT_LR = 0x60;
+
+    /**
+     * Digit shaping option:
+     * Replace European digits (U+0030...U+0039) by Arabic-Indic digits
+     * if the most recent strongly directional character
+     * is an Arabic letter (its Bidi direction value is RIGHT_TO_LEFT_ARABIC).
+     * The initial state at the start of the text is assumed to be an Arabic,
+     * letter, so European digits at the start of the text will change.
+     * Compare to DIGITS_ALEN2AN_INT_LR.
+     * @stable ICU 2.0
+     */
+    public static final int DIGITS_EN2AN_INIT_AL = 0x80;
+
+    /** Not a valid option value. */
+    //private static final int DIGITS_RESERVED = 0xa0;
+
+    /**
+     * Bit mask for digit shaping options.
+     * @stable ICU 2.0
+     */
+    public static final int DIGITS_MASK = 0xe0;
+
+    /**
+     * Digit type option: Use Arabic-Indic digits (U+0660...U+0669).
+     * @stable ICU 2.0
+     */
+    public static final int DIGIT_TYPE_AN = 0;
+
+    /**
+     * Digit type option: Use Eastern (Extended) Arabic-Indic digits (U+06f0...U+06f9).
+     * @stable ICU 2.0
+     */
+    public static final int DIGIT_TYPE_AN_EXTENDED = 0x100;
+
+    /**
+     * Bit mask for digit type options.
+     * @stable ICU 2.0
+     */
+    public static final int DIGIT_TYPE_MASK = 0x0100; // 0x3f00?
+
+    /**
+     * some constants
+     */
+    private static final char HAMZAFE_CHAR       = '\ufe80';
+    private static final char HAMZA06_CHAR       = '\u0621';
+    private static final char YEH_HAMZA_CHAR     = '\u0626';
+    private static final char YEH_HAMZAFE_CHAR   = '\uFE89';
+    private static final char LAMALEF_SPACE_SUB  = '\uffff';
+    private static final char TASHKEEL_SPACE_SUB = '\ufffe';
+    private static final char LAM_CHAR      = '\u0644';
+    private static final char SPACE_CHAR    = '\u0020';
+    private static final char SPACE_CHAR_FOR_LAMALEF = '\ufeff'; // XXX: tweak for TextLine use
+    private static final char SHADDA_CHAR   = '\uFE7C';
+    private static final char TATWEEL_CHAR  = '\u0640';
+    private static final char SHADDA_TATWEEL_CHAR = '\uFE7D';
+    private static final char NEW_TAIL_CHAR = '\uFE73';
+    private static final char OLD_TAIL_CHAR = '\u200B';
+    private static final int SHAPE_MODE      = 0;
+    private static final int DESHAPE_MODE    = 1;
+
+    /**
+     * @stable ICU 2.0
+     */
+    public boolean equals(Object rhs) {
+        return rhs != null &&
+            rhs.getClass() == ArabicShaping.class &&
+            options == ((ArabicShaping)rhs).options;
+    }
+
+    /**
+     * @stable ICU 2.0
+     */
+     ///CLOVER:OFF
+    public int hashCode() {
+        return options;
+    }
+
+    /**
+     * @stable ICU 2.0
+     */
+    public String toString() {
+        StringBuffer buf = new StringBuffer(super.toString());
+        buf.append('[');
+
+        switch (options & LAMALEF_MASK) {
+        case LAMALEF_RESIZE: buf.append("LamAlef resize"); break;
+        case LAMALEF_NEAR: buf.append("LamAlef spaces at near"); break;
+        case LAMALEF_BEGIN: buf.append("LamAlef spaces at begin"); break;
+        case LAMALEF_END: buf.append("LamAlef spaces at end"); break;
+        case LAMALEF_AUTO: buf.append("lamAlef auto"); break;
+        }
+        switch (options & TEXT_DIRECTION_MASK) {
+        case TEXT_DIRECTION_LOGICAL: buf.append(", logical"); break;
+        case TEXT_DIRECTION_VISUAL_LTR: buf.append(", visual"); break;
+        }
+        switch (options & LETTERS_MASK) {
+        case LETTERS_NOOP: buf.append(", no letter shaping"); break;
+        case LETTERS_SHAPE: buf.append(", shape letters"); break;
+        case LETTERS_SHAPE_TASHKEEL_ISOLATED: buf.append(", shape letters tashkeel isolated"); break;
+        case LETTERS_UNSHAPE: buf.append(", unshape letters"); break;
+        }
+        switch (options & SEEN_MASK) {
+        case SEEN_TWOCELL_NEAR: buf.append(", Seen at near"); break;
+        }
+        switch (options & YEHHAMZA_MASK) {
+        case YEHHAMZA_TWOCELL_NEAR: buf.append(", Yeh Hamza at near"); break;
+        }
+        switch (options & TASHKEEL_MASK) {
+        case TASHKEEL_BEGIN: buf.append(", Tashkeel at begin"); break;
+        case TASHKEEL_END: buf.append(", Tashkeel at end"); break;
+        case TASHKEEL_REPLACE_BY_TATWEEL: buf.append(", Tashkeel replace with tatweel"); break;
+        case TASHKEEL_RESIZE: buf.append(", Tashkeel resize"); break;
+        }
+
+        switch (options & DIGITS_MASK) {
+        case DIGITS_NOOP: buf.append(", no digit shaping"); break;
+        case DIGITS_EN2AN: buf.append(", shape digits to AN"); break;
+        case DIGITS_AN2EN: buf.append(", shape digits to EN"); break;
+        case DIGITS_EN2AN_INIT_LR: buf.append(", shape digits to AN contextually: default EN"); break;
+        case DIGITS_EN2AN_INIT_AL: buf.append(", shape digits to AN contextually: default AL"); break;
+        }
+        switch (options & DIGIT_TYPE_MASK) {
+        case DIGIT_TYPE_AN: buf.append(", standard Arabic-Indic digits"); break;
+        case DIGIT_TYPE_AN_EXTENDED: buf.append(", extended Arabic-Indic digits"); break;
+        }
+        buf.append("]");
+
+        return buf.toString();
+    }
+    ///CLOVER:ON
+
+    //
+    // ported api
+    //
+
+    private static final int IRRELEVANT = 4;
+    private static final int LAMTYPE = 16;
+    private static final int ALEFTYPE = 32;
+
+    private static final int LINKR = 1;
+    private static final int LINKL = 2;
+    private static final int LINK_MASK = 3;
+
+    private static final int irrelevantPos[] = {
+        0x0, 0x2, 0x4, 0x6, 0x8, 0xA, 0xC, 0xE
+    };
+
+/*
+    private static final char convertLamAlef[] =  {
+        '\u0622', // FEF5
+        '\u0622', // FEF6
+        '\u0623', // FEF7
+        '\u0623', // FEF8
+        '\u0625', // FEF9
+        '\u0625', // FEFA
+        '\u0627', // FEFB
+        '\u0627'  // FEFC
+    };
+*/
+
+    private static final int tailFamilyIsolatedFinal[] = {
+        /* FEB1 */ 1,
+        /* FEB2 */ 1,
+        /* FEB3 */ 0,
+        /* FEB4 */ 0,
+        /* FEB5 */ 1,
+        /* FEB6 */ 1,
+        /* FEB7 */ 0,
+        /* FEB8 */ 0,
+        /* FEB9 */ 1,
+        /* FEBA */ 1,
+        /* FEBB */ 0,
+        /* FEBC */ 0,
+        /* FEBD */ 1,
+        /* FEBE */ 1
+    };
+
+    private static final int tashkeelMedial[] = {
+        /* FE70 */ 0,
+        /* FE71 */ 1,
+        /* FE72 */ 0,
+        /* FE73 */ 0,
+        /* FE74 */ 0,
+        /* FE75 */ 0,
+        /* FE76 */ 0,
+        /* FE77 */ 1,
+        /* FE78 */ 0,
+        /* FE79 */ 1,
+        /* FE7A */ 0,
+        /* FE7B */ 1,
+        /* FE7C */ 0,
+        /* FE7D */ 1,
+        /* FE7E */ 0,
+        /* FE7F */ 1
+    };
+
+    private static final char yehHamzaToYeh[] =
+    {
+    /* isolated*/ 0xFEEF,
+    /* final   */ 0xFEF0
+    };
+
+    private static final char convertNormalizedLamAlef[] = {
+        '\u0622', // 065C
+        '\u0623', // 065D
+        '\u0625', // 065E
+        '\u0627', // 065F
+    };
+
+    private static final int[] araLink = {
+        1           + 32 + 256 * 0x11,  /*0x0622*/
+        1           + 32 + 256 * 0x13,  /*0x0623*/
+        1                + 256 * 0x15,  /*0x0624*/
+        1           + 32 + 256 * 0x17,  /*0x0625*/
+        1 + 2            + 256 * 0x19,  /*0x0626*/
+        1           + 32 + 256 * 0x1D,  /*0x0627*/
+        1 + 2            + 256 * 0x1F,  /*0x0628*/
+        1                + 256 * 0x23,  /*0x0629*/
+        1 + 2            + 256 * 0x25,  /*0x062A*/
+        1 + 2            + 256 * 0x29,  /*0x062B*/
+        1 + 2            + 256 * 0x2D,  /*0x062C*/
+        1 + 2            + 256 * 0x31,  /*0x062D*/
+        1 + 2            + 256 * 0x35,  /*0x062E*/
+        1                + 256 * 0x39,  /*0x062F*/
+        1                + 256 * 0x3B,  /*0x0630*/
+        1                + 256 * 0x3D,  /*0x0631*/
+        1                + 256 * 0x3F,  /*0x0632*/
+        1 + 2            + 256 * 0x41,  /*0x0633*/
+        1 + 2            + 256 * 0x45,  /*0x0634*/
+        1 + 2            + 256 * 0x49,  /*0x0635*/
+        1 + 2            + 256 * 0x4D,  /*0x0636*/
+        1 + 2            + 256 * 0x51,  /*0x0637*/
+        1 + 2            + 256 * 0x55,  /*0x0638*/
+        1 + 2            + 256 * 0x59,  /*0x0639*/
+        1 + 2            + 256 * 0x5D,  /*0x063A*/
+        0, 0, 0, 0, 0,                  /*0x063B-0x063F*/
+        1 + 2,                          /*0x0640*/
+        1 + 2            + 256 * 0x61,  /*0x0641*/
+        1 + 2            + 256 * 0x65,  /*0x0642*/
+        1 + 2            + 256 * 0x69,  /*0x0643*/
+        1 + 2       + 16 + 256 * 0x6D,  /*0x0644*/
+        1 + 2            + 256 * 0x71,  /*0x0645*/
+        1 + 2            + 256 * 0x75,  /*0x0646*/
+        1 + 2            + 256 * 0x79,  /*0x0647*/
+        1                + 256 * 0x7D,  /*0x0648*/
+        1                + 256 * 0x7F,  /*0x0649*/
+        1 + 2            + 256 * 0x81,  /*0x064A*/
+        4, 4, 4, 4,                     /*0x064B-0x064E*/
+        4, 4, 4, 4,                     /*0x064F-0x0652*/
+        4, 4, 4, 0, 0,                  /*0x0653-0x0657*/
+        0, 0, 0, 0,                     /*0x0658-0x065B*/
+        1                + 256 * 0x85,  /*0x065C*/
+        1                + 256 * 0x87,  /*0x065D*/
+        1                + 256 * 0x89,  /*0x065E*/
+        1                + 256 * 0x8B,  /*0x065F*/
+        0, 0, 0, 0, 0,                  /*0x0660-0x0664*/
+        0, 0, 0, 0, 0,                  /*0x0665-0x0669*/
+        0, 0, 0, 0, 0, 0,               /*0x066A-0x066F*/
+        4,                              /*0x0670*/
+        0,                              /*0x0671*/
+        1           + 32,               /*0x0672*/
+        1           + 32,               /*0x0673*/
+        0,                              /*0x0674*/
+        1           + 32,               /*0x0675*/
+        1, 1,                           /*0x0676-0x0677*/
+        1+2, 1+2, 1+2, 1+2, 1+2, 1+2,   /*0x0678-0x067D*/
+        1+2, 1+2, 1+2, 1+2, 1+2, 1+2,   /*0x067E-0x0683*/
+        1+2, 1+2, 1+2, 1+2,             /*0x0684-0x0687*/
+        1, 1, 1, 1, 1, 1, 1, 1, 1, 1,   /*0x0688-0x0691*/
+        1, 1, 1, 1, 1, 1, 1, 1,         /*0x0692-0x0699*/
+        1+2, 1+2, 1+2, 1+2, 1+2, 1+2,   /*0x069A-0x06A3*/
+        1+2, 1+2, 1+2, 1+2,             /*0x069A-0x06A3*/
+        1+2, 1+2, 1+2, 1+2, 1+2, 1+2,   /*0x06A4-0x06AD*/
+        1+2, 1+2, 1+2, 1+2,             /*0x06A4-0x06AD*/
+        1+2, 1+2, 1+2, 1+2, 1+2, 1+2,   /*0x06AE-0x06B7*/
+        1+2, 1+2, 1+2, 1+2,             /*0x06AE-0x06B7*/
+        1+2, 1+2, 1+2, 1+2, 1+2, 1+2,   /*0x06B8-0x06BF*/
+        1+2, 1+2,                       /*0x06B8-0x06BF*/
+        1,                              /*0x06C0*/
+        1+2,                            /*0x06C1*/
+        1, 1, 1, 1, 1, 1, 1, 1, 1, 1,   /*0x06C2-0x06CB*/
+        1+2,                            /*0x06CC*/
+        1,                              /*0x06CD*/
+        1+2, 1+2, 1+2, 1+2,             /*0x06CE-0x06D1*/
+        1, 1                            /*0x06D2-0x06D3*/
+    };
+
+    private static final int[] presLink = {
+        1 + 2,                        /*0xFE70*/
+        1 + 2,                        /*0xFE71*/
+        1 + 2, 0, 1+ 2, 0, 1+ 2,      /*0xFE72-0xFE76*/
+        1 + 2,                        /*0xFE77*/
+        1+ 2, 1 + 2, 1+2, 1 + 2,      /*0xFE78-0xFE81*/
+        1+ 2, 1 + 2, 1+2, 1 + 2,      /*0xFE82-0xFE85*/
+        0, 0 + 32, 1 + 32, 0 + 32,    /*0xFE86-0xFE89*/
+        1 + 32, 0, 1,  0 + 32,        /*0xFE8A-0xFE8D*/
+        1 + 32, 0, 2,  1 + 2,         /*0xFE8E-0xFE91*/
+        1, 0 + 32, 1 + 32, 0,         /*0xFE92-0xFE95*/
+        2, 1 + 2, 1, 0,               /*0xFE96-0xFE99*/
+        1, 0, 2, 1 + 2,               /*0xFE9A-0xFE9D*/
+        1, 0, 2, 1 + 2,               /*0xFE9E-0xFEA1*/
+        1, 0, 2, 1 + 2,               /*0xFEA2-0xFEA5*/
+        1, 0, 2, 1 + 2,               /*0xFEA6-0xFEA9*/
+        1, 0, 2, 1 + 2,               /*0xFEAA-0xFEAD*/
+        1, 0, 1, 0,                   /*0xFEAE-0xFEB1*/
+        1, 0, 1, 0,                   /*0xFEB2-0xFEB5*/
+        1, 0, 2, 1+2,                 /*0xFEB6-0xFEB9*/
+        1, 0, 2, 1+2,                 /*0xFEBA-0xFEBD*/
+        1, 0, 2, 1+2,                 /*0xFEBE-0xFEC1*/
+        1, 0, 2, 1+2,                 /*0xFEC2-0xFEC5*/
+        1, 0, 2, 1+2,                 /*0xFEC6-0xFEC9*/
+        1, 0, 2, 1+2,                 /*0xFECA-0xFECD*/
+        1, 0, 2, 1+2,                 /*0xFECE-0xFED1*/
+        1, 0, 2, 1+2,                 /*0xFED2-0xFED5*/
+        1, 0, 2, 1+2,                 /*0xFED6-0xFED9*/
+        1, 0, 2, 1+2,                 /*0xFEDA-0xFEDD*/
+        1, 0, 2, 1+2,                 /*0xFEDE-0xFEE1*/
+        1, 0 + 16, 2 + 16, 1 + 2 +16, /*0xFEE2-0xFEE5*/
+        1 + 16, 0, 2, 1+2,            /*0xFEE6-0xFEE9*/
+        1, 0, 2, 1+2,                 /*0xFEEA-0xFEED*/
+        1, 0, 2, 1+2,                 /*0xFEEE-0xFEF1*/
+        1, 0, 1, 0,                   /*0xFEF2-0xFEF5*/
+        1, 0, 2, 1+2,                 /*0xFEF6-0xFEF9*/
+        1, 0, 1, 0,                   /*0xFEFA-0xFEFD*/
+        1, 0, 1, 0,
+        1
+    };
+
+    private static int[] convertFEto06 = {
+        /***********0******1******2******3******4******5******6******7******8******9******A******B******C******D******E******F***/
+        /*FE7*/   0x64B, 0x64B, 0x64C, 0x64C, 0x64D, 0x64D, 0x64E, 0x64E, 0x64F, 0x64F, 0x650, 0x650, 0x651, 0x651, 0x652, 0x652,
+        /*FE8*/   0x621, 0x622, 0x622, 0x623, 0x623, 0x624, 0x624, 0x625, 0x625, 0x626, 0x626, 0x626, 0x626, 0x627, 0x627, 0x628,
+        /*FE9*/   0x628, 0x628, 0x628, 0x629, 0x629, 0x62A, 0x62A, 0x62A, 0x62A, 0x62B, 0x62B, 0x62B, 0x62B, 0x62C, 0x62C, 0x62C,
+        /*FEA*/   0x62C, 0x62D, 0x62D, 0x62D, 0x62D, 0x62E, 0x62E, 0x62E, 0x62E, 0x62F, 0x62F, 0x630, 0x630, 0x631, 0x631, 0x632,
+        /*FEB*/   0x632, 0x633, 0x633, 0x633, 0x633, 0x634, 0x634, 0x634, 0x634, 0x635, 0x635, 0x635, 0x635, 0x636, 0x636, 0x636,
+        /*FEC*/   0x636, 0x637, 0x637, 0x637, 0x637, 0x638, 0x638, 0x638, 0x638, 0x639, 0x639, 0x639, 0x639, 0x63A, 0x63A, 0x63A,
+        /*FED*/   0x63A, 0x641, 0x641, 0x641, 0x641, 0x642, 0x642, 0x642, 0x642, 0x643, 0x643, 0x643, 0x643, 0x644, 0x644, 0x644,
+        /*FEE*/   0x644, 0x645, 0x645, 0x645, 0x645, 0x646, 0x646, 0x646, 0x646, 0x647, 0x647, 0x647, 0x647, 0x648, 0x648, 0x649,
+        /*FEF*/   0x649, 0x64A, 0x64A, 0x64A, 0x64A, 0x65C, 0x65C, 0x65D, 0x65D, 0x65E, 0x65E, 0x65F, 0x65F
+    };
+
+    private static final int shapeTable[][][] = {
+        { {0,0,0,0}, {0,0,0,0}, {0,1,0,3}, {0,1,0,1} },
+        { {0,0,2,2}, {0,0,1,2}, {0,1,1,2}, {0,1,1,3} },
+        { {0,0,0,0}, {0,0,0,0}, {0,1,0,3}, {0,1,0,3} },
+        { {0,0,1,2}, {0,0,1,2}, {0,1,1,2}, {0,1,1,3} }
+    };
+
+    /*
+     * This function shapes European digits to Arabic-Indic digits
+     * in-place, writing over the input characters.  Data is in visual
+     * order.
+     */
+    private void shapeToArabicDigitsWithContext(char[] dest,
+                                                int start,
+                                                int length,
+                                                char digitBase,
+                                                boolean lastStrongWasAL) {
+        digitBase -= '0'; // move common adjustment out of loop
+
+        for(int i = start + length; --i >= start;) {
+            char ch = dest[i];
+            switch (Character.getDirectionality(ch)) {
+            case Character.DIRECTIONALITY_LEFT_TO_RIGHT:
+            case Character.DIRECTIONALITY_RIGHT_TO_LEFT:
+                lastStrongWasAL = false;
+                break;
+            case Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC:
+                lastStrongWasAL = true;
+                break;
+            case Character.DIRECTIONALITY_EUROPEAN_NUMBER:
+                if (lastStrongWasAL && ch <= '\u0039') {
+                    dest[i] = (char)(ch + digitBase);
+                }
+                break;
+            default:
+                break;
+            }
+        }
+    }
+
+    /*
+     * Name    : invertBuffer
+     * Function: This function inverts the buffer, it's used
+     *           in case the user specifies the buffer to be
+     *           TEXT_DIRECTION_LOGICAL
+     */
+    private static void invertBuffer(char[] buffer,
+                                     int start,
+                                     int length) {
+
+        for(int i = start, j = start + length - 1; i < j; i++, --j) {
+            char temp = buffer[i];
+            buffer[i] = buffer[j];
+            buffer[j] = temp;
+        }
+    }
+
+    /*
+     * Name    : changeLamAlef
+     * Function: Converts the Alef characters into an equivalent
+     *           LamAlef location in the 0x06xx Range, this is an
+     *           intermediate stage in the operation of the program
+     *           later it'll be converted into the 0xFExx LamAlefs
+     *           in the shaping function.
+     */
+    private static char changeLamAlef(char ch) {
+        switch(ch) {
+        case '\u0622': return '\u065C';
+        case '\u0623': return '\u065D';
+        case '\u0625': return '\u065E';
+        case '\u0627': return '\u065F';
+        default:  return '\u0000'; // not a lamalef
+        }
+    }
+
+    /*
+     * Name    : specialChar
+     * Function: Special Arabic characters need special handling in the shapeUnicode
+     *           function, this function returns 1 or 2 for these special characters
+     */
+    private static int specialChar(char ch) {
+        if ((ch > '\u0621' && ch < '\u0626') ||
+            (ch == '\u0627') ||
+            (ch > '\u062E' && ch < '\u0633') ||
+            (ch > '\u0647' && ch < '\u064A') ||
+            (ch == '\u0629')) {
+            return 1;
+        } else if (ch >= '\u064B' && ch<= '\u0652') {
+            return 2;
+        } else if (ch >= 0x0653 && ch <= 0x0655 ||
+                   ch == 0x0670 ||
+                   ch >= 0xFE70 && ch <= 0xFE7F) {
+            return 3;
+        } else {
+            return 0;
+        }
+    }
+
+    /*
+     * Name    : getLink
+     * Function: Resolves the link between the characters as
+     *           Arabic characters have four forms :
+     *           Isolated, Initial, Middle and Final Form
+     */
+    private static int getLink(char ch) {
+        if (ch >= '\u0622' && ch <= '\u06D3') {
+            return araLink[ch - '\u0622'];
+        } else if (ch == '\u200D') {
+            return 3;
+        } else if (ch >= '\u206D' && ch <= '\u206F') {
+            return 4;
+        } else if (ch >= '\uFE70' && ch <= '\uFEFC') {
+            return presLink[ch - '\uFE70'];
+        } else {
+            return 0;
+        }
+    }
+
+    /*
+     * Name    : countSpaces
+     * Function: Counts the number of spaces
+     *           at each end of the logical buffer
+     */
+    private static int countSpacesLeft(char[] dest,
+                                       int start,
+                                       int count) {
+        for (int i = start, e = start + count; i < e; ++i) {
+            if (dest[i] != SPACE_CHAR) {
+                return i - start;
+            }
+        }
+        return count;
+    }
+
+    private static int countSpacesRight(char[] dest,
+                                        int start,
+                                        int count) {
+
+        for (int i = start + count; --i >= start;) {
+            if (dest[i] != SPACE_CHAR) {
+                return start + count - 1 - i;
+            }
+        }
+        return count;
+    }
+
+    /*
+     * Name    : isTashkeelChar
+     * Function: Returns true for Tashkeel characters else return false
+     */
+    private static boolean isTashkeelChar(char ch) {
+        return ( ch >='\u064B' && ch <= '\u0652' );
+    }
+
+    /*
+     *Name     : isSeenTailFamilyChar
+     *Function : returns 1 if the character is a seen family isolated character
+     *           in the FE range otherwise returns 0
+     */
+
+    private static int isSeenTailFamilyChar(char ch) {
+        if (ch >= 0xfeb1 && ch < 0xfebf){
+             return tailFamilyIsolatedFinal [ch - 0xFEB1];
+        } else {
+             return 0;
+        }
+    }
+
+     /* Name     : isSeenFamilyChar
+      * Function : returns 1 if the character is a seen family character in the Unicode
+      *            06 range otherwise returns 0
+     */
+
+    private static int isSeenFamilyChar(char  ch){
+        if (ch >= 0x633 && ch <= 0x636){
+            return 1;
+        }else {
+            return 0;
+        }
+    }
+
+    /*
+     *Name     : isTailChar
+     *Function : returns true if the character matches one of the tail characters
+     *           (0xfe73 or 0x200b) otherwise returns false
+     */
+
+    private static boolean isTailChar(char ch) {
+        if(ch == OLD_TAIL_CHAR || ch == NEW_TAIL_CHAR){
+                return true;
+        }else{
+                return false;
+        }
+    }
+
+    /*
+     *Name     : isAlefMaksouraChar
+     *Function : returns true if the character is a Alef Maksoura Final or isolated
+     *           otherwise returns false
+     */
+    private static boolean isAlefMaksouraChar(char ch) {
+        return ( (ch == 0xFEEF) || ( ch == 0xFEF0) || (ch == 0x0649));
+    }
+
+    /*
+     * Name     : isYehHamzaChar
+     * Function : returns true if the character is a yehHamza isolated or yehhamza
+     *            final is found otherwise returns false
+     */
+    private static boolean isYehHamzaChar(char ch) {
+        if((ch==0xFE89)||(ch==0xFE8A)){
+            return true;
+        }else{
+            return false;
+        }
+    }
+
+    /*
+     *Name     : isTashkeelCharFE
+     *Function : Returns true for Tashkeel characters in FE range else return false
+     */
+
+    private static boolean isTashkeelCharFE(char ch) {
+        return ( ch!=0xFE75 &&(ch>=0xFE70 && ch<= 0xFE7F) );
+    }
+
+    /*
+     * Name: isTashkeelOnTatweelChar
+     * Function: Checks if the Tashkeel Character is on Tatweel or not,if the
+     *           Tashkeel on tatweel (FE range), it returns 1 else if the
+     *           Tashkeel with shadda on tatweel (FC range)return 2 otherwise
+     *           returns 0
+     */
+    private static int isTashkeelOnTatweelChar(char ch){
+        if (ch >= 0xfe70 && ch <= 0xfe7f && ch != NEW_TAIL_CHAR && ch != 0xFE75 && ch != SHADDA_TATWEEL_CHAR)
+        {
+            return tashkeelMedial [ch - 0xFE70];
+        } else if( (ch >= 0xfcf2 && ch <= 0xfcf4) || (ch == SHADDA_TATWEEL_CHAR)) {
+            return 2;
+        } else {
+            return 0;
+        }
+    }
+
+    /*
+     * Name: isIsolatedTashkeelChar
+     * Function: Checks if the Tashkeel Character is in the isolated form
+     *           (i.e. Unicode FE range) returns 1 else if the Tashkeel
+     *           with shadda is in the isolated form (i.e. Unicode FC range)
+     *           returns 1 otherwise returns 0
+     */
+    private static int isIsolatedTashkeelChar(char ch){
+        if (ch >= 0xfe70 && ch <= 0xfe7f && ch != NEW_TAIL_CHAR && ch != 0xFE75){
+            return (1 - tashkeelMedial [ch - 0xFE70]);
+        } else if(ch >= 0xfc5e && ch <= 0xfc63){
+            return 1;
+        } else{
+            return 0;
+        }
+    }
+
+    /*
+     * Name    : isAlefChar
+     * Function: Returns 1 for Alef characters else return 0
+     */
+    private static boolean isAlefChar(char ch) {
+        return ch == '\u0622' || ch == '\u0623' || ch == '\u0625' || ch == '\u0627';
+    }
+
+    /*
+     * Name    : isLamAlefChar
+     * Function: Returns true for LamAlef characters else return false
+     */
+    private static boolean isLamAlefChar(char ch) {
+        return ch >= '\uFEF5' && ch <= '\uFEFC';
+    }
+
+    private static boolean isNormalizedLamAlefChar(char ch) {
+        return ch >= '\u065C' && ch <= '\u065F';
+    }
+
+    /*
+     * Name    : calculateSize
+     * Function: This function calculates the destSize to be used in preflighting
+     *           when the destSize is equal to 0
+     */
+    private int calculateSize(char[] source,
+                              int sourceStart,
+                              int sourceLength) {
+
+        int destSize = sourceLength;
+
+        switch (options & LETTERS_MASK) {
+        case LETTERS_SHAPE:
+        case LETTERS_SHAPE_TASHKEEL_ISOLATED:
+            if (isLogical) {
+                for (int i = sourceStart, e = sourceStart + sourceLength - 1; i < e; ++i) {
+                    if ((source[i] == LAM_CHAR && isAlefChar(source[i+1])) || isTashkeelCharFE(source[i])){
+                        --destSize;
+                    }
+                }
+            } else { // visual
+                for(int i = sourceStart + 1, e = sourceStart + sourceLength; i < e; ++i) {
+                    if ((source[i] == LAM_CHAR && isAlefChar(source[i-1])) || isTashkeelCharFE(source[i])) {
+                        --destSize;
+                    }
+                }
+            }
+            break;
+
+        case LETTERS_UNSHAPE:
+            for(int i = sourceStart, e = sourceStart + sourceLength; i < e; ++i) {
+                if (isLamAlefChar(source[i])) {
+                    destSize++;
+                }
+            }
+            break;
+
+        default:
+            break;
+        }
+
+        return destSize;
+    }
+
+
+    /*
+     * Name    : countSpaceSub
+     * Function: Counts number of times the subChar appears in the array
+     */
+    public static int countSpaceSub(char [] dest,int length, char subChar){
+        int i = 0;
+        int count = 0;
+        while (i < length) {
+          if (dest[i] == subChar) {
+              count++;
+              }
+          i++;
+        }
+        return count;
+    }
+
+    /*
+     * Name    : shiftArray
+     * Function: Shifts characters to replace space sub characters
+     */
+    public static void shiftArray(char [] dest,int start, int e, char subChar){
+        int w = e;
+        int r = e;
+        while (--r >= start) {
+          char ch = dest[r];
+          if (ch != subChar) {
+            --w;
+            if (w != r) {
+              dest[w] = ch;
+            }
+          }
+        }
+   }
+
+    /*
+     * Name    : flipArray
+     * Function: inverts array, so that start becomes end and vice versa
+     */
+      public static int flipArray(char [] dest, int start, int e, int w){
+        int r;
+        if (w > start) {
+        // shift, assume small buffer size so don't use arraycopy
+          r = w;
+          w = start;
+          while (r < e) {
+            dest[w++] = dest[r++];
+           }
+         } else {
+             w = e;
+         }
+        return w;
+      }
+
+    /*
+     * Name     : handleTashkeelWithTatweel
+     * Function : Replaces Tashkeel as following:
+     *            Case 1 :if the Tashkeel on tatweel, replace it with Tatweel.
+     *            Case 2 :if the Tashkeel aggregated with Shadda on Tatweel, replace
+     *                   it with Shadda on Tatweel.
+     *            Case 3: if the Tashkeel is isolated replace it with Space.
+     *
+     */
+    private static int handleTashkeelWithTatweel(char[] dest, int sourceLength) {
+                     int i;
+                     for(i = 0; i < sourceLength; i++){
+                         if((isTashkeelOnTatweelChar(dest[i]) == 1)){
+                             dest[i] = TATWEEL_CHAR;
+                        }else if((isTashkeelOnTatweelChar(dest[i]) == 2)){
+                             dest[i] = SHADDA_TATWEEL_CHAR;
+                        }else if((isIsolatedTashkeelChar(dest[i])==1) && dest[i] != SHADDA_CHAR){
+                             dest[i] = SPACE_CHAR;
+                        }
+                     }
+                     return sourceLength;
+    }
+
+    /*
+     *Name     : handleGeneratedSpaces
+     *Function : The shapeUnicode function converts Lam + Alef into LamAlef + space,
+     *           and Tashkeel to space.
+     *           handleGeneratedSpaces function puts these generated spaces
+     *           according to the options the user specifies. LamAlef and Tashkeel
+     *           spaces can be replaced at begin, at end, at near or decrease the
+     *           buffer size.
+     *
+     *           There is also Auto option for LamAlef and tashkeel, which will put
+     *           the spaces at end of the buffer (or end of text if the user used
+     *           the option SPACES_RELATIVE_TO_TEXT_BEGIN_END).
+     *
+     *           If the text type was visual_LTR and the option
+     *           SPACES_RELATIVE_TO_TEXT_BEGIN_END was selected the END
+     *           option will place the space at the beginning of the buffer and
+     *           BEGIN will place the space at the end of the buffer.
+     */
+  private int handleGeneratedSpaces(char[] dest,
+            int start,
+            int length) {
+
+      int lenOptionsLamAlef = options & LAMALEF_MASK;
+      int lenOptionsTashkeel = options & TASHKEEL_MASK;
+      boolean lamAlefOn = false;
+      boolean tashkeelOn = false;
+
+      if (!isLogical & !spacesRelativeToTextBeginEnd) {
+          switch (lenOptionsLamAlef) {
+          case LAMALEF_BEGIN: lenOptionsLamAlef = LAMALEF_END; break;
+          case LAMALEF_END: lenOptionsLamAlef = LAMALEF_BEGIN; break;
+          default: break;
+         }
+          switch (lenOptionsTashkeel){
+          case TASHKEEL_BEGIN: lenOptionsTashkeel = TASHKEEL_END; break;
+          case TASHKEEL_END: lenOptionsTashkeel = TASHKEEL_BEGIN; break;
+          default: break;
+          }
+        }
+
+
+      if (lenOptionsLamAlef == LAMALEF_NEAR) {
+          for (int i = start, e = i + length; i < e; ++i) {
+              if (dest[i] == LAMALEF_SPACE_SUB) {
+                  dest[i] = SPACE_CHAR_FOR_LAMALEF;
+              }
+          }
+
+      } else {
+
+          final int e = start + length;
+          int wL = countSpaceSub(dest, length, LAMALEF_SPACE_SUB);
+          int wT = countSpaceSub(dest, length, TASHKEEL_SPACE_SUB);
+
+          if (lenOptionsLamAlef == LAMALEF_END){
+            lamAlefOn = true;
+          }
+          if (lenOptionsTashkeel == TASHKEEL_END){
+            tashkeelOn = true;
+          }
+
+
+          if (lamAlefOn && (lenOptionsLamAlef == LAMALEF_END)) {
+            shiftArray(dest, start, e, LAMALEF_SPACE_SUB);
+            while (wL > start) {
+                dest[--wL] = SPACE_CHAR;
+            }
+          }
+
+          if (tashkeelOn && (lenOptionsTashkeel == TASHKEEL_END)){
+            shiftArray(dest, start, e, TASHKEEL_SPACE_SUB);
+            while (wT > start) {
+                 dest[--wT] = SPACE_CHAR;
+            }
+          }
+
+          lamAlefOn = false;
+          tashkeelOn = false;
+
+          if (lenOptionsLamAlef == LAMALEF_RESIZE){
+            lamAlefOn = true;
+          }
+          if (lenOptionsTashkeel == TASHKEEL_RESIZE){
+            tashkeelOn = true;
+          }
+
+          if (lamAlefOn && (lenOptionsLamAlef == LAMALEF_RESIZE)){
+              shiftArray(dest, start, e, LAMALEF_SPACE_SUB);
+              wL = flipArray(dest,start,e, wL);
+              length = wL - start;
+          }
+          if (tashkeelOn && (lenOptionsTashkeel == TASHKEEL_RESIZE)) {
+              shiftArray(dest, start, e, TASHKEEL_SPACE_SUB);
+              wT = flipArray(dest,start,e, wT);
+              length = wT - start;
+          }
+
+          lamAlefOn = false;
+          tashkeelOn = false;
+
+          if ((lenOptionsLamAlef == LAMALEF_BEGIN) ||
+              (lenOptionsLamAlef == LAMALEF_AUTO)){
+                lamAlefOn = true;
+          }
+          if (lenOptionsTashkeel == TASHKEEL_BEGIN){
+                tashkeelOn = true;
+          }
+
+          if (lamAlefOn && ((lenOptionsLamAlef == LAMALEF_BEGIN)||
+                            (lenOptionsLamAlef == LAMALEF_AUTO))) { // spaces at beginning
+              shiftArray(dest, start, e, LAMALEF_SPACE_SUB);
+               wL = flipArray(dest,start,e, wL);
+                  while (wL < e) {
+                      dest[wL++] = SPACE_CHAR;
+                  }
+              }
+              if(tashkeelOn && (lenOptionsTashkeel == TASHKEEL_BEGIN)){
+               shiftArray(dest, start, e, TASHKEEL_SPACE_SUB);
+               wT = flipArray(dest,start,e, wT);
+                  while (wT < e) {
+                      dest[wT++] = SPACE_CHAR;
+                  }
+              }
+           }
+
+      return length;
+  }
+
+
+  /*
+   *Name     :expandCompositCharAtBegin
+   *Function :Expands the LamAlef character to Lam and Alef consuming the required
+   *         space from beginning of the buffer. If the text type was visual_LTR
+   *         and the option SPACES_RELATIVE_TO_TEXT_BEGIN_END was selected
+   *         the spaces will be located at end of buffer.
+   *         If there are no spaces to expand the LamAlef, an exception is thrown.
+*/
+ private boolean expandCompositCharAtBegin(char[] dest,int start, int length,
+                            int lacount) {
+     boolean spaceNotFound = false;
+
+     if (lacount > countSpacesRight(dest, start, length)) {
+         spaceNotFound = true;
+         return spaceNotFound;
+     }
+     for (int r = start + length - lacount, w = start + length; --r >= start;) {
+         char ch = dest[r];
+         if (isNormalizedLamAlefChar(ch)) {
+             dest[--w] = LAM_CHAR;
+             dest[--w] = convertNormalizedLamAlef[ch - '\u065C'];
+         } else {
+             dest[--w] = ch;
+         }
+     }
+     return spaceNotFound;
+
+  }
+
+  /*
+   *Name     : expandCompositCharAtEnd
+   *Function : Expands the LamAlef character to Lam and Alef consuming the
+   *           required space from end of the buffer. If the text type was
+   *           Visual LTR and the option SPACES_RELATIVE_TO_TEXT_BEGIN_END
+   *           was used, the spaces will be consumed from begin of buffer. If
+   *           there are no spaces to expand the LamAlef, an exception is thrown.
+   */
+
+  private boolean  expandCompositCharAtEnd(char[] dest,int start, int length,
+                          int lacount){
+      boolean spaceNotFound = false;
+
+      if (lacount > countSpacesLeft(dest, start, length)) {
+          spaceNotFound = true;
+          return spaceNotFound;
+      }
+      for (int r = start + lacount, w = start, e = start + length; r < e; ++r) {
+          char ch = dest[r];
+          if (isNormalizedLamAlefChar(ch)) {
+              dest[w++] = convertNormalizedLamAlef[ch - '\u065C'];
+              dest[w++] = LAM_CHAR;
+          } else {
+              dest[w++] = ch;
+          }
+      }
+      return spaceNotFound;
+  }
+
+  /*
+   *Name     : expandCompositCharAtNear
+   *Function : Expands the LamAlef character into Lam + Alef, YehHamza character
+   *           into Yeh + Hamza, SeenFamily character into SeenFamily character
+   *           + Tail, while consuming the space next to the character.
+   */
+
+  private boolean expandCompositCharAtNear(char[] dest,int start, int length,
+                                       int yehHamzaOption, int seenTailOption, int lamAlefOption){
+
+      boolean spaceNotFound = false;
+
+
+
+      if (isNormalizedLamAlefChar(dest[start])) {
+          spaceNotFound = true;
+          return spaceNotFound;
+      }
+      for (int i = start + length; --i >=start;) {
+          char ch = dest[i];
+          if (lamAlefOption == 1 && isNormalizedLamAlefChar(ch)) {
+              if (i>start &&dest[i-1] == SPACE_CHAR) {
+                  dest[i] = LAM_CHAR;
+                  dest[--i] = convertNormalizedLamAlef[ch - '\u065C'];
+              } else {
+                  spaceNotFound = true;
+                  return spaceNotFound;
+              }
+          }else if(seenTailOption == 1 && isSeenTailFamilyChar(ch) == 1){
+              if(i>start &&dest[i-1] == SPACE_CHAR){
+                  dest[i-1] = tailChar;
+              } else{
+                  spaceNotFound = true;
+                  return spaceNotFound;
+              }
+          }else if(yehHamzaOption == 1 && isYehHamzaChar(ch)){
+
+               if(i>start &&dest[i-1] == SPACE_CHAR){
+                  dest[i] = yehHamzaToYeh[ch - YEH_HAMZAFE_CHAR];
+                  dest[i-1] = HAMZAFE_CHAR;
+              }else{
+                  spaceNotFound = true;
+                  return spaceNotFound;
+                }
+
+
+          }
+      }
+      return false;
+
+  }
+
+    /*
+     * Name    : expandCompositChar
+     * Function: LamAlef needs special handling as the LamAlef is
+     *           one character while expanding it will give two
+     *           characters Lam + Alef, so we need to expand the LamAlef
+     *           in near or far spaces according to the options the user
+     *           specifies or increase the buffer size.
+     *           Dest has enough room for the expansion if we are growing.
+     *           lamalef are normalized to the 'special characters'
+     */
+    private int expandCompositChar(char[] dest,
+                              int start,
+                              int length,
+                              int lacount,
+                              int shapingMode) throws ArabicShapingException {
+
+        int lenOptionsLamAlef = options & LAMALEF_MASK;
+        int lenOptionsSeen = options & SEEN_MASK;
+        int lenOptionsYehHamza = options & YEHHAMZA_MASK;
+        boolean spaceNotFound = false;
+
+        if (!isLogical && !spacesRelativeToTextBeginEnd) {
+            switch (lenOptionsLamAlef) {
+            case LAMALEF_BEGIN: lenOptionsLamAlef = LAMALEF_END; break;
+            case LAMALEF_END: lenOptionsLamAlef = LAMALEF_BEGIN; break;
+            default: break;
+            }
+        }
+
+        if(shapingMode == 1){
+            if(lenOptionsLamAlef == LAMALEF_AUTO){
+                if(isLogical){
+                    spaceNotFound = expandCompositCharAtEnd(dest, start, length, lacount);
+                    if(spaceNotFound){
+                        spaceNotFound = expandCompositCharAtBegin(dest, start, length, lacount);
+                    }
+                    if(spaceNotFound){
+                        spaceNotFound = expandCompositCharAtNear(dest, start, length,0,0,1);
+                    }
+                    if(spaceNotFound){
+                        throw new ArabicShapingException("No spacefor lamalef");
+                    }
+                }else{
+                    spaceNotFound = expandCompositCharAtBegin(dest, start, length, lacount);
+                    if(spaceNotFound){
+                        spaceNotFound = expandCompositCharAtEnd(dest, start, length, lacount);
+                    }
+                    if(spaceNotFound){
+                        spaceNotFound = expandCompositCharAtNear(dest, start, length,0,0,1);
+                    }
+                    if(spaceNotFound){
+                        throw new ArabicShapingException("No spacefor lamalef");
+                    }
+                }
+            }else if(lenOptionsLamAlef == LAMALEF_END){
+                spaceNotFound = expandCompositCharAtEnd(dest, start, length, lacount);
+                if(spaceNotFound){
+                    throw new ArabicShapingException("No spacefor lamalef");
+                }
+            }else if(lenOptionsLamAlef == LAMALEF_BEGIN){
+                spaceNotFound = expandCompositCharAtBegin(dest, start, length, lacount);
+                if(spaceNotFound){
+                    throw new ArabicShapingException("No spacefor lamalef");
+                }
+            }else if(lenOptionsLamAlef == LAMALEF_NEAR){
+                spaceNotFound = expandCompositCharAtNear(dest, start, length,0,0,1);
+                if(spaceNotFound){
+                    throw new ArabicShapingException("No spacefor lamalef");
+            }
+            }else if(lenOptionsLamAlef == LAMALEF_RESIZE){
+                for (int r = start + length, w = r + lacount; --r >= start;) {
+                    char ch = dest[r];
+                    if (isNormalizedLamAlefChar(ch)) {
+                        dest[--w] = '\u0644';
+                        dest[--w] = convertNormalizedLamAlef[ch - '\u065C'];
+                    } else {
+                        dest[--w] = ch;
+                    }
+                }
+                length += lacount;
+            }
+            }else{
+                if(lenOptionsSeen == SEEN_TWOCELL_NEAR){
+                spaceNotFound = expandCompositCharAtNear(dest, start, length,0,1,0);
+                if(spaceNotFound){
+                    throw new ArabicShapingException("No space for Seen tail expansion");
+                }
+            }
+            if(lenOptionsYehHamza == YEHHAMZA_TWOCELL_NEAR){
+                spaceNotFound = expandCompositCharAtNear(dest, start, length,1,0,0);
+                if(spaceNotFound){
+                    throw new ArabicShapingException("No space for YehHamza expansion");
+                }
+            }
+            }
+        return length;
+    }
+
+
+    /* Convert the input buffer from FExx Range into 06xx Range
+     * to put all characters into the 06xx range
+     * even the lamalef is converted to the special region in
+     * the 06xx range.  Return the number of lamalef chars found.
+     */
+    private int normalize(char[] dest, int start, int length) {
+        int lacount = 0;
+        for (int i = start, e = i + length; i < e; ++i) {
+            char ch = dest[i];
+            if (ch >= '\uFE70' && ch <= '\uFEFC') {
+                if (isLamAlefChar(ch)) {
+                    ++lacount;
+                }
+                dest[i] = (char)convertFEto06[ch - '\uFE70'];
+            }
+        }
+        return lacount;
+    }
+
+    /*
+     * Name    : deshapeNormalize
+     * Function: Convert the input buffer from FExx Range into 06xx Range
+     *           even the lamalef is converted to the special region in the 06xx range.
+     *           According to the options the user enters, all seen family characters
+     *           followed by a tail character are merged to seen tail family character and
+     *           any yeh followed by a hamza character are merged to yehhamza character.
+     *           Method returns the number of lamalef chars found.
+     */
+    private int deshapeNormalize(char[] dest, int start, int length) {
+        int lacount = 0;
+        int yehHamzaComposeEnabled = 0;
+        int seenComposeEnabled = 0;
+
+        yehHamzaComposeEnabled = ((options&YEHHAMZA_MASK) == YEHHAMZA_TWOCELL_NEAR) ? 1 : 0;
+        seenComposeEnabled = ((options&SEEN_MASK) == SEEN_TWOCELL_NEAR)? 1 : 0;
+
+        for (int i = start, e = i + length; i < e; ++i) {
+            char ch = dest[i];
+
+        if( (yehHamzaComposeEnabled == 1) && ((ch == HAMZA06_CHAR) || (ch == HAMZAFE_CHAR))
+               && (i < (length - 1)) && isAlefMaksouraChar(dest[i+1] )) {
+                dest[i] = SPACE_CHAR;
+                dest[i+1] = YEH_HAMZA_CHAR;
+       } else if ( (seenComposeEnabled == 1) && (isTailChar(ch)) && (i< (length - 1))
+                       && (isSeenTailFamilyChar(dest[i+1])==1) ) {
+               dest[i] = SPACE_CHAR;
+       }
+       else if (ch >= '\uFE70' && ch <= '\uFEFC') {
+                if (isLamAlefChar(ch)) {
+                    ++lacount;
+                }
+                dest[i] = (char)convertFEto06[ch - '\uFE70'];
+            }
+        }
+        return lacount;
+    }
+
+    /*
+     * Name    : shapeUnicode
+     * Function: Converts an Arabic Unicode buffer in 06xx Range into a shaped
+     *           arabic Unicode buffer in FExx Range
+     */
+    private int shapeUnicode(char[] dest,
+                             int start,
+                             int length,
+                             int destSize,
+                             int tashkeelFlag)throws ArabicShapingException {
+
+        int lamalef_count = normalize(dest, start, length);
+
+        // resolve the link between the characters.
+        // Arabic characters have four forms: Isolated, Initial, Medial and Final.
+        // Tashkeel characters have two, isolated or medial, and sometimes only isolated.
+        // tashkeelFlag == 0: shape normally, 1: shape isolated, 2: don't shape
+
+        boolean lamalef_found = false, seenfam_found = false;
+        boolean yehhamza_found = false, tashkeel_found = false;
+        int i = start + length - 1;
+        int currLink = getLink(dest[i]);
+        int nextLink = 0;
+        int prevLink = 0;
+        int lastLink = 0;
+        //int prevPos = i;
+        int lastPos = i;
+        int nx = -2;
+        int nw = 0;
+
+        while (i >= 0) {
+            // If high byte of currLink > 0 then there might be more than one shape
+            if ((currLink & '\uFF00') > 0 || isTashkeelChar(dest[i])) {
+                nw = i - 1;
+                nx = -2;
+                while (nx < 0) { // we need to know about next char
+                    if (nw == -1) {
+                        nextLink = 0;
+                        nx = Integer.MAX_VALUE;
+                    } else {
+                        nextLink = getLink(dest[nw]);
+                        if ((nextLink & IRRELEVANT) == 0) {
+                            nx = nw;
+                        } else {
+                            --nw;
+                        }
+                    }
+                }
+
+                if (((currLink & ALEFTYPE) > 0) && ((lastLink & LAMTYPE) > 0)) {
+                    lamalef_found = true;
+                    char wLamalef = changeLamAlef(dest[i]); // get from 0x065C-0x065f
+                    if (wLamalef != '\u0000') {
+                        // replace alef by marker, it will be removed later
+                        dest[i] = '\uffff';
+                        dest[lastPos] = wLamalef;
+                        i = lastPos;
+                    }
+
+                    lastLink = prevLink;
+                    currLink = getLink(wLamalef); // requires '\u0000', unfortunately
+                }
+                if ((i > 0) && (dest[i-1] == SPACE_CHAR))
+                {
+                    if ( isSeenFamilyChar(dest[i]) == 1){
+                        seenfam_found = true;
+                    } else if (dest[i] == YEH_HAMZA_CHAR) {
+                        yehhamza_found = true;
+                    }
+                }
+                else if(i==0){
+                    if ( isSeenFamilyChar(dest[i]) == 1){
+                        seenfam_found = true;
+                    } else if (dest[i] == YEH_HAMZA_CHAR) {
+                        yehhamza_found = true;
+                    }
+                }
+
+
+                // get the proper shape according to link ability of neighbors
+                // and of character; depends on the order of the shapes
+                // (isolated, initial, middle, final) in the compatibility area
+
+                int flag = specialChar(dest[i]);
+
+                int shape = shapeTable[nextLink & LINK_MASK]
+                    [lastLink & LINK_MASK]
+                    [currLink & LINK_MASK];
+
+                if (flag == 1) {
+                    shape &= 0x1;
+                } else if (flag == 2) {
+                    if (tashkeelFlag == 0 &&
+                        ((lastLink & LINKL) != 0) &&
+                        ((nextLink & LINKR) != 0) &&
+                        dest[i] != '\u064C' &&
+                        dest[i] != '\u064D' &&
+                        !((nextLink & ALEFTYPE) == ALEFTYPE &&
+                          (lastLink & LAMTYPE) == LAMTYPE)) {
+
+                        shape = 1;
+                    } else {
+                        shape = 0;
+                    }
+                }
+                if (flag == 2) {
+                    if (tashkeelFlag == 2) {
+                        dest[i] = TASHKEEL_SPACE_SUB;
+                        tashkeel_found = true;
+                    }
+                    else{
+                        dest[i] = (char)('\uFE70' + irrelevantPos[dest[i] - '\u064B'] + shape);
+                    }
+                    // else leave tashkeel alone
+                } else {
+                    dest[i] = (char)('\uFE70' + (currLink >> 8) + shape);
+                }
+            }
+
+            // move one notch forward
+            if ((currLink & IRRELEVANT) == 0) {
+                prevLink = lastLink;
+                lastLink = currLink;
+                //prevPos = lastPos;
+                lastPos = i;
+            }
+
+            --i;
+            if (i == nx) {
+                currLink = nextLink;
+                nx = -2;
+            } else if (i != -1) {
+                currLink = getLink(dest[i]);
+            }
+        }
+
+        // If we found a lam/alef pair in the buffer
+        // call handleGeneratedSpaces to remove the spaces that were added
+
+        destSize = length;
+        if (lamalef_found || tashkeel_found) {
+            destSize = handleGeneratedSpaces(dest, start, length);
+        }
+        if (seenfam_found || yehhamza_found){
+            destSize = expandCompositChar(dest, start, destSize, lamalef_count, SHAPE_MODE);
+        }
+        return destSize;
+    }
+
+    /*
+     * Name    : deShapeUnicode
+     * Function: Converts an Arabic Unicode buffer in FExx Range into unshaped
+     *           arabic Unicode buffer in 06xx Range
+     */
+    private int deShapeUnicode(char[] dest,
+                               int start,
+                               int length,
+                               int destSize) throws ArabicShapingException {
+
+        int lamalef_count = deshapeNormalize(dest, start, length);
+
+        // If there was a lamalef in the buffer call expandLamAlef
+        if (lamalef_count != 0) {
+            // need to adjust dest to fit expanded buffer... !!!
+            destSize = expandCompositChar(dest, start, length, lamalef_count,DESHAPE_MODE);
+        } else {
+            destSize = length;
+        }
+
+        return destSize;
+    }
+
+    private int internalShape(char[] source,
+                              int sourceStart,
+                              int sourceLength,
+                              char[] dest,
+                              int destStart,
+                              int destSize) throws ArabicShapingException {
+
+        if (sourceLength == 0) {
+            return 0;
+        }
+
+        if (destSize == 0) {
+            if (((options & LETTERS_MASK) != LETTERS_NOOP) &&
+                ((options & LAMALEF_MASK) == LAMALEF_RESIZE)) {
+
+                return calculateSize(source, sourceStart, sourceLength);
+            } else {
+                return sourceLength; // by definition
+            }
+        }
+
+        // always use temp buffer
+        char[] temp = new char[sourceLength * 2]; // all lamalefs requiring expansion
+        System.arraycopy(source, sourceStart, temp, 0, sourceLength);
+
+        if (isLogical) {
+            invertBuffer(temp, 0, sourceLength);
+        }
+
+        int outputSize = sourceLength;
+
+        switch (options & LETTERS_MASK) {
+        case LETTERS_SHAPE_TASHKEEL_ISOLATED:
+            outputSize = shapeUnicode(temp, 0, sourceLength, destSize, 1);
+            break;
+
+        case LETTERS_SHAPE:
+            if( ((options&TASHKEEL_MASK)> 0) &&
+                ((options&TASHKEEL_MASK) !=TASHKEEL_REPLACE_BY_TATWEEL)) {
+                   /* Call the shaping function with tashkeel flag == 2 for removal of tashkeel */
+                outputSize = shapeUnicode(temp, 0, sourceLength, destSize, 2);
+                }else {
+                   //default Call the shaping function with tashkeel flag == 1 */
+                    outputSize = shapeUnicode(temp, 0, sourceLength, destSize, 0);
+
+                   /*After shaping text check if user wants to remove tashkeel and replace it with tatweel*/
+                   if( (options&TASHKEEL_MASK) == TASHKEEL_REPLACE_BY_TATWEEL){
+                       outputSize = handleTashkeelWithTatweel(temp,sourceLength);
+                   }
+               }
+            break;
+
+        case LETTERS_UNSHAPE:
+            outputSize = deShapeUnicode(temp, 0, sourceLength, destSize);
+            break;
+
+        default:
+            break;
+        }
+
+        if (outputSize > destSize) {
+            throw new ArabicShapingException("not enough room for result data");
+        }
+
+        if ((options & DIGITS_MASK) != DIGITS_NOOP) {
+            char digitBase = '\u0030'; // European digits
+            switch (options & DIGIT_TYPE_MASK) {
+            case DIGIT_TYPE_AN:
+                digitBase = '\u0660';  // Arabic-Indic digits
+                break;
+
+            case DIGIT_TYPE_AN_EXTENDED:
+                digitBase = '\u06f0';  // Eastern Arabic-Indic digits (Persian and Urdu)
+                break;
+
+            default:
+                break;
+            }
+
+            switch (options & DIGITS_MASK) {
+            case DIGITS_EN2AN:
+                {
+                    int digitDelta = digitBase - '\u0030';
+                    for (int i = 0; i < outputSize; ++i) {
+                        char ch = temp[i];
+                        if (ch <= '\u0039' && ch >= '\u0030') {
+                            temp[i] += digitDelta;
+                        }
+                    }
+                }
+                break;
+
+            case DIGITS_AN2EN:
+                {
+                    char digitTop = (char)(digitBase + 9);
+                    int digitDelta = '\u0030' - digitBase;
+                    for (int i = 0; i < outputSize; ++i) {
+                        char ch = temp[i];
+                        if (ch <= digitTop && ch >= digitBase) {
+                            temp[i] += digitDelta;
+                        }
+                    }
+                }
+                break;
+
+            case DIGITS_EN2AN_INIT_LR:
+                shapeToArabicDigitsWithContext(temp, 0, outputSize, digitBase, false);
+                break;
+
+            case DIGITS_EN2AN_INIT_AL:
+                shapeToArabicDigitsWithContext(temp, 0, outputSize, digitBase, true);
+                break;
+
+            default:
+                break;
+            }
+        }
+
+        if (isLogical) {
+            invertBuffer(temp, 0, outputSize);
+        }
+
+        System.arraycopy(temp, 0, dest, destStart, outputSize);
+
+        return outputSize;
+    }
+
+    private static class ArabicShapingException extends RuntimeException {
+        ArabicShapingException(String msg) {
+            super(msg);
+        }
+    }
+}
diff --git a/icu4j/license.html b/icu4j/license.html
new file mode 100644
index 0000000..b905ddf
--- /dev/null
+++ b/icu4j/license.html
@@ -0,0 +1,51 @@
+<html>
+
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=us-ascii"></meta>
+<title>ICU License - ICU 1.8.1 and later</title>
+</head>
+
+<body BGCOLOR="#ffffff">
+<h2>ICU License - ICU 1.8.1 and later</h2>
+
+<p>COPYRIGHT AND PERMISSION NOTICE</p>
+
+<p>
+Copyright (c) 1995-2006 International Business Machines Corporation and others
+</p>
+<p>
+All rights reserved.
+</p>
+<p>
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"),
+to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, and/or sell
+copies of the Software, and to permit persons
+to whom the Software is furnished to do so, provided that the above
+copyright notice(s) and this permission notice appear in all copies
+of the Software and that both the above copyright notice(s) and this
+permission notice appear in supporting documentation.
+</p>
+<p>
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 
+INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. IN NO EVENT SHALL
+THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM,
+OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER
+RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE
+USE OR PERFORMANCE OF THIS SOFTWARE.
+</p>
+<p>
+Except as contained in this notice, the name of a copyright holder shall not be
+used in advertising or otherwise to promote the sale, use or other dealings in
+this Software without prior written authorization of the copyright holder.
+</p>
+
+<hr>
+<p><small>
+All trademarks and registered trademarks mentioned herein are the property of their respective owners.
+</small></p>
+</body>
+</html>
diff --git a/libs/rs/Android.mk b/libs/rs/Android.mk
index 98464a0..0b06022 100644
--- a/libs/rs/Android.mk
+++ b/libs/rs/Android.mk
@@ -76,16 +76,18 @@
 LOCAL_SRC_FILES:= \
 	rsAdapter.cpp \
 	rsAllocation.cpp \
+	rsAnimation.cpp \
 	rsComponent.cpp \
 	rsContext.cpp \
 	rsDevice.cpp \
 	rsElement.cpp \
-        rsFileA3D.cpp \
+    rsFileA3D.cpp \
 	rsLight.cpp \
 	rsLocklessFifo.cpp \
 	rsObjectBase.cpp \
 	rsMatrix.cpp \
-        rsMesh.cpp \
+    rsMesh.cpp \
+    rsMutex.cpp \
 	rsNoise.cpp \
 	rsProgram.cpp \
 	rsProgramFragment.cpp \
@@ -96,7 +98,8 @@
 	rsScript.cpp \
 	rsScriptC.cpp \
 	rsScriptC_Lib.cpp \
-        rsShaderCache.cpp \
+    rsShaderCache.cpp \
+	rsSignal.cpp \
 	rsSimpleMesh.cpp \
 	rsThreadIO.cpp \
 	rsType.cpp \
diff --git a/libs/rs/RenderScript.h b/libs/rs/RenderScript.h
index d280f50..7415ba9 100644
--- a/libs/rs/RenderScript.h
+++ b/libs/rs/RenderScript.h
@@ -30,6 +30,7 @@
 typedef void * RsAdapter1D;
 typedef void * RsAdapter2D;
 typedef void * RsAllocation;
+typedef void * RsAnimation;
 typedef void * RsContext;
 typedef void * RsDevice;
 typedef void * RsElement;
@@ -205,7 +206,27 @@
 enum RsError {
     RS_ERROR_NONE,
     RS_ERROR_BAD_SHADER,
-    RS_ERROR_BAD_SCRIPT
+    RS_ERROR_BAD_SCRIPT,
+    RS_ERROR_BAD_VALUE,
+    RS_ERROR_OUT_OF_MEMORY
+};
+
+enum RsAnimationInterpolation {
+    RS_ANIMATION_INTERPOLATION_STEP,
+    RS_ANIMATION_INTERPOLATION_LINEAR,
+    RS_ANIMATION_INTERPOLATION_BEZIER,
+    RS_ANIMATION_INTERPOLATION_CARDINAL,
+    RS_ANIMATION_INTERPOLATION_HERMITE,
+    RS_ANIMATION_INTERPOLATION_BSPLINE
+};
+
+enum RsAnimationEdge {
+    RS_ANIMATION_EDGE_UNDEFINED,
+    RS_ANIMATION_EDGE_CONSTANT,
+    RS_ANIMATION_EDGE_GRADIENT,
+    RS_ANIMATION_EDGE_CYCLE,
+    RS_ANIMATION_EDGE_OSCILLATE,
+    RS_ANIMATION_EDGE_CYLE_RELATIVE
 };
 
 #ifndef NO_RS_FUNCS
diff --git a/libs/rs/java/Fountain/res/raw/fountain2.rs b/libs/rs/java/Fountain/res/raw/fountain2.rs
index 3301140..5d36e35 100644
--- a/libs/rs/java/Fountain/res/raw/fountain2.rs
+++ b/libs/rs/java/Fountain/res/raw/fountain2.rs
@@ -1,9 +1,9 @@
 // Fountain test script
 #pragma version(1)
 
-#include "rs_types.rsh"
-#include "rs_math.rsh"
-#include "rs_graphics.rsh"
+#include "../../../../scriptc/rs_types.rsh"
+#include "../../../../scriptc/rs_math.rsh"
+#include "../../../../scriptc/rs_graphics.rsh"
 
 static int newPart = 0;
 
@@ -12,15 +12,15 @@
     int rate;
     int count;
     float r, g, b;
-    rs_allocation partBuffer;
     rs_mesh partMesh;
+    rs_allocation partBuffer;
 } Control_t;
 Control_t *Control;
 
 typedef struct Point_s{
     float2 delta;
-    float2 position;
-    unsigned int color;
+    rs_position2 pos;
+    rs_color4u color;
 } Point_t;
 Point_t *point;
 
@@ -33,8 +33,6 @@
 
     if (rate) {
         float rMax = ((float)rate) * 0.005f;
-        int x = Control->x;
-        int y = Control->y;
         int color = ((int)(Control->r * 255.f)) |
                     ((int)(Control->g * 255.f)) << 8 |
                     ((int)(Control->b * 255.f)) << 16 |
@@ -42,9 +40,11 @@
         Point_t * np = &p[newPart];
 
         while (rate--) {
-            np->delta = vec2Rand(rMax);
-            np->position.x = x;
-            np->position.y = y;
+            np->delta.x = rand(rMax);
+            np->delta.y = rand(rMax);
+            //np->delta = vec2Rand(rMax);
+            np->pos.x = Control->x;
+            np->pos.y = Control->y;
             np->color = color;
             newPart++;
             np++;
@@ -57,13 +57,13 @@
 
     for (ct=0; ct < count; ct++) {
         float dy = p->delta.y + 0.15f;
-        float posy = p->position.y + dy;
+        float posy = p->pos.y + dy;
         if ((posy > height) && (dy > 0)) {
             dy *= -0.3f;
         }
         p->delta.y = dy;
-        p->position.x += p->delta.x;
-        p->position.y = posy;
+        p->pos.x += p->delta.x;
+        p->pos.y = posy;
         p++;
     }
 
diff --git a/libs/rs/java/ImageProcessing/res/raw/threshold2.rs b/libs/rs/java/ImageProcessing/res/raw/threshold2.rs
new file mode 100644
index 0000000..9f687b5
--- /dev/null
+++ b/libs/rs/java/ImageProcessing/res/raw/threshold2.rs
@@ -0,0 +1,49 @@
+#pragma version(1)
+
+#include "../../../../scriptc/rs_types.rsh"
+#include "../../../../scriptc/rs_math.rsh"
+#include "../../../../scriptc/rs_graphics.rsh"
+
+typedef struct Params_s{
+    int inHeight;
+    int inWidth;
+    int outHeight;
+    int outWidth;
+    float threshold;
+} Params_t;
+
+Params_t * Params;
+rs_color4u * InPixel;
+rs_color4u * OutPixel;
+
+
+int main() {
+    int t = uptimeMillis();
+
+    rs_color4u *in = InPixel;
+    rs_color4u *out = OutPixel;
+
+    int count = Params->inWidth * Params->inHeight;
+    int i;
+    float threshold = Params->threshold * 255.f;
+
+    for (i = 0; i < count; i++) {
+        float luminance = 0.2125f * in->x +
+                          0.7154f * in->y +
+                          0.0721f * in->z;
+        if (luminance > threshold) {
+            *out = *in;
+        } else {
+            *((int *)out) = *((int *)in) & 0xff000000;
+        }
+
+        in++;
+        out++;
+    }
+
+    t= uptimeMillis() - t;
+    debugI32("Filter time", t);
+
+    sendToClient(&count, 1, 4, 0);
+    return 0;
+}
diff --git a/libs/rs/rs.spec b/libs/rs/rs.spec
index cb9937c..08aa369 100644
--- a/libs/rs/rs.spec
+++ b/libs/rs/rs.spec
@@ -480,3 +480,13 @@
 	param uint32_t slot
 	}
 
+AnimationCreate {
+	param const float *inValues
+	param const float *outValues
+	param uint32_t valueCount
+	param RsAnimationInterpolation interp
+	param RsAnimationEdge pre
+	param RsAnimationEdge post
+	ret RsAnimation
+	}
+
diff --git a/libs/rs/rsAllocation.cpp b/libs/rs/rsAllocation.cpp
index 4e8278d..e5ff1d7 100644
--- a/libs/rs/rsAllocation.cpp
+++ b/libs/rs/rsAllocation.cpp
@@ -170,6 +170,7 @@
         glGenerateMipmap(GL_TEXTURE_2D);
     }
 
+    rsc->checkError("Allocation::uploadToTexture");
 }
 
 void Allocation::deferedUploadToBufferObject(const Context *rsc)
@@ -201,6 +202,7 @@
     glBindBuffer(GL_ARRAY_BUFFER, mBufferID);
     glBufferData(GL_ARRAY_BUFFER, mType->getSizeBytes(), getPtr(), GL_DYNAMIC_DRAW);
     glBindBuffer(GL_ARRAY_BUFFER, 0);
+    rsc->checkError("Allocation::uploadToBufferObject");
 }
 
 void Allocation::uploadCheck(const Context *rsc)
diff --git a/libs/rs/rsAnimation.cpp b/libs/rs/rsAnimation.cpp
new file mode 100644
index 0000000..48c9334
--- /dev/null
+++ b/libs/rs/rsAnimation.cpp
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2009 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 "rsContext.h"
+#include "rsAnimation.h"
+
+
+using namespace android;
+using namespace android::renderscript;
+
+/*
+Animation::Animation(Context *rsc) : ObjectBase(rsc)
+{
+    mAllocFile = __FILE__;
+    mAllocLine = __LINE__;
+
+    mValuesInput = NULL;
+    mValuesOutput = NULL;
+    mValueCount = 0;
+    mInterpolation = RS_ANIMATION_INTERPOLATION_STEP;
+    mEdgePre = RS_ANIMATION_EDGE_UNDEFINED;
+    mEdgePost = RS_ANIMATION_EDGE_UNDEFINED;
+    mInputMin = 0;
+    mInputMax = 0;
+}
+
+Animation * Animation::create(Context *rsc,
+                              const float *inValues, const float *outValues,
+                              uint32_t valueCount, RsAnimationInterpolation interp,
+                              RsAnimationEdge pre, RsAnimationEdge post)
+{
+    if (valueCount < 2) {
+        rsc->setError(RS_ERROR_BAD_VALUE, "Animations require more than 2 values.");
+        return NULL;
+    }
+    Animation *a = new Animation(rsc);
+    if (!a) {
+        rsc->setError(RS_ERROR_OUT_OF_MEMORY);
+        return NULL;
+    }
+
+    float *vin = (float *)malloc(valueCount * sizeof(float));
+    float *vout = (float *)malloc(valueCount * sizeof(float));
+    a->mValuesInput = vin;
+    a->mValuesOutput = vout;
+    if (a->mValuesInput == NULL || a->mValuesOutput == NULL) {
+        delete a;
+        rsc->setError(RS_ERROR_OUT_OF_MEMORY);
+        return NULL;
+    }
+
+    a->mEdgePre = pre;
+    a->mEdgePost = post;
+    a->mInterpolation = interp;
+    a->mValueCount = valueCount;
+
+    memcpy(vin, inValues, valueCount * sizeof(float));
+    memcpy(vout, outValues, valueCount * sizeof(float));
+    a->mInputMin = inValues[0];
+    a->mInputMax = inValues[0];
+
+    bool needSort = false;
+    for (uint32_t ct=1; ct < valueCount; ct++) {
+        if (a->mInputMin > vin[ct]) {
+            needSort = true;
+            a->mInputMin = vin[ct];
+        }
+        if (a->mInputMax < vin[ct]) {
+            a->mInputMax = vin[ct];
+        } else {
+            needSort = true;
+        }
+    }
+
+    while (1) {
+        bool changed = false;
+        for (uint32_t ct=1; ct < valueCount; ct++) {
+            if (vin[ct-1] > vin[ct]) {
+                float t = vin[ct-1];
+                vin[ct-1] = vin[ct];
+                vin[ct] = t;
+                t = vout[ct-1];
+                vout[ct-1] = vout[ct];
+                vout[ct] = t;
+                changed = true;
+            }
+        }
+        if (!changed) break;
+    }
+
+    return a;
+}
+*/
+
+
+/////////////////////////////////////////
+//
+
+namespace android {
+namespace renderscript {
+
+RsAnimation rsi_AnimationCreate(Context *rsc,
+                                const float *inValues,
+                                const float *outValues,
+                                uint32_t valueCount,
+                                RsAnimationInterpolation interp,
+                                RsAnimationEdge pre,
+                                RsAnimationEdge post)
+{
+    //LOGE("rsi_ElementCreate %i %i %i %i", dt, dk, norm, vecSize);
+    Animation *a = NULL;//Animation::create(rsc, inValues, outValues, valueCount, interp, pre, post);
+    if (a != NULL) {
+        a->incUserRef();
+    }
+    return (RsAnimation)a;
+}
+
+
+}
+}
+
diff --git a/libs/rs/rsAnimation.h b/libs/rs/rsAnimation.h
new file mode 100644
index 0000000..b8db661
--- /dev/null
+++ b/libs/rs/rsAnimation.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_RS_ANIMATION_H
+#define ANDROID_RS_ANIMATION_H
+
+#include "rsUtils.h"
+#include "rsObjectBase.h"
+
+// ---------------------------------------------------------------------------
+namespace android {
+namespace renderscript {
+
+
+class Animation : public ObjectBase
+{
+public:
+    ~Animation();
+
+    static Animation * create(Context *rsc,
+                              const float *inValues, const float *outValues,
+                              uint32_t valueCount, RsAnimationInterpolation,
+                              RsAnimationEdge pre, RsAnimationEdge post);
+
+    float eval(float) const;
+
+
+protected:
+    Animation(Context *rsc);
+
+
+
+    float evalInRange(float) const;
+
+
+
+    const float *mValuesInput;
+    const float *mValuesOutput;
+    uint32_t mValueCount;
+    RsAnimationInterpolation mInterpolation;
+    RsAnimationEdge mEdgePre;
+    RsAnimationEdge mEdgePost;
+
+    // derived
+    float mInputMin;
+    float mInputMax;
+};
+
+
+
+
+}
+}
+#endif //ANDROID_STRUCTURED_ELEMENT_H
+
diff --git a/libs/rs/rsContext.cpp b/libs/rs/rsContext.cpp
index d8a9a99..4107229 100644
--- a/libs/rs/rsContext.cpp
+++ b/libs/rs/rsContext.cpp
@@ -664,8 +664,7 @@
 
 bool Context::objDestroyOOBInit()
 {
-    int status = pthread_mutex_init(&mObjDestroy.mMutex, NULL);
-    if (status) {
+    if (!mObjDestroy.mMutex.init()) {
         LOGE("Context::ObjDestroyOOBInit mutex init failure");
         return false;
     }
@@ -675,9 +674,8 @@
 void Context::objDestroyOOBRun()
 {
     if (mObjDestroy.mNeedToEmpty) {
-        int status = pthread_mutex_lock(&mObjDestroy.mMutex);
-        if (status) {
-            LOGE("Context::ObjDestroyOOBRun: error %i locking for OOBRun.", status);
+        if (!mObjDestroy.mMutex.lock()) {
+            LOGE("Context::ObjDestroyOOBRun: error locking for OOBRun.");
             return;
         }
 
@@ -686,35 +684,25 @@
         }
         mObjDestroy.mDestroyList.clear();
         mObjDestroy.mNeedToEmpty = false;
-
-        status = pthread_mutex_unlock(&mObjDestroy.mMutex);
-        if (status) {
-            LOGE("Context::ObjDestroyOOBRun: error %i unlocking for set condition.", status);
-        }
+        mObjDestroy.mMutex.unlock();
     }
 }
 
 void Context::objDestroyOOBDestroy()
 {
     rsAssert(!mObjDestroy.mNeedToEmpty);
-    pthread_mutex_destroy(&mObjDestroy.mMutex);
 }
 
 void Context::objDestroyAdd(ObjectBase *obj)
 {
-    int status = pthread_mutex_lock(&mObjDestroy.mMutex);
-    if (status) {
-        LOGE("Context::ObjDestroyOOBRun: error %i locking for OOBRun.", status);
+    if (!mObjDestroy.mMutex.lock()) {
+        LOGE("Context::ObjDestroyOOBRun: error locking for OOBRun.");
         return;
     }
 
     mObjDestroy.mNeedToEmpty = true;
     mObjDestroy.mDestroyList.add(obj);
-
-    status = pthread_mutex_unlock(&mObjDestroy.mMutex);
-    if (status) {
-        LOGE("Context::ObjDestroyOOBRun: error %i unlocking for set condition.", status);
-    }
+    mObjDestroy.mMutex.unlock();
 }
 
 uint32_t Context::getMessageToClient(void *data, size_t *receiveLen, size_t bufferLen, bool wait)
diff --git a/libs/rs/rsContext.h b/libs/rs/rsContext.h
index 82c3687..8e755a9 100644
--- a/libs/rs/rsContext.h
+++ b/libs/rs/rsContext.h
@@ -18,6 +18,7 @@
 #define ANDROID_RS_CONTEXT_H
 
 #include "rsUtils.h"
+#include "rsMutex.h"
 
 #include "rsThreadIO.h"
 #include "rsType.h"
@@ -161,7 +162,7 @@
     void dumpDebug() const;
     void checkError(const char *) const;
     const char * getError(RsError *);
-    void setError(RsError e, const char *msg);
+    void setError(RsError e, const char *msg = NULL);
 
     mutable const ObjectBase * mObjHead;
 
@@ -227,7 +228,7 @@
 
 
     struct ObjDestroyOOB {
-        pthread_mutex_t mMutex;
+        Mutex mMutex;
         Vector<ObjectBase *> mDestroyList;
         bool mNeedToEmpty;
     };
diff --git a/libs/rs/rsLocklessFifo.cpp b/libs/rs/rsLocklessFifo.cpp
index c796520..76ca32e 100644
--- a/libs/rs/rsLocklessFifo.cpp
+++ b/libs/rs/rsLocklessFifo.cpp
@@ -17,7 +17,7 @@
 #include "rsLocklessFifo.h"
 
 using namespace android;
-
+using namespace android::renderscript;
 
 LocklessCommandFifo::LocklessCommandFifo()
 {
@@ -128,15 +128,19 @@
     //dumpState("flush 2");
 }
 
+void LocklessCommandFifo::wait()
+{
+    while(isEmpty() && !mInShutdown) {
+        mSignalToControl.set();
+        mSignalToWorker.wait();
+    }
+}
+
 const void * LocklessCommandFifo::get(uint32_t *command, uint32_t *bytesData)
 {
     while(1) {
         //dumpState("get");
-        while(isEmpty() && !mInShutdown) {
-            mSignalToControl.set();
-            mSignalToWorker.wait();
-        }
-
+        wait();
         if (mInShutdown) {
             *command = 0;
             *bytesData = 0;
@@ -192,79 +196,3 @@
     LOGV("%s  put %p, get %p,  buf %p,  end %p", s, mPut, mGet, mBuffer, mEnd);
 }
 
-LocklessCommandFifo::Signal::Signal()
-{
-    mSet = true;
-}
-
-LocklessCommandFifo::Signal::~Signal()
-{
-    pthread_mutex_destroy(&mMutex);
-    pthread_cond_destroy(&mCondition);
-}
-
-bool LocklessCommandFifo::Signal::init()
-{
-    int status = pthread_mutex_init(&mMutex, NULL);
-    if (status) {
-        LOGE("LocklessFifo mutex init failure");
-        return false;
-    }
-
-    status = pthread_cond_init(&mCondition, NULL);
-    if (status) {
-        LOGE("LocklessFifo condition init failure");
-        pthread_mutex_destroy(&mMutex);
-        return false;
-    }
-
-    return true;
-}
-
-void LocklessCommandFifo::Signal::set()
-{
-    int status;
-
-    status = pthread_mutex_lock(&mMutex);
-    if (status) {
-        LOGE("LocklessCommandFifo: error %i locking for set condition.", status);
-        return;
-    }
-
-    mSet = true;
-
-    status = pthread_cond_signal(&mCondition);
-    if (status) {
-        LOGE("LocklessCommandFifo: error %i on set condition.", status);
-    }
-
-    status = pthread_mutex_unlock(&mMutex);
-    if (status) {
-        LOGE("LocklessCommandFifo: error %i unlocking for set condition.", status);
-    }
-}
-
-void LocklessCommandFifo::Signal::wait()
-{
-    int status;
-
-    status = pthread_mutex_lock(&mMutex);
-    if (status) {
-        LOGE("LocklessCommandFifo: error %i locking for condition.", status);
-        return;
-    }
-
-    if (!mSet) {
-        status = pthread_cond_wait(&mCondition, &mMutex);
-        if (status) {
-            LOGE("LocklessCommandFifo: error %i waiting on condition.", status);
-        }
-    }
-    mSet = false;
-
-    status = pthread_mutex_unlock(&mMutex);
-    if (status) {
-        LOGE("LocklessCommandFifo: error %i unlocking for condition.", status);
-    }
-}
-
diff --git a/libs/rs/rsLocklessFifo.h b/libs/rs/rsLocklessFifo.h
index d0a4356..ae906ca 100644
--- a/libs/rs/rsLocklessFifo.h
+++ b/libs/rs/rsLocklessFifo.h
@@ -19,8 +19,10 @@
 
 
 #include "rsUtils.h"
+#include "rsSignal.h"
 
 namespace android {
+namespace renderscript {
 
 
 // A simple FIFO to be used as a producer / consumer between two
@@ -37,24 +39,7 @@
     LocklessCommandFifo();
     ~LocklessCommandFifo();
 
-
 protected:
-    class Signal {
-    public:
-        Signal();
-        ~Signal();
-
-        bool init();
-
-        void set();
-        void wait();
-
-    protected:
-        bool mSet;
-        pthread_mutex_t mMutex;
-        pthread_cond_t mCondition;
-    };
-
     uint8_t * volatile mPut;
     uint8_t * volatile mGet;
     uint8_t * mBuffer;
@@ -65,14 +50,14 @@
     Signal mSignalToWorker;
     Signal mSignalToControl;
 
-
-
 public:
     void * reserve(uint32_t bytes);
     void commit(uint32_t command, uint32_t bytes);
     void commitSync(uint32_t command, uint32_t bytes);
 
     void flush();
+    void wait();
+
     const void * get(uint32_t *command, uint32_t *bytesData);
     void next();
 
@@ -88,4 +73,5 @@
 
 
 }
+}
 #endif
diff --git a/libs/rs/rsMutex.cpp b/libs/rs/rsMutex.cpp
new file mode 100644
index 0000000..37752f2
--- /dev/null
+++ b/libs/rs/rsMutex.cpp
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2009 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 "rsMutex.h"
+
+using namespace android;
+using namespace android::renderscript;
+
+
+Mutex::Mutex()
+{
+}
+
+Mutex::~Mutex()
+{
+    pthread_mutex_destroy(&mMutex);
+}
+
+bool Mutex::init()
+{
+    int status = pthread_mutex_init(&mMutex, NULL);
+    if (status) {
+        LOGE("Mutex::Mutex init failure");
+        return false;
+    }
+    return true;
+}
+
+bool Mutex::lock()
+{
+    int status;
+    status = pthread_mutex_lock(&mMutex);
+    if (status) {
+        LOGE("Mutex: error %i locking.", status);
+        return false;
+    }
+    return true;
+}
+
+bool Mutex::unlock()
+{
+    int status;
+    status = pthread_mutex_unlock(&mMutex);
+    if (status) {
+        LOGE("Mutex error %i unlocking.", status);
+        return false;
+    }
+    return true;
+}
+
+
diff --git a/graphics/java/android/renderscript/Vector2f.java b/libs/rs/rsMutex.h
similarity index 69%
copy from graphics/java/android/renderscript/Vector2f.java
copy to libs/rs/rsMutex.h
index 567d57fa..47725d7 100644
--- a/graphics/java/android/renderscript/Vector2f.java
+++ b/libs/rs/rsMutex.h
@@ -14,24 +14,30 @@
  * limitations under the License.
  */
 
-package android.renderscript;
-
-import java.lang.Math;
-import android.util.Log;
+#ifndef ANDROID_RS_MUTEX_H
+#define ANDROID_RS_MUTEX_H
 
 
-/**
- * @hide
- *
- **/
-public class Vector2f {
-    public Vector2f() {
-    }
+#include "rsUtils.h"
 
-    public float x;
-    public float y;
+namespace android {
+namespace renderscript {
+
+class Mutex {
+public:
+    Mutex();
+    ~Mutex();
+
+    bool init();
+    bool lock();
+    bool unlock();
+
+protected:
+    pthread_mutex_t mMutex;
+};
+
+}
 }
 
-
-
+#endif
 
diff --git a/libs/rs/rsSignal.cpp b/libs/rs/rsSignal.cpp
new file mode 100644
index 0000000..9239bfd
--- /dev/null
+++ b/libs/rs/rsSignal.cpp
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2009 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 "rsSignal.h"
+
+using namespace android;
+using namespace android::renderscript;
+
+
+Signal::Signal()
+{
+    mSet = true;
+}
+
+Signal::~Signal()
+{
+    pthread_mutex_destroy(&mMutex);
+    pthread_cond_destroy(&mCondition);
+}
+
+bool Signal::init()
+{
+    int status = pthread_mutex_init(&mMutex, NULL);
+    if (status) {
+        LOGE("LocklessFifo mutex init failure");
+        return false;
+    }
+
+    status = pthread_cond_init(&mCondition, NULL);
+    if (status) {
+        LOGE("LocklessFifo condition init failure");
+        pthread_mutex_destroy(&mMutex);
+        return false;
+    }
+
+    return true;
+}
+
+void Signal::set()
+{
+    int status;
+
+    status = pthread_mutex_lock(&mMutex);
+    if (status) {
+        LOGE("LocklessCommandFifo: error %i locking for set condition.", status);
+        return;
+    }
+
+    mSet = true;
+
+    status = pthread_cond_signal(&mCondition);
+    if (status) {
+        LOGE("LocklessCommandFifo: error %i on set condition.", status);
+    }
+
+    status = pthread_mutex_unlock(&mMutex);
+    if (status) {
+        LOGE("LocklessCommandFifo: error %i unlocking for set condition.", status);
+    }
+}
+
+void Signal::wait()
+{
+    int status;
+
+    status = pthread_mutex_lock(&mMutex);
+    if (status) {
+        LOGE("LocklessCommandFifo: error %i locking for condition.", status);
+        return;
+    }
+
+    if (!mSet) {
+        status = pthread_cond_wait(&mCondition, &mMutex);
+        if (status) {
+            LOGE("LocklessCommandFifo: error %i waiting on condition.", status);
+        }
+    }
+    mSet = false;
+
+    status = pthread_mutex_unlock(&mMutex);
+    if (status) {
+        LOGE("LocklessCommandFifo: error %i unlocking for condition.", status);
+    }
+}
+
diff --git a/graphics/java/android/renderscript/Vector2f.java b/libs/rs/rsSignal.h
similarity index 65%
copy from graphics/java/android/renderscript/Vector2f.java
copy to libs/rs/rsSignal.h
index 567d57fa..2e760f1 100644
--- a/graphics/java/android/renderscript/Vector2f.java
+++ b/libs/rs/rsSignal.h
@@ -14,24 +14,33 @@
  * limitations under the License.
  */
 
-package android.renderscript;
-
-import java.lang.Math;
-import android.util.Log;
+#ifndef ANDROID_RS_SIGNAL_H
+#define ANDROID_RS_SIGNAL_H
 
 
-/**
- * @hide
- *
- **/
-public class Vector2f {
-    public Vector2f() {
-    }
+#include "rsUtils.h"
 
-    public float x;
-    public float y;
+namespace android {
+namespace renderscript {
+
+class Signal {
+public:
+    Signal();
+    ~Signal();
+
+    bool init();
+
+    void set();
+    void wait();
+
+protected:
+    bool mSet;
+    pthread_mutex_t mMutex;
+    pthread_cond_t mCondition;
+};
+
+}
 }
 
-
-
+#endif
 
diff --git a/libs/rs/rsg_ScriptJavaClass.cpp b/libs/rs/rsg_ScriptJavaClass.cpp
index cee9f52..0169b98 100644
--- a/libs/rs/rsg_ScriptJavaClass.cpp
+++ b/libs/rs/rsg_ScriptJavaClass.cpp
@@ -7,8 +7,12 @@
 struct Element;
 
 struct ElementField {
+    // An Element Field is a combination of an Element with a name assigned.
+
     const char *name;
     Element *e;
+
+
     ElementField(const char *n, Element *_e) {
         name = n;
         e = _e;
@@ -20,12 +24,21 @@
 };
 
 struct Element {
+    // An Element can take one of two forms.
+    // 1: Basic.  It contains a single basic type and vector size.
+    // 2: Complex.  It contains a list of fields with names.  Each field
+    // will in turn be another element.
+
     ElementField *fields;
-    size_t fieldCount;
+    size_t fieldCount;  // If field count is 0, the element is a Basic type.
     const char *name;
     bool generated;
 
+    // The basic data type from RenderScript.h
     RsDataType compType;
+
+    // The vector size of the data type for float2, float3, ....
+    // Allowed sizes are 2,3,4,8,16
     uint32_t compVectorSize;
 
     Element() {
diff --git a/libs/rs/scriptc/rs_geom.rsh b/libs/rs/scriptc/rs_geom.rsh
new file mode 100644
index 0000000..6e9e9fc
--- /dev/null
+++ b/libs/rs/scriptc/rs_geom.rsh
@@ -0,0 +1,26 @@
+
+extern float3 __attribute__((overloadable)) cross(float3, float3);
+extern float4 __attribute__((overloadable)) cross(float4, float4);
+
+//extern float __attribute__((overloadable)) dot(float, float);
+extern float __attribute__((overloadable)) dot(float2, float2);
+extern float __attribute__((overloadable)) dot(float3, float3);
+extern float __attribute__((overloadable)) dot(float4, float4);
+
+//extern float __attribute__((overloadable)) distance(float, float);
+extern float __attribute__((overloadable)) distance(float2, float2);
+extern float __attribute__((overloadable)) distance(float3, float3);
+extern float __attribute__((overloadable)) distance(float4, float4);
+
+//extern float __attribute__((overloadable)) length(float);
+extern float __attribute__((overloadable)) length(float2);
+extern float __attribute__((overloadable)) length(float3);
+extern float __attribute__((overloadable)) length(float4);
+
+extern float2 __attribute__((overloadable)) normalize(float2);
+extern float3 __attribute__((overloadable)) normalize(float3);
+extern float4 __attribute__((overloadable)) normalize(float4);
+
+
+
+
diff --git a/libs/rs/scriptc/rs_graphics.rsh b/libs/rs/scriptc/rs_graphics.rsh
index 70cd562..0f03732 100644
--- a/libs/rs/scriptc/rs_graphics.rsh
+++ b/libs/rs/scriptc/rs_graphics.rsh
@@ -1,5 +1,7 @@
 
 
+extern float rand(float max);
+
 extern float2 vec2Rand(float len);
 
 extern float3 float3Norm(float3);
diff --git a/libs/rs/scriptc/rs_math.rsh b/libs/rs/scriptc/rs_math.rsh
index 613c7ca..fba0f8f 100644
--- a/libs/rs/scriptc/rs_math.rsh
+++ b/libs/rs/scriptc/rs_math.rsh
@@ -1,264 +1,262 @@
 // Float ops
 
 extern float __attribute__((overloadable)) abs(float);
-extern float2 __attribute__((overloadable)) abs(float2);
-extern float3 __attribute__((overloadable)) abs(float3);
-extern float4 __attribute__((overloadable)) abs(float4);
-extern float8 __attribute__((overloadable)) abs(float8);
-extern float16 __attribute__((overloadable)) abs(float16);
+//extern float2 __attribute__((overloadable)) abs(float2);
+//extern float3 __attribute__((overloadable)) abs(float3);
+//extern float4 __attribute__((overloadable)) abs(float4);
+//extern float8 __attribute__((overloadable)) abs(float8);
+//extern float16 __attribute__((overloadable)) abs(float16);
 
 extern float __attribute__((overloadable)) acos(float);
-extern float2 __attribute__((overloadable)) acos(float2);
-extern float3 __attribute__((overloadable)) acos(float3);
-extern float4 __attribute__((overloadable)) acos(float4);
-extern float8 __attribute__((overloadable)) acos(float8);
-extern float16 __attribute__((overloadable)) acos(float16);
+//extern float2 __attribute__((overloadable)) acos(float2);
+//extern float3 __attribute__((overloadable)) acos(float3);
+//extern float4 __attribute__((overloadable)) acos(float4);
+//extern float8 __attribute__((overloadable)) acos(float8);
+//extern float16 __attribute__((overloadable)) acos(float16);
 
 extern float __attribute__((overloadable)) asin(float);
-extern float2 __attribute__((overloadable)) asin(float2);
-extern float3 __attribute__((overloadable)) asin(float3);
-extern float4 __attribute__((overloadable)) asin(float4);
-extern float8 __attribute__((overloadable)) asin(float8);
-extern float16 __attribute__((overloadable)) asin(float16);
+//extern float2 __attribute__((overloadable)) asin(float2);
+//extern float3 __attribute__((overloadable)) asin(float3);
+//extern float4 __attribute__((overloadable)) asin(float4);
+//extern float8 __attribute__((overloadable)) asin(float8);
+//extern float16 __attribute__((overloadable)) asin(float16);
 
 extern float __attribute__((overloadable)) atan(float);
-extern float2 __attribute__((overloadable)) atan(float2);
-extern float3 __attribute__((overloadable)) atan(float3);
-extern float4 __attribute__((overloadable)) atan(float4);
-extern float8 __attribute__((overloadable)) atan(float8);
-extern float16 __attribute__((overloadable)) atan(float16);
+//extern float2 __attribute__((overloadable)) atan(float2);
+//extern float3 __attribute__((overloadable)) atan(float3);
+//extern float4 __attribute__((overloadable)) atan(float4);
+//extern float8 __attribute__((overloadable)) atan(float8);
+//extern float16 __attribute__((overloadable)) atan(float16);
 
 extern float __attribute__((overloadable)) atan2(float, float);
-extern float2 __attribute__((overloadable)) atan2(float2, float2);
-extern float3 __attribute__((overloadable)) atan2(float3, float3);
-extern float4 __attribute__((overloadable)) atan2(float4, float4);
-extern float8 __attribute__((overloadable)) atan2(float8, float8);
-extern float16 __attribute__((overloadable)) atan2(float16, float16);
+//extern float2 __attribute__((overloadable)) atan2(float2, float2);
+//extern float3 __attribute__((overloadable)) atan2(float3, float3);
+//extern float4 __attribute__((overloadable)) atan2(float4, float4);
+//extern float8 __attribute__((overloadable)) atan2(float8, float8);
+//extern float16 __attribute__((overloadable)) atan2(float16, float16);
 
 extern float __attribute__((overloadable)) ceil(float);
-extern float2 __attribute__((overloadable)) ceil(float2);
-extern float3 __attribute__((overloadable)) ceil(float3);
-extern float4 __attribute__((overloadable)) ceil(float4);
-extern float8 __attribute__((overloadable)) ceil(float8);
-extern float16 __attribute__((overloadable)) ceil(float16);
+//extern float2 __attribute__((overloadable)) ceil(float2);
+//extern float3 __attribute__((overloadable)) ceil(float3);
+//extern float4 __attribute__((overloadable)) ceil(float4);
+//extern float8 __attribute__((overloadable)) ceil(float8);
+//extern float16 __attribute__((overloadable)) ceil(float16);
 
 extern float __attribute__((overloadable)) clamp(float, float, float);
-extern float2 __attribute__((overloadable)) clamp(float2, float2, float2);
-extern float3 __attribute__((overloadable)) clamp(float3, float3, float3);
-extern float4 __attribute__((overloadable)) clamp(float4, float4, float4);
-extern float8 __attribute__((overloadable)) clamp(float8, float8, float8);
-extern float16 __attribute__((overloadable)) clamp(float16, float16, float16);
-extern float __attribute__((overloadable)) clamp(float, float, float);
-extern float2 __attribute__((overloadable)) clamp(float2, float, float);
-extern float3 __attribute__((overloadable)) clamp(float3, float, float);
-extern float4 __attribute__((overloadable)) clamp(float4, float, float);
-extern float8 __attribute__((overloadable)) clamp(float8, float, float);
-extern float16 __attribute__((overloadable)) clamp(float16, float, float);
+//extern float2 __attribute__((overloadable)) clamp(float2, float2, float2);
+//extern float3 __attribute__((overloadable)) clamp(float3, float3, float3);
+//extern float4 __attribute__((overloadable)) clamp(float4, float4, float4);
+//extern float8 __attribute__((overloadable)) clamp(float8, float8, float8);
+//extern float16 __attribute__((overloadable)) clamp(float16, float16, float16);
+//extern float2 __attribute__((overloadable)) clamp(float2, float, float);
+//extern float3 __attribute__((overloadable)) clamp(float3, float, float);
+//extern float4 __attribute__((overloadable)) clamp(float4, float, float);
+//extern float8 __attribute__((overloadable)) clamp(float8, float, float);
+//extern float16 __attribute__((overloadable)) clamp(float16, float, float);
 
 extern float __attribute__((overloadable)) copysign(float, float);
-extern float2 __attribute__((overloadable)) copysign(float2, float2);
-extern float3 __attribute__((overloadable)) copysign(float3, float3);
-extern float4 __attribute__((overloadable)) copysign(float4, float4);
-extern float8 __attribute__((overloadable)) copysign(float8, float8);
-extern float16 __attribute__((overloadable)) copysign(float16, float16);
+//extern float2 __attribute__((overloadable)) copysign(float2, float2);
+//extern float3 __attribute__((overloadable)) copysign(float3, float3);
+//extern float4 __attribute__((overloadable)) copysign(float4, float4);
+//extern float8 __attribute__((overloadable)) copysign(float8, float8);
+//extern float16 __attribute__((overloadable)) copysign(float16, float16);
 
 extern float __attribute__((overloadable)) cos(float);
-extern float2 __attribute__((overloadable)) cos(float2);
-extern float3 __attribute__((overloadable)) cos(float3);
-extern float4 __attribute__((overloadable)) cos(float4);
-extern float8 __attribute__((overloadable)) cos(float8);
-extern float16 __attribute__((overloadable)) cos(float16);
+//extern float2 __attribute__((overloadable)) cos(float2);
+//extern float3 __attribute__((overloadable)) cos(float3);
+//extern float4 __attribute__((overloadable)) cos(float4);
+//extern float8 __attribute__((overloadable)) cos(float8);
+//extern float16 __attribute__((overloadable)) cos(float16);
 
 extern float __attribute__((overloadable)) degrees(float);
-extern float2 __attribute__((overloadable)) degrees(float2);
-extern float3 __attribute__((overloadable)) degrees(float3);
-extern float4 __attribute__((overloadable)) degrees(float4);
-extern float8 __attribute__((overloadable)) degrees(float8);
-extern float16 __attribute__((overloadable)) degrees(float16);
+//extern float2 __attribute__((overloadable)) degrees(float2);
+//extern float3 __attribute__((overloadable)) degrees(float3);
+//extern float4 __attribute__((overloadable)) degrees(float4);
+//extern float8 __attribute__((overloadable)) degrees(float8);
+//extern float16 __attribute__((overloadable)) degrees(float16);
 
 extern float __attribute__((overloadable)) exp(float);
-extern float2 __attribute__((overloadable)) exp(float2);
-extern float3 __attribute__((overloadable)) exp(float3);
-extern float4 __attribute__((overloadable)) exp(float4);
-extern float8 __attribute__((overloadable)) exp(float8);
-extern float16 __attribute__((overloadable)) exp(float16);
+//extern float2 __attribute__((overloadable)) exp(float2);
+//extern float3 __attribute__((overloadable)) exp(float3);
+//extern float4 __attribute__((overloadable)) exp(float4);
+//extern float8 __attribute__((overloadable)) exp(float8);
+//extern float16 __attribute__((overloadable)) exp(float16);
 
 extern float __attribute__((overloadable)) exp2(float);
-extern float2 __attribute__((overloadable)) exp2(float2);
-extern float3 __attribute__((overloadable)) exp2(float3);
-extern float4 __attribute__((overloadable)) exp2(float4);
-extern float8 __attribute__((overloadable)) exp2(float8);
-extern float16 __attribute__((overloadable)) exp2(float16);
+//extern float2 __attribute__((overloadable)) exp2(float2);
+//extern float3 __attribute__((overloadable)) exp2(float3);
+//extern float4 __attribute__((overloadable)) exp2(float4);
+//extern float8 __attribute__((overloadable)) exp2(float8);
+//extern float16 __attribute__((overloadable)) exp2(float16);
 
 extern float __attribute__((overloadable)) exp10(float);
-extern float2 __attribute__((overloadable)) exp10(float2);
-extern float3 __attribute__((overloadable)) exp10(float3);
-extern float4 __attribute__((overloadable)) exp10(float4);
-extern float8 __attribute__((overloadable)) exp10(float8);
-extern float16 __attribute__((overloadable)) exp10(float16);
+//extern float2 __attribute__((overloadable)) exp10(float2);
+//extern float3 __attribute__((overloadable)) exp10(float3);
+//extern float4 __attribute__((overloadable)) exp10(float4);
+//extern float8 __attribute__((overloadable)) exp10(float8);
+//extern float16 __attribute__((overloadable)) exp10(float16);
 
 extern float __attribute__((overloadable)) fabs(float);
-extern float2 __attribute__((overloadable)) fabs(float2);
-extern float3 __attribute__((overloadable)) fabs(float3);
-extern float4 __attribute__((overloadable)) fabs(float4);
-extern float8 __attribute__((overloadable)) fabs(float8);
-extern float16 __attribute__((overloadable)) fabs(float16);
+//extern float2 __attribute__((overloadable)) fabs(float2);
+//extern float3 __attribute__((overloadable)) fabs(float3);
+//extern float4 __attribute__((overloadable)) fabs(float4);
+//extern float8 __attribute__((overloadable)) fabs(float8);
+//extern float16 __attribute__((overloadable)) fabs(float16);
 
 extern float __attribute__((overloadable)) floor(float);
-extern float2 __attribute__((overloadable)) floor(float2);
-extern float3 __attribute__((overloadable)) floor(float3);
-extern float4 __attribute__((overloadable)) floor(float4);
-extern float8 __attribute__((overloadable)) floor(float8);
-extern float16 __attribute__((overloadable)) floor(float16);
+//extern float2 __attribute__((overloadable)) floor(float2);
+//extern float3 __attribute__((overloadable)) floor(float3);
+//extern float4 __attribute__((overloadable)) floor(float4);
+//extern float8 __attribute__((overloadable)) floor(float8);
+//extern float16 __attribute__((overloadable)) floor(float16);
 
 extern float __attribute__((overloadable)) fmax(float, float);
-extern float2 __attribute__((overloadable)) fmax(float2, float2);
-extern float3 __attribute__((overloadable)) fmax(float3, float3);
-extern float4 __attribute__((overloadable)) fmax(float4, float4);
-extern float8 __attribute__((overloadable)) fmax(float8, float8);
-extern float16 __attribute__((overloadable)) fmax(float16, float16);
-extern float2 __attribute__((overloadable)) fmax(float2, float);
-extern float3 __attribute__((overloadable)) fmax(float3, float);
-extern float4 __attribute__((overloadable)) fmax(float4, float);
-extern float8 __attribute__((overloadable)) fmax(float8, float);
-extern float16 __attribute__((overloadable)) fmax(float16, float);
+//extern float2 __attribute__((overloadable)) fmax(float2, float2);
+//extern float3 __attribute__((overloadable)) fmax(float3, float3);
+//extern float4 __attribute__((overloadable)) fmax(float4, float4);
+//extern float8 __attribute__((overloadable)) fmax(float8, float8);
+//extern float16 __attribute__((overloadable)) fmax(float16, float16);
+//extern float2 __attribute__((overloadable)) fmax(float2, float);
+//extern float3 __attribute__((overloadable)) fmax(float3, float);
+//extern float4 __attribute__((overloadable)) fmax(float4, float);
+//extern float8 __attribute__((overloadable)) fmax(float8, float);
+//extern float16 __attribute__((overloadable)) fmax(float16, float);
 
 extern float __attribute__((overloadable)) fmin(float, float);
-extern float2 __attribute__((overloadable)) fmin(float2, float2);
-extern float3 __attribute__((overloadable)) fmin(float3, float3);
-extern float4 __attribute__((overloadable)) fmin(float4, float4);
-extern float8 __attribute__((overloadable)) fmin(float8, float8);
-extern float16 __attribute__((overloadable)) fmin(float16, float16);
-extern float2 __attribute__((overloadable)) fmin(float2, float);
-extern float3 __attribute__((overloadable)) fmin(float3, float);
-extern float4 __attribute__((overloadable)) fmin(float4, float);
-extern float8 __attribute__((overloadable)) fmin(float8, float);
-extern float16 __attribute__((overloadable)) fmin(float16, float);
+//extern float2 __attribute__((overloadable)) fmin(float2, float2);
+//extern float3 __attribute__((overloadable)) fmin(float3, float3);
+//extern float4 __attribute__((overloadable)) fmin(float4, float4);
+//extern float8 __attribute__((overloadable)) fmin(float8, float8);
+//extern float16 __attribute__((overloadable)) fmin(float16, float16);
+//extern float2 __attribute__((overloadable)) fmin(float2, float);
+//extern float3 __attribute__((overloadable)) fmin(float3, float);
+//extern float4 __attribute__((overloadable)) fmin(float4, float);
+//extern float8 __attribute__((overloadable)) fmin(float8, float);
+//extern float16 __attribute__((overloadable)) fmin(float16, float);
 
 extern float __attribute__((overloadable)) fmod(float, float);
-extern float2 __attribute__((overloadable)) fmod(float2, float2);
-extern float3 __attribute__((overloadable)) fmod(float3, float3);
-extern float4 __attribute__((overloadable)) fmod(float4, float4);
-extern float8 __attribute__((overloadable)) fmod(float8, float8);
-extern float16 __attribute__((overloadable)) fmod(float16, float16);
+//extern float2 __attribute__((overloadable)) fmod(float2, float2);
+//extern float3 __attribute__((overloadable)) fmod(float3, float3);
+//extern float4 __attribute__((overloadable)) fmod(float4, float4);
+//extern float8 __attribute__((overloadable)) fmod(float8, float8);
+//extern float16 __attribute__((overloadable)) fmod(float16, float16);
 
 extern float __attribute__((overloadable)) log(float);
-extern float2 __attribute__((overloadable)) log(float2);
-extern float3 __attribute__((overloadable)) log(float3);
-extern float4 __attribute__((overloadable)) log(float4);
-extern float8 __attribute__((overloadable)) log(float8);
-extern float16 __attribute__((overloadable)) log(float16);
+//extern float2 __attribute__((overloadable)) log(float2);
+//extern float3 __attribute__((overloadable)) log(float3);
+//extern float4 __attribute__((overloadable)) log(float4);
+//extern float8 __attribute__((overloadable)) log(float8);
+//extern float16 __attribute__((overloadable)) log(float16);
 
 extern float __attribute__((overloadable)) log2(float);
-extern float2 __attribute__((overloadable)) log2(float2);
-extern float3 __attribute__((overloadable)) log2(float3);
-extern float4 __attribute__((overloadable)) log2(float4);
-extern float8 __attribute__((overloadable)) log2(float8);
-extern float16 __attribute__((overloadable)) log2(float16);
+//extern float2 __attribute__((overloadable)) log2(float2);
+//extern float3 __attribute__((overloadable)) log2(float3);
+//extern float4 __attribute__((overloadable)) log2(float4);
+//extern float8 __attribute__((overloadable)) log2(float8);
+//extern float16 __attribute__((overloadable)) log2(float16);
 
 extern float __attribute__((overloadable)) log10(float);
-extern float2 __attribute__((overloadable)) log10(float2);
-extern float3 __attribute__((overloadable)) log10(float3);
-extern float4 __attribute__((overloadable)) log10(float4);
-extern float8 __attribute__((overloadable)) log10(float8);
-extern float16 __attribute__((overloadable)) log10(float16);
+//extern float2 __attribute__((overloadable)) log10(float2);
+//extern float3 __attribute__((overloadable)) log10(float3);
+//extern float4 __attribute__((overloadable)) log10(float4);
+//extern float8 __attribute__((overloadable)) log10(float8);
+//extern float16 __attribute__((overloadable)) log10(float16);
 
 extern float __attribute__((overloadable)) max(float, float);
-extern float2 __attribute__((overloadable)) max(float2, float2);
-extern float3 __attribute__((overloadable)) max(float3, float3);
-extern float4 __attribute__((overloadable)) max(float4, float4);
-extern float8 __attribute__((overloadable)) max(float8, float8);
-extern float16 __attribute__((overloadable)) max(float16, float16);
+//extern float2 __attribute__((overloadable)) max(float2, float2);
+//extern float3 __attribute__((overloadable)) max(float3, float3);
+//extern float4 __attribute__((overloadable)) max(float4, float4);
+//extern float8 __attribute__((overloadable)) max(float8, float8);
+//extern float16 __attribute__((overloadable)) max(float16, float16);
 
 extern float __attribute__((overloadable)) min(float, float);
-extern float2 __attribute__((overloadable)) min(float2, float2);
-extern float3 __attribute__((overloadable)) min(float3, float3);
-extern float4 __attribute__((overloadable)) min(float4, float4);
-extern float8 __attribute__((overloadable)) min(float8, float8);
-extern float16 __attribute__((overloadable)) min(float16, float16);
+//extern float2 __attribute__((overloadable)) min(float2, float2);
+//extern float3 __attribute__((overloadable)) min(float3, float3);
+//extern float4 __attribute__((overloadable)) min(float4, float4);
+//extern float8 __attribute__((overloadable)) min(float8, float8);
+//extern float16 __attribute__((overloadable)) min(float16, float16);
 
 extern float __attribute__((overloadable)) mix(float, float, float);
-extern float2 __attribute__((overloadable)) mix(float2, float2, float2);
-extern float3 __attribute__((overloadable)) mix(float3, float3, float3);
-extern float4 __attribute__((overloadable)) mix(float4, float4, float4);
-extern float8 __attribute__((overloadable)) mix(float8, float8, float8);
-extern float16 __attribute__((overloadable)) mix(float16, float16, float16);
-extern float __attribute__((overloadable)) mix(float, float, float);
-extern float2 __attribute__((overloadable)) mix(float2, float2, float);
-extern float3 __attribute__((overloadable)) mix(float3, float3, float);
-extern float4 __attribute__((overloadable)) mix(float4, float4, float);
-extern float8 __attribute__((overloadable)) mix(float8, float8, float);
-extern float16 __attribute__((overloadable)) mix(float16, float16, float);
+//extern float2 __attribute__((overloadable)) mix(float2, float2, float2);
+//extern float3 __attribute__((overloadable)) mix(float3, float3, float3);
+//extern float4 __attribute__((overloadable)) mix(float4, float4, float4);
+//extern float8 __attribute__((overloadable)) mix(float8, float8, float8);
+//extern float16 __attribute__((overloadable)) mix(float16, float16, float16);
+//extern float2 __attribute__((overloadable)) mix(float2, float2, float);
+//extern float3 __attribute__((overloadable)) mix(float3, float3, float);
+//extern float4 __attribute__((overloadable)) mix(float4, float4, float);
+//extern float8 __attribute__((overloadable)) mix(float8, float8, float);
+//extern float16 __attribute__((overloadable)) mix(float16, float16, float);
 
 extern float __attribute__((overloadable)) pow(float, float);
-extern float2 __attribute__((overloadable)) pow(float2, float2);
-extern float3 __attribute__((overloadable)) pow(float3, float3);
-extern float4 __attribute__((overloadable)) pow(float4, float4);
-extern float8 __attribute__((overloadable)) pow(float8, float8);
-extern float16 __attribute__((overloadable)) pow(float16, float16);
+//extern float2 __attribute__((overloadable)) pow(float2, float2);
+//extern float3 __attribute__((overloadable)) pow(float3, float3);
+//extern float4 __attribute__((overloadable)) pow(float4, float4);
+//extern float8 __attribute__((overloadable)) pow(float8, float8);
+//extern float16 __attribute__((overloadable)) pow(float16, float16);
 
 extern float __attribute__((overloadable)) radians(float);
-extern float2 __attribute__((overloadable)) radians(float2);
-extern float3 __attribute__((overloadable)) radians(float3);
-extern float4 __attribute__((overloadable)) radians(float4);
-extern float8 __attribute__((overloadable)) radians(float8);
-extern float16 __attribute__((overloadable)) radians(float16);
+//extern float2 __attribute__((overloadable)) radians(float2);
+//extern float3 __attribute__((overloadable)) radians(float3);
+//extern float4 __attribute__((overloadable)) radians(float4);
+//extern float8 __attribute__((overloadable)) radians(float8);
+//extern float16 __attribute__((overloadable)) radians(float16);
 
 extern float __attribute__((overloadable)) rint(float);
-extern float2 __attribute__((overloadable)) rint(float2);
-extern float3 __attribute__((overloadable)) rint(float3);
-extern float4 __attribute__((overloadable)) rint(float4);
-extern float8 __attribute__((overloadable)) rint(float8);
-extern float16 __attribute__((overloadable)) rint(float16);
+//extern float2 __attribute__((overloadable)) rint(float2);
+//extern float3 __attribute__((overloadable)) rint(float3);
+//extern float4 __attribute__((overloadable)) rint(float4);
+//extern float8 __attribute__((overloadable)) rint(float8);
+//extern float16 __attribute__((overloadable)) rint(float16);
 
 extern float __attribute__((overloadable)) round(float);
-extern float2 __attribute__((overloadable)) round(float2);
-extern float3 __attribute__((overloadable)) round(float3);
-extern float4 __attribute__((overloadable)) round(float4);
-extern float8 __attribute__((overloadable)) round(float8);
-extern float16 __attribute__((overloadable)) round(float16);
+//extern float2 __attribute__((overloadable)) round(float2);
+//extern float3 __attribute__((overloadable)) round(float3);
+//extern float4 __attribute__((overloadable)) round(float4);
+//extern float8 __attribute__((overloadable)) round(float8);
+//extern float16 __attribute__((overloadable)) round(float16);
 
 extern float __attribute__((overloadable)) rsqrt(float);
-extern float2 __attribute__((overloadable)) rsqrt(float2);
-extern float3 __attribute__((overloadable)) rsqrt(float3);
-extern float4 __attribute__((overloadable)) rsqrt(float4);
-extern float8 __attribute__((overloadable)) rsqrt(float8);
-extern float16 __attribute__((overloadable)) rsqrt(float16);
+//extern float2 __attribute__((overloadable)) rsqrt(float2);
+//extern float3 __attribute__((overloadable)) rsqrt(float3);
+//extern float4 __attribute__((overloadable)) rsqrt(float4);
+//extern float8 __attribute__((overloadable)) rsqrt(float8);
+//extern float16 __attribute__((overloadable)) rsqrt(float16);
 
 extern float __attribute__((overloadable)) sign(float);
-extern float2 __attribute__((overloadable)) sign(float2);
-extern float3 __attribute__((overloadable)) sign(float3);
-extern float4 __attribute__((overloadable)) sign(float4);
-extern float8 __attribute__((overloadable)) sign(float8);
-extern float16 __attribute__((overloadable)) sign(float16);
+//extern float2 __attribute__((overloadable)) sign(float2);
+//extern float3 __attribute__((overloadable)) sign(float3);
+//extern float4 __attribute__((overloadable)) sign(float4);
+//extern float8 __attribute__((overloadable)) sign(float8);
+//extern float16 __attribute__((overloadable)) sign(float16);
 
 extern float __attribute__((overloadable)) sin(float);
-extern float2 __attribute__((overloadable)) sin(float2);
-extern float3 __attribute__((overloadable)) sin(float3);
-extern float4 __attribute__((overloadable)) sin(float4);
-extern float8 __attribute__((overloadable)) sin(float8);
-extern float16 __attribute__((overloadable)) sin(float16);
+//extern float2 __attribute__((overloadable)) sin(float2);
+//extern float3 __attribute__((overloadable)) sin(float3);
+//extern float4 __attribute__((overloadable)) sin(float4);
+//extern float8 __attribute__((overloadable)) sin(float8);
+//extern float16 __attribute__((overloadable)) sin(float16);
 
 extern float __attribute__((overloadable)) sqrt(float);
-extern float2 __attribute__((overloadable)) sqrt(float2);
-extern float3 __attribute__((overloadable)) sqrt(float3);
-extern float4 __attribute__((overloadable)) sqrt(float4);
-extern float8 __attribute__((overloadable)) sqrt(float8);
-extern float16 __attribute__((overloadable)) sqrt(float16);
+//extern float2 __attribute__((overloadable)) sqrt(float2);
+//extern float3 __attribute__((overloadable)) sqrt(float3);
+//extern float4 __attribute__((overloadable)) sqrt(float4);
+//extern float8 __attribute__((overloadable)) sqrt(float8);
+//extern float16 __attribute__((overloadable)) sqrt(float16);
 
 extern float __attribute__((overloadable)) tan(float);
-extern float2 __attribute__((overloadable)) tan(float2);
-extern float3 __attribute__((overloadable)) tan(float3);
-extern float4 __attribute__((overloadable)) tan(float4);
-extern float8 __attribute__((overloadable)) tan(float8);
-extern float16 __attribute__((overloadable)) tan(float16);
+//extern float2 __attribute__((overloadable)) tan(float2);
+//extern float3 __attribute__((overloadable)) tan(float3);
+//extern float4 __attribute__((overloadable)) tan(float4);
+//extern float8 __attribute__((overloadable)) tan(float8);
+//extern float16 __attribute__((overloadable)) tan(float16);
 
 extern float __attribute__((overloadable)) trunc(float);
-extern float2 __attribute__((overloadable)) trunc(float2);
-extern float3 __attribute__((overloadable)) trunc(float3);
-extern float4 __attribute__((overloadable)) trunc(float4);
-extern float8 __attribute__((overloadable)) trunc(float8);
-extern float16 __attribute__((overloadable)) trunc(float16);
+//extern float2 __attribute__((overloadable)) trunc(float2);
+//extern float3 __attribute__((overloadable)) trunc(float3);
+//extern float4 __attribute__((overloadable)) trunc(float4);
+//extern float8 __attribute__((overloadable)) trunc(float8);
+//extern float16 __attribute__((overloadable)) trunc(float16);
 
 
 
@@ -268,11 +266,11 @@
 // Int ops
 
 extern int __attribute__((overloadable)) abs(int);
-extern int2 __attribute__((overloadable)) abs(int2);
-extern int3 __attribute__((overloadable)) abs(int3);
-extern int4 __attribute__((overloadable)) abs(int4);
-extern int8 __attribute__((overloadable)) abs(int8);
-extern int16 __attribute__((overloadable)) abs(int16);
+//extern int2 __attribute__((overloadable)) abs(int2);
+//extern int3 __attribute__((overloadable)) abs(int3);
+//extern int4 __attribute__((overloadable)) abs(int4);
+//extern int8 __attribute__((overloadable)) abs(int8);
+//extern int16 __attribute__((overloadable)) abs(int16);
 
 
 
diff --git a/libs/rs/scriptc/rs_types.rsh b/libs/rs/scriptc/rs_types.rsh
index 4198a74..b710146 100644
--- a/libs/rs/scriptc/rs_types.rsh
+++ b/libs/rs/scriptc/rs_types.rsh
@@ -68,4 +68,29 @@
 typedef int int16 __attribute__((ext_vector_type(16)));
 
 
+// RS_KIND_POSITION
+typedef float rs_position1;
+typedef float2 rs_position2;
+typedef float3 rs_position3;
+typedef float4 rs_position4;
+
+// RS_KIND_COLOR
+typedef float3 rs_color3f;
+typedef float4 rs_color4f;
+typedef uchar4 rs_color4u;
+
+// RS_KIND_NORMAL
+typedef float3 rs_normal;
+
+// RS_KIND_POINT_SIZE
+typedef float rs_point_size;
+
+// RS_KIND_TEXTURE
+typedef float rs_texture_coord1;
+typedef float2 rs_texture_coord2;
+typedef float3 rs_texture_coord3;
+typedef float4 rs_texture_coord4;
+
+// RS_KIND_INDEX
+typedef ushort rs_index;
 
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 47e2da2..2e3eae0 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -1523,4 +1523,22 @@
       * {@hide}
       */
      private IBinder mICallBack = new Binder();
+
+    /**
+     * Checks whether the phone is in silent mode, with or without vibrate.
+     *
+     * @return true if phone is in silent mode, with or without vibrate.
+     *
+     * @see #getRingerMode()
+     *
+     * @hide pending API Council approval
+     */
+    public boolean isSilentMode() {
+        int ringerMode = getRingerMode();
+        boolean silentMode =
+            (ringerMode == RINGER_MODE_SILENT) ||
+            (ringerMode == RINGER_MODE_VIBRATE);
+        return silentMode;
+    }
+
 }
diff --git a/opengl/tests/testViewport/Android.mk b/opengl/tests/testViewport/Android.mk
new file mode 100644
index 0000000..ab37809
--- /dev/null
+++ b/opengl/tests/testViewport/Android.mk
@@ -0,0 +1,26 @@
+#########################################################################
+# OpenGL ES JNI sample
+# This makefile builds both an activity and a shared library.
+#########################################################################
+ifneq ($(TARGET_SIMULATOR),true) # not 64 bit clean
+
+TOP_LOCAL_PATH:= $(call my-dir)
+
+# Build activity
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_PACKAGE_NAME := TestViewport
+
+# Set a specific SDK version so we can run on Froyo.
+
+LOCAL_SDK_VERSION := 8
+
+include $(BUILD_PACKAGE)
+
+endif # TARGET_SIMULATOR
diff --git a/opengl/tests/testViewport/AndroidManifest.xml b/opengl/tests/testViewport/AndroidManifest.xml
new file mode 100644
index 0000000..90a9d2d
--- /dev/null
+++ b/opengl/tests/testViewport/AndroidManifest.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2009, 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.
+*/
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.test">
+    <uses-sdk android:targetSdkVersion="8" android:minSdkVersion="8" />
+    <uses-permission android:name="android.permission.INTERNET" />
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+    <application
+            android:label="@string/test_activity">
+        <activity android:name="TestActivity"
+                android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
+            	android:configChanges="orientation|keyboardHidden">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
diff --git a/opengl/tests/testViewport/README b/opengl/tests/testViewport/README
new file mode 100644
index 0000000..c06abc9
--- /dev/null
+++ b/opengl/tests/testViewport/README
@@ -0,0 +1,28 @@
+Repro steps:
+
+build, install and run the attached test program TestViewport.apk
+
+Run on Sapphire with Froyo.
+
+The program clears the screen to blue, then draws a full screen white quad that
+is alligned to the screen.
+(Therefore the whole screen should appear to be white.)
+
+
+Note that screen is all white.
+
+Rotate screen 90 degrees.
+
+Expected: screen is still all white.
+
+Actual: screen is blue with offset white rectangle.
+
+This bug only happens on Sapphire, it works correctly on Passion.
+
+What happens:
+
+I think the bug is that the gl.glViewport() call in onSurfaceChanged() is
+being ignored by the OpenGL driver.
+
+NOTE: If a gl.glViewport call is added at the beginning of the onDrawFrame()
+call (which means it is called before every draw), the program runs correctly.
diff --git a/opengl/tests/testViewport/res/values/strings.xml b/opengl/tests/testViewport/res/values/strings.xml
new file mode 100644
index 0000000..f4b8bbb
--- /dev/null
+++ b/opengl/tests/testViewport/res/values/strings.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2006, 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.
+*/
+-->
+
+<!-- This file contains resource definitions for displayed strings, allowing
+     them to be changed based on the locale and options. -->
+
+<resources>
+    <!-- Simple strings. -->
+    <string name="test_activity">Test Viewport</string>
+
+</resources>
+
diff --git a/opengl/tests/testViewport/src/com/android/test/TestActivity.java b/opengl/tests/testViewport/src/com/android/test/TestActivity.java
new file mode 100644
index 0000000..cc7e450
--- /dev/null
+++ b/opengl/tests/testViewport/src/com/android/test/TestActivity.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.test;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.util.Log;
+
+public class TestActivity extends Activity {
+    private final static String TAG = "TestActivity";
+    TestView mView;
+
+    @Override
+    protected void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        mView = new TestView(getApplication());
+	    mView.setFocusableInTouchMode(true);
+	    setContentView(mView);
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+        mView.onPause();
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        mView.onResume();
+    }
+}
diff --git a/opengl/tests/testViewport/src/com/android/test/TestView.java b/opengl/tests/testViewport/src/com/android/test/TestView.java
new file mode 100644
index 0000000..23cc37d
--- /dev/null
+++ b/opengl/tests/testViewport/src/com/android/test/TestView.java
@@ -0,0 +1,262 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.test;
+/*
+ * Copyright (C) 2008 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.
+ */
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.CharBuffer;
+import java.nio.FloatBuffer;
+
+import android.content.Context;
+import android.opengl.GLSurfaceView;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+
+import javax.microedition.khronos.egl.EGL10;
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.opengles.GL;
+import javax.microedition.khronos.opengles.GL10;
+import javax.microedition.khronos.opengles.GL11;
+/**
+ * An implementation of SurfaceView that uses the dedicated surface for
+ * displaying an OpenGL animation.  This allows the animation to run in a
+ * separate thread, without requiring that it be driven by the update mechanism
+ * of the view hierarchy.
+ *
+ * The application-specific rendering code is delegated to a GLView.Renderer
+ * instance.
+ */
+class TestView extends GLSurfaceView {
+    TestView(Context context) {
+        super(context);
+        init();
+    }
+
+    public TestView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        init();
+    }
+
+    private void init() {
+        setRenderer(new Renderer());
+        setRenderMode(RENDERMODE_WHEN_DIRTY);
+    }
+    
+        /** A grid is a topologically rectangular array of vertices.
+    *
+    * The vertex and index data are held in VBO objects because on most
+    * GPUs VBO objects are the fastest way of rendering static vertex
+    * and index data.
+    *
+    */
+
+   private static class Grid {
+       // Size of vertex data elements in bytes:
+       final static int FLOAT_SIZE = 4;
+       final static int CHAR_SIZE = 2;
+
+       // Vertex structure:
+       // float x, y, z;
+
+       final static int VERTEX_SIZE = 3 * FLOAT_SIZE;
+
+       private int mVertexBufferObjectId;
+       private int mElementBufferObjectId;
+
+       // These buffers are used to hold the vertex and index data while
+       // constructing the grid. Once createBufferObjects() is called
+       // the buffers are nulled out to save memory.
+
+       private ByteBuffer mVertexByteBuffer;
+       private FloatBuffer mVertexBuffer;
+       private CharBuffer mIndexBuffer;
+
+       private int mW;
+       private int mH;
+       private int mIndexCount;
+
+       public Grid(int w, int h) {
+           if (w < 0 || w >= 65536) {
+               throw new IllegalArgumentException("w");
+           }
+           if (h < 0 || h >= 65536) {
+               throw new IllegalArgumentException("h");
+           }
+           if (w * h >= 65536) {
+               throw new IllegalArgumentException("w * h >= 65536");
+           }
+
+           mW = w;
+           mH = h;
+           int size = w * h;
+
+           mVertexByteBuffer = ByteBuffer.allocateDirect(VERTEX_SIZE * size)
+               .order(ByteOrder.nativeOrder());
+           mVertexBuffer = mVertexByteBuffer.asFloatBuffer();
+
+           int quadW = mW - 1;
+           int quadH = mH - 1;
+           int quadCount = quadW * quadH;
+           int indexCount = quadCount * 6;
+           mIndexCount = indexCount;
+           mIndexBuffer = ByteBuffer.allocateDirect(CHAR_SIZE * indexCount)
+               .order(ByteOrder.nativeOrder()).asCharBuffer();
+
+           /*
+            * Initialize triangle list mesh.
+            *
+            *     [0]-----[  1] ...
+            *      |    /   |
+            *      |   /    |
+            *      |  /     |
+            *     [w]-----[w+1] ...
+            *      |       |
+            *
+            */
+
+           {
+               int i = 0;
+               for (int y = 0; y < quadH; y++) {
+                   for (int x = 0; x < quadW; x++) {
+                       char a = (char) (y * mW + x);
+                       char b = (char) (y * mW + x + 1);
+                       char c = (char) ((y + 1) * mW + x);
+                       char d = (char) ((y + 1) * mW + x + 1);
+
+                       mIndexBuffer.put(i++, a);
+                       mIndexBuffer.put(i++, c);
+                       mIndexBuffer.put(i++, b);
+
+                       mIndexBuffer.put(i++, b);
+                       mIndexBuffer.put(i++, c);
+                       mIndexBuffer.put(i++, d);
+                   }
+               }
+           }
+
+       }
+
+       public void set(int i, int j, float x, float y, float z) {
+           if (i < 0 || i >= mW) {
+               throw new IllegalArgumentException("i");
+           }
+           if (j < 0 || j >= mH) {
+               throw new IllegalArgumentException("j");
+           }
+
+           int index = mW * j + i;
+
+           mVertexBuffer.position(index * VERTEX_SIZE / FLOAT_SIZE);
+           mVertexBuffer.put(x);
+           mVertexBuffer.put(y);
+           mVertexBuffer.put(z);
+       }
+
+       public void createBufferObjects(GL gl) {
+           // Generate a the vertex and element buffer IDs
+           int[] vboIds = new int[2];
+           GL11 gl11 = (GL11) gl;
+           gl11.glGenBuffers(2, vboIds, 0);
+           mVertexBufferObjectId = vboIds[0];
+           mElementBufferObjectId = vboIds[1];
+
+           // Upload the vertex data
+           gl11.glBindBuffer(GL11.GL_ARRAY_BUFFER, mVertexBufferObjectId);
+           mVertexByteBuffer.position(0);
+           gl11.glBufferData(GL11.GL_ARRAY_BUFFER, mVertexByteBuffer.capacity(), mVertexByteBuffer, GL11.GL_STATIC_DRAW);
+
+           gl11.glBindBuffer(GL11.GL_ELEMENT_ARRAY_BUFFER, mElementBufferObjectId);
+           mIndexBuffer.position(0);
+           gl11.glBufferData(GL11.GL_ELEMENT_ARRAY_BUFFER, mIndexBuffer.capacity() * CHAR_SIZE, mIndexBuffer, GL11.GL_STATIC_DRAW);
+
+           // We don't need the in-memory data any more
+           mVertexBuffer = null;
+           mVertexByteBuffer = null;
+           mIndexBuffer = null;
+       }
+
+       public void draw(GL10 gl) {
+           GL11 gl11 = (GL11) gl;
+
+           gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
+
+           gl11.glBindBuffer(GL11.GL_ARRAY_BUFFER, mVertexBufferObjectId);
+           gl11.glVertexPointer(3, GL10.GL_FLOAT, VERTEX_SIZE, 0);
+           
+           gl11.glBindBuffer(GL11.GL_ELEMENT_ARRAY_BUFFER, mElementBufferObjectId);
+           gl11.glDrawElements(GL10.GL_TRIANGLES, mIndexCount, GL10.GL_UNSIGNED_SHORT, 0);
+           gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
+           gl11.glBindBuffer(GL11.GL_ARRAY_BUFFER, 0);
+           gl11.glBindBuffer(GL11.GL_ELEMENT_ARRAY_BUFFER, 0);
+       }
+   }
+
+
+    private class Renderer implements GLSurfaceView.Renderer {
+        private static final String TAG = "Renderer";
+        private Grid mGrid;
+        
+        public void onDrawFrame(GL10 gl) {
+			gl.glClearColor(0,0,1,1);
+			gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
+            mGrid.draw(gl);
+        }
+
+        public void onSurfaceChanged(GL10 gl, int width, int height) {
+            gl.glViewport(0, 0, width, height);
+			gl.glMatrixMode(GL11.GL_PROJECTION);
+			gl.glLoadIdentity();
+			gl.glOrthof(0, width, height, 0, -1, 1);
+			gl.glMatrixMode(GL11.GL_MODELVIEW);
+            createGrid(gl, width, height);
+        }
+
+        public void onSurfaceCreated(GL10 gl, EGLConfig config) {
+        }
+        
+        private void createGrid(GL10 gl, float w, float h) {
+        mGrid = new Grid(2, 2);
+			for (int j = 0; j < 2; j++) {
+				for (int i = 0; i < 2; i++) {
+					float x = w * i;
+					float y = h * j;
+					float z = 0.0f;
+					mGrid.set(i,j, x, y, z);
+				}
+			}
+			mGrid.createBufferObjects(gl);
+		}
+    }
+}
+
diff --git a/policy/com/android/internal/policy/impl/LockScreen.java b/policy/com/android/internal/policy/impl/LockScreen.java
index a5ef1fa..b3707b0 100644
--- a/policy/com/android/internal/policy/impl/LockScreen.java
+++ b/policy/com/android/internal/policy/impl/LockScreen.java
@@ -220,7 +220,6 @@
             }
         });
 
-
         setFocusable(true);
         setFocusableInTouchMode(true);
         setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
@@ -518,7 +517,7 @@
                 mScreenLocked.setText("");
 
                 // layout
-                mScreenLocked.setVisibility(View.VISIBLE);
+                mScreenLocked.setVisibility(View.INVISIBLE);
                 mSelector.setVisibility(View.VISIBLE);
                 mEmergencyCallText.setVisibility(View.GONE);
                 break;
@@ -658,7 +657,6 @@
     /** {@inheritDoc} */
     public void onResume() {
         resetStatusInfo(mUpdateMonitor);
-        mLockPatternUtils.updateEmergencyCallButtonState(mEmergencyCallButton);
     }
 
     /** {@inheritDoc} */
@@ -676,6 +674,5 @@
     }
 
     public void onPhoneStateChanged(String newState) {
-        mLockPatternUtils.updateEmergencyCallButtonState(mEmergencyCallButton);
     }
 }
diff --git a/services/java/Android.mk b/services/java/Android.mk
index 934712c..c756d29 100644
--- a/services/java/Android.mk
+++ b/services/java/Android.mk
@@ -13,7 +13,9 @@
 
 LOCAL_JAVA_LIBRARIES := android.policy
 
+LOCAL_NO_EMMA_INSTRUMENT := true
+LOCAL_NO_EMMA_COMPILE := true
+
 include $(BUILD_JAVA_LIBRARY)
 
 include $(BUILD_DROIDDOC)
-
diff --git a/services/java/com/android/server/AccessibilityManagerService.java b/services/java/com/android/server/AccessibilityManagerService.java
index 87de79a..83ce3e3 100644
--- a/services/java/com/android/server/AccessibilityManagerService.java
+++ b/services/java/com/android/server/AccessibilityManagerService.java
@@ -269,14 +269,10 @@
             });
     }
 
-    public void addClient(IAccessibilityManagerClient client) {
+    public boolean addClient(IAccessibilityManagerClient client) {
         synchronized (mLock) {
-            try {
-                client.setEnabled(mIsEnabled);
-                mClients.add(client);
-            } catch (RemoteException re) {
-                Slog.w(LOG_TAG, "Dead AccessibilityManagerClient: " + client, re);
-            }
+            mClients.add(client);
+            return mIsEnabled;
         }
     }
 
diff --git a/services/java/com/android/server/LocationManagerService.java b/services/java/com/android/server/LocationManagerService.java
index f9c1a93..b92480f 100644
--- a/services/java/com/android/server/LocationManagerService.java
+++ b/services/java/com/android/server/LocationManagerService.java
@@ -462,10 +462,11 @@
         mEnabledProviders.add(passiveProvider.getName());
 
         // initialize external network location and geocoder services
+        PackageManager pm = mContext. getPackageManager();
         Resources resources = mContext.getResources();
         String serviceName = resources.getString(
                 com.android.internal.R.string.config_networkLocationProvider);
-        if (serviceName != null) {
+        if (serviceName != null && pm.resolveService(new Intent(serviceName), 0) != null) {
             mNetworkLocationProvider =
                 new LocationProviderProxy(mContext, LocationManager.NETWORK_PROVIDER,
                         serviceName, mLocationHandler);
@@ -473,7 +474,7 @@
         }
 
         serviceName = resources.getString(com.android.internal.R.string.config_geocodeProvider);
-        if (serviceName != null) {
+        if (serviceName != null && pm.resolveService(new Intent(serviceName), 0) != null) {
             mGeocodeProvider = new GeocoderProxy(mContext, serviceName);
         }
 
diff --git a/services/java/com/android/server/WindowManagerService.java b/services/java/com/android/server/WindowManagerService.java
index f1c67d2..3952a86 100644
--- a/services/java/com/android/server/WindowManagerService.java
+++ b/services/java/com/android/server/WindowManagerService.java
@@ -148,7 +148,6 @@
     static final boolean DEBUG_STARTING_WINDOW = false;
     static final boolean DEBUG_REORDER = false;
     static final boolean DEBUG_WALLPAPER = false;
-    static final boolean DEBUG_FREEZE = false;
     static final boolean SHOW_TRANSACTIONS = false;
     static final boolean HIDE_STACK_CRAWLS = true;
     static final boolean MEASURE_LATENCY = false;
@@ -4419,8 +4418,7 @@
             final int N = mWindows.size();
             for (int i=0; i<N; i++) {
                 WindowState w = (WindowState)mWindows.get(i);
-                if (w.isVisibleLw() && !w.mObscured
-                        && (w.mOrientationChanging || !w.isDrawnLw())) {
+                if (w.isVisibleLw() && !w.mObscured && !w.isDrawnLw()) {
                     return;
                 }
             }
@@ -7933,7 +7931,7 @@
             final AppWindowToken atoken = mAppToken;
             return mSurface != null && !mAttachedHidden
                     && (atoken == null ? mPolicyVisibility : !atoken.hiddenRequested)
-                    && (mOrientationChanging || (!mDrawPending && !mCommitDrawPending))
+                    && !mDrawPending && !mCommitDrawPending
                     && !mExiting && !mDestroying;
         }
 
@@ -8037,14 +8035,12 @@
 
         /**
          * Returns true if the window has a surface that it has drawn a
-         * complete UI in to.  Note that this returns true if the orientation
-         * is changing even if the window hasn't redrawn because we don't want
-         * to stop things from executing during that time.
+         * complete UI in to.
          */
         public boolean isDrawnLw() {
             final AppWindowToken atoken = mAppToken;
             return mSurface != null && !mDestroying
-                && (mOrientationChanging || (!mDrawPending && !mCommitDrawPending));
+                && !mDrawPending && !mCommitDrawPending;
         }
 
         public boolean fillsScreenLw(int screenWidth, int screenHeight,
@@ -10302,12 +10298,6 @@
                     if (w.mAttachedHidden || !w.isReadyForDisplay()) {
                         if (!w.mLastHidden) {
                             //dump();
-                            if (DEBUG_CONFIGURATION) Slog.v(TAG, "Window hiding: waitingToShow="
-                                    + w.mRootToken.waitingToShow + " polvis="
-                                    + w.mPolicyVisibility + " atthid="
-                                    + w.mAttachedHidden + " tokhid="
-                                    + w.mRootToken.hidden + " vis="
-                                    + w.mViewVisibility);
                             w.mLastHidden = true;
                             if (SHOW_TRANSACTIONS) logSurface(w,
                                     "HIDE (performLayout)", null);
@@ -10703,28 +10693,23 @@
         } else if (animating) {
             requestAnimationLocked(currentTime+(1000/60)-SystemClock.uptimeMillis());
         }
-        
-        if (DEBUG_FREEZE) Slog.v(TAG, "Layout: mDisplayFrozen=" + mDisplayFrozen
-                + " holdScreen=" + holdScreen);
-        if (!mDisplayFrozen) {
-            mQueue.setHoldScreenLocked(holdScreen != null);
-            if (screenBrightness < 0 || screenBrightness > 1.0f) {
-                mPowerManager.setScreenBrightnessOverride(-1);
-            } else {
-                mPowerManager.setScreenBrightnessOverride((int)
-                        (screenBrightness * Power.BRIGHTNESS_ON));
-            }
-            if (buttonBrightness < 0 || buttonBrightness > 1.0f) {
-                mPowerManager.setButtonBrightnessOverride(-1);
-            } else {
-                mPowerManager.setButtonBrightnessOverride((int)
-                        (buttonBrightness * Power.BRIGHTNESS_ON));
-            }
-            if (holdScreen != mHoldingScreenOn) {
-                mHoldingScreenOn = holdScreen;
-                Message m = mH.obtainMessage(H.HOLD_SCREEN_CHANGED, holdScreen);
-                mH.sendMessage(m);
-            }
+        mQueue.setHoldScreenLocked(holdScreen != null);
+        if (screenBrightness < 0 || screenBrightness > 1.0f) {
+            mPowerManager.setScreenBrightnessOverride(-1);
+        } else {
+            mPowerManager.setScreenBrightnessOverride((int)
+                    (screenBrightness * Power.BRIGHTNESS_ON));
+        }
+        if (buttonBrightness < 0 || buttonBrightness > 1.0f) {
+            mPowerManager.setButtonBrightnessOverride(-1);
+        } else {
+            mPowerManager.setButtonBrightnessOverride((int)
+                    (buttonBrightness * Power.BRIGHTNESS_ON));
+        }
+        if (holdScreen != mHoldingScreenOn) {
+            mHoldingScreenOn = holdScreen;
+            Message m = mH.obtainMessage(H.HOLD_SCREEN_CHANGED, holdScreen);
+            mH.sendMessage(m);
         }
 
         if (mTurnOnScreen) {
@@ -11001,8 +10986,6 @@
             mFreezeGcPending = now;
         }
 
-        if (DEBUG_FREEZE) Slog.v(TAG, "*** FREEZING DISPLAY", new RuntimeException());
-        
         mDisplayFrozen = true;
         if (mNextAppTransition != WindowManagerPolicy.TRANSIT_UNSET) {
             mNextAppTransition = WindowManagerPolicy.TRANSIT_UNSET;
@@ -11026,8 +11009,6 @@
             return;
         }
         
-        if (DEBUG_FREEZE) Slog.v(TAG, "*** UNFREEZING DISPLAY", new RuntimeException());
-        
         mDisplayFrozen = false;
         mH.removeMessages(H.APP_FREEZE_TIMEOUT);
         if (PROFILE_ORIENTATION) {
diff --git a/services/jni/com_android_server_location_GpsLocationProvider.cpp b/services/jni/com_android_server_location_GpsLocationProvider.cpp
index afd6ca8..28cefd4 100755
--- a/services/jni/com_android_server_location_GpsLocationProvider.cpp
+++ b/services/jni/com_android_server_location_GpsLocationProvider.cpp
@@ -276,9 +276,9 @@
         sAGpsInterface->init(&sAGpsCallbacks);
 
     if (!sGpsNiInterface)
-       sGpsNiInterface = (const GpsNiInterface*)sGpsInterface->get_extension(GPS_NI_INTERFACE);
+        sGpsNiInterface = (const GpsNiInterface*)sGpsInterface->get_extension(GPS_NI_INTERFACE);
     if (sGpsNiInterface)
-       sGpsNiInterface->init(&sGpsNiCallbacks);
+        sGpsNiInterface->init(&sGpsNiCallbacks);
 
     if (!sGpsDebugInterface)
        sGpsDebugInterface = (const GpsDebugInterface*)sGpsInterface->get_extension(GPS_DEBUG_INTERFACE);
@@ -533,12 +533,10 @@
 static void android_location_GpsLocationProvider_send_ni_response(JNIEnv* env, jobject obj,
       jint notifId, jint response)
 {
-    if (!sGpsNiInterface) {
+    if (!sGpsNiInterface)
         sGpsNiInterface = (const GpsNiInterface*)sGpsInterface->get_extension(GPS_NI_INTERFACE);
-    }
-    if (sGpsNiInterface) {
+    if (sGpsNiInterface)
         sGpsNiInterface->respond(notifId, response);
-    }
 }
 
 static jstring android_location_GpsLocationProvider_get_internal_state(JNIEnv* env, jobject obj)
diff --git a/services/tests/servicestests/Android.mk b/services/tests/servicestests/Android.mk
index b07a10b..186b349 100644
--- a/services/tests/servicestests/Android.mk
+++ b/services/tests/servicestests/Android.mk
@@ -7,8 +7,10 @@
 # Include all test java files.
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
+LOCAL_STATIC_JAVA_LIBRARIES := easymocklib
 
 LOCAL_JAVA_LIBRARIES := android.test.runner services
+
 LOCAL_PACKAGE_NAME := FrameworksServicesTests
 
 LOCAL_CERTIFICATE := platform
diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml
index 5ce109f..f115f42 100644
--- a/services/tests/servicestests/AndroidManifest.xml
+++ b/services/tests/servicestests/AndroidManifest.xml
@@ -23,6 +23,19 @@
     
     <application>
         <uses-library android:name="android.test.runner" />
+
+        <service android:name="com.android.server.AccessibilityManagerServiceTest$MyFirstMockAccessibilityService">
+          <intent-filter>
+            <action android:name="android.accessibilityservice.AccessibilityService"/>
+          </intent-filter>
+        </service>
+
+        <service android:name="com.android.server.AccessibilityManagerServiceTest$MySecondMockAccessibilityService">
+          <intent-filter>
+            <action android:name="android.accessibilityservice.AccessibilityService"/>
+          </intent-filter>
+        </service>
+
     </application>
 
     <instrumentation
diff --git a/services/tests/servicestests/src/com/android/server/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/AccessibilityManagerServiceTest.java
new file mode 100644
index 0000000..0410635
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/AccessibilityManagerServiceTest.java
@@ -0,0 +1,691 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import android.accessibilityservice.AccessibilityService;
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.ServiceInfo;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.provider.Settings;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.IAccessibilityManager;
+import android.view.accessibility.IAccessibilityManagerClient;
+
+/**
+ * This test exercises the
+ * {@link com.android.server.AccessibilityManagerService} by mocking the
+ * {@link android.view.accessibility.AccessibilityManager} which talks to to the
+ * service. The service itself is interacting with the platform. Note: Testing
+ * the service in full isolation would require significant amount of work for
+ * mocking all system interactions. It would also require a lot of mocking code.
+ */
+public class AccessibilityManagerServiceTest extends AndroidTestCase {
+
+    /**
+     * Timeout required for pending Binder calls or event processing to
+     * complete.
+     */
+    private static final long TIMEOUT_BINDER_CALL = 100;
+
+    /**
+     * Timeout used for testing that a service is notified only upon a
+     * notification timeout.
+     */
+    private static final long TIMEOUT_TEST_NOTIFICATION_TIMEOUT = 300;
+
+    /**
+     * The package name.
+     */
+    private static String sPackageName;
+
+    /**
+     * The interface used to talk to the tested service.
+     */
+    private IAccessibilityManager mManagerService;
+
+    @Override
+    public void setContext(Context context) {
+        super.setContext(context);
+        if (sPackageName == null) {
+            sPackageName = context.getPackageName();
+        }
+    }
+
+    /**
+     * Creates a new instance.
+     */
+    public AccessibilityManagerServiceTest() {
+        IBinder iBinder = ServiceManager.getService(Context.ACCESSIBILITY_SERVICE);
+        mManagerService = IAccessibilityManager.Stub.asInterface(iBinder);
+    }
+
+    @LargeTest
+    public void testAddClient_AccessibilityDisabledThenEnabled() throws Exception {
+        // make sure accessibility is disabled
+        ensureAccessibilityEnabled(mContext, false);
+
+        // create a client mock instance
+        MyMockAccessibilityManagerClient mockClient = new MyMockAccessibilityManagerClient();
+
+        // invoke the method under test
+        boolean enabledAccessibilityDisabled = mManagerService.addClient(mockClient);
+
+        // check expected result
+        assertFalse("The client must be disabled since accessibility is disabled.",
+                enabledAccessibilityDisabled);
+
+        // enable accessibility
+        ensureAccessibilityEnabled(mContext, true);
+
+        // invoke the method under test
+        boolean enabledAccessibilityEnabled = mManagerService.addClient(mockClient);
+
+        // check expected result
+        assertTrue("The client must be enabled since accessibility is enabled.",
+                enabledAccessibilityEnabled);
+    }
+
+    @LargeTest
+    public void testAddClient_AccessibilityEnabledThenDisabled() throws Exception {
+        // enable accessibility before registering the client
+        ensureAccessibilityEnabled(mContext, true);
+
+        // create a client mock instance
+        MyMockAccessibilityManagerClient mockClient = new MyMockAccessibilityManagerClient();
+
+        // invoke the method under test
+        boolean enabledAccessibilityEnabled = mManagerService.addClient(mockClient);
+
+        // check expected result
+        assertTrue("The client must be enabled since accessibility is enabled.",
+                enabledAccessibilityEnabled);
+
+        // disable accessibility
+        ensureAccessibilityEnabled(mContext, false);
+
+        // invoke the method under test
+        boolean enabledAccessibilityDisabled = mManagerService.addClient(mockClient);
+
+        // check expected result
+        assertFalse("The client must be disabled since accessibility is disabled.",
+                enabledAccessibilityDisabled);
+    }
+
+    @LargeTest
+    public void testGetAccessibilityServicesList() throws Exception {
+        boolean firstMockServiceInstalled = false;
+        boolean secondMockServiceInstalled = false;
+
+        String packageName = getContext().getPackageName();
+        String firstMockServiceClassName = MyFirstMockAccessibilityService.class.getName();
+        String secondMockServiceClassName = MySecondMockAccessibilityService.class.getName();
+
+        // look for the two mock services
+        for (ServiceInfo serviceInfo : mManagerService.getAccessibilityServiceList()) {
+            if (packageName.equals(serviceInfo.packageName)) {
+                if (firstMockServiceClassName.equals(serviceInfo.name)) {
+                    firstMockServiceInstalled = true;
+                } else if (secondMockServiceClassName.equals(serviceInfo.name)) {
+                    secondMockServiceInstalled = true;
+                }
+            }
+        }
+
+        // check expected result
+        assertTrue("First mock service must be installed", firstMockServiceInstalled);
+        assertTrue("Second mock service must be installed", secondMockServiceInstalled);
+    }
+
+    @LargeTest
+    public void testSendAccessibilityEvent_OneService_MatchingPackageAndEventType()
+            throws Exception {
+        // set the accessibility setting value
+        ensureAccessibilityEnabled(mContext, true);
+
+        // enable the mock accessibility service
+        ensureOnlyMockServicesEnabled(mContext, MyFirstMockAccessibilityService.sComponentName);
+
+        // configure the mock service
+        MockAccessibilityService service = MyFirstMockAccessibilityService.sInstance;
+        service.setServiceInfo(MockAccessibilityService.createDefaultInfo());
+
+        // wait for the binder call to #setService to complete
+        Thread.sleep(TIMEOUT_BINDER_CALL);
+
+        // create and populate an event to be sent
+        AccessibilityEvent sentEvent = AccessibilityEvent.obtain();
+        fullyPopulateDefaultAccessibilityEvent(sentEvent);
+
+        // set expectations
+        service.expectEvent(sentEvent);
+        service.replay();
+
+        // send the event
+        mManagerService.sendAccessibilityEvent(sentEvent);
+
+        // verify if all expected methods have been called
+        assertMockServiceVerifiedWithinTimeout(service);
+    }
+
+    @LargeTest
+    public void testSendAccessibilityEvent_OneService_NotMatchingPackage() throws Exception {
+        // set the accessibility setting value
+        ensureAccessibilityEnabled(mContext, true);
+
+        // enable the mock accessibility service
+        ensureOnlyMockServicesEnabled(mContext, MyFirstMockAccessibilityService.sComponentName);
+
+        // configure the mock service
+        MockAccessibilityService service = MyFirstMockAccessibilityService.sInstance;
+        service.setServiceInfo(MockAccessibilityService.createDefaultInfo());
+
+        // wait for the binder call to #setService to complete
+        Thread.sleep(TIMEOUT_BINDER_CALL);
+
+        // create and populate an event to be sent
+        AccessibilityEvent sentEvent = AccessibilityEvent.obtain();
+        fullyPopulateDefaultAccessibilityEvent(sentEvent);
+        sentEvent.setPackageName("no.service.registered.for.this.package");
+
+        // set expectations
+        service.replay();
+
+        // send the event
+        mManagerService.sendAccessibilityEvent(sentEvent);
+
+        // verify if all expected methods have been called
+        assertMockServiceVerifiedWithinTimeout(service);
+    }
+
+    @LargeTest
+    public void testSendAccessibilityEvent_OneService_NotMatchingEventType() throws Exception {
+        // set the accessibility setting value
+        ensureAccessibilityEnabled(mContext, true);
+
+        // enable the mock accessibility service
+        ensureOnlyMockServicesEnabled(mContext, MyFirstMockAccessibilityService.sComponentName);
+
+        // configure the mock service
+        MockAccessibilityService service = MyFirstMockAccessibilityService.sInstance;
+        service.setServiceInfo(MockAccessibilityService.createDefaultInfo());
+
+        // wait for the binder call to #setService to complete
+        Thread.sleep(TIMEOUT_BINDER_CALL);
+
+        // create and populate an event to be sent
+        AccessibilityEvent sentEvent = AccessibilityEvent.obtain();
+        fullyPopulateDefaultAccessibilityEvent(sentEvent);
+        sentEvent.setEventType(AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED);
+
+        // set expectations
+        service.replay();
+
+        // send the event
+        mManagerService.sendAccessibilityEvent(sentEvent);
+
+        // verify if all expected methods have been called
+        assertMockServiceVerifiedWithinTimeout(service);
+    }
+
+    @LargeTest
+    public void testSendAccessibilityEvent_OneService_NotifivationAfterTimeout() throws Exception {
+        // set the accessibility setting value
+        ensureAccessibilityEnabled(mContext, true);
+
+        // enable the mock accessibility service
+        ensureOnlyMockServicesEnabled(mContext, MyFirstMockAccessibilityService.sComponentName);
+
+        // configure the mock service
+        MockAccessibilityService service = MyFirstMockAccessibilityService.sInstance;
+        AccessibilityServiceInfo info = MockAccessibilityService.createDefaultInfo();
+        info.notificationTimeout = TIMEOUT_TEST_NOTIFICATION_TIMEOUT;
+        service.setServiceInfo(info);
+
+        // wait for the binder call to #setService to complete
+        Thread.sleep(TIMEOUT_BINDER_CALL);
+
+        // create and populate the first event to be sent
+        AccessibilityEvent firstEvent = AccessibilityEvent.obtain();
+        fullyPopulateDefaultAccessibilityEvent(firstEvent);
+
+        // create and populate the second event to be sent
+        AccessibilityEvent secondEvent = AccessibilityEvent.obtain();
+        fullyPopulateDefaultAccessibilityEvent(secondEvent);
+
+        // set expectations
+        service.expectEvent(secondEvent);
+        service.replay();
+
+        // send the events
+        mManagerService.sendAccessibilityEvent(firstEvent);
+        mManagerService.sendAccessibilityEvent(secondEvent);
+
+        // wait for #sendAccessibilityEvent to reach the backing service
+        Thread.sleep(TIMEOUT_BINDER_CALL);
+
+        try {
+            service.verify();
+            fail("No events must be dispatched before the expiration of the notification timeout.");
+        } catch (IllegalStateException ise) {
+            /* expected */
+        }
+
+        // wait for the configured notification timeout to expire
+        Thread.sleep(TIMEOUT_TEST_NOTIFICATION_TIMEOUT);
+
+        // verify if all expected methods have been called
+        assertMockServiceVerifiedWithinTimeout(service);
+    }
+
+    @LargeTest
+    public void testSendAccessibilityEvent_TwoServices_MatchingPackageAndEventType_DiffFeedback()
+            throws Exception {
+        // set the accessibility setting value
+        ensureAccessibilityEnabled(mContext, true);
+
+        // enable the mock accessibility services
+        ensureOnlyMockServicesEnabled(mContext, MyFirstMockAccessibilityService.sComponentName,
+                MySecondMockAccessibilityService.sComponentName);
+
+        // configure the first mock service
+        MockAccessibilityService firstService = MyFirstMockAccessibilityService.sInstance;
+        AccessibilityServiceInfo firstInfo = MockAccessibilityService.createDefaultInfo();
+        firstInfo.feedbackType = AccessibilityServiceInfo.FEEDBACK_AUDIBLE;
+        firstService.setServiceInfo(firstInfo);
+
+        // configure the second mock service
+        MockAccessibilityService secondService = MySecondMockAccessibilityService.sInstance;
+        AccessibilityServiceInfo secondInfo = MockAccessibilityService.createDefaultInfo();
+        secondInfo.feedbackType = AccessibilityServiceInfo.FEEDBACK_HAPTIC;
+        secondService.setServiceInfo(secondInfo);
+
+        // wait for the binder calls to #setService to complete
+        Thread.sleep(TIMEOUT_BINDER_CALL);
+
+        // create and populate an event to be sent
+        AccessibilityEvent sentEvent = AccessibilityEvent.obtain();
+        fullyPopulateDefaultAccessibilityEvent(sentEvent);
+
+        // set expectations for the first mock service
+        firstService.expectEvent(sentEvent);
+        firstService.replay();
+
+        // set expectations for the second mock service
+        secondService.expectEvent(sentEvent);
+        secondService.replay();
+
+        // send the event
+        mManagerService.sendAccessibilityEvent(sentEvent);
+
+        // verify if all expected methods have been called
+        assertMockServiceVerifiedWithinTimeout(firstService);
+        assertMockServiceVerifiedWithinTimeout(secondService);
+    }
+
+    @LargeTest
+    public void testSendAccessibilityEvent_TwoServices_MatchingPackageAndEventType()
+            throws Exception {
+        // set the accessibility setting value
+        ensureAccessibilityEnabled(mContext, true);
+
+        // enable the mock accessibility services
+        ensureOnlyMockServicesEnabled(mContext, MyFirstMockAccessibilityService.sComponentName,
+                MySecondMockAccessibilityService.sComponentName);
+
+        // configure the first mock service
+        MockAccessibilityService firstService = MyFirstMockAccessibilityService.sInstance;
+        firstService.setServiceInfo(MockAccessibilityService.createDefaultInfo());
+
+        // configure the second mock service
+        MockAccessibilityService secondService = MySecondMockAccessibilityService.sInstance;
+        secondService.setServiceInfo(MockAccessibilityService.createDefaultInfo());
+
+        // wait for the binder calls to #setService to complete
+        Thread.sleep(TIMEOUT_BINDER_CALL);
+
+        // create and populate an event to be sent
+        AccessibilityEvent sentEvent = AccessibilityEvent.obtain();
+        fullyPopulateDefaultAccessibilityEvent(sentEvent);
+
+        // set expectations for the first mock service
+        firstService.expectEvent(sentEvent);
+        firstService.replay();
+
+        // set expectations for the second mock service
+        secondService.replay();
+
+        // send the event
+        mManagerService.sendAccessibilityEvent(sentEvent);
+
+        // verify if all expected methods have been called
+        assertMockServiceVerifiedWithinTimeout(firstService);
+        assertMockServiceVerifiedWithinTimeout(secondService);
+    }
+
+    @LargeTest
+    public void testSendAccessibilityEvent_TwoServices_MatchingPackageAndEventType_OneDefault()
+            throws Exception {
+        // set the accessibility setting value
+        ensureAccessibilityEnabled(mContext, true);
+
+        // enable the mock accessibility services
+        ensureOnlyMockServicesEnabled(mContext, MyFirstMockAccessibilityService.sComponentName,
+                MySecondMockAccessibilityService.sComponentName);
+
+        // configure the first mock service
+        MockAccessibilityService firstService = MyFirstMockAccessibilityService.sInstance;
+        AccessibilityServiceInfo firstInfo = MyFirstMockAccessibilityService.createDefaultInfo();
+        firstInfo.flags = AccessibilityServiceInfo.DEFAULT;
+        firstService.setServiceInfo(firstInfo);
+
+        // configure the second mock service
+        MockAccessibilityService secondService = MySecondMockAccessibilityService.sInstance;
+        secondService.setServiceInfo(MySecondMockAccessibilityService.createDefaultInfo());
+
+        // wait for the binder calls to #setService to complete
+        Thread.sleep(TIMEOUT_BINDER_CALL);
+
+        // create and populate an event to be sent
+        AccessibilityEvent sentEvent = AccessibilityEvent.obtain();
+        fullyPopulateDefaultAccessibilityEvent(sentEvent);
+
+        // set expectations for the first mock service
+        firstService.replay();
+
+        // set expectations for the second mock service
+        secondService.expectEvent(sentEvent);
+        secondService.replay();
+
+        // send the event
+        mManagerService.sendAccessibilityEvent(sentEvent);
+
+        // verify if all expected methods have been called
+        assertMockServiceVerifiedWithinTimeout(firstService);
+        assertMockServiceVerifiedWithinTimeout(secondService);
+    }
+
+    @LargeTest
+    public void testSendAccessibilityEvent_TwoServices_MatchingPackageAndEventType_TwoDefault()
+            throws Exception {
+        // set the accessibility setting value
+        ensureAccessibilityEnabled(mContext, true);
+
+        // enable the mock accessibility services
+        ensureOnlyMockServicesEnabled(mContext, MyFirstMockAccessibilityService.sComponentName,
+                MySecondMockAccessibilityService.sComponentName);
+
+        // configure the first mock service
+        MockAccessibilityService firstService = MyFirstMockAccessibilityService.sInstance;
+        AccessibilityServiceInfo firstInfo = MyFirstMockAccessibilityService.createDefaultInfo();
+        firstInfo.flags = AccessibilityServiceInfo.DEFAULT;
+        firstService.setServiceInfo(firstInfo);
+
+        // configure the second mock service
+        MockAccessibilityService secondService = MySecondMockAccessibilityService.sInstance;
+        AccessibilityServiceInfo secondInfo = MyFirstMockAccessibilityService.createDefaultInfo();
+        secondInfo.flags = AccessibilityServiceInfo.DEFAULT;
+        secondService.setServiceInfo(firstInfo);
+
+        // wait for the binder calls to #setService to complete
+        Thread.sleep(TIMEOUT_BINDER_CALL);
+
+        // create and populate an event to be sent
+        AccessibilityEvent sentEvent = AccessibilityEvent.obtain();
+        fullyPopulateDefaultAccessibilityEvent(sentEvent);
+
+        // set expectations for the first mock service
+        firstService.expectEvent(sentEvent);
+        firstService.replay();
+
+        // set expectations for the second mock service
+        secondService.replay();
+
+        // send the event
+        mManagerService.sendAccessibilityEvent(sentEvent);
+
+        // verify if all expected methods have been called
+        assertMockServiceVerifiedWithinTimeout(firstService);
+        assertMockServiceVerifiedWithinTimeout(secondService);
+    }
+
+    @LargeTest
+    public void testInterrupt() throws Exception {
+        // set the accessibility setting value
+        ensureAccessibilityEnabled(mContext, true);
+
+        // enable the mock accessibility services
+        ensureOnlyMockServicesEnabled(mContext, MyFirstMockAccessibilityService.sComponentName,
+                MySecondMockAccessibilityService.sComponentName);
+
+        // configure the first mock service
+        MockAccessibilityService firstService = MyFirstMockAccessibilityService.sInstance;
+        firstService.setServiceInfo(MockAccessibilityService.createDefaultInfo());
+
+        // configure the second mock service
+        MockAccessibilityService secondService = MySecondMockAccessibilityService.sInstance;
+        secondService.setServiceInfo(MockAccessibilityService.createDefaultInfo());
+
+        // wait for the binder calls to #setService to complete
+        Thread.sleep(TIMEOUT_BINDER_CALL);
+
+        // set expectations for the first mock service
+        firstService.expectInterrupt();
+        firstService.replay();
+
+        // set expectations for the second mock service
+        secondService.expectInterrupt();
+        secondService.replay();
+
+        // call the method under test
+        mManagerService.interrupt();
+
+        // verify if all expected methods have been called
+        assertMockServiceVerifiedWithinTimeout(firstService);
+        assertMockServiceVerifiedWithinTimeout(secondService);
+    }
+
+    /**
+     * Fully populates the {@link AccessibilityEvent} to marshal.
+     *
+     * @param sentEvent The event to populate.
+     */
+    private void fullyPopulateDefaultAccessibilityEvent(AccessibilityEvent sentEvent) {
+        sentEvent.setAddedCount(1);
+        sentEvent.setBeforeText("BeforeText");
+        sentEvent.setChecked(true);
+        sentEvent.setClassName("foo.bar.baz.Class");
+        sentEvent.setContentDescription("ContentDescription");
+        sentEvent.setCurrentItemIndex(1);
+        sentEvent.setEnabled(true);
+        sentEvent.setEventType(AccessibilityEvent.TYPE_VIEW_CLICKED);
+        sentEvent.setEventTime(1000);
+        sentEvent.setFromIndex(1);
+        sentEvent.setFullScreen(true);
+        sentEvent.setItemCount(1);
+        sentEvent.setPackageName("foo.bar.baz");
+        sentEvent.setParcelableData(Message.obtain(null, 1, null));
+        sentEvent.setPassword(true);
+        sentEvent.setRemovedCount(1);
+    }
+
+    /**
+     * This class is a mock {@link IAccessibilityManagerClient}.
+     */
+    public class MyMockAccessibilityManagerClient extends IAccessibilityManagerClient.Stub {
+        boolean mIsEnabled;
+
+        public void setEnabled(boolean enabled) {
+            mIsEnabled = enabled;
+        }
+    }
+
+    /**
+     * Ensures accessibility is in a given state by writing the state to the
+     * settings and waiting until the accessibility manager service pick it up.
+     *
+     * @param context A context handle to access the settings.
+     * @param enabled The accessibility state to write to the settings.
+     * @throws Exception If any error occurs.
+     */
+    private void ensureAccessibilityEnabled(Context context, boolean enabled) throws Exception {
+        boolean isEnabled = (Settings.Secure.getInt(context.getContentResolver(),
+                Settings.Secure.ACCESSIBILITY_ENABLED, 0) == 1 ? true : false);
+
+        if (isEnabled == enabled) {
+            return;
+        }
+
+        Settings.Secure.putInt(context.getContentResolver(), Settings.Secure.ACCESSIBILITY_ENABLED,
+                enabled ? 1 : 0);
+
+        // wait the accessibility manager service to pick the change up
+        Thread.sleep(TIMEOUT_BINDER_CALL);
+    }
+
+    /**
+     * Ensures the only {@link MockAccessibilityService}s with given component
+     * names are enabled by writing to the system settings and waiting until the
+     * accessibility manager service picks that up.
+     *
+     * @param context A context handle to access the settings.
+     * @param componentNames The string representation of the
+     *            {@link ComponentName}s to enable.
+     * @throws Exception Exception If any error occurs.
+     */
+    private void ensureOnlyMockServicesEnabled(Context context, String... componentNames)
+            throws Exception {
+        String enabledServices = Settings.Secure.getString(context.getContentResolver(),
+                Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
+
+        StringBuilder servicesToEnable = new StringBuilder();
+        for (String componentName : componentNames) {
+            servicesToEnable.append(componentName).append(":");
+        }
+
+        if (servicesToEnable.equals(enabledServices)) {
+            return;
+        }
+
+        Settings.Secure.putString(context.getContentResolver(),
+                Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, servicesToEnable.toString());
+
+        // wait the system to perform asynchronous processing
+        Thread.sleep(TIMEOUT_BINDER_CALL);
+    }
+
+    /**
+     * Asserts the the mock accessibility service has been successfully verified
+     * (which is it has received the expected method calls with expected
+     * arguments) within the {@link #TIMEOUT_BINDER_CALL}. The verified state is
+     * checked by polling upon small intervals.
+     *
+     * @param service The service to verify.
+     * @throws Exception If the verification has failed with exception after the
+     *             {@link #TIMEOUT_BINDER_CALL}.
+     */
+    private void assertMockServiceVerifiedWithinTimeout(MockAccessibilityService service)
+            throws Exception {
+        Exception lastVerifyException = null;
+        long beginTime = SystemClock.uptimeMillis();
+        long pollTmeout = TIMEOUT_BINDER_CALL / 5;
+
+        // poll until the timeout has elapsed
+        while (SystemClock.uptimeMillis() - beginTime < TIMEOUT_BINDER_CALL) {
+            // sleep first since immediate call will always fail
+            try {
+                Thread.sleep(pollTmeout);
+            } catch (InterruptedException ie) {
+                /* ignore */
+            }
+            // poll for verification and if this fails save the exception and
+            // keep polling
+            try {
+                service.verify();
+                // reset so it does not accept more events
+                service.reset();
+                return;
+            } catch (Exception e) {
+                lastVerifyException = e;
+            }
+        }
+
+        // reset, we have already failed
+        service.reset();
+
+        // always not null
+        throw lastVerifyException;
+    }
+
+    /**
+     * This class is the first mock {@link AccessibilityService}.
+     */
+    public static class MyFirstMockAccessibilityService extends MockAccessibilityService {
+
+        /**
+         * The service {@link ComponentName} flattened as a string.
+         */
+        static final String sComponentName = new ComponentName(
+                sPackageName,
+                MyFirstMockAccessibilityService.class.getName()
+                ).flattenToShortString();
+
+        /**
+         * Handle to the service instance.
+         */
+        static MyFirstMockAccessibilityService sInstance;
+
+        /**
+         * Creates a new instance.
+         */
+        public MyFirstMockAccessibilityService() {
+            sInstance = this;
+        }
+    }
+
+    /**
+     * This class is the first mock {@link AccessibilityService}.
+     */
+    public static class MySecondMockAccessibilityService extends MockAccessibilityService {
+
+        /**
+         * The service {@link ComponentName} flattened as a string.
+         */
+        static final String sComponentName = new ComponentName(
+                sPackageName,
+                MySecondMockAccessibilityService.class.getName()
+                ).flattenToShortString();
+
+        /**
+         * Handle to the service instance.
+         */
+        static MySecondMockAccessibilityService sInstance;
+
+        /**
+         * Creates a new instance.
+         */
+        public MySecondMockAccessibilityService() {
+            sInstance = this;
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/AccessibilityManagerTest.java b/services/tests/servicestests/src/com/android/server/AccessibilityManagerTest.java
new file mode 100644
index 0000000..38fed22
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/AccessibilityManagerTest.java
@@ -0,0 +1,257 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import static org.easymock.EasyMock.createStrictMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.reportMatcher;
+import static org.easymock.EasyMock.reset;
+import static org.easymock.EasyMock.verify;
+
+import org.easymock.IArgumentMatcher;
+
+import android.content.pm.ServiceInfo;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.IAccessibilityManager;
+import android.view.accessibility.IAccessibilityManagerClient;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Tests for the AccessibilityManager which mocking the backing service.
+ */
+public class AccessibilityManagerTest extends AndroidTestCase {
+
+    /**
+     * Timeout required for pending Binder calls or event processing to
+     * complete.
+     */
+    public static final long TIMEOUT_BINDER_CALL = 50;
+
+    /**
+     * The reusable mock {@link IAccessibilityManager}.
+     */
+    private final IAccessibilityManager mMockServiceInterface =
+        createStrictMock(IAccessibilityManager.class);
+
+    @Override
+    public void setUp() throws Exception {
+        reset(mMockServiceInterface);
+    }
+
+    @MediumTest
+    public void testGetAccessibilityServiceList() throws Exception {
+        // create a list of installed accessibility services the mock service returns
+        List<ServiceInfo> expectedServices = new ArrayList<ServiceInfo>();
+        ServiceInfo serviceInfo = new ServiceInfo();
+        serviceInfo.name = "TestServiceInfoName";
+        expectedServices.add(serviceInfo);
+
+        // configure the mock service behavior
+        IAccessibilityManager mockServiceInterface = mMockServiceInterface;
+        expect(mockServiceInterface.addClient(anyIAccessibilityManagerClient())).andReturn(true);
+        expect(mockServiceInterface.getAccessibilityServiceList()).andReturn(expectedServices);
+        replay(mockServiceInterface);
+
+        // invoke the method under test
+        AccessibilityManager manager = new AccessibilityManager(mContext, mockServiceInterface);
+        List<ServiceInfo> receivedServices = manager.getAccessibilityServiceList();
+
+        // check expected result (list equals() compares it contents as well)
+        assertEquals("All expected services must be returned", receivedServices, expectedServices);
+
+        // verify the mock service was properly called
+        verify(mockServiceInterface);
+    }
+
+    @MediumTest
+    public void testInterrupt() throws Exception {
+        // configure the mock service behavior
+        IAccessibilityManager mockServiceInterface = mMockServiceInterface;
+        expect(mockServiceInterface.addClient(anyIAccessibilityManagerClient())).andReturn(true);
+        mockServiceInterface.interrupt();
+        replay(mockServiceInterface);
+
+        // invoke the method under test
+        AccessibilityManager manager = new AccessibilityManager(mContext, mockServiceInterface);
+        manager.interrupt();
+
+        // verify the mock service was properly called
+        verify(mockServiceInterface);
+    }
+
+    @LargeTest
+    public void testIsEnabled() throws Exception {
+        // configure the mock service behavior
+        IAccessibilityManager mockServiceInterface = mMockServiceInterface;
+        expect(mockServiceInterface.addClient(anyIAccessibilityManagerClient())).andReturn(true);
+        replay(mockServiceInterface);
+
+        // invoke the method under test
+        AccessibilityManager manager = new AccessibilityManager(mContext, mockServiceInterface);
+        boolean isEnabledServiceEnabled = manager.isEnabled();
+
+        // check expected result
+        assertTrue("Must be enabled since the mock service is enabled", isEnabledServiceEnabled);
+
+        // disable accessibility
+        manager.getClient().setEnabled(false);
+
+        // wait for the asynchronous IBinder call to complete
+        Thread.sleep(TIMEOUT_BINDER_CALL);
+
+        // invoke the method under test
+        boolean isEnabledServcieDisabled = manager.isEnabled();
+
+        // check expected result
+        assertFalse("Must be disabled since the mock service is disabled",
+                isEnabledServcieDisabled);
+
+        // verify the mock service was properly called
+        verify(mockServiceInterface);
+    }
+
+    @MediumTest
+    public void testSendAccessibilityEvent_AccessibilityEnabled() throws Exception {
+        // create an event to be dispatched
+        AccessibilityEvent sentEvent = AccessibilityEvent.obtain();
+
+        // configure the mock service behavior
+        IAccessibilityManager mockServiceInterface = mMockServiceInterface;
+        expect(mockServiceInterface.addClient(anyIAccessibilityManagerClient())).andReturn(true);
+        expect(mockServiceInterface.sendAccessibilityEvent(eqAccessibilityEvent(sentEvent)))
+                .andReturn(true);
+        expect(mockServiceInterface.sendAccessibilityEvent(eqAccessibilityEvent(sentEvent)))
+                .andReturn(false);
+        replay(mockServiceInterface);
+
+        // invoke the method under test (manager and service in different processes)
+        AccessibilityManager manager = new AccessibilityManager(mContext, mockServiceInterface);
+        manager.sendAccessibilityEvent(sentEvent);
+
+        // check expected result
+        AccessibilityEvent nextEventDifferentProcesses = AccessibilityEvent.obtain();
+        assertSame("The manager and the service are in different processes, so the event must be " +
+                "recycled", sentEvent, nextEventDifferentProcesses);
+
+        // invoke the method under test (manager and service in the same process)
+        manager.sendAccessibilityEvent(sentEvent);
+
+        // check expected result
+        AccessibilityEvent nextEventSameProcess = AccessibilityEvent.obtain();
+        assertNotSame("The manager and the service are in the same process, so the event must not" +
+                "be recycled", sentEvent, nextEventSameProcess);
+
+        // verify the mock service was properly called
+        verify(mockServiceInterface);
+    }
+
+    @MediumTest
+    public void testSendAccessibilityEvent_AccessibilityDisabled() throws Exception {
+        // create an event to be dispatched
+        AccessibilityEvent sentEvent = AccessibilityEvent.obtain();
+
+        // configure the mock service behavior
+        IAccessibilityManager mockServiceInterface = mMockServiceInterface;
+        expect(mockServiceInterface.addClient(anyIAccessibilityManagerClient())).andReturn(false);
+        replay(mockServiceInterface);
+
+        // invoke the method under test (accessibility disabled)
+        AccessibilityManager manager = new AccessibilityManager(mContext, mockServiceInterface);
+        try {
+            manager.sendAccessibilityEvent(sentEvent);
+            fail("No accessibility events are sent if accessibility is disabled");
+        } catch (IllegalStateException ise) {
+            // check expected result
+            assertEquals("Accessibility off. Did you forget to check that?", ise.getMessage());
+        }
+
+        // verify the mock service was properly called
+        verify(mockServiceInterface);
+    }
+
+    /**
+     * Determines if an {@link AccessibilityEvent} passed as a method argument
+     * matches expectations.
+     *
+     * @param matched The event to check.
+     * @return True if expectations are matched.
+     */
+    private static AccessibilityEvent eqAccessibilityEvent(AccessibilityEvent matched) {
+        reportMatcher(new AccessibilityEventMather(matched));
+        return null;
+    }
+
+    /**
+     * Determines if an {@link IAccessibilityManagerClient} passed as a method argument
+     * matches expectations which in this case are that any instance is accepted.
+     *
+     * @return <code>null</code>.
+     */
+    private static IAccessibilityManagerClient anyIAccessibilityManagerClient() {
+        reportMatcher(new AnyIAccessibilityManagerClientMather());
+        return null;
+    }
+
+    /**
+     * Matcher for {@link AccessibilityEvent}s.
+     */
+    private static class AccessibilityEventMather implements IArgumentMatcher {
+        private AccessibilityEvent mExpectedEvent;
+
+        public AccessibilityEventMather(AccessibilityEvent expectedEvent) {
+            mExpectedEvent = expectedEvent;
+        }
+
+        public boolean matches(Object matched) {
+            if (!(matched instanceof AccessibilityEvent)) {
+                return false;
+            }
+            AccessibilityEvent receivedEvent = (AccessibilityEvent) matched;
+            return mExpectedEvent.getEventType() == receivedEvent.getEventType();
+        }
+
+        public void appendTo(StringBuffer buffer) {
+            buffer.append("sendAccessibilityEvent()");
+            buffer.append(" with event type \"");
+            buffer.append(mExpectedEvent.getEventType());
+            buffer.append("\"");
+        }
+    }
+
+    /**
+     * Matcher for {@link IAccessibilityManagerClient}s.
+     */
+    private static class AnyIAccessibilityManagerClientMather implements IArgumentMatcher {
+        public boolean matches(Object matched) {
+            if (!(matched instanceof IAccessibilityManagerClient)) {
+                return false;
+            }
+            return true;
+        }
+
+        public void appendTo(StringBuffer buffer) {
+            buffer.append("addClient() with any IAccessibilityManagerClient");
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/MockAccessibilityService.java b/services/tests/servicestests/src/com/android/server/MockAccessibilityService.java
new file mode 100644
index 0000000..663c121
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/MockAccessibilityService.java
@@ -0,0 +1,230 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import android.accessibilityservice.AccessibilityService;
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.os.Message;
+import android.view.accessibility.AccessibilityEvent;
+
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Queue;
+
+import junit.framework.TestCase;
+
+/**
+ * This is the base class for mock {@link AccessibilityService}s.
+ */
+public abstract class MockAccessibilityService extends AccessibilityService {
+
+    /**
+     * The event this service expects to receive.
+     */
+    private final Queue<AccessibilityEvent> mExpectedEvents = new LinkedList<AccessibilityEvent>();
+
+    /**
+     * Interruption call this service expects to receive.
+     */
+    private boolean mExpectedInterrupt;
+
+    /**
+     * Flag if the mock is currently replaying.
+     */
+    private boolean mReplaying;
+
+    /**
+     * Creates an {@link AccessibilityServiceInfo} populated with default
+     * values.
+     *
+     * @return The default info.
+     */
+    public static AccessibilityServiceInfo createDefaultInfo() {
+        AccessibilityServiceInfo defaultInfo = new AccessibilityServiceInfo();
+        defaultInfo.eventTypes = AccessibilityEvent.TYPE_VIEW_CLICKED;
+        defaultInfo.feedbackType = AccessibilityServiceInfo.FEEDBACK_AUDIBLE;
+        defaultInfo.flags = 0;
+        defaultInfo.notificationTimeout = 0;
+        defaultInfo.packageNames = new String[] {
+            "foo.bar.baz"
+        };
+
+        return defaultInfo;
+    }
+
+    /**
+     * Starts replaying the mock.
+     */
+    public void replay() {
+        mReplaying = true;
+    }
+
+    /**
+     * Verifies if all expected service methods have been called.
+     */
+    public void verify() {
+        if (!mReplaying) {
+            throw new IllegalStateException("Did you forget to call replay()");
+        }
+
+        if (mExpectedInterrupt) {
+            throw new IllegalStateException("Expected call to #interrupt() not received");
+        }
+        if (!mExpectedEvents.isEmpty()) {
+            throw new IllegalStateException("Expected a call to onAccessibilityEvent() for "
+                    + "events \"" + mExpectedEvents + "\" not received");
+        }
+    }
+
+    /**
+     * Resets this instance so it can be reused.
+     */
+    public void reset() {
+        mExpectedEvents.clear();
+        mExpectedInterrupt = false;
+        mReplaying = false;
+    }
+
+    /**
+     * Sets an expected call to
+     * {@link #onAccessibilityEvent(AccessibilityEvent)} with given event as
+     * argument.
+     *
+     * @param expectedEvent The expected event argument.
+     */
+    public void expectEvent(AccessibilityEvent expectedEvent) {
+        mExpectedEvents.add(expectedEvent);
+    }
+
+    /**
+     * Sets an expected call of {@link #onInterrupt()}.
+     */
+    public void expectInterrupt() {
+        mExpectedInterrupt = true;
+    }
+
+    @Override
+    public void onAccessibilityEvent(AccessibilityEvent receivedEvent) {
+        if (!mReplaying) {
+            return;
+        }
+
+        if (mExpectedEvents.isEmpty()) {
+            throw new IllegalStateException("Unexpected event: " + receivedEvent);
+        }
+
+        AccessibilityEvent expectedEvent = mExpectedEvents.poll();
+        assertEqualsAccessiblityEvent(expectedEvent, receivedEvent);
+    }
+
+    @Override
+    public void onInterrupt() {
+        if (!mReplaying) {
+            return;
+        }
+
+        if (!mExpectedInterrupt) {
+            throw new IllegalStateException("Unexpected call to onInterrupt()");
+        }
+
+        mExpectedInterrupt = false;
+    }
+
+    /**
+     * Compares all properties of the <code>expectedEvent</code> and the
+     * <code>receviedEvent</code> to verify that the received event is the one
+     * that is expected.
+     */
+    private void assertEqualsAccessiblityEvent(AccessibilityEvent expectedEvent,
+            AccessibilityEvent receivedEvent) {
+        TestCase.assertEquals("addedCount has incorrect value", expectedEvent.getAddedCount(),
+                receivedEvent.getAddedCount());
+        TestCase.assertEquals("beforeText has incorrect value", expectedEvent.getBeforeText(),
+                receivedEvent.getBeforeText());
+        TestCase.assertEquals("checked has incorrect value", expectedEvent.isChecked(),
+                receivedEvent.isChecked());
+        TestCase.assertEquals("className has incorrect value", expectedEvent.getClassName(),
+                receivedEvent.getClassName());
+        TestCase.assertEquals("contentDescription has incorrect value", expectedEvent
+                .getContentDescription(), receivedEvent.getContentDescription());
+        TestCase.assertEquals("currentItemIndex has incorrect value", expectedEvent
+                .getCurrentItemIndex(), receivedEvent.getCurrentItemIndex());
+        TestCase.assertEquals("enabled has incorrect value", expectedEvent.isEnabled(),
+                receivedEvent.isEnabled());
+        TestCase.assertEquals("eventType has incorrect value", expectedEvent.getEventType(),
+                receivedEvent.getEventType());
+        TestCase.assertEquals("fromIndex has incorrect value", expectedEvent.getFromIndex(),
+                receivedEvent.getFromIndex());
+        TestCase.assertEquals("fullScreen has incorrect value", expectedEvent.isFullScreen(),
+                receivedEvent.isFullScreen());
+        TestCase.assertEquals("itemCount has incorrect value", expectedEvent.getItemCount(),
+                receivedEvent.getItemCount());
+        assertEqualsNotificationAsParcelableData(expectedEvent, receivedEvent);
+        TestCase.assertEquals("password has incorrect value", expectedEvent.isPassword(),
+                receivedEvent.isPassword());
+        TestCase.assertEquals("removedCount has incorrect value", expectedEvent.getRemovedCount(),
+                receivedEvent.getRemovedCount());
+        assertEqualsText(expectedEvent, receivedEvent);
+    }
+
+    /**
+     * Compares the {@link android.os.Parcelable} data of the
+     * <code>expectedEvent</code> and <code>receivedEvent</code> to verify that
+     * the received event is the one that is expected.
+     */
+    private void assertEqualsNotificationAsParcelableData(AccessibilityEvent expectedEvent,
+            AccessibilityEvent receivedEvent) {
+        String message = "parcelableData has incorrect value";
+        Message expectedMessage = (Message) expectedEvent.getParcelableData();
+        Message receivedMessage = (Message) receivedEvent.getParcelableData();
+
+        if (expectedMessage == null) {
+            if (receivedMessage == null) {
+                return;
+            }
+        }
+
+        TestCase.assertNotNull(message, receivedMessage);
+
+        // we do a very simple sanity check since we do not test Message
+        TestCase.assertEquals(message, expectedMessage.what, receivedMessage.what);
+    }
+
+    /**
+     * Compares the text of the <code>expectedEvent</code> and
+     * <code>receivedEvent</code> by comparing the string representation of the
+     * corresponding {@link CharSequence}s.
+     */
+    private void assertEqualsText(AccessibilityEvent expectedEvent,
+            AccessibilityEvent receivedEvent) {
+        String message = "text has incorrect value";
+        List<CharSequence> expectedText = expectedEvent.getText();
+        List<CharSequence> receivedText = receivedEvent.getText();
+
+        TestCase.assertEquals(message, expectedText.size(), receivedText.size());
+
+        Iterator<CharSequence> expectedTextIterator = expectedText.iterator();
+        Iterator<CharSequence> receivedTextIterator = receivedText.iterator();
+
+        for (int i = 0; i < expectedText.size(); i++) {
+            // compare the string representation
+            TestCase.assertEquals(message, expectedTextIterator.next().toString(),
+                    receivedTextIterator.next().toString());
+        }
+    }
+}
diff --git a/telephony/java/android/telephony/PhoneNumberUtils.java b/telephony/java/android/telephony/PhoneNumberUtils.java
index 32e7176..666ad72 100644
--- a/telephony/java/android/telephony/PhoneNumberUtils.java
+++ b/telephony/java/android/telephony/PhoneNumberUtils.java
@@ -586,7 +586,7 @@
             }
         } else {
             // In the US, 1-650-555-1234 must be equal to 650-555-1234,
-            // while 090-1234-1234 must not be equalt to 90-1234-1234 in Japan.
+            // while 090-1234-1234 must not be equal to 90-1234-1234 in Japan.
             // This request exists just in US (with 1 trunk (NDD) prefix).
             // In addition, "011 11 7005554141" must not equal to "+17005554141",
             // while "011 1 7005554141" must equal to "+17005554141"
@@ -747,10 +747,10 @@
         if (prependPlus) {
             // This is an "international number" and should have
             // a plus prepended to the dialing number. But there
-            // can also be Gsm MMI codes as defined in TS 22.030 6.5.2
+            // can also be GSM MMI codes as defined in TS 22.030 6.5.2
             // so we need to handle those also.
             //
-            // http://web.telia.com/~u47904776/gsmkode.htm is a
+            // http://web.telia.com/~u47904776/gsmkode.htm
             // has a nice list of some of these GSM codes.
             //
             // Examples are:
@@ -838,10 +838,10 @@
 
             // FIXME(mkf) TS 23.040 9.1.2.3 says
             // "if a mobile receives 1111 in a position prior to
-            // the last semi-octet then processing shall commense with
+            // the last semi-octet then processing shall commence with
             // the next semi-octet and the intervening
             // semi-octet shall be ignored"
-            // How does this jive with 24,008 10.5.4.7
+            // How does this jive with 24.008 10.5.4.7
 
             b = (byte)((bytes[i] >> 4) & 0xf);
 
@@ -972,7 +972,7 @@
      * Convert a dialing number to BCD byte array
      *
      * @param number dialing number string
-     *        if the dialing number starts with '+', set to internationl TOA
+     *        if the dialing number starts with '+', set to international TOA
      * @return BCD byte array
      */
     public static byte[]
@@ -1076,10 +1076,10 @@
      *
      * @param source the phone number to format
      * @param defaultFormattingType The default formatting rules to apply if the number does
-     * not begin with +<country_code>
+     * not begin with +[country_code]
      * @return The phone number formatted with the given formatting type.
      *
-     * @hide TODO:Shuold be unhidden.
+     * @hide TODO: Should be unhidden.
      */
     public static String formatNumber(String source, int defaultFormattingType) {
         SpannableStringBuilder text = new SpannableStringBuilder(source);
@@ -1106,7 +1106,7 @@
      *
      * @param text The number to be formatted, will be modified with the formatting
      * @param defaultFormattingType The default formatting rules to apply if the number does
-     * not begin with +<country_code>
+     * not begin with +[country_code]
      */
     public static void formatNumber(Editable text, int defaultFormattingType) {
         int formatType = defaultFormattingType;
@@ -1502,7 +1502,7 @@
      * @hide
      */
     public static String
-    cdmaCheckAndProcessPlusCodeByNumberFormat(String dialStr,int currFormat,int defaultFormt) {
+    cdmaCheckAndProcessPlusCodeByNumberFormat(String dialStr,int currFormat,int defaultFormat) {
         String retStr = dialStr;
 
         // Checks if the plus sign character is in the passed-in dial string
@@ -1510,7 +1510,7 @@
             dialStr.lastIndexOf(PLUS_SIGN_STRING) != -1) {
             // Format the string based on the rules for the country the number is from,
             // and the current country the phone is camped on.
-            if ((currFormat == defaultFormt) && (currFormat == FORMAT_NANP)) {
+            if ((currFormat == defaultFormat) && (currFormat == FORMAT_NANP)) {
                 // Handle case where default and current telephone numbering plans are NANP.
                 String postDialStr = null;
                 String tempDialStr = dialStr;
@@ -1688,7 +1688,7 @@
         return -1;
     }
 
-    // This function appends the non-diablable P/W character to the original
+    // This function appends the non-dialable P/W character to the original
     // dial string based on the dialable index passed in
     private static String
     appendPwCharBackToOrigDialStr(int dialableIndex,String origStr, String dialStr) {
@@ -1708,7 +1708,7 @@
         return retStr;
     }
 
-    //===== Begining of utility methods used in compareLoosely() =====
+    //===== Beginning of utility methods used in compareLoosely() =====
 
     /**
      * Phone numbers are stored in "lookup" form in the database
@@ -1830,12 +1830,12 @@
 
     //===== End of utility methods used only in compareLoosely() =====
 
-    //===== Beggining of utility methods used only in compareStrictly() ====
+    //===== Beginning of utility methods used only in compareStrictly() ====
 
     /*
      * If true, the number is country calling code.
      */
-    private static final boolean COUNTLY_CALLING_CALL[] = {
+    private static final boolean COUNTRY_CALLING_CALL[] = {
         true, true, false, false, false, false, false, true, false, false,
         false, false, false, false, false, false, false, false, false, false,
         true, false, false, false, false, false, false, true, true, false,
@@ -1847,18 +1847,18 @@
         false, true, true, true, true, false, true, false, false, true,
         true, true, true, true, true, true, false, false, true, false,
     };
-    private static final int CCC_LENGTH = COUNTLY_CALLING_CALL.length;
+    private static final int CCC_LENGTH = COUNTRY_CALLING_CALL.length;
 
     /**
      * @return true when input is valid Country Calling Code.
      */
     private static boolean isCountryCallingCode(int countryCallingCodeCandidate) {
         return countryCallingCodeCandidate > 0 && countryCallingCodeCandidate < CCC_LENGTH &&
-                COUNTLY_CALLING_CALL[countryCallingCodeCandidate];
+                COUNTRY_CALLING_CALL[countryCallingCodeCandidate];
     }
 
     /**
-     * Returns interger corresponding to the input if input "ch" is
+     * Returns integer corresponding to the input if input "ch" is
      * ISO-LATIN characters 0-9.
      * Returns -1 otherwise
      */
@@ -1993,7 +1993,7 @@
 
     /**
      * Return true if the prefix of "str" is "ignorable". Here, "ignorable" means
-     * that "str" has only one digit and separater characters. The one digit is
+     * that "str" has only one digit and separator characters. The one digit is
      * assumed to be trunk prefix.
      */
     private static boolean checkPrefixIsIgnorable(final String str,
diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java
index 3122722..2a969bb 100644
--- a/telephony/java/android/telephony/SmsManager.java
+++ b/telephony/java/android/telephony/SmsManager.java
@@ -21,7 +21,6 @@
 import android.os.ServiceManager;
 import android.text.TextUtils;
 
-import com.android.internal.telephony.EncodeException;
 import com.android.internal.telephony.ISms;
 import com.android.internal.telephony.IccConstants;
 import com.android.internal.telephony.SmsRawData;
@@ -33,7 +32,7 @@
 /*
  * TODO(code review): Curious question... Why are a lot of these
  * methods not declared as static, since they do not seem to require
- * any local object state?  Assumedly this cannot be changed without
+ * any local object state?  Presumably this cannot be changed without
  * interfering with the API...
  */
 
@@ -42,7 +41,8 @@
  * Get this object by calling the static method SmsManager.getDefault().
  */
 public final class SmsManager {
-    private static SmsManager sInstance;
+    /** Singleton object constructed during class initialization. */
+    private static final SmsManager sInstance = new SmsManager();
 
     /**
      * Send a text based SMS.
@@ -52,8 +52,8 @@
      *  the current default SMSC
      * @param text the body of the message to send
      * @param sentIntent if not NULL this <code>PendingIntent</code> is
-     *  broadcast when the message is sucessfully sent, or failed.
-     *  The result code will be <code>Activity.RESULT_OK<code> for success,
+     *  broadcast when the message is successfully sent, or failed.
+     *  The result code will be <code>Activity.RESULT_OK</code> for success,
      *  or one of these errors:<br>
      *  <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
      *  <code>RESULT_ERROR_RADIO_OFF</code><br>
@@ -116,7 +116,7 @@
      * @param sentIntents if not null, an <code>ArrayList</code> of
      *   <code>PendingIntent</code>s (one for each message part) that is
      *   broadcast when the corresponding message part has been sent.
-     *   The result code will be <code>Activity.RESULT_OK<code> for success,
+     *   The result code will be <code>Activity.RESULT_OK</code> for success,
      *   or one of these errors:<br>
      *   <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
      *   <code>RESULT_ERROR_RADIO_OFF</code><br>
@@ -125,7 +125,7 @@
      *   the extra "errorCode" containing a radio technology specific value,
      *   generally only useful for troubleshooting.<br>
      *   The per-application based SMS control checks sentIntent. If sentIntent
-     *   is NULL the caller will be checked against all unknown applicaitons,
+     *   is NULL the caller will be checked against all unknown applications,
      *   which cause smaller number of SMS to be sent in checking period.
      * @param deliveryIntents if not null, an <code>ArrayList</code> of
      *   <code>PendingIntent</code>s (one for each message part) that is
@@ -178,8 +178,8 @@
      * @param destinationPort the port to deliver the message to
      * @param data the body of the message to send
      * @param sentIntent if not NULL this <code>PendingIntent</code> is
-     *  broadcast when the message is sucessfully sent, or failed.
-     *  The result code will be <code>Activity.RESULT_OK<code> for success,
+     *  broadcast when the message is successfully sent, or failed.
+     *  The result code will be <code>Activity.RESULT_OK</code> for success,
      *  or one of these errors:<br>
      *  <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
      *  <code>RESULT_ERROR_RADIO_OFF</code><br>
@@ -188,7 +188,7 @@
      *  the extra "errorCode" containing a radio technology specific value,
      *  generally only useful for troubleshooting.<br>
      *  The per-application based SMS control checks sentIntent. If sentIntent
-     *  is NULL the caller will be checked against all unknown applicaitons,
+     *  is NULL the caller will be checked against all unknown applications,
      *  which cause smaller number of SMS to be sent in checking period.
      * @param deliveryIntent if not NULL this <code>PendingIntent</code> is
      *  broadcast when the message is delivered to the recipient.  The
@@ -224,9 +224,6 @@
      * @return the default instance of the SmsManager
      */
     public static SmsManager getDefault() {
-        if (sInstance == null) {
-            sInstance = new SmsManager();
-        }
         return sInstance;
     }
 
diff --git a/telephony/java/android/telephony/SmsMessage.java b/telephony/java/android/telephony/SmsMessage.java
index a284ea5..d899430 100644
--- a/telephony/java/android/telephony/SmsMessage.java
+++ b/telephony/java/android/telephony/SmsMessage.java
@@ -20,7 +20,6 @@
 import android.util.Log;
 
 import com.android.internal.telephony.GsmAlphabet;
-import com.android.internal.telephony.EncodeException;
 import com.android.internal.telephony.SmsHeader;
 import com.android.internal.telephony.SmsMessageBase;
 import com.android.internal.telephony.SmsMessageBase.SubmitPduBase;
diff --git a/telephony/java/com/android/internal/telephony/CommandsInterface.java b/telephony/java/com/android/internal/telephony/CommandsInterface.java
index d90c305..9055203 100644
--- a/telephony/java/com/android/internal/telephony/CommandsInterface.java
+++ b/telephony/java/com/android/internal/telephony/CommandsInterface.java
@@ -27,8 +27,8 @@
  */
 public interface CommandsInterface {
     enum RadioState {
-        RADIO_OFF,         /* Radio explictly powered off (eg CFUN=0) */
-        RADIO_UNAVAILABLE, /* Radio unavailable (eg, resetting or not booted) */
+        RADIO_OFF,         /* Radio explicitly powered off (e.g. CFUN=0) */
+        RADIO_UNAVAILABLE, /* Radio unavailable (e.g. resetting or not booted) */
         SIM_NOT_READY,     /* Radio is on, but the SIM interface is not ready */
         SIM_LOCKED_OR_ABSENT,  /* SIM PIN locked, PUK required, network
                                personalization, or SIM absent */
@@ -121,7 +121,7 @@
     // See 27.007 +CCFC or +CLCK
     static final int SERVICE_CLASS_NONE     = 0; // no user input
     static final int SERVICE_CLASS_VOICE    = (1 << 0);
-    static final int SERVICE_CLASS_DATA     = (1 << 1); //synoym for 16+32+64+128
+    static final int SERVICE_CLASS_DATA     = (1 << 1); //synonym for 16+32+64+128
     static final int SERVICE_CLASS_FAX      = (1 << 2);
     static final int SERVICE_CLASS_SMS      = (1 << 3);
     static final int SERVICE_CLASS_DATA_SYNC = (1 << 4);
@@ -939,19 +939,19 @@
     void writeSmsToRuim(int status, String pdu, Message response);
 
     /**
-     * @deprecated
      * @param apn
      * @param user
      * @param password
      * @param response
      */
+    @Deprecated
     void setupDefaultPDP(String apn, String user, String password, Message response);
 
     /**
-     * @deprecated
      * @param cid
      * @param response
      */
+    @Deprecated
     void deactivateDefaultPDP(int cid, Message response);
 
     void setRadioPower(boolean on, Message response);
@@ -961,7 +961,7 @@
     void acknowledgeLastIncomingCdmaSms(boolean success, int cause, Message response);
 
     /**
-     * parameters equivilient to 27.007 AT+CRSM command
+     * parameters equivalent to 27.007 AT+CRSM command
      * response.obj will be an AsyncResult
      * response.obj.userObj will be a IccIoResult on success
      */
@@ -1066,7 +1066,7 @@
 
     /**
      * (AsyncResult)response.obj).result will be an Integer representing
-     * the sum of enabled serivice classes (sum of SERVICE_CLASS_*)
+     * the sum of enabled service classes (sum of SERVICE_CLASS_*)
      *
      * @param facility one of CB_FACILTY_*
      * @param password password or "" if not required
@@ -1139,7 +1139,7 @@
 
     /**
      * Request to enable/disable network state change notifications when
-     * location informateion (lac and/or cid) has changed.
+     * location information (lac and/or cid) has changed.
      *
      * @param enable true to enable, false to disable
      * @param response callback message
@@ -1170,7 +1170,7 @@
 
     /**
      * Indicates to the vendor ril that StkService is running
-     * rand is eady to receive RIL_UNSOL_STK_XXXX commands.
+     * and is ready to receive RIL_UNSOL_STK_XXXX commands.
      *
      * @param result callback message
      */
diff --git a/telephony/java/com/android/internal/telephony/GsmAlphabet.java b/telephony/java/com/android/internal/telephony/GsmAlphabet.java
index ebdd220..75ea116 100644
--- a/telephony/java/com/android/internal/telephony/GsmAlphabet.java
+++ b/telephony/java/com/android/internal/telephony/GsmAlphabet.java
@@ -16,14 +16,13 @@
 
 package com.android.internal.telephony;
 
-import android.telephony.SmsMessage;
 import android.util.SparseIntArray;
 
 import android.util.Log;
 
 /**
  * This class implements the character set mapping between
- * the GSM SMS 7-bit alphabet specifed in TS 23.038 6.2.1
+ * the GSM SMS 7-bit alphabet specified in TS 23.038 6.2.1
  * and UTF-16
  *
  * {@hide}
@@ -171,7 +170,7 @@
      * array cannot contain more than 255 septets.
      *
      * @param data The text string to encode.
-     * @param header Optional header (includeing length byte) that precedes
+     * @param header Optional header (including length byte) that precedes
      * the encoded data, padded to septet boundary.
      * @return Byte array containing header and encoded data.
      */
@@ -204,7 +203,7 @@
      * the packed septets. The returned array cannot contain more than 255
      * septets.
      *
-     * @param data the data string to endcode
+     * @param data the data string to encode
      * @throws EncodeException if String is too large to encode
      */
     public static byte[] stringToGsm7BitPacked(String data)
@@ -223,7 +222,7 @@
      *
      * @param data the text to convert to septets
      * @param startingSeptetOffset the number of padding septets to put before
-     *  the character data at the begining of the array
+     *  the character data at the beginning of the array
      * @param throwException If true, throws EncodeException on invalid char.
      *   If false, replaces unencodable char with GSM alphabet space char.
      *
@@ -257,7 +256,7 @@
     }
 
     /**
-     * Pack a 7-bit char into its appropirate place in a byte array
+     * Pack a 7-bit char into its appropriate place in a byte array
      *
      * @param bitOffset the bit offset that the septet should be packed at
      *                  (septet index * 7)
@@ -320,7 +319,7 @@
 
                 gsmVal = (0x7f & (pdu[offset + byteOffset] >> shift));
 
-                // if it crosses a byte boundry
+                // if it crosses a byte boundary
                 if (shift > 1) {
                     // set msb bits to 0
                     gsmVal &= 0x7f >> (shift - 1);
diff --git a/telephony/java/com/android/internal/telephony/IccSmsInterfaceManager.java b/telephony/java/com/android/internal/telephony/IccSmsInterfaceManager.java
index 8a5a6ae..5fef6de 100644
--- a/telephony/java/com/android/internal/telephony/IccSmsInterfaceManager.java
+++ b/telephony/java/com/android/internal/telephony/IccSmsInterfaceManager.java
@@ -57,7 +57,7 @@
      * @param destPort the port to deliver the message to
      * @param data the body of the message to send
      * @param sentIntent if not NULL this <code>PendingIntent</code> is
-     *  broadcast when the message is sucessfully sent, or failed.
+     *  broadcast when the message is successfully sent, or failed.
      *  The result code will be <code>Activity.RESULT_OK<code> for success,
      *  or one of these errors:<br>
      *  <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
@@ -67,7 +67,7 @@
      *  the extra "errorCode" containing a radio technology specific value,
      *  generally only useful for troubleshooting.<br>
      *  The per-application based SMS control checks sentIntent. If sentIntent
-     *  is NULL the caller will be checked against all unknown applicaitons,
+     *  is NULL the caller will be checked against all unknown applications,
      *  which cause smaller number of SMS to be sent in checking period.
      * @param deliveryIntent if not NULL this <code>PendingIntent</code> is
      *  broadcast when the message is delivered to the recipient.  The
@@ -94,7 +94,7 @@
      *  the current default SMSC
      * @param text the body of the message to send
      * @param sentIntent if not NULL this <code>PendingIntent</code> is
-     *  broadcast when the message is sucessfully sent, or failed.
+     *  broadcast when the message is successfully sent, or failed.
      *  The result code will be <code>Activity.RESULT_OK<code> for success,
      *  or one of these errors:<br>
      *  <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
@@ -140,7 +140,7 @@
      *   <code>RESULT_ERROR_RADIO_OFF</code>
      *   <code>RESULT_ERROR_NULL_PDU</code>.
      *  The per-application based SMS control checks sentIntent. If sentIntent
-     *  is NULL the caller will be checked against all unknown applicaitons,
+     *  is NULL the caller will be checked against all unknown applications,
      *  which cause smaller number of SMS to be sent in checking period.
      * @param deliveryIntents if not null, an <code>ArrayList</code> of
      *   <code>PendingIntent</code>s (one for each message part) that is
diff --git a/telephony/java/com/android/internal/telephony/IccUtils.java b/telephony/java/com/android/internal/telephony/IccUtils.java
index 71936f1..95bce13 100644
--- a/telephony/java/com/android/internal/telephony/IccUtils.java
+++ b/telephony/java/com/android/internal/telephony/IccUtils.java
@@ -265,9 +265,11 @@
 
 
     /**
-     * Converts a byte array into a String hexidecimal characters
+     * Converts a byte array into a String of hexadecimal characters.
      *
-     * null returns null
+     * @param bytes an array of bytes
+     *
+     * @return hex string representation of bytes array
      */
     public static String
     bytesToHexString(byte[] bytes) {
diff --git a/telephony/java/com/android/internal/telephony/RIL.java b/telephony/java/com/android/internal/telephony/RIL.java
index d8e313a..b942ad7 100644
--- a/telephony/java/com/android/internal/telephony/RIL.java
+++ b/telephony/java/com/android/internal/telephony/RIL.java
@@ -285,9 +285,8 @@
         }
 
 
-        //***** Handler implemementation
-
-        public void
+        //***** Handler implementation
+        @Override public void
         handleMessage(Message msg) {
             RILRequest rr = (RILRequest)(msg.obj);
             RILRequest req = null;
@@ -780,7 +779,7 @@
         send(rr);
     }
 
-    public void
+    @Deprecated public void
     getPDPContextList(Message result) {
         getDataCallList(result);
     }
diff --git a/telephony/java/com/android/internal/telephony/SMSDispatcher.java b/telephony/java/com/android/internal/telephony/SMSDispatcher.java
index 764d12e..fb58e14 100644
--- a/telephony/java/com/android/internal/telephony/SMSDispatcher.java
+++ b/telephony/java/com/android/internal/telephony/SMSDispatcher.java
@@ -142,7 +142,7 @@
 
     private SmsCounter mCounter;
 
-    private ArrayList mSTrackers = new ArrayList(MO_MSG_QUEUE_LIMIT);
+    private ArrayList<SmsTracker> mSTrackers = new ArrayList<SmsTracker>(MO_MSG_QUEUE_LIMIT);
 
     /** Wake lock to ensure device stays awake while dispatching the SMS intent. */
     private PowerManager.WakeLock mWakeLock;
@@ -264,6 +264,7 @@
         mCm.unregisterForOn(this);
     }
 
+    @Override
     protected void finalize() {
         Log.d(TAG, "SMSDispatcher finalized");
     }
@@ -344,7 +345,7 @@
             msg.obj = null;
             if (mSTrackers.isEmpty() == false) {
                 try {
-                    SmsTracker sTracker = (SmsTracker)mSTrackers.remove(0);
+                    SmsTracker sTracker = mSTrackers.remove(0);
                     sTracker.mSentIntent.send(RESULT_ERROR_LIMIT_EXCEEDED);
                 } catch (CanceledException ex) {
                     Log.e(TAG, "failed to send back RESULT_ERROR_LIMIT_EXCEEDED");
@@ -357,7 +358,7 @@
 
         case EVENT_SEND_CONFIRMED_SMS:
             if (mSTrackers.isEmpty() == false) {
-                SmsTracker sTracker = (SmsTracker)mSTrackers.remove(mSTrackers.size() - 1);
+                SmsTracker sTracker = mSTrackers.remove(mSTrackers.size() - 1);
                 if (isMultipartTracker(sTracker)) {
                     sendMultipartSms(sTracker);
                 } else {
@@ -371,7 +372,7 @@
             if (mSTrackers.isEmpty() == false) {
                 // Remove the latest one.
                 try {
-                    SmsTracker sTracker = (SmsTracker)mSTrackers.remove(mSTrackers.size() - 1);
+                    SmsTracker sTracker = mSTrackers.remove(mSTrackers.size() - 1);
                     sTracker.mSentIntent.send(RESULT_ERROR_LIMIT_EXCEEDED);
                 } catch (CanceledException ex) {
                     Log.e(TAG, "failed to send back RESULT_ERROR_LIMIT_EXCEEDED");
@@ -671,7 +672,7 @@
      * @param destPort the port to deliver the message to
      * @param data the body of the message to send
      * @param sentIntent if not NULL this <code>PendingIntent</code> is
-     *  broadcast when the message is sucessfully sent, or failed.
+     *  broadcast when the message is successfully sent, or failed.
      *  The result code will be <code>Activity.RESULT_OK<code> for success,
      *  or one of these errors:<br>
      *  <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
@@ -681,7 +682,7 @@
      *  the extra "errorCode" containing a radio technology specific value,
      *  generally only useful for troubleshooting.<br>
      *  The per-application based SMS control checks sentIntent. If sentIntent
-     *  is NULL the caller will be checked against all unknown applicaitons,
+     *  is NULL the caller will be checked against all unknown applications,
      *  which cause smaller number of SMS to be sent in checking period.
      * @param deliveryIntent if not NULL this <code>PendingIntent</code> is
      *  broadcast when the message is delivered to the recipient.  The
@@ -698,7 +699,7 @@
      *  the current default SMSC
      * @param text the body of the message to send
      * @param sentIntent if not NULL this <code>PendingIntent</code> is
-     *  broadcast when the message is sucessfully sent, or failed.
+     *  broadcast when the message is successfully sent, or failed.
      *  The result code will be <code>Activity.RESULT_OK<code> for success,
      *  or one of these errors:<br>
      *  <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
@@ -734,7 +735,7 @@
      *   <code>RESULT_ERROR_RADIO_OFF</code>
      *   <code>RESULT_ERROR_NULL_PDU</code>.
      *  The per-application based SMS control checks sentIntent. If sentIntent
-     *  is NULL the caller will be checked against all unknown applicaitons,
+     *  is NULL the caller will be checked against all unknown applications,
      *  which cause smaller number of SMS to be sent in checking period.
      * @param deliveryIntents if not null, an <code>ArrayList</code> of
      *   <code>PendingIntent</code>s (one for each message part) that is
@@ -750,17 +751,17 @@
      * Send a SMS
      *
      * @param smsc the SMSC to send the message through, or NULL for the
-     *  defatult SMSC
+     *  default SMSC
      * @param pdu the raw PDU to send
      * @param sentIntent if not NULL this <code>Intent</code> is
-     *  broadcast when the message is sucessfully sent, or failed.
+     *  broadcast when the message is successfully sent, or failed.
      *  The result code will be <code>Activity.RESULT_OK<code> for success,
      *  or one of these errors:
      *  <code>RESULT_ERROR_GENERIC_FAILURE</code>
      *  <code>RESULT_ERROR_RADIO_OFF</code>
      *  <code>RESULT_ERROR_NULL_PDU</code>.
      *  The per-application based SMS control checks sentIntent. If sentIntent
-     *  is NULL the caller will be checked against all unknown applicaitons,
+     *  is NULL the caller will be checked against all unknown applications,
      *  which cause smaller number of SMS to be sent in checking period.
      * @param deliveryIntent if not NULL this <code>Intent</code> is
      *  broadcast when the message is delivered to the recipient.  The
@@ -929,14 +930,14 @@
      */
     static protected class SmsTracker {
         // fields need to be public for derived SmsDispatchers
-        public HashMap mData;
+        public HashMap<String, Object> mData;
         public int mRetryCount;
         public int mMessageRef;
 
         public PendingIntent mSentIntent;
         public PendingIntent mDeliveryIntent;
 
-        SmsTracker(HashMap data, PendingIntent sentIntent,
+        SmsTracker(HashMap<String, Object> data, PendingIntent sentIntent,
                 PendingIntent deliveryIntent) {
             mData = data;
             mSentIntent = sentIntent;
@@ -945,7 +946,7 @@
         }
     }
 
-    protected SmsTracker SmsTrackerFactory(HashMap data, PendingIntent sentIntent,
+    protected SmsTracker SmsTrackerFactory(HashMap<String, Object> data, PendingIntent sentIntent,
             PendingIntent deliveryIntent) {
         return new SmsTracker(data, sentIntent, deliveryIntent);
     }
diff --git a/telephony/java/com/android/internal/telephony/SmsHeader.java b/telephony/java/com/android/internal/telephony/SmsHeader.java
index 7872eec..7a65162 100644
--- a/telephony/java/com/android/internal/telephony/SmsHeader.java
+++ b/telephony/java/com/android/internal/telephony/SmsHeader.java
@@ -30,7 +30,7 @@
  */
 public class SmsHeader {
 
-    // TODO(cleanup): this datastructure is generally referred to as
+    // TODO(cleanup): this data structure is generally referred to as
     // the 'user data header' or UDH, and so the class name should
     // change to reflect this...
 
diff --git a/telephony/java/com/android/internal/telephony/SmsMessageBase.java b/telephony/java/com/android/internal/telephony/SmsMessageBase.java
index af6c5f8..cbd8606 100644
--- a/telephony/java/com/android/internal/telephony/SmsMessageBase.java
+++ b/telephony/java/com/android/internal/telephony/SmsMessageBase.java
@@ -16,7 +16,6 @@
 
 package com.android.internal.telephony;
 
-import android.util.Log;
 import com.android.internal.telephony.SmsHeader;
 import java.util.Arrays;
 
@@ -366,13 +365,13 @@
     /**
      * Try to parse this message as an email gateway message
      * There are two ways specified in TS 23.040 Section 3.8 :
-     *  - SMS message "may have its TP-PID set for internet electronic mail - MT
+     *  - SMS message "may have its TP-PID set for Internet electronic mail - MT
      * SMS format: [<from-address><space>]<message> - "Depending on the
      * nature of the gateway, the destination/origination address is either
      * derived from the content of the SMS TP-OA or TP-DA field, or the
      * TP-OA/TP-DA field contains a generic gateway address and the to/from
      * address is added at the beginning as shown above." (which is supported here)
-     * - Multiple addreses separated by commas, no spaces, Subject field delimited
+     * - Multiple addresses separated by commas, no spaces, Subject field delimited
      * by '()' or '##' and '#' Section 9.2.3.24.11 (which are NOT supported here)
      */
     protected void extractEmailAddressFromMessageBody() {
diff --git a/telephony/java/com/android/internal/telephony/TelephonyProperties.java b/telephony/java/com/android/internal/telephony/TelephonyProperties.java
index a113787..136d5b1 100644
--- a/telephony/java/com/android/internal/telephony/TelephonyProperties.java
+++ b/telephony/java/com/android/internal/telephony/TelephonyProperties.java
@@ -45,7 +45,7 @@
      *  CDMA networks.
      */
     static final String PROPERTY_OPERATOR_ALPHA = "gsm.operator.alpha";
-    //TODO: most of these proprieties are generic, substitute gsm. with phone. bug 1856959
+    //TODO: most of these properties are generic, substitute gsm. with phone. bug 1856959
 
     /** Numeric name (MCC+MNC) of current registered operator.<p>
      *  Availability: when registered to a network. Result may be unreliable on
@@ -83,12 +83,12 @@
 
     /** The MCC+MNC (mobile country code+mobile network code) of the
      *  provider of the SIM. 5 or 6 decimal digits.
-     *  Availablity: SIM state must be "READY"
+     *  Availability: SIM state must be "READY"
      */
     static String PROPERTY_ICC_OPERATOR_NUMERIC = "gsm.sim.operator.numeric";
 
     /** PROPERTY_ICC_OPERATOR_ALPHA is also known as the SPN, or Service Provider Name.
-     *  Availablity: SIM state must be "READY"
+     *  Availability: SIM state must be "READY"
      */
     static String PROPERTY_ICC_OPERATOR_ALPHA = "gsm.sim.operator.alpha";
 
@@ -127,7 +127,7 @@
         "ro.telephony.call_ring.multiple";
 
     /**
-     * The number of milli-seconds between CALL_RING notifications.
+     * The number of milliseconds between CALL_RING notifications.
      */
     static final String PROPERTY_CALL_RING_DELAY = "ro.telephony.call_ring.delay";
 
diff --git a/telephony/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java b/telephony/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java
index ed93aea..1b08aed 100644
--- a/telephony/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java
+++ b/telephony/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java
@@ -51,7 +51,6 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
-import java.lang.Boolean;
 
 
 final class CdmaSMSDispatcher extends SMSDispatcher {
@@ -75,6 +74,7 @@
      * @param ar AsyncResult passed into the message handler.  ar.result should
      *           be a String representing the status report PDU, as ASCII hex.
      */
+    @Override
     protected void handleStatusReport(AsyncResult ar) {
         Log.d(TAG, "handleStatusReport is a special GSM function, should never be called in CDMA!");
     }
@@ -97,6 +97,7 @@
     }
 
     /** {@inheritDoc} */
+    @Override
     protected int dispatchMessage(SmsMessageBase smsb) {
 
         // If sms is null, means there was a parsing error.
@@ -176,7 +177,7 @@
          * TODO(cleanup): Why are we using a getter method for this
          * (and for so many other sms fields)?  Trivial getters and
          * setters like this are direct violations of the style guide.
-         * If the purpose is to protect agaist writes (by not
+         * If the purpose is to protect against writes (by not
          * providing a setter) then any protection is illusory (and
          * hence bad) for cases where the values are not primitives,
          * such as this call for the header.  Since this is an issue
@@ -340,6 +341,7 @@
     }
 
     /** {@inheritDoc} */
+    @Override
     protected void sendData(String destAddr, String scAddr, int destPort,
             byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent) {
         SmsMessage.SubmitPdu pdu = SmsMessage.getSubmitPdu(
@@ -348,6 +350,7 @@
     }
 
     /** {@inheritDoc} */
+    @Override
     protected void sendText(String destAddr, String scAddr, String text,
             PendingIntent sentIntent, PendingIntent deliveryIntent) {
         SmsMessage.SubmitPdu pdu = SmsMessage.getSubmitPdu(
@@ -356,6 +359,7 @@
     }
 
     /** {@inheritDoc} */
+    @Override
     protected void sendMultipartText(String destAddr, String scAddr,
             ArrayList<String> parts, ArrayList<PendingIntent> sentIntents,
             ArrayList<PendingIntent> deliveryIntents) {
@@ -364,7 +368,7 @@
          * TODO(cleanup): There is no real code difference between
          * this and the GSM version, and hence it should be moved to
          * the base class or consolidated somehow, provided calling
-         * the proper submitpdu stuff can be arranged.
+         * the proper submit pdu stuff can be arranged.
          */
 
         int refNumber = getNextConcatenatedRef() & 0x00FF;
@@ -437,8 +441,9 @@
     }
 
     /** {@inheritDoc} */
+    @Override
     protected void sendSms(SmsTracker tracker) {
-        HashMap map = tracker.mData;
+        HashMap<String, Object> map = tracker.mData;
 
         byte smsc[] = (byte[]) map.get("smsc");
         byte pdu[] = (byte[]) map.get("pdu");
@@ -449,11 +454,13 @@
     }
 
      /** {@inheritDoc} */
+    @Override
     protected void sendMultipartSms (SmsTracker tracker) {
         Log.d(TAG, "TODO: CdmaSMSDispatcher.sendMultipartSms not implemented");
     }
 
     /** {@inheritDoc} */
+    @Override
     protected void acknowledgeLastIncomingSms(boolean success, int result, Message response){
         // FIXME unit test leaves cm == null. this should change
 
@@ -474,16 +481,19 @@
     }
 
     /** {@inheritDoc} */
+    @Override
     protected void activateCellBroadcastSms(int activate, Message response) {
         mCm.setCdmaBroadcastActivation((activate == 0), response);
     }
 
     /** {@inheritDoc} */
+    @Override
     protected void getCellBroadcastSmsConfig(Message response) {
         mCm.getCdmaBroadcastConfig(response);
     }
 
     /** {@inheritDoc} */
+    @Override
     protected void setCellBroadcastConfig(int[] configValuesArray, Message response) {
         mCm.setCdmaBroadcastConfig(configValuesArray, response);
     }
diff --git a/telephony/java/com/android/internal/telephony/cdma/RuimSmsInterfaceManager.java b/telephony/java/com/android/internal/telephony/cdma/RuimSmsInterfaceManager.java
index cfcfd98..a9df375 100644
--- a/telephony/java/com/android/internal/telephony/cdma/RuimSmsInterfaceManager.java
+++ b/telephony/java/com/android/internal/telephony/cdma/RuimSmsInterfaceManager.java
@@ -67,8 +67,7 @@
                     ar = (AsyncResult)msg.obj;
                     synchronized (mLock) {
                         if (ar.exception == null) {
-                            mSms  = (List<SmsRawData>)
-                                    buildValidRawData((ArrayList<byte[]>) ar.result);
+                            mSms = buildValidRawData((ArrayList<byte[]>) ar.result);
                         } else {
                             if(DBG) log("Cannot load Sms records");
                             if (mSms != null)
diff --git a/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java b/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java
index 403b7a1..be6d27331 100755
--- a/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java
+++ b/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java
@@ -18,11 +18,8 @@
 
 import android.os.Parcel;
 import android.os.SystemProperties;
-import android.text.format.Time;
 import android.util.Config;
 import android.util.Log;
-import com.android.internal.telephony.EncodeException;
-import com.android.internal.telephony.GsmAlphabet;
 import com.android.internal.telephony.IccUtils;
 import com.android.internal.telephony.SmsHeader;
 import com.android.internal.telephony.SmsMessageBase;
@@ -33,7 +30,6 @@
 import com.android.internal.telephony.cdma.sms.UserData;
 import com.android.internal.util.HexDump;
 
-import java.io.BufferedInputStream;
 import java.io.BufferedOutputStream;
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
@@ -58,7 +54,7 @@
 /**
  * TODO(cleanup): internally returning null in many places makes
  * debugging very hard (among many other reasons) and should be made
- * more meaningful (replaced with execptions for example).  Null
+ * more meaningful (replaced with exceptions for example).  Null
  * returns should only occur at the very outside of the module/class
  * scope.
  */
@@ -287,7 +283,7 @@
      * @param destAddr              Address of the recipient.
      * @param message               String representation of the message payload.
      * @param statusReportRequested Indicates whether a report is requested for this message.
-     * @param headerData            Array containing the data for the User Data Header, preceded
+     * @param smsHeader             Array containing the data for the User Data Header, preceded
      *                              by the Element Identifiers.
      * @return a <code>SubmitPdu</code> containing the encoded SC
      *         address, if applicable, and the encoded message.
@@ -355,7 +351,7 @@
      * Get an SMS-SUBMIT PDU for a data message to a destination address &amp; port
      *
      * @param destAddr the address of the destination for the message
-     * @param userDara the data for the message
+     * @param userData the data for the message
      * @param statusReportRequested Indicates whether a report is requested for this message.
      * @return a <code>SubmitPdu</code> containing the encoded SC
      *         address, if applicable, and the encoded message.
@@ -614,7 +610,7 @@
      * incrementing within the range 1..65535 remembering the state
      * via a persistent system property.  (See C.S0015-B, v2.0,
      * 4.3.1.5) Since this routine is expected to be accessed via via
-     * binder-call, and hence should be threadsafe, it has been
+     * binder-call, and hence should be thread-safe, it has been
      * synchronized.
      */
     private synchronized static int getNextMessageId() {
diff --git a/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java b/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java
index c7032ac..e9fea55 100644
--- a/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java
+++ b/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java
@@ -21,7 +21,6 @@
 import static android.telephony.SmsMessage.MAX_USER_DATA_BYTES_WITH_HEADER;
 
 import android.util.Log;
-import android.util.SparseIntArray;
 
 import android.telephony.SmsMessage;
 
@@ -30,10 +29,8 @@
 import com.android.internal.telephony.IccUtils;
 import com.android.internal.telephony.GsmAlphabet;
 import com.android.internal.telephony.SmsHeader;
-import com.android.internal.telephony.cdma.sms.UserData;
 import com.android.internal.telephony.SmsMessageBase.TextEncodingDetails;
 
-import com.android.internal.util.HexDump;
 import com.android.internal.util.BitwiseInputStream;
 import com.android.internal.util.BitwiseOutputStream;
 
@@ -45,13 +42,13 @@
     private final static String LOG_TAG = "SMS";
 
     /**
-     * Bearer Data Subparameter Indentifiers
+     * Bearer Data Subparameter Identifiers
      * (See 3GPP2 C.S0015-B, v2.0, table 4.5-1)
      * NOTE: Commented subparameter types are not implemented.
      */
     private final static byte SUBPARAM_MESSAGE_IDENTIFIER               = 0x00;
     private final static byte SUBPARAM_USER_DATA                        = 0x01;
-    private final static byte SUBPARAM_USER_REPONSE_CODE                = 0x02;
+    private final static byte SUBPARAM_USER_RESPONSE_CODE               = 0x02;
     private final static byte SUBPARAM_MESSAGE_CENTER_TIME_STAMP        = 0x03;
     private final static byte SUBPARAM_VALIDITY_PERIOD_ABSOLUTE         = 0x04;
     private final static byte SUBPARAM_VALIDITY_PERIOD_RELATIVE         = 0x05;
@@ -405,7 +402,7 @@
     /**
      * Calculate the message text encoding length, fragmentation, and other details.
      *
-     * @param force ignore (but still count) illegal characters if true
+     * @param force7BitEncoding ignore (but still count) illegal characters if true
      * @return septet count, or -1 on failure
      */
     public static TextEncodingDetails calcTextEncodingDetails(CharSequence msg,
@@ -695,7 +692,7 @@
     /*
      * TODO(cleanup): CdmaSmsAddress encoding should make use of
      * CdmaSmsAddress.parse provided that DTMF encoding is unified,
-     * and the difference in 4bit vs 8bit is resolved.
+     * and the difference in 4-bit vs. 8-bit is resolved.
      */
 
     private static void encodeCdmaSmsAddress(CdmaSmsAddress addr) throws CodingException {
@@ -802,9 +799,9 @@
      * Create serialized representation for BearerData object.
      * (See 3GPP2 C.R1001-F, v1.0, section 4.5 for layout details)
      *
-     * @param bearerData an instance of BearerData.
+     * @param bData an instance of BearerData.
      *
-     * @return data byta array of raw encoded SMS bearer data.
+     * @return data byte array of raw encoded SMS bearer data.
      */
     public static byte[] encode(BearerData bData) {
         bData.hasUserDataHeader = ((bData.userData != null) &&
@@ -914,7 +911,7 @@
     private static String decodeUtf16(byte[] data, int offset, int numFields)
         throws CodingException
     {
-        // Start reading from the next 16-bit aligned boundry after offset.
+        // Start reading from the next 16-bit aligned boundary after offset.
         int padding = offset % 2;
         numFields -= (offset + padding) / 2;
         try {
@@ -960,7 +957,7 @@
     private static String decode7bitGsm(byte[] data, int offset, int numFields)
         throws CodingException
     {
-        // Start reading from the next 7-bit aligned boundry after offset.
+        // Start reading from the next 7-bit aligned boundary after offset.
         int offsetBits = offset * 8;
         int offsetSeptets = (offsetBits + 6) / 7;
         numFields -= offsetSeptets;
@@ -1553,7 +1550,7 @@
                 case SUBPARAM_USER_DATA:
                     decodeSuccess = decodeUserData(bData, inStream);
                     break;
-                case SUBPARAM_USER_REPONSE_CODE:
+                case SUBPARAM_USER_RESPONSE_CODE:
                     decodeSuccess = decodeUserResponseCode(bData, inStream);
                     break;
                 case SUBPARAM_REPLY_OPTION:
diff --git a/telephony/java/com/android/internal/telephony/cdma/sms/SmsEnvelope.java b/telephony/java/com/android/internal/telephony/cdma/sms/SmsEnvelope.java
index 0dcacc1..a67327a 100644
--- a/telephony/java/com/android/internal/telephony/cdma/sms/SmsEnvelope.java
+++ b/telephony/java/com/android/internal/telephony/cdma/sms/SmsEnvelope.java
@@ -17,7 +17,7 @@
 package com.android.internal.telephony.cdma.sms;
 
 
-public final class SmsEnvelope{
+public final class SmsEnvelope {
     /**
      * Message Types
      * (See 3GPP2 C.S0015-B 3.4.1)
diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java b/telephony/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java
index 6ae316d..fbc3d84 100644
--- a/telephony/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java
+++ b/telephony/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java
@@ -57,6 +57,7 @@
      * @param ar AsyncResult passed into the message handler.  ar.result should
      *           be a String representing the status report PDU, as ASCII hex.
      */
+    @Override
     protected void handleStatusReport(AsyncResult ar) {
         String pduString = (String) ar.result;
         SmsMessage sms = SmsMessage.newFromCDS(pduString);
@@ -85,6 +86,7 @@
 
 
     /** {@inheritDoc} */
+    @Override
     protected int dispatchMessage(SmsMessageBase smsb) {
 
         // If sms is null, means there was a parsing error.
@@ -145,6 +147,7 @@
     }
 
     /** {@inheritDoc} */
+    @Override
     protected void sendData(String destAddr, String scAddr, int destPort,
             byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent) {
         SmsMessage.SubmitPdu pdu = SmsMessage.getSubmitPdu(
@@ -153,6 +156,7 @@
     }
 
     /** {@inheritDoc} */
+    @Override
     protected void sendText(String destAddr, String scAddr, String text,
             PendingIntent sentIntent, PendingIntent deliveryIntent) {
         SmsMessage.SubmitPdu pdu = SmsMessage.getSubmitPdu(
@@ -161,6 +165,7 @@
     }
 
     /** {@inheritDoc} */
+    @Override
     protected void sendMultipartText(String destinationAddress, String scAddress,
             ArrayList<String> parts, ArrayList<PendingIntent> sentIntents,
             ArrayList<PendingIntent> deliveryIntents) {
@@ -300,8 +305,9 @@
     }
 
     /** {@inheritDoc} */
+    @Override
     protected void sendSms(SmsTracker tracker) {
-        HashMap map = tracker.mData;
+        HashMap<String, Object> map = tracker.mData;
 
         byte smsc[] = (byte[]) map.get("smsc");
         byte pdu[] = (byte[]) map.get("pdu");
@@ -316,12 +322,13 @@
      *
      * @param tracker holds the multipart Sms tracker ready to be sent
      */
+    @Override
     protected void sendMultipartSms (SmsTracker tracker) {
         ArrayList<String> parts;
         ArrayList<PendingIntent> sentIntents;
         ArrayList<PendingIntent> deliveryIntents;
 
-        HashMap map = tracker.mData;
+        HashMap<String, Object> map = tracker.mData;
 
         String destinationAddress = (String) map.get("destination");
         String scAddress = (String) map.get("scaddress");
@@ -336,6 +343,7 @@
     }
 
     /** {@inheritDoc} */
+    @Override
     protected void acknowledgeLastIncomingSms(boolean success, int result, Message response){
         // FIXME unit test leaves cm == null. this should change
         if (mCm != null) {
@@ -344,6 +352,7 @@
     }
 
     /** {@inheritDoc} */
+    @Override
     protected void activateCellBroadcastSms(int activate, Message response) {
         // Unless CBS is implemented for GSM, this point should be unreachable.
         Log.e(TAG, "Error! The functionality cell broadcast sms is not implemented for GSM.");
@@ -351,6 +360,7 @@
     }
 
     /** {@inheritDoc} */
+    @Override
     protected void getCellBroadcastSmsConfig(Message response){
         // Unless CBS is implemented for GSM, this point should be unreachable.
         Log.e(TAG, "Error! The functionality cell broadcast sms is not implemented for GSM.");
@@ -358,6 +368,7 @@
     }
 
     /** {@inheritDoc} */
+    @Override
     protected  void setCellBroadcastConfig(int[] configValuesArray, Message response) {
         // Unless CBS is implemented for GSM, this point should be unreachable.
         Log.e(TAG, "Error! The functionality cell broadcast sms is not implemented for GSM.");
diff --git a/telephony/java/com/android/internal/telephony/gsm/SimSmsInterfaceManager.java b/telephony/java/com/android/internal/telephony/gsm/SimSmsInterfaceManager.java
index 2028ca4..f000d79 100644
--- a/telephony/java/com/android/internal/telephony/gsm/SimSmsInterfaceManager.java
+++ b/telephony/java/com/android/internal/telephony/gsm/SimSmsInterfaceManager.java
@@ -86,6 +86,7 @@
     public void dispose() {
     }
 
+    @Override
     protected void finalize() {
         try {
             super.finalize();
@@ -191,6 +192,7 @@
         return mSms;
     }
 
+    @Override
     protected void log(String msg) {
         Log.d(LOG_TAG, "[SimSmsInterfaceManager] " + msg);
     }
diff --git a/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java b/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java
index d627baf..4eb667c 100644
--- a/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java
+++ b/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java
@@ -26,7 +26,6 @@
 import com.android.internal.telephony.GsmAlphabet;
 import com.android.internal.telephony.SmsHeader;
 import com.android.internal.telephony.SmsMessageBase;
-import com.android.internal.telephony.SmsMessageBase.TextEncodingDetails;
 
 import java.io.ByteArrayOutputStream;
 import java.io.UnsupportedEncodingException;
@@ -45,7 +44,7 @@
  * A Short Message Service message.
  *
  */
-public class SmsMessage extends SmsMessageBase{
+public class SmsMessage extends SmsMessageBase {
     static final String LOG_TAG = "GSM";
 
     private MessageClass messageClass;
@@ -303,7 +302,7 @@
             // the receiver's SIM card. You can then send messages to yourself
             // (on a phone with this change) and they'll end up on the SIM card.
             bo.write(0x00);
-        } else { //assume UCS-2
+        } else { // assume UCS-2
             if ((0xff & userData[0]) > MAX_USER_DATA_BYTES) {
                 // Message too long
                 return null;
@@ -369,7 +368,7 @@
      * @param destinationAddress the address of the destination for the message
      * @param destinationPort the port to deliver the message to at the
      *        destination
-     * @param data the dat for the message
+     * @param data the data for the message
      * @return a <code>SubmitPdu</code> containing the encoded SC
      *         address, if applicable, and the encoded message.
      *         Returns null on encode error.
@@ -474,7 +473,7 @@
         return bo;
     }
 
-    static class PduParser {
+    private static class PduParser {
         byte pdu[];
         int cur;
         SmsHeader userDataHeader;
@@ -482,10 +481,6 @@
         int mUserDataSeptetPadding;
         int mUserDataSize;
 
-        PduParser(String s) {
-            this(IccUtils.hexStringToBytes(s));
-        }
-
         PduParser(byte[] pdu) {
             this.pdu = pdu;
             cur = 0;
@@ -537,7 +532,7 @@
             GsmSmsAddress ret;
 
             // "The Address-Length field is an integer representation of
-            // the number field, i.e. excludes any semi octet containing only
+            // the number field, i.e. excludes any semi-octet containing only
             // fill bits."
             // The TOA field is not included as part of this
             int addressLength = pdu[cur] & 0xff;
@@ -565,7 +560,7 @@
             int second = IccUtils.gsmBcdByteToInt(pdu[cur++]);
 
             // For the timezone, the most significant bit of the
-            // least signficant nibble is the sign byte
+            // least significant nibble is the sign byte
             // (meaning the max range of this field is 79 quarter-hours,
             // which is more than enough)
 
@@ -624,7 +619,7 @@
                 /*
                  * Here we just create the user data length to be the remainder of
                  * the pdu minus the user data header, since userDataLength means
-                 * the number of uncompressed sepets.
+                 * the number of uncompressed septets.
                  */
                 bufferLen = pdu.length - offset;
             } else {
@@ -663,10 +658,10 @@
         }
 
         /**
-         * Returns the number of padding bits at the begining of the user data
+         * Returns the number of padding bits at the beginning of the user data
          * array before the start of the septets.
          *
-         * @return the number of padding bits at the begining of the user data
+         * @return the number of padding bits at the beginning of the user data
          * array before the start of the septets
          */
         int getUserDataSeptetPadding() {
@@ -686,7 +681,7 @@
         XXX Not sure what this one is supposed to be doing, and no one is using
         it.
         String getUserDataGSM8bit() {
-            // System.out.println("remainder of pud:" +
+            // System.out.println("remainder of pdu:" +
             // HexDump.dumpHexString(pdu, cur, pdu.length - cur));
             int count = pdu[cur++] & 0xff;
             int size = pdu[cur++];
@@ -817,11 +812,13 @@
     }
 
     /** {@inheritDoc} */
+    @Override
     public int getProtocolIdentifier() {
         return protocolIdentifier;
     }
 
     /** {@inheritDoc} */
+    @Override
     public boolean isReplace() {
         return (protocolIdentifier & 0xc0) == 0x40
                 && (protocolIdentifier & 0x3f) > 0
@@ -829,12 +826,14 @@
     }
 
     /** {@inheritDoc} */
+    @Override
     public boolean isCphsMwiMessage() {
         return ((GsmSmsAddress) originatingAddress).isCphsVoiceMessageClear()
                 || ((GsmSmsAddress) originatingAddress).isCphsVoiceMessageSet();
     }
 
     /** {@inheritDoc} */
+    @Override
     public boolean isMWIClearMessage() {
         if (isMwi && (mwiSense == false)) {
             return true;
@@ -845,6 +844,7 @@
     }
 
     /** {@inheritDoc} */
+    @Override
     public boolean isMWISetMessage() {
         if (isMwi && (mwiSense == true)) {
             return true;
@@ -855,6 +855,7 @@
     }
 
     /** {@inheritDoc} */
+    @Override
     public boolean isMwiDontStore() {
         if (isMwi && mwiDontStore) {
             return true;
@@ -874,31 +875,34 @@
     }
 
     /** {@inheritDoc} */
+    @Override
     public int getStatus() {
         return status;
     }
 
     /** {@inheritDoc} */
+    @Override
     public boolean isStatusReportMessage() {
         return isStatusReportMessage;
     }
 
     /** {@inheritDoc} */
+    @Override
     public boolean isReplyPathPresent() {
         return replyPathPresent;
     }
 
     /**
-     * TS 27.005 3.1, <pdu> definition "In the case of SMS: 3GPP TS 24.011 [6]
+     * TS 27.005 3.1, &lt;pdu&gt; definition "In the case of SMS: 3GPP TS 24.011 [6]
      * SC address followed by 3GPP TS 23.040 [3] TPDU in hexadecimal format:
      * ME/TA converts each octet of TP data unit into two IRA character long
-     * hexad number (e.g. octet with integer value 42 is presented to TE as two
+     * hex number (e.g. octet with integer value 42 is presented to TE as two
      * characters 2A (IRA 50 and 65))" ...in the case of cell broadcast,
      * something else...
      */
     private void parsePdu(byte[] pdu) {
         mPdu = pdu;
-        // Log.d(LOG_TAG, "raw sms mesage:");
+        // Log.d(LOG_TAG, "raw sms message:");
         // Log.d(LOG_TAG, s);
 
         PduParser p = new PduParser(pdu);
@@ -1150,6 +1154,7 @@
     /**
      * {@inheritDoc}
      */
+    @Override
     public MessageClass getMessageClass() {
         return messageClass;
     }
diff --git a/telephony/java/com/android/internal/telephony/test/SimulatedCommands.java b/telephony/java/com/android/internal/telephony/test/SimulatedCommands.java
index 11b3fd6..3675e78 100644
--- a/telephony/java/com/android/internal/telephony/test/SimulatedCommands.java
+++ b/telephony/java/com/android/internal/telephony/test/SimulatedCommands.java
@@ -16,7 +16,6 @@
 
 package com.android.internal.telephony.test;
 
-
 import android.os.AsyncResult;
 import android.os.HandlerThread;
 import android.os.Looper;
@@ -27,7 +26,6 @@
 import com.android.internal.telephony.CommandException;
 import com.android.internal.telephony.CommandsInterface;
 import com.android.internal.telephony.DataCallState;
-import com.android.internal.telephony.IccCard;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.gsm.CallFailCause;
 import com.android.internal.telephony.gsm.SmsBroadcastConfigInfo;
@@ -334,7 +332,7 @@
 
     /**
      * (AsyncResult)response.obj).result will be an Integer representing
-     * the sum of enabled serivice classes (sum of SERVICE_CLASS_*)
+     * the sum of enabled service classes (sum of SERVICE_CLASS_*)
      *
      * @param facility one of CB_FACILTY_*
      * @param pin password or "" if not required
@@ -440,7 +438,7 @@
      *  returned message
      *  retMsg.obj = AsyncResult ar
      *  ar.exception carries exception on failure
-     *  ar.userObject contains the orignal value of result.obj
+     *  ar.userObject contains the original value of result.obj
      *  ar.result contains a List of DriverCall
      *      The ar.result List is sorted by DriverCall.index
      */
@@ -467,7 +465,7 @@
      *  returned message
      *  retMsg.obj = AsyncResult ar
      *  ar.exception carries exception on failure
-     *  ar.userObject contains the orignal value of result.obj
+     *  ar.userObject contains the original value of result.obj
      *  ar.result contains a List of DataCallState
      */
     public void getDataCallList(Message result) {
@@ -478,7 +476,7 @@
      *  returned message
      *  retMsg.obj = AsyncResult ar
      *  ar.exception carries exception on failure
-     *  ar.userObject contains the orignal value of result.obj
+     *  ar.userObject contains the original value of result.obj
      *  ar.result is null on success and failure
      *
      * CLIR_DEFAULT     == on "use subscription default value"
@@ -495,7 +493,7 @@
      *  returned message
      *  retMsg.obj = AsyncResult ar
      *  ar.exception carries exception on failure
-     *  ar.userObject contains the orignal value of result.obj
+     *  ar.userObject contains the original value of result.obj
      *  ar.result is String containing IMSI on success
      */
     public void getIMSI(Message result) {
@@ -506,7 +504,7 @@
      *  returned message
      *  retMsg.obj = AsyncResult ar
      *  ar.exception carries exception on failure
-     *  ar.userObject contains the orignal value of result.obj
+     *  ar.userObject contains the original value of result.obj
      *  ar.result is String containing IMEI on success
      */
     public void getIMEI(Message result) {
@@ -517,7 +515,7 @@
      *  returned message
      *  retMsg.obj = AsyncResult ar
      *  ar.exception carries exception on failure
-     *  ar.userObject contains the orignal value of result.obj
+     *  ar.userObject contains the original value of result.obj
      *  ar.result is String containing IMEISV on success
      */
     public void getIMEISV(Message result) {
@@ -529,7 +527,7 @@
      *  returned message
      *  retMsg.obj = AsyncResult ar
      *  ar.exception carries exception on failure
-     *  ar.userObject contains the orignal value of result.obj
+     *  ar.userObject contains the original value of result.obj
      *  ar.result is null on success and failure
      *
      *  3GPP 22.030 6.5.5
@@ -554,7 +552,7 @@
      *  "Releases all held calls or sets User Determined User Busy (UDUB)
      *   for a waiting call."
      *  ar.exception carries exception on failure
-     *  ar.userObject contains the orignal value of result.obj
+     *  ar.userObject contains the original value of result.obj
      *  ar.result is null on success and failure
      */
     public void hangupWaitingOrBackground (Message result) {
@@ -575,7 +573,7 @@
      *  the other (held or waiting) call."
      *
      *  ar.exception carries exception on failure
-     *  ar.userObject contains the orignal value of result.obj
+     *  ar.userObject contains the original value of result.obj
      *  ar.result is null on success and failure
      */
     public void hangupForegroundResumeBackground (Message result) {
@@ -596,7 +594,7 @@
      *  the other (held or waiting) call."
      *
      *  ar.exception carries exception on failure
-     *  ar.userObject contains the orignal value of result.obj
+     *  ar.userObject contains the original value of result.obj
      *  ar.result is null on success and failure
      */
     public void switchWaitingOrHoldingAndActive (Message result) {
@@ -616,7 +614,7 @@
      * "Adds a held call to the conversation"
      *
      *  ar.exception carries exception on failure
-     *  ar.userObject contains the orignal value of result.obj
+     *  ar.userObject contains the original value of result.obj
      *  ar.result is null on success and failure
      */
     public void conference (Message result) {
@@ -636,7 +634,7 @@
      * "Connects the two calls and disconnects the subscriber from both calls"
      *
      *  ar.exception carries exception on failure
-     *  ar.userObject contains the orignal value of result.obj
+     *  ar.userObject contains the original value of result.obj
      *  ar.result is null on success and failure
      */
     public void explicitCallTransfer (Message result) {
@@ -672,7 +670,7 @@
     /**
      *
      *  ar.exception carries exception on failure
-     *  ar.userObject contains the orignal value of result.obj
+     *  ar.userObject contains the original value of result.obj
      *  ar.result is null on success and failure
      */
     public void acceptCall (Message result) {
@@ -690,7 +688,7 @@
     /**
      *  also known as UDUB
      *  ar.exception carries exception on failure
-     *  ar.userObject contains the orignal value of result.obj
+     *  ar.userObject contains the original value of result.obj
      *  ar.result is null on success and failure
      */
     public void rejectCall (Message result) {
@@ -767,7 +765,7 @@
      *
      * @param result is callback message
      *        ((AsyncResult)response.obj).result  is an int[] with every
-     *        element representing one avialable BM_*_BAND
+     *        element representing one available BM_*_BAND
      */
     public void queryAvailableBandMode (Message result) {
         int ret[] = new int [4];
@@ -826,7 +824,6 @@
         ret[11] = null;
         ret[12] = null;
         ret[13] = null;
-        ret[14] = null;
 
         resultSuccess(result, ret);
     }
@@ -877,7 +874,7 @@
 
     /**
      *  ar.exception carries exception on failure
-     *  ar.userObject contains the orignal value of result.obj
+     *  ar.userObject contains the original value of result.obj
      *  ar.result is null on success and failure
      */
     public void sendDtmf(char c, Message result) {
@@ -886,7 +883,7 @@
 
     /**
      *  ar.exception carries exception on failure
-     *  ar.userObject contains the orignal value of result.obj
+     *  ar.userObject contains the original value of result.obj
      *  ar.result is null on success and failure
      */
     public void startDtmf(char c, Message result) {
@@ -895,7 +892,7 @@
 
     /**
      *  ar.exception carries exception on failure
-     *  ar.userObject contains the orignal value of result.obj
+     *  ar.userObject contains the original value of result.obj
      *  ar.result is null on success and failure
      */
     public void stopDtmf(Message result) {
@@ -904,7 +901,7 @@
 
     /**
      *  ar.exception carries exception on failure
-     *  ar.userObject contains the orignal value of result.obj
+     *  ar.userObject contains the original value of result.obj
      *  ar.result is null on success and failure
      */
     public void sendBurstDtmf(String dtmfString, int on, int off, Message result) {
@@ -939,6 +936,7 @@
         unimplemented(response);
     }
 
+    @Deprecated
     public void setupDefaultPDP(String apn, String user, String password, Message result) {
         unimplemented(result);
     }
@@ -950,9 +948,7 @@
 
     public void deactivateDataCall(int cid, Message result) {unimplemented(result);}
 
-    /**
-     * @deprecated
-     */
+    @Deprecated
     public void deactivateDefaultPDP(int cid, Message result) {unimplemented(result);}
 
     public void setPreferredNetworkType(int networkType , Message result) {
@@ -1029,7 +1025,7 @@
     }
 
     /**
-     * parameters equivilient to 27.007 AT+CRSM command
+     * parameters equivalent to 27.007 AT+CRSM command
      * response.obj will be an AsyncResult
      * response.obj.userObj will be a SimIoResult on success
      */
diff --git a/telephony/tests/telephonytests/src/com/android/internal/telephony/GsmAlphabetTest.java b/telephony/tests/telephonytests/src/com/android/internal/telephony/GsmAlphabetTest.java
index 3a9c511..a6b9a2a 100644
--- a/telephony/tests/telephonytests/src/com/android/internal/telephony/GsmAlphabetTest.java
+++ b/telephony/tests/telephonytests/src/com/android/internal/telephony/GsmAlphabetTest.java
@@ -51,7 +51,7 @@
         // '@' maps to char 0
         assertEquals(0, GsmAlphabet.charToGsm('@'));
 
-        // `a (a with grave accent) maps to last GSM charater
+        // `a (a with grave accent) maps to last GSM character
         assertEquals(0x7f, GsmAlphabet.charToGsm('\u00e0'));
 
         //
diff --git a/telephony/tests/telephonytests/src/com/android/internal/telephony/GsmSmsTest.java b/telephony/tests/telephonytests/src/com/android/internal/telephony/GsmSmsTest.java
index 3103fc1..215c6ce 100644
--- a/telephony/tests/telephonytests/src/com/android/internal/telephony/GsmSmsTest.java
+++ b/telephony/tests/telephonytests/src/com/android/internal/telephony/GsmSmsTest.java
@@ -24,8 +24,6 @@
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
 
-import android.util.Log;
-
 public class GsmSmsTest extends AndroidTestCase {
 
     @SmallTest
diff --git a/telephony/tests/telephonytests/src/android/telephony/PhoneNumberUtilsTest.java b/telephony/tests/telephonytests/src/com/android/internal/telephony/PhoneNumberUtilsTest.java
similarity index 100%
rename from telephony/tests/telephonytests/src/android/telephony/PhoneNumberUtilsTest.java
rename to telephony/tests/telephonytests/src/com/android/internal/telephony/PhoneNumberUtilsTest.java
diff --git a/telephony/tests/telephonytests/src/android/telephony/PhoneNumberWatcherTest.java b/telephony/tests/telephonytests/src/com/android/internal/telephony/PhoneNumberWatcherTest.java
similarity index 100%
rename from telephony/tests/telephonytests/src/android/telephony/PhoneNumberWatcherTest.java
rename to telephony/tests/telephonytests/src/com/android/internal/telephony/PhoneNumberWatcherTest.java
diff --git a/telephony/tests/telephonytests/src/com/android/internal/telephony/gsm/GSMPhoneTest.java b/telephony/tests/telephonytests/src/com/android/internal/telephony/gsm/GSMPhoneTest.java
index b96743a..485542b 100644
--- a/telephony/tests/telephonytests/src/com/android/internal/telephony/gsm/GSMPhoneTest.java
+++ b/telephony/tests/telephonytests/src/com/android/internal/telephony/gsm/GSMPhoneTest.java
@@ -18,27 +18,21 @@
 
 import android.os.AsyncResult;
 import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Looper;
 import android.os.Message;
-import android.os.Process;
 import android.telephony.ServiceState;
 import android.test.AndroidTestCase;
 import android.test.PerformanceTestCase;
-import android.util.Log;
 
 import com.android.internal.telephony.Call;
 import com.android.internal.telephony.CallStateException;
 import com.android.internal.telephony.Connection;
 import com.android.internal.telephony.MmiCode;
 import com.android.internal.telephony.Phone;
-import com.android.internal.telephony.TestPhoneNotifier;
 import com.android.internal.telephony.gsm.CallFailCause;
 import com.android.internal.telephony.gsm.GSMPhone;
 import com.android.internal.telephony.gsm.GSMTestHandler;
 import com.android.internal.telephony.gsm.GsmMmiCode;
 import com.android.internal.telephony.gsm.SuppServiceNotification;
-import com.android.internal.telephony.test.SimulatedCommands;
 import com.android.internal.telephony.test.SimulatedRadioControl;
 
 import java.util.List;
diff --git a/test-runner/src/android/test/suitebuilder/TestGrouping.java b/test-runner/src/android/test/suitebuilder/TestGrouping.java
index df6da70..a2b94ff 100644
--- a/test-runner/src/android/test/suitebuilder/TestGrouping.java
+++ b/test-runner/src/android/test/suitebuilder/TestGrouping.java
@@ -46,6 +46,8 @@
  */
 public class TestGrouping {
 
+    private static final String LOG_TAG = "TestGrouping";
+
     SortedSet<Class<? extends TestCase>> testCaseClasses;
 
     public static final Comparator<Class<? extends TestCase>> SORT_BY_SIMPLE_NAME
@@ -114,7 +116,7 @@
         for (String packageName : packageNames) {
             List<Class<? extends TestCase>> addedClasses = testCaseClassesInPackage(packageName);
             if (addedClasses.isEmpty()) {
-                Log.w("TestGrouping", "Invalid Package: '" + packageName
+                Log.w(LOG_TAG, "Invalid Package: '" + packageName
                         + "' could not be found or has no tests");
             }
             testCaseClasses.addAll(addedClasses);
@@ -234,6 +236,10 @@
                     }
                 }
             }
+            Log.i(LOG_TAG, String.format(
+                    "TestCase class %s is missing a public constructor with no parameters " +
+                    "or a single String parameter - skipping",
+                    aClass.getName()));
             return false;
         }
     }
diff --git a/test-runner/tests/src/android/test/suitebuilder/TestGroupingTest.java b/test-runner/tests/src/android/test/suitebuilder/TestGroupingTest.java
new file mode 100644
index 0000000..f4477d1
--- /dev/null
+++ b/test-runner/tests/src/android/test/suitebuilder/TestGroupingTest.java
@@ -0,0 +1,47 @@
+/*
+ * 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.test.suitebuilder;
+
+import java.util.List;
+
+import junit.framework.TestCase;
+
+/**
+ * Unit tests for {@link TestGrouping}
+ */
+public class TestGroupingTest extends TestCase {
+
+    private TestGrouping mGrouping;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mGrouping = new TestGrouping(TestGrouping.SORT_BY_SIMPLE_NAME);
+    }
+
+    /**
+     * Verifies that TestCases with no public constructor are not loaded.
+     * Relies on fixture classes in android.test.suitebuilder.examples.constructor
+     */
+    public void testGetTests_noPublicConstructor() {
+        mGrouping.addPackagesRecursive("android.test.suitebuilder.examples.constructor");
+        List<TestMethod> tests = mGrouping.getTests();
+        // only the PublicConstructorTest's test method should be present
+        assertEquals(1, tests.size());
+        assertEquals("testPublicConstructor", tests.get(0).getName());
+    }
+}
diff --git a/test-runner/tests/src/android/test/suitebuilder/examples/constructor/NoPublicConstructorTest.java b/test-runner/tests/src/android/test/suitebuilder/examples/constructor/NoPublicConstructorTest.java
new file mode 100644
index 0000000..d7909a1
--- /dev/null
+++ b/test-runner/tests/src/android/test/suitebuilder/examples/constructor/NoPublicConstructorTest.java
@@ -0,0 +1,35 @@
+/*
+ * 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.test.suitebuilder.examples.constructor;
+
+import junit.framework.TestCase;
+
+/**
+ * A {@link TestCase} which should not be loaded since it has non-public constructors with no args.
+ */
+public class NoPublicConstructorTest extends TestCase {
+
+    NoPublicConstructorTest() {
+    }
+
+    public NoPublicConstructorTest(String foo, String foo2) {
+    }
+
+    public void testNotRun() {
+        fail("method in NoPublicConstructorTest run unexpectedly");
+    }
+}
diff --git a/test-runner/tests/src/android/test/suitebuilder/examples/constructor/ProtectedConstructorTest.java b/test-runner/tests/src/android/test/suitebuilder/examples/constructor/ProtectedConstructorTest.java
new file mode 100644
index 0000000..d2862fd
--- /dev/null
+++ b/test-runner/tests/src/android/test/suitebuilder/examples/constructor/ProtectedConstructorTest.java
@@ -0,0 +1,33 @@
+/*
+ * 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.test.suitebuilder.examples.constructor;
+
+import junit.framework.TestCase;
+
+/**
+ * A protected constructor test case that should not be loaded.
+ */
+public class ProtectedConstructorTest extends TestCase {
+
+    protected ProtectedConstructorTest() {
+    }
+
+    public void testNotRun() {
+        fail("method in ProtectedConstructorTest run unexpectedly");
+    }
+
+}
diff --git a/graphics/java/android/renderscript/Vector2f.java b/test-runner/tests/src/android/test/suitebuilder/examples/constructor/PublicConstructorTest.java
similarity index 65%
copy from graphics/java/android/renderscript/Vector2f.java
copy to test-runner/tests/src/android/test/suitebuilder/examples/constructor/PublicConstructorTest.java
index 567d57fa..a11e25d 100644
--- a/graphics/java/android/renderscript/Vector2f.java
+++ b/test-runner/tests/src/android/test/suitebuilder/examples/constructor/PublicConstructorTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2009 The Android Open Source Project
+ * 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.
@@ -14,24 +14,15 @@
  * limitations under the License.
  */
 
-package android.renderscript;
+package android.test.suitebuilder.examples.constructor;
 
-import java.lang.Math;
-import android.util.Log;
-
+import junit.framework.TestCase;
 
 /**
- * @hide
- *
- **/
-public class Vector2f {
-    public Vector2f() {
+ * A public constructor test case that should be loaded.
+ */
+public class PublicConstructorTest extends TestCase {
+
+    public void testPublicConstructor() {
     }
-
-    public float x;
-    public float y;
 }
-
-
-
-
diff --git a/tests/DumpRenderTree/assets/run_layout_tests.py b/tests/DumpRenderTree/assets/run_layout_tests.py
index b6e7bf3..ceac5d2 100755
--- a/tests/DumpRenderTree/assets/run_layout_tests.py
+++ b/tests/DumpRenderTree/assets/run_layout_tests.py
@@ -176,7 +176,7 @@
   # Count crashed tests.
   crashed_tests = []
 
-  timeout_ms = '30000'
+  timeout_ms = '15000'
   if options.time_out_ms:
     timeout_ms = options.time_out_ms
 
diff --git a/tests/DumpRenderTree/src/com/android/dumprendertree/FileFilter.java b/tests/DumpRenderTree/src/com/android/dumprendertree/FileFilter.java
index 77fd3ed..fcb37a0 100644
--- a/tests/DumpRenderTree/src/com/android/dumprendertree/FileFilter.java
+++ b/tests/DumpRenderTree/src/com/android/dumprendertree/FileFilter.java
@@ -79,19 +79,22 @@
     };
 
     static void fillIgnoreResultList() {
-        // This first block of tests are for HTML5 features, for which Android
+        // This first block of tests are for features for which Android
         // should pass all tests. They are skipped only temporarily.
         // TODO: Fix these failing tests and remove them from this list.
+        ignoreResultList.add("fast/events/touch/basic-multi-touch-events.html"); // Requires multi-touch
+        ignoreResultList.add("fast/events/touch/basic-single-touch-events.html"); // Delta of touch move is not great enough for Android. Fixed upstream in WebKit change 58208.
+        ignoreResultList.add("fast/events/touch/send-oncancel-event.html"); // Expected output is incorrect. Fixed upstream in WebKit change 58058.
+        ignoreResultList.add("fast/events/touch/touch-target.html"); // Requires multi-touch
         ignoreResultList.add("http/tests/appcache/empty-manifest.html"); // flaky
         ignoreResultList.add("http/tests/appcache/foreign-iframe-main.html"); // flaky - skips states
         ignoreResultList.add("http/tests/appcache/manifest-with-empty-file.html"); // flaky
         ignoreResultList.add("storage/database-lock-after-reload.html"); // Succeeds but DumpRenderTree does not read result correctly
         ignoreResultList.add("storage/hash-change-with-xhr.html"); // Succeeds but DumpRenderTree does not read result correctly
 
-        // Will always fail
-        ignoreResultList.add("dom/svg/level3/xpath"); // XPath not supported
+        // Expected failures due to unsupported features.
+        ignoreResultList.add("fast/events/touch/touch-coords-in-zoom-and-scroll.html"); // Requires eventSender.zoomPageIn(),zoomPageOut()
         ignoreResultList.add("fast/workers"); // workers not supported
-        ignoreResultList.add("fast/xpath"); // XPath not supported
         ignoreResultList.add("http/tests/eventsource/workers"); // workers not supported
         ignoreResultList.add("http/tests/workers"); // workers not supported
         ignoreResultList.add("http/tests/xmlhttprequest/workers"); // workers not supported
@@ -104,12 +107,9 @@
         ignoreResultList.add("fast/css/case-transform.html"); // will not fix #619707
         ignoreResultList.add("fast/dom/Element/offsetLeft-offsetTop-body-quirk.html"); // different screen size result in extra spaces in Apple compared to us
         ignoreResultList.add("fast/dom/Window/Plug-ins.html"); // need test plugin
-        ignoreResultList.add("fast/dom/Window/window-properties.html"); // xslt and xpath elements missing from property list
         ignoreResultList.add("fast/dom/Window/window-screen-properties.html"); // pixel depth
         ignoreResultList.add("fast/dom/Window/window-xy-properties.html"); // requires eventSender.mouseDown(),mouseUp()
         ignoreResultList.add("fast/dom/attribute-namespaces-get-set.html"); // http://b/733229
-        ignoreResultList.add("fast/dom/gc-9.html"); // requires xpath support
-        ignoreResultList.add("fast/dom/global-constructors.html"); // requires xslt and xpath support
         ignoreResultList.add("fast/dom/object-embed-plugin-scripting.html"); // dynamic plugins not supported
         ignoreResultList.add("fast/dom/tabindex-clamp.html"); // there is extra spacing in the file due to multiple input boxes fitting on one line on Apple, ours are wrapped. Space at line ends are stripped.
         ignoreResultList.add("fast/events/anchor-image-scrolled-x-y.html"); // requires eventSender.mouseDown(),mouseUp()
@@ -172,8 +172,6 @@
         ignoreResultList.add("fast/replaced/image-map.html"); // requires eventSender.mouseDown(),mouseUp()
         ignoreResultList.add("fast/text/plain-text-line-breaks.html"); // extra spacing because iFrames rendered next to each other on Apple
         ignoreResultList.add("profiler"); // profiler is not supported
-        ignoreResultList.add("svg"); // svg is not supported
-
     }
 
 }
diff --git a/tests/DumpRenderTree/src/com/android/dumprendertree/LayoutTestsAutoTest.java b/tests/DumpRenderTree/src/com/android/dumprendertree/LayoutTestsAutoTest.java
index 042158a..0e39054 100644
--- a/tests/DumpRenderTree/src/com/android/dumprendertree/LayoutTestsAutoTest.java
+++ b/tests/DumpRenderTree/src/com/android/dumprendertree/LayoutTestsAutoTest.java
@@ -18,12 +18,9 @@
 
 import com.android.dumprendertree.TestShellActivity.DumpDataType;
 import com.android.dumprendertree.forwarder.AdbUtils;
-import com.android.dumprendertree.forwarder.ForwardServer;
 import com.android.dumprendertree.forwarder.ForwardService;
 
-import android.app.Instrumentation;
 import android.content.Intent;
-import android.os.Bundle;
 import android.test.ActivityInstrumentationTestCase2;
 import android.util.Log;
 
@@ -158,16 +155,7 @@
     private boolean mFinished;
 
     public LayoutTestsAutoTest() {
-      super("com.android.dumprendertree", TestShellActivity.class);
-    }
-
-    // This function writes the result of the layout test to
-    // Am status so that it can be picked up from a script.
-    private void passOrFailCallback(String file, boolean result) {
-      Instrumentation inst = getInstrumentation();
-      Bundle bundle = new Bundle();
-      bundle.putBoolean(file, result);
-      inst.sendStatus(0, bundle);
+      super(TestShellActivity.class);
     }
 
     private void getTestList() {
@@ -391,7 +379,7 @@
             resumeTestList();
 
         TestShellActivity activity = getActivity();
-        activity.setDefaultDumpDataType(DumpDataType.DUMP_AS_TEXT);
+        activity.setDefaultDumpDataType(DumpDataType.EXT_REPR);
 
         // Run tests.
         int addr = -1;
diff --git a/tests/DumpRenderTree/src/com/android/dumprendertree/TestShellActivity.java b/tests/DumpRenderTree/src/com/android/dumprendertree/TestShellActivity.java
index 81d5b08..2b1a781 100644
--- a/tests/DumpRenderTree/src/com/android/dumprendertree/TestShellActivity.java
+++ b/tests/DumpRenderTree/src/com/android/dumprendertree/TestShellActivity.java
@@ -34,6 +34,7 @@
 import android.os.Message;
 import android.util.Log;
 import android.view.ViewGroup;
+import android.webkit.ConsoleMessage;
 import android.webkit.GeolocationPermissions;
 import android.webkit.HttpAuthHandler;
 import android.webkit.JsPromptResult;
@@ -675,15 +676,28 @@
         }
 
         @Override
-        public void onConsoleMessage(String message, int lineNumber,
-                String sourceID) {
+        public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
+            String msg = "CONSOLE MESSAGE: line " + consoleMessage.lineNumber() + ": "
+                    + consoleMessage.message() + "\n";
             if (mConsoleMessages == null) {
                 mConsoleMessages = new StringBuffer();
             }
-            String consoleMessage = "CONSOLE MESSAGE: line "
-                    + lineNumber +": "+ message +"\n";
-            mConsoleMessages.append(consoleMessage);
-            Log.v(LOGTAG, "LOG: "+consoleMessage);
+            mConsoleMessages.append(msg);
+            Log.v(LOGTAG, "LOG: " + msg);
+            // the rationale here is that if there's an error of either type, and the test was
+            // waiting for "notifyDone" signal to finish, then there's no point in waiting
+            // anymore because the JS execution is already terminated at this point and a
+            // "notifyDone" will never come out so it's just wasting time till timeout kicks in
+            if (msg.contains("Uncaught ReferenceError:") || msg.contains("Uncaught TypeError:")
+                    && mWaitUntilDone) {
+                Log.w(LOGTAG, "Terminating test case on uncaught ReferenceError or TypeError.");
+                mHandler.postDelayed(new Runnable() {
+                    public void run() {
+                        notifyDone();
+                    }
+                }, 500);
+            }
+            return true;
         }
 
         @Override
@@ -742,6 +756,7 @@
         mDumpWebKitData = false;
         mGetDrawtime = false;
         mSaveImagePath = null;
+        setDefaultWebSettings(mWebView);
     }
 
     private long[] getDrawWebViewTime(WebView view, int count) {
@@ -786,6 +801,19 @@
             return;
         }
 
+        setDefaultWebSettings(webview);
+
+        webview.setWebChromeClient(mChromeClient);
+        webview.setWebViewClient(mViewClient);
+        // Setting a touch interval of -1 effectively disables the optimisation in WebView
+        // that stops repeated touch events flooding WebCore. The Event Sender only sends a
+        // single event rather than a stream of events (like what would generally happen in
+        // a real use of touch events in a WebView)  and so if the WebView drops the event,
+        // the test will fail as the test expects one callback for every touch it synthesizes.
+        webview.setTouchInterval(-1);
+    }
+
+    public void setDefaultWebSettings(WebView webview) {
         WebSettings settings = webview.getSettings();
         settings.setAppCacheEnabled(true);
         settings.setAppCachePath(getApplicationContext().getCacheDir().getPath());
@@ -798,15 +826,6 @@
         settings.setDatabasePath(getDir("databases",0).getAbsolutePath());
         settings.setDomStorageEnabled(true);
         settings.setWorkersEnabled(false);
-
-        webview.setWebChromeClient(mChromeClient);
-        webview.setWebViewClient(mViewClient);
-        // Setting a touch interval of -1 effectively disables the optimisation in WebView
-        // that stops repeated touch events flooding WebCore. The Event Sender only sends a
-        // single event rather than a stream of events (like what would generally happen in
-        // a real use of touch events in a WebView)  and so if the WebView drops the event,
-        // the test will fail as the test expects one callback for every touch it synthesizes.
-        webview.setTouchInterval(-1);
     }
 
     private WebView mWebView;
diff --git a/tools/layoutlib/README b/tools/layoutlib/README
new file mode 100644
index 0000000..0fea9bd
--- /dev/null
+++ b/tools/layoutlib/README
@@ -0,0 +1,4 @@
+Layoutlib is a custom version of the android View framework designed to run inside Eclipse.
+The goal of the library is to provide layout rendering in Eclipse that are very very close to their rendering on devices.
+
+None of the com.android.* or android.* classes in layoutlib run on devices.
\ No newline at end of file