resolved conflicts for merge of 1b7e4d5a to master

Change-Id: Ib3f8e1bffb759dbe5ef5e681ac872e9a9b70c24e
diff --git a/Android.mk b/Android.mk
index af50134..22216b0 100644
--- a/Android.mk
+++ b/Android.mk
@@ -100,9 +100,11 @@
 	core/java/android/bluetooth/IBluetoothCallback.aidl \
 	core/java/android/bluetooth/IBluetoothHeadset.aidl \
 	core/java/android/bluetooth/IBluetoothPbap.aidl \
+	core/java/android/content/IClipboard.aidl \
 	core/java/android/content/IContentService.aidl \
 	core/java/android/content/IIntentReceiver.aidl \
 	core/java/android/content/IIntentSender.aidl \
+	core/java/android/content/IOnPrimaryClipChangedListener.aidl \
 	core/java/android/content/ISyncAdapter.aidl \
 	core/java/android/content/ISyncContext.aidl \
 	core/java/android/content/ISyncStatusObserver.aidl \
@@ -132,7 +134,6 @@
     core/java/android/service/wallpaper/IWallpaperConnection.aidl \
     core/java/android/service/wallpaper/IWallpaperEngine.aidl \
     core/java/android/service/wallpaper/IWallpaperService.aidl \
-	core/java/android/text/IClipboard.aidl \
 	core/java/android/view/accessibility/IAccessibilityManager.aidl \
 	core/java/android/view/accessibility/IAccessibilityManagerClient.aidl \
 	core/java/android/view/IApplicationToken.aidl \
@@ -162,6 +163,9 @@
 	core/java/com/android/internal/view/IInputMethodClient.aidl \
 	core/java/com/android/internal/view/IInputMethodManager.aidl \
 	core/java/com/android/internal/view/IInputMethodSession.aidl \
+	core/java/com/android/internal/widget/IRemoteViewsFactory.aidl \
+	location/java/android/location/ICountryDetector.aidl \
+	location/java/android/location/ICountryListener.aidl \
 	location/java/android/location/IGeocodeProvider.aidl \
 	location/java/android/location/IGpsStatusListener.aidl \
 	location/java/android/location/IGpsStatusProvider.aidl \
@@ -359,6 +363,7 @@
     -since ./frameworks/base/api/6.xml 6 \
     -since ./frameworks/base/api/7.xml 7 \
     -since ./frameworks/base/api/8.xml 8 \
+    -since ./frameworks/base/api/current.xml HC \
 		-error 1 -error 2 -warning 3 -error 4 -error 6 -error 8 \
 		-overview $(LOCAL_PATH)/core/java/overview.html
 
@@ -415,7 +420,9 @@
 		-samplecode $(sample_dir)/WiktionarySimple \
 		            resources/samples/WiktionarySimple "Wiktionary (Simplified)" \
 		-samplecode $(sample_dir)/VoiceRecognitionService \
-		            resources/samples/VoiceRecognitionService "Voice Recognition Service"
+		            resources/samples/VoiceRecognitionService "Voice Recognition Service" \
+		-samplecode $(sample_dir)/XmlAdapters \
+		            resources/samples/XmlAdapters "XML Adapters"
 
 ## SDK version identifiers used in the published docs
   # major[.minor] version for current SDK. (full releases only)
@@ -466,7 +473,8 @@
 
 include $(BUILD_DROIDDOC)
 
-$(full_target): $(framework_built)
+# $(gen), i.e. framework.aidl, is also needed while building against the current stub.
+$(full_target): $(framework_built) $(gen)
 $(INTERNAL_PLATFORM_API_FILE): $(full_target)
 $(call dist-for-goals,sdk,$(INTERNAL_PLATFORM_API_FILE))
 
@@ -568,10 +576,14 @@
 
 ext_dirs := \
 	../../external/apache-http/src \
-	../../external/tagsoup/src
+	../../external/tagsoup/src \
+	../../external/libphonenumber/java/src
 
 ext_src_files := $(call all-java-files-under,$(ext_dirs))
 
+ext_res_dirs := \
+	../../external/libphonenumber/java/src
+
 # ====  the library  =========================================
 include $(CLEAR_VARS)
 
@@ -579,7 +591,7 @@
 
 LOCAL_NO_STANDARD_LIBRARIES := true
 LOCAL_JAVA_LIBRARIES := core
-
+LOCAL_JAVA_RESOURCE_DIRS := $(ext_res_dirs)
 LOCAL_MODULE := ext
 
 LOCAL_NO_EMMA_INSTRUMENT := true
diff --git a/CleanSpec.mk b/CleanSpec.mk
index 1efe77c..1b7daa6 100644
--- a/CleanSpec.mk
+++ b/CleanSpec.mk
@@ -67,6 +67,7 @@
 $(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/SHARED_LIBRARIES/libreverb_intermediates)
 $(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/SHARED_LIBRARIES/libreverbtest_intermediates)
 $(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/lib/soundfx/)
+$(call add-clean-step, find . -type f -name "*.rs" -print0 | xargs -0 touch)
 
 # ************************************************
 # NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
diff --git a/api/9.xml b/api/9.xml
index f18623f..abb67f9 100644
--- a/api/9.xml
+++ b/api/9.xml
@@ -147782,17 +147782,6 @@
  visibility="public"
 >
 </constructor>
-<method name="abortUpdates"
- return="void"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-</method>
 <method name="close"
  return="void"
  abstract="false"
@@ -147804,30 +147793,6 @@
  visibility="public"
 >
 </method>
-<method name="commitUpdates"
- return="boolean"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-</method>
-<method name="commitUpdates"
- return="boolean"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="values" type="java.util.Map&lt;? extends java.lang.Long, ? extends java.util.Map&lt;java.lang.String, java.lang.Object&gt;&gt;">
-</parameter>
-</method>
 <method name="copyStringToBuffer"
  return="void"
  abstract="false"
@@ -147854,17 +147819,6 @@
  visibility="public"
 >
 </method>
-<method name="deleteRow"
- return="boolean"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-</method>
 <method name="getBlob"
  return="byte[]"
  abstract="false"
@@ -148061,17 +148015,6 @@
  visibility="public"
 >
 </method>
-<method name="hasUpdates"
- return="boolean"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-</method>
 <method name="isAfterLast"
  return="boolean"
  abstract="false"
@@ -148275,17 +148218,6 @@
 <parameter name="uri" type="android.net.Uri">
 </parameter>
 </method>
-<method name="supportsUpdates"
- return="boolean"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-</method>
 <method name="unregisterContentObserver"
  return="void"
  abstract="false"
@@ -148312,124 +148244,6 @@
 <parameter name="observer" type="android.database.DataSetObserver">
 </parameter>
 </method>
-<method name="updateBlob"
- return="boolean"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="columnIndex" type="int">
-</parameter>
-<parameter name="value" type="byte[]">
-</parameter>
-</method>
-<method name="updateDouble"
- return="boolean"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="columnIndex" type="int">
-</parameter>
-<parameter name="value" type="double">
-</parameter>
-</method>
-<method name="updateFloat"
- return="boolean"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="columnIndex" type="int">
-</parameter>
-<parameter name="value" type="float">
-</parameter>
-</method>
-<method name="updateInt"
- return="boolean"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="columnIndex" type="int">
-</parameter>
-<parameter name="value" type="int">
-</parameter>
-</method>
-<method name="updateLong"
- return="boolean"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="columnIndex" type="int">
-</parameter>
-<parameter name="value" type="long">
-</parameter>
-</method>
-<method name="updateShort"
- return="boolean"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="columnIndex" type="int">
-</parameter>
-<parameter name="value" type="short">
-</parameter>
-</method>
-<method name="updateString"
- return="boolean"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="columnIndex" type="int">
-</parameter>
-<parameter name="value" type="java.lang.String">
-</parameter>
-</method>
-<method name="updateToNull"
- return="boolean"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="columnIndex" type="int">
-</parameter>
-</method>
 </class>
 <class name="MockDialogInterface"
  extends="java.lang.Object"
@@ -164696,7 +164510,7 @@
  type="java.lang.String"
  transient="false"
  volatile="false"
- value="&quot;((aero|arpa|asia|a[cdefgilmnoqrstuwxz])|(biz|b[abdefghijmnorstvwyz])|(cat|com|coop|c[acdfghiklmnoruvxyz])|d[ejkmoz]|(edu|e[cegrstu])|f[ijkmor]|(gov|g[abdefghilmnpqrstuwy])|h[kmnrtu]|(info|int|i[delmnoqrst])|(jobs|j[emop])|k[eghimnprwyz]|l[abcikrstuvy]|(mil|mobi|museum|m[acdeghklmnopqrstuvwxyz])|(name|net|n[acefgilopruz])|(org|om)|(pro|p[aefghklmnrstwy])|qa|r[eosuw]|s[abcdeghijklmnortuvyz]|(tel|travel|t[cdfghjklmnoprtvwz])|u[agksyz]|v[aceginu]|w[fs]|(xn\\-\\-0zwm56d|xn\\-\\-11b5bs3a9aj6g|xn\\-\\-80akhbyknj4f|xn\\-\\-9t4b11yi5a|xn\\-\\-deba0ad|xn\\-\\-g6w251d|xn\\-\\-hgbk6aj7f53bba|xn\\-\\-hlcj6aya9esc7a|xn\\-\\-jxalpdlp|xn\\-\\-kgbechtv|xn\\-\\-zckzah)|y[etu]|z[amw])&quot;"
+ value="&quot;((aero|arpa|asia|a[cdefgilmnoqrstuwxz])|(biz|b[abdefghijmnorstvwyz])|(cat|com|coop|c[acdfghiklmnoruvxyz])|d[ejkmoz]|(edu|e[cegrstu])|f[ijkmor]|(gov|g[abdefghilmnpqrstuwy])|h[kmnrtu]|(info|int|i[delmnoqrst])|(jobs|j[emop])|k[eghimnprwyz]|l[abcikrstuvy]|(mil|mobi|museum|m[acdeghklmnopqrstuvwxyz])|(name|net|n[acefgilopruz])|(org|om)|(pro|p[aefghklmnrstwy])|qa|r[eosuw]|s[abcdeghijklmnortuvyz]|(tel|travel|t[cdfghjklmnoprtvwz])|u[agksyz]|v[aceginu]|w[fs]|(xn\\-\\-0zwm56d|xn\\-\\-11b5bs3a9aj6g|xn\\-\\-80akhbyknj4f|xn\\-\\-9t4b11yi5a|xn\\-\\-deba0ad|xn\\-\\-g6w251d|xn\\-\\-hgbk6aj7f53bba|xn\\-\\-hlcj6aya9esc7a|xn\\-\\-jxalpdlp|xn\\-\\-kgbechtv|xn\\-\\-mgbaam7a8h|xn\\-\\-mgberp4a5d4ar|xn\\-\\-wgbh1c|xn\\-\\-zckzah)|y[et]|z[amw])&quot;"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -164707,7 +164521,7 @@
  type="java.lang.String"
  transient="false"
  volatile="false"
- value="&quot;(?:(?:aero|arpa|asia|a[cdefgilmnoqrstuwxz])|(?:biz|b[abdefghijmnorstvwyz])|(?:cat|com|coop|c[acdfghiklmnoruvxyz])|d[ejkmoz]|(?:edu|e[cegrstu])|f[ijkmor]|(?:gov|g[abdefghilmnpqrstuwy])|h[kmnrtu]|(?:info|int|i[delmnoqrst])|(?:jobs|j[emop])|k[eghimnprwyz]|l[abcikrstuvy]|(?:mil|mobi|museum|m[acdeghklmnopqrstuvwxyz])|(?:name|net|n[acefgilopruz])|(?:org|om)|(?:pro|p[aefghklmnrstwy])|qa|r[eosuw]|s[abcdeghijklmnortuvyz]|(?:tel|travel|t[cdfghjklmnoprtvwz])|u[agksyz]|v[aceginu]|w[fs]|(?:xn\\-\\-0zwm56d|xn\\-\\-11b5bs3a9aj6g|xn\\-\\-80akhbyknj4f|xn\\-\\-9t4b11yi5a|xn\\-\\-deba0ad|xn\\-\\-g6w251d|xn\\-\\-hgbk6aj7f53bba|xn\\-\\-hlcj6aya9esc7a|xn\\-\\-jxalpdlp|xn\\-\\-kgbechtv|xn\\-\\-zckzah)|y[etu]|z[amw]))&quot;"
+ value="&quot;(?:(?:aero|arpa|asia|a[cdefgilmnoqrstuwxz])|(?:biz|b[abdefghijmnorstvwyz])|(?:cat|com|coop|c[acdfghiklmnoruvxyz])|d[ejkmoz]|(?:edu|e[cegrstu])|f[ijkmor]|(?:gov|g[abdefghilmnpqrstuwy])|h[kmnrtu]|(?:info|int|i[delmnoqrst])|(?:jobs|j[emop])|k[eghimnprwyz]|l[abcikrstuvy]|(?:mil|mobi|museum|m[acdeghklmnopqrstuvwxyz])|(?:name|net|n[acefgilopruz])|(?:org|om)|(?:pro|p[aefghklmnrstwy])|qa|r[eosuw]|s[abcdeghijklmnortuvyz]|(?:tel|travel|t[cdfghjklmnoprtvwz])|u[agksyz]|v[aceginu]|w[fs]|(?:xn\\-\\-0zwm56d|xn\\-\\-11b5bs3a9aj6g|xn\\-\\-80akhbyknj4f|xn\\-\\-9t4b11yi5a|xn\\-\\-deba0ad|xn\\-\\-g6w251d|xn\\-\\-hgbk6aj7f53bba|xn\\-\\-hlcj6aya9esc7a|xn\\-\\-jxalpdlp|xn\\-\\-kgbechtv|xn\\-\\-mgbaam7a8h|xn\\-\\-mgberp4a5d4ar|xn\\-\\-wgbh1c|xn\\-\\-zckzah)|y[et]|z[amw]))&quot;"
  static="true"
  final="true"
  deprecated="not deprecated"
diff --git a/api/current.xml b/api/current.xml
index e80635a..7a9d251 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -2088,6 +2088,61 @@
  visibility="public"
 >
 </field>
+<field name="actionButtonPadding"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843547"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="actionButtonStyle"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843545"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="actionDropDownStyle"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843544"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="actionModeBackground"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843549"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="actionModeCloseDrawable"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843550"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="activityCloseEnterAnimation"
  type="int"
  transient="false"
@@ -2132,6 +2187,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 +2231,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"
@@ -2253,6 +2330,17 @@
  visibility="public"
 >
 </field>
+<field name="animateFirstView"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843542"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="animateOnClick"
  type="int"
  transient="false"
@@ -2341,6 +2429,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 +3100,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"
@@ -3144,6 +3254,17 @@
  visibility="public"
 >
 </field>
+<field name="customNavigationLayout"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843539"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="cycles"
  type="int"
  transient="false"
@@ -3408,6 +3529,17 @@
  visibility="public"
 >
 </field>
+<field name="displayOptions"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843537"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="dither"
  type="int"
  transient="false"
@@ -3606,6 +3738,17 @@
  visibility="public"
 >
 </field>
+<field name="dropDownSpinnerStyle"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843543"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="dropDownVerticalOffset"
  type="int"
  transient="false"
@@ -4156,6 +4299,17 @@
  visibility="public"
 >
 </field>
+<field name="fragment"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843557"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="freezesText"
  type="int"
  transient="false"
@@ -4167,6 +4321,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 +4354,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"
@@ -4475,6 +4651,17 @@
  visibility="public"
 >
 </field>
+<field name="hardwareAccelerated"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843540"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="hasCode"
  type="int"
  transient="false"
@@ -6686,6 +6873,17 @@
  visibility="public"
 >
 </field>
+<field name="measureWithLargestChild"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843541"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="menuCategory"
  type="int"
  transient="false"
@@ -6818,6 +7016,17 @@
  visibility="public"
 >
 </field>
+<field name="navigationMode"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843536"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="negativeButtonText"
  type="int"
  transient="false"
@@ -6983,6 +7192,17 @@
  visibility="public"
 >
 </field>
+<field name="ordering"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843556"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="orderingFromXml"
  type="int"
  transient="false"
@@ -7379,6 +7599,17 @@
  visibility="public"
 >
 </field>
+<field name="previewImage"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843548"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="priority"
  type="int"
  transient="false"
@@ -7533,6 +7764,17 @@
  visibility="public"
 >
 </field>
+<field name="propertyName"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843555"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="protectionLevel"
  type="int"
  transient="false"
@@ -8303,6 +8545,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"
@@ -8413,6 +8666,17 @@
  visibility="public"
 >
 </field>
+<field name="showAsAction"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843546"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="showDefault"
  type="int"
  transient="false"
@@ -8490,6 +8754,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"
@@ -8842,6 +9117,17 @@
  visibility="public"
 >
 </field>
+<field name="subtitle"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843538"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="suggestActionMsg"
  type="int"
  transient="false"
@@ -9678,6 +9964,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 +9997,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 +10184,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"
@@ -9909,6 +10228,39 @@
  visibility="public"
 >
 </field>
+<field name="valueFrom"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843552"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="valueTo"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843553"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="valueType"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843554"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="variablePadding"
  type="int"
  transient="false"
@@ -10217,6 +10569,39 @@
  visibility="public"
 >
 </field>
+<field name="windowActionBar"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843534"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="windowActionBarStyle"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843535"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="windowActionModeOverlay"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843551"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="windowAnimationStyle"
  type="int"
  transient="false"
@@ -10426,6 +10811,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"
@@ -14075,6 +14482,17 @@
  visibility="public"
 >
 </field>
+<field name="home"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16908353"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="icon"
  type="int"
  transient="false"
@@ -14460,6 +14878,17 @@
  visibility="public"
 >
 </field>
+<field name="selectTextMode"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16908354"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="selectedIcon"
  type="int"
  transient="false"
@@ -15000,6 +15429,17 @@
  visibility="public"
 >
 </field>
+<field name="list_content"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="17367073"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="preference_category"
  type="int"
  transient="false"
@@ -15568,6 +16008,17 @@
  visibility="public"
 >
 </field>
+<field name="selectTextMode"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="17039408"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="unknownName"
  type="int"
  transient="false"
@@ -16058,6 +16509,17 @@
  visibility="public"
 >
 </field>
+<field name="Theme_Dialog_NoFrame"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16973972"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="Theme_InputMethod"
  type="int"
  transient="false"
@@ -16157,6 +16619,17 @@
  visibility="public"
 >
 </field>
+<field name="Theme_NoTitleBar_OverlayActionModes"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16973973"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="Theme_Panel"
  type="int"
  transient="false"
@@ -16245,6 +16718,17 @@
  visibility="public"
 >
 </field>
+<field name="Theme_WithActionBar"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16973969"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="Widget"
  type="int"
  transient="false"
@@ -16267,6 +16751,17 @@
  visibility="public"
 >
 </field>
+<field name="Widget_ActionButton"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16973971"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="Widget_AutoCompleteTextView"
  type="int"
  transient="false"
@@ -16641,6 +17136,17 @@
  visibility="public"
 >
 </field>
+<field name="Widget_Spinner_DropDown"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16973970"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="Widget_TabWidget"
  type="int"
  transient="false"
@@ -19065,8 +19571,1934 @@
 </constructor>
 </class>
 </package>
+<package name="android.animation"
+>
+<class name="Animatable"
+ extends="java.lang.Object"
+ abstract="true"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="Animatable"
+ type="android.animation.Animatable"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</constructor>
+<method name="addListener"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="listener" type="android.animation.Animatable.AnimatableListener">
+</parameter>
+</method>
+<method name="cancel"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="end"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getListeners"
+ return="java.util.ArrayList&lt;android.animation.Animatable.AnimatableListener&gt;"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="removeAllListeners"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="removeListener"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="listener" type="android.animation.Animatable.AnimatableListener">
+</parameter>
+</method>
+<method name="start"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+</class>
+<interface name="Animatable.AnimatableListener"
+ abstract="true"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<method name="onAnimationCancel"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="animation" type="android.animation.Animatable">
+</parameter>
+</method>
+<method name="onAnimationEnd"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="animation" type="android.animation.Animatable">
+</parameter>
+</method>
+<method name="onAnimationRepeat"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="animation" type="android.animation.Animatable">
+</parameter>
+</method>
+<method name="onAnimationStart"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="animation" type="android.animation.Animatable">
+</parameter>
+</method>
+</interface>
+<class name="AnimatableListenerAdapter"
+ extends="java.lang.Object"
+ abstract="true"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<implements name="android.animation.Animatable.AnimatableListener">
+</implements>
+<constructor name="AnimatableListenerAdapter"
+ type="android.animation.AnimatableListenerAdapter"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</constructor>
+<method name="onAnimationCancel"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="animation" type="android.animation.Animatable">
+</parameter>
+</method>
+<method name="onAnimationEnd"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="animation" type="android.animation.Animatable">
+</parameter>
+</method>
+<method name="onAnimationRepeat"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="animation" type="android.animation.Animatable">
+</parameter>
+</method>
+<method name="onAnimationStart"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="animation" type="android.animation.Animatable">
+</parameter>
+</method>
+</class>
+<class name="Animator"
+ extends="android.animation.Animatable"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="Animator"
+ type="android.animation.Animator"
+ 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="Animator"
+ type="android.animation.Animator"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="duration" type="long">
+</parameter>
+<parameter name="keyframes" type="android.animation.Keyframe...">
+</parameter>
+</constructor>
+<constructor name="Animator"
+ type="android.animation.Animator"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="duration" type="long">
+</parameter>
+<parameter name="valueFrom" type="float">
+</parameter>
+<parameter name="valueTo" type="float">
+</parameter>
+</constructor>
+<constructor name="Animator"
+ type="android.animation.Animator"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="duration" type="long">
+</parameter>
+<parameter name="valueFrom" type="int">
+</parameter>
+<parameter name="valueTo" type="int">
+</parameter>
+</constructor>
+<constructor name="Animator"
+ type="android.animation.Animator"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="duration" type="long">
+</parameter>
+<parameter name="valueFrom" type="double">
+</parameter>
+<parameter name="valueTo" type="double">
+</parameter>
+</constructor>
+<constructor name="Animator"
+ type="android.animation.Animator"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="duration" type="long">
+</parameter>
+<parameter name="valueFrom" type="java.lang.Object">
+</parameter>
+<parameter name="valueTo" type="java.lang.Object">
+</parameter>
+</constructor>
+<method name="addUpdateListener"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="listener" type="android.animation.Animator.AnimatorUpdateListener">
+</parameter>
+</method>
+<method name="getAnimatedValue"
+ return="java.lang.Object"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getCurrentPlayTime"
+ return="long"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getFrameDelay"
+ return="long"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getRepeatCount"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getRepeatMode"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getStartDelay"
+ return="long"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getValueFrom"
+ return="java.lang.Object"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getValueTo"
+ return="java.lang.Object"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="removeUpdateListener"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="listener" type="android.animation.Animator.AnimatorUpdateListener">
+</parameter>
+</method>
+<method name="setCurrentPlayTime"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="playTime" type="long">
+</parameter>
+</method>
+<method name="setEvaluator"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="value" type="android.animation.TypeEvaluator">
+</parameter>
+</method>
+<method name="setFrameDelay"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="frameDelay" type="long">
+</parameter>
+</method>
+<method name="setInterpolator"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="value" type="android.view.animation.Interpolator">
+</parameter>
+</method>
+<method name="setRepeatCount"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="value" type="int">
+</parameter>
+</method>
+<method name="setRepeatMode"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="value" type="int">
+</parameter>
+</method>
+<method name="setStartDelay"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="startDelay" type="long">
+</parameter>
+</method>
+<method name="setValueFrom"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="valueFrom" type="java.lang.Object">
+</parameter>
+</method>
+<method name="setValueTo"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="valueTo" type="java.lang.Object">
+</parameter>
+</method>
+<field name="INFINITE"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="-1"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="RESTART"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="1"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="REVERSE"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="2"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+</class>
+<interface name="Animator.AnimatorUpdateListener"
+ abstract="true"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<method name="onAnimationUpdate"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="animation" type="android.animation.Animator">
+</parameter>
+</method>
+</interface>
+<class name="DoubleEvaluator"
+ extends="java.lang.Object"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<implements name="android.animation.TypeEvaluator">
+</implements>
+<constructor name="DoubleEvaluator"
+ type="android.animation.DoubleEvaluator"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</constructor>
+<method name="evaluate"
+ return="java.lang.Object"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="fraction" type="float">
+</parameter>
+<parameter name="startValue" type="java.lang.Object">
+</parameter>
+<parameter name="endValue" type="java.lang.Object">
+</parameter>
+</method>
+</class>
+<class name="FloatEvaluator"
+ extends="java.lang.Object"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<implements name="android.animation.TypeEvaluator">
+</implements>
+<constructor name="FloatEvaluator"
+ type="android.animation.FloatEvaluator"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</constructor>
+<method name="evaluate"
+ return="java.lang.Object"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="fraction" type="float">
+</parameter>
+<parameter name="startValue" type="java.lang.Object">
+</parameter>
+<parameter name="endValue" type="java.lang.Object">
+</parameter>
+</method>
+</class>
+<class name="IntEvaluator"
+ extends="java.lang.Object"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<implements name="android.animation.TypeEvaluator">
+</implements>
+<constructor name="IntEvaluator"
+ type="android.animation.IntEvaluator"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</constructor>
+<method name="evaluate"
+ return="java.lang.Object"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="fraction" type="float">
+</parameter>
+<parameter name="startValue" type="java.lang.Object">
+</parameter>
+<parameter name="endValue" type="java.lang.Object">
+</parameter>
+</method>
+</class>
+<class name="Keyframe"
+ extends="java.lang.Object"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="Keyframe"
+ type="android.animation.Keyframe"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="fraction" type="float">
+</parameter>
+<parameter name="value" type="java.lang.Object">
+</parameter>
+</constructor>
+<constructor name="Keyframe"
+ type="android.animation.Keyframe"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="fraction" type="float">
+</parameter>
+<parameter name="value" type="int">
+</parameter>
+</constructor>
+<constructor name="Keyframe"
+ type="android.animation.Keyframe"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="fraction" type="float">
+</parameter>
+<parameter name="value" type="float">
+</parameter>
+</constructor>
+<constructor name="Keyframe"
+ type="android.animation.Keyframe"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="fraction" type="float">
+</parameter>
+<parameter name="value" type="double">
+</parameter>
+</constructor>
+<method name="getFraction"
+ return="float"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getInterpolator"
+ return="android.view.animation.Interpolator"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getType"
+ return="java.lang.Class"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getValue"
+ return="java.lang.Object"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="setFraction"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="fraction" type="float">
+</parameter>
+</method>
+<method name="setInterpolator"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="interpolator" type="android.view.animation.Interpolator">
+</parameter>
+</method>
+<method name="setValue"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="value" type="java.lang.Object">
+</parameter>
+</method>
+</class>
+<class name="PropertyAnimator"
+ extends="android.animation.Animator"
+ abstract="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="PropertyAnimator"
+ type="android.animation.PropertyAnimator"
+ 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="PropertyAnimator"
+ type="android.animation.PropertyAnimator"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="duration" type="int">
+</parameter>
+<parameter name="target" type="java.lang.Object">
+</parameter>
+<parameter name="propertyName" type="java.lang.String">
+</parameter>
+<parameter name="valueFrom" type="float">
+</parameter>
+<parameter name="valueTo" type="float">
+</parameter>
+</constructor>
+<constructor name="PropertyAnimator"
+ type="android.animation.PropertyAnimator"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="duration" type="int">
+</parameter>
+<parameter name="target" type="java.lang.Object">
+</parameter>
+<parameter name="propertyName" type="java.lang.String">
+</parameter>
+<parameter name="valueTo" type="float">
+</parameter>
+</constructor>
+<constructor name="PropertyAnimator"
+ type="android.animation.PropertyAnimator"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="duration" type="int">
+</parameter>
+<parameter name="target" type="java.lang.Object">
+</parameter>
+<parameter name="propertyName" type="java.lang.String">
+</parameter>
+<parameter name="valueFrom" type="int">
+</parameter>
+<parameter name="valueTo" type="int">
+</parameter>
+</constructor>
+<constructor name="PropertyAnimator"
+ type="android.animation.PropertyAnimator"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="duration" type="int">
+</parameter>
+<parameter name="target" type="java.lang.Object">
+</parameter>
+<parameter name="propertyName" type="java.lang.String">
+</parameter>
+<parameter name="valueTo" type="int">
+</parameter>
+</constructor>
+<constructor name="PropertyAnimator"
+ type="android.animation.PropertyAnimator"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="duration" type="int">
+</parameter>
+<parameter name="target" type="java.lang.Object">
+</parameter>
+<parameter name="propertyName" type="java.lang.String">
+</parameter>
+<parameter name="valueFrom" type="double">
+</parameter>
+<parameter name="valueTo" type="double">
+</parameter>
+</constructor>
+<constructor name="PropertyAnimator"
+ type="android.animation.PropertyAnimator"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="duration" type="int">
+</parameter>
+<parameter name="target" type="java.lang.Object">
+</parameter>
+<parameter name="propertyName" type="java.lang.String">
+</parameter>
+<parameter name="valueTo" type="double">
+</parameter>
+</constructor>
+<constructor name="PropertyAnimator"
+ type="android.animation.PropertyAnimator"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="duration" type="int">
+</parameter>
+<parameter name="target" type="java.lang.Object">
+</parameter>
+<parameter name="propertyName" type="java.lang.String">
+</parameter>
+<parameter name="valueFrom" type="java.lang.Object">
+</parameter>
+<parameter name="valueTo" type="java.lang.Object">
+</parameter>
+</constructor>
+<constructor name="PropertyAnimator"
+ type="android.animation.PropertyAnimator"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="duration" type="int">
+</parameter>
+<parameter name="target" type="java.lang.Object">
+</parameter>
+<parameter name="propertyName" type="java.lang.String">
+</parameter>
+<parameter name="valueTo" type="java.lang.Object">
+</parameter>
+</constructor>
+<constructor name="PropertyAnimator"
+ type="android.animation.PropertyAnimator"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="duration" type="int">
+</parameter>
+<parameter name="target" type="java.lang.Object">
+</parameter>
+<parameter name="propertyName" type="java.lang.String">
+</parameter>
+<parameter name="keyframes" type="android.animation.Keyframe...">
+</parameter>
+</constructor>
+<method name="getGetter"
+ return="java.lang.reflect.Method"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getPropertyName"
+ return="java.lang.String"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getSetter"
+ return="java.lang.reflect.Method"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getTarget"
+ return="java.lang.Object"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="setGetter"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="getter" type="java.lang.reflect.Method">
+</parameter>
+</method>
+<method name="setPropertyName"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="propertyName" type="java.lang.String">
+</parameter>
+</method>
+<method name="setSetter"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="setter" type="java.lang.reflect.Method">
+</parameter>
+</method>
+<method name="setTarget"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="target" type="java.lang.Object">
+</parameter>
+</method>
+</class>
+<class name="RGBEvaluator"
+ extends="java.lang.Object"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<implements name="android.animation.TypeEvaluator">
+</implements>
+<constructor name="RGBEvaluator"
+ type="android.animation.RGBEvaluator"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</constructor>
+<method name="evaluate"
+ return="java.lang.Object"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="fraction" type="float">
+</parameter>
+<parameter name="startValue" type="java.lang.Object">
+</parameter>
+<parameter name="endValue" type="java.lang.Object">
+</parameter>
+</method>
+</class>
+<class name="Sequencer"
+ extends="android.animation.Animatable"
+ abstract="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="Sequencer"
+ type="android.animation.Sequencer"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</constructor>
+<method name="getChildAnimations"
+ return="java.util.ArrayList&lt;android.animation.Animatable&gt;"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="play"
+ return="android.animation.Sequencer.Builder"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="anim" type="android.animation.Animatable">
+</parameter>
+</method>
+<method name="playSequentially"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="sequenceItems" type="android.animation.Animatable...">
+</parameter>
+</method>
+<method name="playTogether"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="sequenceItems" type="android.animation.Animatable...">
+</parameter>
+</method>
+</class>
+<class name="Sequencer.Builder"
+ extends="java.lang.Object"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<method name="after"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="anim" type="android.animation.Animatable">
+</parameter>
+</method>
+<method name="after"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="delay" type="long">
+</parameter>
+</method>
+<method name="before"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="anim" type="android.animation.Animatable">
+</parameter>
+</method>
+<method name="with"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="anim" type="android.animation.Animatable">
+</parameter>
+</method>
+</class>
+<interface name="TypeEvaluator"
+ abstract="true"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<method name="evaluate"
+ return="java.lang.Object"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="fraction" type="float">
+</parameter>
+<parameter name="startValue" type="java.lang.Object">
+</parameter>
+<parameter name="endValue" type="java.lang.Object">
+</parameter>
+</method>
+</interface>
+</package>
 <package name="android.app"
 >
+<class name="ActionBar"
+ extends="java.lang.Object"
+ abstract="true"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="ActionBar"
+ type="android.app.ActionBar"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</constructor>
+<method name="addTab"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="tab" type="android.app.ActionBar.Tab">
+</parameter>
+</method>
+<method name="getCustomNavigationView"
+ return="android.view.View"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getDisplayOptions"
+ return="int"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getNavigationMode"
+ return="int"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getSelectedNavigationItem"
+ return="int"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getSubtitle"
+ return="java.lang.CharSequence"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getTitle"
+ return="java.lang.CharSequence"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="insertTab"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="tab" type="android.app.ActionBar.Tab">
+</parameter>
+<parameter name="position" type="int">
+</parameter>
+</method>
+<method name="newTab"
+ return="android.app.ActionBar.Tab"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="removeTab"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="tab" type="android.app.ActionBar.Tab">
+</parameter>
+</method>
+<method name="removeTabAt"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="position" type="int">
+</parameter>
+</method>
+<method name="selectTab"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="tab" type="android.app.ActionBar.Tab">
+</parameter>
+</method>
+<method name="setBackgroundDrawable"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="d" type="android.graphics.drawable.Drawable">
+</parameter>
+</method>
+<method name="setCustomNavigationMode"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="view" type="android.view.View">
+</parameter>
+</method>
+<method name="setDisplayOptions"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="options" type="int">
+</parameter>
+</method>
+<method name="setDisplayOptions"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="options" type="int">
+</parameter>
+<parameter name="mask" type="int">
+</parameter>
+</method>
+<method name="setDropdownNavigationMode"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="adapter" type="android.widget.SpinnerAdapter">
+</parameter>
+<parameter name="callback" type="android.app.ActionBar.NavigationCallback">
+</parameter>
+</method>
+<method name="setDropdownNavigationMode"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="adapter" type="android.widget.SpinnerAdapter">
+</parameter>
+<parameter name="callback" type="android.app.ActionBar.NavigationCallback">
+</parameter>
+<parameter name="defaultSelectedPosition" type="int">
+</parameter>
+</method>
+<method name="setSelectedNavigationItem"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="position" type="int">
+</parameter>
+</method>
+<method name="setStandardNavigationMode"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="title" type="java.lang.CharSequence">
+</parameter>
+<parameter name="subtitle" type="java.lang.CharSequence">
+</parameter>
+</method>
+<method name="setStandardNavigationMode"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="titleResId" type="int">
+</parameter>
+<parameter name="subtitleResId" type="int">
+</parameter>
+</method>
+<method name="setStandardNavigationMode"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="title" type="java.lang.CharSequence">
+</parameter>
+</method>
+<method name="setStandardNavigationMode"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="titleResId" type="int">
+</parameter>
+</method>
+<method name="setStandardNavigationMode"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="setSubtitle"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="subtitle" type="java.lang.CharSequence">
+</parameter>
+</method>
+<method name="setSubtitle"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="resId" type="int">
+</parameter>
+</method>
+<method name="setTabNavigationMode"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="setTabNavigationMode"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="containerViewId" type="int">
+</parameter>
+</method>
+<method name="setTitle"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="title" type="java.lang.CharSequence">
+</parameter>
+</method>
+<method name="setTitle"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="resId" type="int">
+</parameter>
+</method>
+<field name="DISPLAY_HIDE_HOME"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="2"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="DISPLAY_USE_LOGO"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="1"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="NAVIGATION_MODE_CUSTOM"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="3"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="NAVIGATION_MODE_DROPDOWN_LIST"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="1"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="NAVIGATION_MODE_STANDARD"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="0"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="NAVIGATION_MODE_TABS"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="2"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+</class>
+<interface name="ActionBar.NavigationCallback"
+ abstract="true"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<method name="onNavigationItemSelected"
+ return="boolean"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="itemPosition" type="int">
+</parameter>
+<parameter name="itemId" type="long">
+</parameter>
+</method>
+</interface>
+<class name="ActionBar.Tab"
+ extends="java.lang.Object"
+ abstract="true"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="ActionBar.Tab"
+ type="android.app.ActionBar.Tab"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</constructor>
+<method name="getFragment"
+ return="android.app.Fragment"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getIcon"
+ return="android.graphics.drawable.Drawable"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getPosition"
+ return="int"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getText"
+ return="java.lang.CharSequence"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="select"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="setFragment"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="fragment" type="android.app.Fragment">
+</parameter>
+</method>
+<method name="setIcon"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="icon" type="android.graphics.drawable.Drawable">
+</parameter>
+</method>
+<method name="setText"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="text" type="java.lang.CharSequence">
+</parameter>
+</method>
+<field name="INVALID_POSITION"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="-1"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+</class>
 <class name="Activity"
  extends="android.view.ContextThemeWrapper"
  abstract="false"
@@ -19212,6 +21644,32 @@
 <parameter name="ev" type="android.view.MotionEvent">
 </parameter>
 </method>
+<method name="findFragmentById"
+ return="android.app.Fragment"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="id" type="int">
+</parameter>
+</method>
+<method name="findFragmentByTag"
+ return="android.app.Fragment"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="tag" type="java.lang.String">
+</parameter>
+</method>
 <method name="findViewById"
  return="android.view.View"
  abstract="false"
@@ -19277,6 +21735,17 @@
 <parameter name="child" type="android.app.Activity">
 </parameter>
 </method>
+<method name="getActionBar"
+ return="android.app.ActionBar"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
 <method name="getApplication"
  return="android.app.Application"
  abstract="false"
@@ -19387,6 +21856,17 @@
  visibility="public"
 >
 </method>
+<method name="getLoaderManager"
+ return="android.app.LoaderManager"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
 <method name="getLocalClassName"
  return="java.lang.String"
  abstract="false"
@@ -19521,6 +22001,28 @@
  visibility="public"
 >
 </method>
+<method name="invalidateOptionsMenu"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="isChangingConfigurations"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
 <method name="isChild"
  return="boolean"
  abstract="false"
@@ -19616,6 +22118,19 @@
 <parameter name="data" type="android.content.Intent">
 </parameter>
 </method>
+<method name="onAttachFragment"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="fragment" type="android.app.Fragment">
+</parameter>
+</method>
 <method name="onAttachedToWindow"
  return="void"
  abstract="false"
@@ -20202,6 +22717,19 @@
  visibility="protected"
 >
 </method>
+<method name="onStartActionMode"
+ return="android.view.ActionMode"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="callback" type="android.view.ActionMode.Callback">
+</parameter>
+</method>
 <method name="onStop"
  return="void"
  abstract="false"
@@ -20315,6 +22843,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"
@@ -20341,6 +22880,47 @@
 <parameter name="exitAnim" type="int">
 </parameter>
 </method>
+<method name="popBackStack"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="popBackStack"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="name" type="java.lang.String">
+</parameter>
+<parameter name="flags" type="int">
+</parameter>
+</method>
+<method name="popBackStack"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="id" type="int">
+</parameter>
+<parameter name="flags" type="int">
+</parameter>
+</method>
 <method name="registerForContextMenu"
  return="void"
  abstract="false"
@@ -20745,6 +23325,19 @@
 <parameter name="args" type="android.os.Bundle">
 </parameter>
 </method>
+<method name="startActionMode"
+ return="android.view.ActionMode"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="callback" type="android.view.ActionMode.Callback">
+</parameter>
+</method>
 <method name="startActivityForResult"
  return="void"
  abstract="false"
@@ -20777,6 +23370,23 @@
 <parameter name="requestCode" type="int">
 </parameter>
 </method>
+<method name="startActivityFromFragment"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="fragment" type="android.app.Fragment">
+</parameter>
+<parameter name="intent" type="android.content.Intent">
+</parameter>
+<parameter name="requestCode" type="int">
+</parameter>
+</method>
 <method name="startActivityIfNeeded"
  return="boolean"
  abstract="false"
@@ -21009,6 +23619,17 @@
  visibility="protected"
 >
 </field>
+<field name="POP_BACK_STACK_INCLUSIVE"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="1"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="RESULT_CANCELED"
  type="int"
  transient="false"
@@ -24170,6 +26791,19 @@
  visibility="protected"
 >
 </method>
+<method name="onStartActionMode"
+ return="android.view.ActionMode"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="callback" type="android.view.ActionMode.Callback">
+</parameter>
+</method>
 <method name="onStop"
  return="void"
  abstract="false"
@@ -24578,6 +27212,211 @@
 </parameter>
 </method>
 </class>
+<class name="DialogFragment"
+ extends="android.app.Fragment"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<implements name="android.content.DialogInterface.OnCancelListener">
+</implements>
+<implements name="android.content.DialogInterface.OnDismissListener">
+</implements>
+<constructor name="DialogFragment"
+ type="android.app.DialogFragment"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</constructor>
+<constructor name="DialogFragment"
+ type="android.app.DialogFragment"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="style" type="int">
+</parameter>
+<parameter name="theme" type="int">
+</parameter>
+</constructor>
+<method name="dismiss"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getCancelable"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getDialog"
+ return="android.app.Dialog"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getTheme"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="onCancel"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="dialog" type="android.content.DialogInterface">
+</parameter>
+</method>
+<method name="onCreateDialog"
+ return="android.app.Dialog"
+ 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="onDismiss"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="dialog" type="android.content.DialogInterface">
+</parameter>
+</method>
+<method name="setCancelable"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="cancelable" type="boolean">
+</parameter>
+</method>
+<method name="show"
+ 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>
+<parameter name="tag" type="java.lang.String">
+</parameter>
+</method>
+<method name="show"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="activity" type="android.app.Activity">
+</parameter>
+<parameter name="transaction" type="android.app.FragmentTransaction">
+</parameter>
+<parameter name="tag" type="java.lang.String">
+</parameter>
+</method>
+<field name="STYLE_NORMAL"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="0"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="STYLE_NO_FRAME"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="2"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="STYLE_NO_INPUT"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="3"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="STYLE_NO_TITLE"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="1"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+</class>
 <class name="ExpandableListActivity"
  extends="android.app.Activity"
  abstract="false"
@@ -24737,6 +27576,986 @@
 </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>
+<implements name="android.view.View.OnCreateContextMenuListener">
+</implements>
+<constructor name="Fragment"
+ type="android.app.Fragment"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</constructor>
+<method name="equals"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="o" type="java.lang.Object">
+</parameter>
+</method>
+<method name="getActivity"
+ return="android.app.Activity"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getId"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getLoaderManager"
+ return="android.app.LoaderManager"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getRetainInstance"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getTag"
+ return="java.lang.String"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getView"
+ return="android.view.View"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="hashCode"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="instantiate"
+ return="android.app.Fragment"
+ 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="fname" type="java.lang.String">
+</parameter>
+<exception name="ClassNotFoundException" type="java.lang.ClassNotFoundException">
+</exception>
+<exception name="IllegalAccessException" type="java.lang.IllegalAccessException">
+</exception>
+<exception name="IllegalArgumentException" type="java.lang.IllegalArgumentException">
+</exception>
+<exception name="InstantiationException" type="java.lang.InstantiationException">
+</exception>
+<exception name="InvocationTargetException" type="java.lang.reflect.InvocationTargetException">
+</exception>
+<exception name="NoSuchMethodException" type="java.lang.NoSuchMethodException">
+</exception>
+</method>
+<method name="isAdded"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="isHidden"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="isResumed"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="isVisible"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="onActivityCreated"
+ 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="onActivityResult"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="requestCode" type="int">
+</parameter>
+<parameter name="resultCode" type="int">
+</parameter>
+<parameter name="data" type="android.content.Intent">
+</parameter>
+</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="onContextItemSelected"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="item" type="android.view.MenuItem">
+</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="onCreateAnimation"
+ return="android.view.animation.Animation"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="transit" type="int">
+</parameter>
+<parameter name="enter" type="boolean">
+</parameter>
+<parameter name="nextAnim" type="int">
+</parameter>
+</method>
+<method name="onCreateContextMenu"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="menu" type="android.view.ContextMenu">
+</parameter>
+<parameter name="v" type="android.view.View">
+</parameter>
+<parameter name="menuInfo" type="android.view.ContextMenu.ContextMenuInfo">
+</parameter>
+</method>
+<method name="onCreateOptionsMenu"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="menu" type="android.view.Menu">
+</parameter>
+<parameter name="inflater" type="android.view.MenuInflater">
+</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>
+<parameter name="savedInstanceState" type="android.os.Bundle">
+</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="onDestroyView"
+ 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="onHiddenChanged"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="hidden" type="boolean">
+</parameter>
+</method>
+<method name="onInflate"
+ 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>
+<parameter name="attrs" type="android.util.AttributeSet">
+</parameter>
+<parameter name="savedInstanceState" type="android.os.Bundle">
+</parameter>
+</method>
+<method name="onLowMemory"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="onOptionsItemSelected"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="item" type="android.view.MenuItem">
+</parameter>
+</method>
+<method name="onOptionsMenuClosed"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="menu" type="android.view.Menu">
+</parameter>
+</method>
+<method name="onPause"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="onPrepareOptionsMenu"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="menu" type="android.view.Menu">
+</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="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>
+<method name="registerForContextMenu"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="view" type="android.view.View">
+</parameter>
+</method>
+<method name="setHasOptionsMenu"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="hasMenu" type="boolean">
+</parameter>
+</method>
+<method name="setRetainInstance"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="retain" type="boolean">
+</parameter>
+</method>
+<method name="startActivity"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="intent" type="android.content.Intent">
+</parameter>
+</method>
+<method name="startActivityForResult"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="intent" type="android.content.Intent">
+</parameter>
+<parameter name="requestCode" type="int">
+</parameter>
+</method>
+<method name="unregisterForContextMenu"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="view" type="android.view.View">
+</parameter>
+</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="tag" type="java.lang.String">
+</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="containerViewId" type="int">
+</parameter>
+<parameter name="fragment" type="android.app.Fragment">
+</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="containerViewId" type="int">
+</parameter>
+<parameter name="fragment" type="android.app.Fragment">
+</parameter>
+<parameter name="tag" type="java.lang.String">
+</parameter>
+</method>
+<method name="addToBackStack"
+ return="android.app.FragmentTransaction"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="name" type="java.lang.String">
+</parameter>
+</method>
+<method name="commit"
+ return="int"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="hide"
+ 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>
+<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>
+<method name="replace"
+ return="android.app.FragmentTransaction"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="containerViewId" type="int">
+</parameter>
+<parameter name="fragment" type="android.app.Fragment">
+</parameter>
+</method>
+<method name="replace"
+ return="android.app.FragmentTransaction"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="containerViewId" type="int">
+</parameter>
+<parameter name="fragment" type="android.app.Fragment">
+</parameter>
+<parameter name="tag" type="java.lang.String">
+</parameter>
+</method>
+<method name="setCustomAnimations"
+ return="android.app.FragmentTransaction"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="enter" type="int">
+</parameter>
+<parameter name="exit" type="int">
+</parameter>
+</method>
+<method name="setTransition"
+ return="android.app.FragmentTransaction"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="transit" type="int">
+</parameter>
+</method>
+<method name="setTransitionStyle"
+ return="android.app.FragmentTransaction"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="styleRes" type="int">
+</parameter>
+</method>
+<method name="show"
+ 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>
+<field name="TRANSIT_ACTIVITY_CLOSE"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="8199"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="TRANSIT_ACTIVITY_OPEN"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="4102"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="TRANSIT_ENTER"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="4097"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="TRANSIT_ENTER_MASK"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="4096"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="TRANSIT_EXIT"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="8194"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="TRANSIT_EXIT_MASK"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="8192"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="TRANSIT_HIDE"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="8196"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="TRANSIT_NONE"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="0"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="TRANSIT_PREVIEW_DONE"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="5"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="TRANSIT_SHOW"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="4099"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="TRANSIT_TASK_CLOSE"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="8201"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="TRANSIT_TASK_OPEN"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="4104"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="TRANSIT_TASK_TO_BACK"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="8203"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="TRANSIT_TASK_TO_FRONT"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="4106"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="TRANSIT_UNSET"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="-1"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="TRANSIT_WALLPAPER_CLOSE"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="8204"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="TRANSIT_WALLPAPER_INTRA_CLOSE"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="8207"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="TRANSIT_WALLPAPER_INTRA_OPEN"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="4110"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="TRANSIT_WALLPAPER_OPEN"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="4109"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+</interface>
 <class name="Instrumentation"
  extends="java.lang.Object"
  abstract="false"
@@ -26186,6 +30005,373 @@
 </parameter>
 </method>
 </class>
+<class name="ListFragment"
+ extends="android.app.Fragment"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="ListFragment"
+ type="android.app.ListFragment"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</constructor>
+<method name="getListAdapter"
+ return="android.widget.ListAdapter"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getListView"
+ return="android.widget.ListView"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getSelectedItemId"
+ return="long"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getSelectedItemPosition"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="onListItemClick"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="l" type="android.widget.ListView">
+</parameter>
+<parameter name="v" type="android.view.View">
+</parameter>
+<parameter name="position" type="int">
+</parameter>
+<parameter name="id" type="long">
+</parameter>
+</method>
+<method name="setEmptyText"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="text" type="java.lang.CharSequence">
+</parameter>
+</method>
+<method name="setListAdapter"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="adapter" type="android.widget.ListAdapter">
+</parameter>
+</method>
+<method name="setListShown"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="shown" type="boolean">
+</parameter>
+</method>
+<method name="setListShownNoAnimation"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="shown" type="boolean">
+</parameter>
+</method>
+<method name="setSelection"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="position" type="int">
+</parameter>
+</method>
+</class>
+<interface name="LoaderManager"
+ abstract="true"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<method name="getLoader"
+ return="android.content.Loader&lt;D&gt;"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="id" type="int">
+</parameter>
+</method>
+<method name="initLoader"
+ return="android.content.Loader&lt;D&gt;"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="id" type="int">
+</parameter>
+<parameter name="args" type="android.os.Bundle">
+</parameter>
+<parameter name="callback" type="android.app.LoaderManager.LoaderCallbacks&lt;D&gt;">
+</parameter>
+</method>
+<method name="restartLoader"
+ return="android.content.Loader&lt;D&gt;"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="id" type="int">
+</parameter>
+<parameter name="args" type="android.os.Bundle">
+</parameter>
+<parameter name="callback" type="android.app.LoaderManager.LoaderCallbacks&lt;D&gt;">
+</parameter>
+</method>
+<method name="stopLoader"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="id" type="int">
+</parameter>
+</method>
+</interface>
+<interface name="LoaderManager.LoaderCallbacks"
+ abstract="true"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<method name="onCreateLoader"
+ return="android.content.Loader&lt;D&gt;"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="id" type="int">
+</parameter>
+<parameter name="args" type="android.os.Bundle">
+</parameter>
+</method>
+<method name="onLoadFinished"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="loader" type="android.content.Loader&lt;D&gt;">
+</parameter>
+<parameter name="data" type="D">
+</parameter>
+</method>
+</interface>
+<class name="LoaderManagingFragment"
+ extends="android.app.Fragment"
+ abstract="true"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<implements name="android.content.Loader.OnLoadCompleteListener">
+</implements>
+<constructor name="LoaderManagingFragment"
+ type="android.app.LoaderManagingFragment"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</constructor>
+<method name="getLoader"
+ return="android.content.Loader&lt;D&gt;"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="id" type="int">
+</parameter>
+</method>
+<method name="onCreateLoader"
+ return="android.content.Loader&lt;D&gt;"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="protected"
+>
+<parameter name="id" type="int">
+</parameter>
+<parameter name="args" type="android.os.Bundle">
+</parameter>
+</method>
+<method name="onInitializeLoaders"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="protected"
+>
+</method>
+<method name="onLoadComplete"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="loader" type="android.content.Loader&lt;D&gt;">
+</parameter>
+<parameter name="data" type="D">
+</parameter>
+</method>
+<method name="onLoadFinished"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="protected"
+>
+<parameter name="loader" type="android.content.Loader&lt;D&gt;">
+</parameter>
+<parameter name="data" type="D">
+</parameter>
+</method>
+<method name="startLoading"
+ return="android.content.Loader&lt;D&gt;"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="id" type="int">
+</parameter>
+<parameter name="args" type="android.os.Bundle">
+</parameter>
+</method>
+<method name="stopLoading"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="id" type="int">
+</parameter>
+</method>
+</class>
 <class name="LocalActivityManager"
  extends="java.lang.Object"
  abstract="false"
@@ -29899,6 +34085,17 @@
  visibility="public"
 >
 </field>
+<field name="USES_POLICY_SETS_GLOBAL_PROXY"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="5"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="USES_POLICY_WATCH_LOGIN"
  type="int"
  transient="false"
@@ -30188,6 +34385,17 @@
  visibility="public"
 >
 </method>
+<method name="getGlobalProxyAdmin"
+ return="android.content.ComponentName"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
 <method name="getMaximumFailedPasswordsForWipe"
  return="int"
  abstract="false"
@@ -30214,6 +34422,19 @@
 <parameter name="admin" type="android.content.ComponentName">
 </parameter>
 </method>
+<method name="getPasswordHistoryLength"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="admin" type="android.content.ComponentName">
+</parameter>
+</method>
 <method name="getPasswordMaximumLength"
  return="int"
  abstract="false"
@@ -30240,6 +34461,84 @@
 <parameter name="admin" type="android.content.ComponentName">
 </parameter>
 </method>
+<method name="getPasswordMinimumLetters"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="admin" type="android.content.ComponentName">
+</parameter>
+</method>
+<method name="getPasswordMinimumLowerCase"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="admin" type="android.content.ComponentName">
+</parameter>
+</method>
+<method name="getPasswordMinimumNonLetter"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="admin" type="android.content.ComponentName">
+</parameter>
+</method>
+<method name="getPasswordMinimumNumeric"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="admin" type="android.content.ComponentName">
+</parameter>
+</method>
+<method name="getPasswordMinimumSymbols"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="admin" type="android.content.ComponentName">
+</parameter>
+</method>
+<method name="getPasswordMinimumUpperCase"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="admin" type="android.content.ComponentName">
+</parameter>
+</method>
 <method name="getPasswordQuality"
  return="int"
  abstract="false"
@@ -30316,6 +34615,23 @@
 <parameter name="flags" type="int">
 </parameter>
 </method>
+<method name="setGlobalProxy"
+ return="android.content.ComponentName"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="admin" type="android.content.ComponentName">
+</parameter>
+<parameter name="proxySpec" type="java.net.Proxy">
+</parameter>
+<parameter name="exclusionList" type="java.util.List&lt;java.lang.String&gt;">
+</parameter>
+</method>
 <method name="setMaximumFailedPasswordsForWipe"
  return="void"
  abstract="false"
@@ -30346,6 +34662,21 @@
 <parameter name="timeMs" type="long">
 </parameter>
 </method>
+<method name="setPasswordHistoryLength"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="admin" type="android.content.ComponentName">
+</parameter>
+<parameter name="length" type="int">
+</parameter>
+</method>
 <method name="setPasswordMinimumLength"
  return="void"
  abstract="false"
@@ -30361,6 +34692,96 @@
 <parameter name="length" type="int">
 </parameter>
 </method>
+<method name="setPasswordMinimumLetters"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="admin" type="android.content.ComponentName">
+</parameter>
+<parameter name="length" type="int">
+</parameter>
+</method>
+<method name="setPasswordMinimumLowerCase"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="admin" type="android.content.ComponentName">
+</parameter>
+<parameter name="length" type="int">
+</parameter>
+</method>
+<method name="setPasswordMinimumNonLetter"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="admin" type="android.content.ComponentName">
+</parameter>
+<parameter name="length" type="int">
+</parameter>
+</method>
+<method name="setPasswordMinimumNumeric"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="admin" type="android.content.ComponentName">
+</parameter>
+<parameter name="length" type="int">
+</parameter>
+</method>
+<method name="setPasswordMinimumSymbols"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="admin" type="android.content.ComponentName">
+</parameter>
+<parameter name="length" type="int">
+</parameter>
+</method>
+<method name="setPasswordMinimumUpperCase"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="admin" type="android.content.ComponentName">
+</parameter>
+<parameter name="length" type="int">
+</parameter>
+</method>
 <method name="setPasswordQuality"
  return="void"
  abstract="false"
@@ -30455,6 +34876,17 @@
  visibility="public"
 >
 </field>
+<field name="PASSWORD_QUALITY_COMPLEX"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="393216"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="PASSWORD_QUALITY_NUMERIC"
  type="int"
  transient="false"
@@ -31438,6 +35870,40 @@
 <parameter name="context" type="android.content.Context">
 </parameter>
 </method>
+<method name="notifyAppWidgetViewDataChanged"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="appWidgetIds" type="int[]">
+</parameter>
+<parameter name="views" type="android.widget.RemoteViews">
+</parameter>
+<parameter name="viewId" type="int">
+</parameter>
+</method>
+<method name="notifyAppWidgetViewDataChanged"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="appWidgetId" type="int">
+</parameter>
+<parameter name="views" type="android.widget.RemoteViews">
+</parameter>
+<parameter name="viewId" type="int">
+</parameter>
+</method>
 <method name="updateAppWidget"
  return="void"
  abstract="false"
@@ -34049,6 +38515,71 @@
 </parameter>
 </constructor>
 </class>
+<class name="AsyncTaskLoader"
+ extends="android.content.Loader"
+ abstract="true"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="AsyncTaskLoader"
+ type="android.content.AsyncTaskLoader"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="context" type="android.content.Context">
+</parameter>
+</constructor>
+<method name="cancelLoad"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="forceLoad"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="loadInBackground"
+ return="D"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="onCancelled"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="data" type="D">
+</parameter>
+</method>
+</class>
 <class name="BroadcastReceiver"
  extends="java.lang.Object"
  abstract="true"
@@ -34279,6 +38810,336 @@
 </parameter>
 </method>
 </class>
+<class name="ClipboardManager"
+ extends="android.text.ClipboardManager"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<method name="addPrimaryClipChangedListener"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="what" type="android.content.ClipboardManager.OnPrimaryClipChangedListener">
+</parameter>
+</method>
+<method name="getPrimaryClip"
+ return="android.content.ClippedData"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getText"
+ return="java.lang.CharSequence"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="deprecated"
+ visibility="public"
+>
+</method>
+<method name="hasPrimaryClip"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="hasText"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="removePrimaryClipChangedListener"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="what" type="android.content.ClipboardManager.OnPrimaryClipChangedListener">
+</parameter>
+</method>
+<method name="setPrimaryClip"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="clip" type="android.content.ClippedData">
+</parameter>
+</method>
+<method name="setText"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="deprecated"
+ visibility="public"
+>
+<parameter name="text" type="java.lang.CharSequence">
+</parameter>
+</method>
+</class>
+<interface name="ClipboardManager.OnPrimaryClipChangedListener"
+ abstract="true"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<method name="onPrimaryClipChanged"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+</interface>
+<class name="ClippedData"
+ extends="java.lang.Object"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<implements name="android.os.Parcelable">
+</implements>
+<constructor name="ClippedData"
+ type="android.content.ClippedData"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="label" type="java.lang.CharSequence">
+</parameter>
+<parameter name="icon" type="android.graphics.Bitmap">
+</parameter>
+<parameter name="item" type="android.content.ClippedData.Item">
+</parameter>
+</constructor>
+<method name="addItem"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="item" type="android.content.ClippedData.Item">
+</parameter>
+</method>
+<method name="describeContents"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getIcon"
+ return="android.graphics.Bitmap"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getItem"
+ return="android.content.ClippedData.Item"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="index" type="int">
+</parameter>
+</method>
+<method name="getItemCount"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getLabel"
+ return="java.lang.CharSequence"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="writeToParcel"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="dest" type="android.os.Parcel">
+</parameter>
+<parameter name="flags" type="int">
+</parameter>
+</method>
+<field name="CREATOR"
+ type="android.os.Parcelable.Creator"
+ transient="false"
+ volatile="false"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+</class>
+<class name="ClippedData.Item"
+ extends="java.lang.Object"
+ abstract="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="ClippedData.Item"
+ type="android.content.ClippedData.Item"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="text" type="java.lang.CharSequence">
+</parameter>
+</constructor>
+<constructor name="ClippedData.Item"
+ type="android.content.ClippedData.Item"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="intent" type="android.content.Intent">
+</parameter>
+</constructor>
+<constructor name="ClippedData.Item"
+ type="android.content.ClippedData.Item"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="uri" type="android.net.Uri">
+</parameter>
+</constructor>
+<constructor name="ClippedData.Item"
+ type="android.content.ClippedData.Item"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="text" type="java.lang.CharSequence">
+</parameter>
+<parameter name="intent" type="android.content.Intent">
+</parameter>
+<parameter name="uri" type="android.net.Uri">
+</parameter>
+</constructor>
+<method name="getIntent"
+ return="android.content.Intent"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getText"
+ return="java.lang.CharSequence"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getUri"
+ return="android.net.Uri"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+</class>
 <interface name="ComponentCallbacks"
  abstract="true"
  static="false"
@@ -34848,6 +39709,17 @@
 <parameter name="permission" type="java.lang.String">
 </parameter>
 </method>
+<method name="shutdown"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
 <method name="update"
  return="int"
  abstract="true"
@@ -36642,6 +41514,17 @@
 <parameter name="key" type="java.lang.String">
 </parameter>
 </method>
+<method name="keySet"
+ 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="put"
  return="void"
  abstract="false"
@@ -37692,6 +42575,25 @@
 <parameter name="factory" type="android.database.sqlite.SQLiteDatabase.CursorFactory">
 </parameter>
 </method>
+<method name="openOrCreateDatabase"
+ return="android.database.sqlite.SQLiteDatabase"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="name" type="java.lang.String">
+</parameter>
+<parameter name="mode" type="int">
+</parameter>
+<parameter name="factory" type="android.database.sqlite.SQLiteDatabase.CursorFactory">
+</parameter>
+<parameter name="errorHandler" type="android.database.DatabaseErrorHandler">
+</parameter>
+</method>
 <method name="peekWallpaper"
  return="android.graphics.drawable.Drawable"
  abstract="true"
@@ -39130,6 +44032,25 @@
 <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="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="name" type="java.lang.String">
+</parameter>
+<parameter name="mode" type="int">
+</parameter>
+<parameter name="factory" type="android.database.sqlite.SQLiteDatabase.CursorFactory">
+</parameter>
+<parameter name="errorHandler" type="android.database.DatabaseErrorHandler">
+</parameter>
+</method>
 <method name="peekWallpaper"
  return="android.graphics.drawable.Drawable"
  abstract="false"
@@ -39456,6 +44377,225 @@
 </parameter>
 </method>
 </class>
+<class name="CursorLoader"
+ extends="android.content.AsyncTaskLoader"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="CursorLoader"
+ type="android.content.CursorLoader"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="context" type="android.content.Context">
+</parameter>
+<parameter name="uri" type="android.net.Uri">
+</parameter>
+<parameter name="projection" type="java.lang.String[]">
+</parameter>
+<parameter name="selection" type="java.lang.String">
+</parameter>
+<parameter name="selectionArgs" type="java.lang.String[]">
+</parameter>
+<parameter name="sortOrder" type="java.lang.String">
+</parameter>
+</constructor>
+<method name="deliverResult"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="cursor" type="android.database.Cursor">
+</parameter>
+</method>
+<method name="destroy"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getProjection"
+ return="java.lang.String[]"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getSelection"
+ return="java.lang.String"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getSelectionArgs"
+ return="java.lang.String[]"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getSortOrder"
+ return="java.lang.String"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getUri"
+ return="android.net.Uri"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="loadInBackground"
+ return="android.database.Cursor"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="onCancelled"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="cursor" type="android.database.Cursor">
+</parameter>
+</method>
+<method name="setProjection"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="projection" type="java.lang.String[]">
+</parameter>
+</method>
+<method name="setSelection"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="selection" type="java.lang.String">
+</parameter>
+</method>
+<method name="setSelectionArgs"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="selectionArgs" type="java.lang.String[]">
+</parameter>
+</method>
+<method name="setSortOrder"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="sortOrder" type="java.lang.String">
+</parameter>
+</method>
+<method name="setUri"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="uri" type="android.net.Uri">
+</parameter>
+</method>
+<method name="startLoading"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="stopLoading"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+</class>
 <interface name="DialogInterface"
  abstract="true"
  static="false"
@@ -39817,6 +44957,93 @@
 >
 </method>
 </interface>
+<interface name="IOnPrimaryClipChangedListener"
+ abstract="true"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<implements name="android.os.IInterface">
+</implements>
+<method name="dispatchPrimaryClipChanged"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<exception name="RemoteException" type="android.os.RemoteException">
+</exception>
+</method>
+</interface>
+<class name="IOnPrimaryClipChangedListener.Stub"
+ extends="android.os.Binder"
+ abstract="true"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<implements name="android.content.IOnPrimaryClipChangedListener">
+</implements>
+<constructor name="IOnPrimaryClipChangedListener.Stub"
+ type="android.content.IOnPrimaryClipChangedListener.Stub"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</constructor>
+<method name="asBinder"
+ return="android.os.IBinder"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="asInterface"
+ return="android.content.IOnPrimaryClipChangedListener"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="obj" type="android.os.IBinder">
+</parameter>
+</method>
+<method name="onTransact"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="code" type="int">
+</parameter>
+<parameter name="data" type="android.os.Parcel">
+</parameter>
+<parameter name="reply" type="android.os.Parcel">
+</parameter>
+<parameter name="flags" type="int">
+</parameter>
+<exception name="RemoteException" type="android.os.RemoteException">
+</exception>
+</method>
+</class>
 <class name="Intent"
  extends="java.lang.Object"
  abstract="false"
@@ -44433,6 +49660,183 @@
 </parameter>
 </constructor>
 </class>
+<class name="Loader"
+ extends="java.lang.Object"
+ abstract="true"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="Loader"
+ type="android.content.Loader"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="context" type="android.content.Context">
+</parameter>
+</constructor>
+<method name="deliverResult"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="data" type="D">
+</parameter>
+</method>
+<method name="destroy"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="forceLoad"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getContext"
+ return="android.content.Context"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getId"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="onContentChanged"
+ return="void"
+ 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="id" type="int">
+</parameter>
+<parameter name="listener" type="android.content.Loader.OnLoadCompleteListener&lt;D&gt;">
+</parameter>
+</method>
+<method name="startLoading"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="stopLoading"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</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.content.Loader.OnLoadCompleteListener&lt;D&gt;">
+</parameter>
+</method>
+</class>
+<class name="Loader.ForceLoadContentObserver"
+ extends="android.database.ContentObserver"
+ abstract="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="Loader.ForceLoadContentObserver"
+ type="android.content.Loader.ForceLoadContentObserver"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</constructor>
+</class>
+<interface name="Loader.OnLoadCompleteListener"
+ abstract="true"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<method name="onLoadComplete"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="loader" type="android.content.Loader&lt;D&gt;">
+</parameter>
+<parameter name="data" type="D">
+</parameter>
+</method>
+</interface>
 <class name="MutableContextWrapper"
  extends="android.content.ContextWrapper"
  abstract="false"
@@ -44972,6 +50376,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"
@@ -45103,6 +50522,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"
@@ -45820,6 +51254,145 @@
 >
 </field>
 </class>
+<class name="XmlDocumentProvider"
+ extends="android.content.ContentProvider"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="XmlDocumentProvider"
+ type="android.content.XmlDocumentProvider"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</constructor>
+<method name="delete"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="uri" type="android.net.Uri">
+</parameter>
+<parameter name="selection" type="java.lang.String">
+</parameter>
+<parameter name="selectionArgs" type="java.lang.String[]">
+</parameter>
+</method>
+<method name="getResourceXmlPullParser"
+ return="org.xmlpull.v1.XmlPullParser"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="protected"
+>
+<parameter name="resourceUri" type="android.net.Uri">
+</parameter>
+</method>
+<method name="getType"
+ return="java.lang.String"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="uri" type="android.net.Uri">
+</parameter>
+</method>
+<method name="getUriXmlPullParser"
+ return="org.xmlpull.v1.XmlPullParser"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="protected"
+>
+<parameter name="url" type="java.lang.String">
+</parameter>
+</method>
+<method name="insert"
+ return="android.net.Uri"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="uri" type="android.net.Uri">
+</parameter>
+<parameter name="values" type="android.content.ContentValues">
+</parameter>
+</method>
+<method name="onCreate"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="query"
+ return="android.database.Cursor"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="uri" type="android.net.Uri">
+</parameter>
+<parameter name="projection" type="java.lang.String[]">
+</parameter>
+<parameter name="selection" type="java.lang.String">
+</parameter>
+<parameter name="selectionArgs" type="java.lang.String[]">
+</parameter>
+<parameter name="sortOrder" type="java.lang.String">
+</parameter>
+</method>
+<method name="update"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="uri" type="android.net.Uri">
+</parameter>
+<parameter name="values" type="android.content.ContentValues">
+</parameter>
+<parameter name="selection" type="java.lang.String">
+</parameter>
+<parameter name="selectionArgs" type="java.lang.String[]">
+</parameter>
+</method>
+</class>
 </package>
 <package name="android.content.pm"
 >
@@ -46085,6 +51658,17 @@
  visibility="public"
 >
 </field>
+<field name="FLAG_HARDWARE_ACCELERATED"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="1024"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="FLAG_IMMERSIVE"
  type="int"
  transient="false"
@@ -53542,6 +59126,19 @@
 <parameter name="column" type="int">
 </parameter>
 </method>
+<method name="getType"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="column" type="int">
+</parameter>
+</method>
 <method name="getUpdatedField"
  return="java.lang.Object"
  abstract="false"
@@ -53549,7 +59146,7 @@
  synchronized="false"
  static="false"
  final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
  visibility="protected"
 >
 <parameter name="columnIndex" type="int">
@@ -53617,7 +59214,7 @@
  synchronized="false"
  static="false"
  final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
  visibility="protected"
 >
 <parameter name="columnIndex" type="int">
@@ -53903,7 +59500,7 @@
  volatile="false"
  static="false"
  final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
  visibility="protected"
 >
 </field>
@@ -54039,7 +59636,7 @@
  synchronized="false"
  static="false"
  final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
  visibility="public"
 >
 <parameter name="columnIndex" type="int">
@@ -54052,7 +59649,7 @@
  synchronized="false"
  static="false"
  final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
  visibility="public"
 >
 <parameter name="columnIndex" type="int">
@@ -54065,7 +59662,7 @@
  synchronized="false"
  static="false"
  final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
  visibility="public"
 >
 <parameter name="columnIndex" type="int">
@@ -54091,7 +59688,7 @@
  synchronized="false"
  static="false"
  final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
  visibility="public"
 >
 <parameter name="columnIndex" type="int">
@@ -54565,6 +60162,19 @@
 <parameter name="columnIndex" type="int">
 </parameter>
 </method>
+<method name="getType"
+ return="int"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="columnIndex" type="int">
+</parameter>
+</method>
 <method name="getWantsAllOnMoveCalls"
  return="boolean"
  abstract="true"
@@ -54805,6 +60415,61 @@
 <parameter name="observer" type="android.database.DataSetObserver">
 </parameter>
 </method>
+<field name="FIELD_TYPE_BLOB"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="4"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="FIELD_TYPE_FLOAT"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="2"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="FIELD_TYPE_INTEGER"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="1"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="FIELD_TYPE_NULL"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="0"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="FIELD_TYPE_STRING"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="3"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 </interface>
 <class name="CursorIndexOutOfBoundsException"
  extends="java.lang.IndexOutOfBoundsException"
@@ -55162,6 +60827,21 @@
 <parameter name="col" type="int">
 </parameter>
 </method>
+<method name="getType"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="row" type="int">
+</parameter>
+<parameter name="col" type="int">
+</parameter>
+</method>
 <method name="isBlob"
  return="boolean"
  abstract="false"
@@ -55169,7 +60849,7 @@
  synchronized="false"
  static="false"
  final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
  visibility="public"
 >
 <parameter name="row" type="int">
@@ -55184,7 +60864,7 @@
  synchronized="false"
  static="false"
  final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
  visibility="public"
 >
 <parameter name="row" type="int">
@@ -55199,7 +60879,7 @@
  synchronized="false"
  static="false"
  final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
  visibility="public"
 >
 <parameter name="row" type="int">
@@ -55214,7 +60894,7 @@
  synchronized="false"
  static="false"
  final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
  visibility="public"
 >
 <parameter name="row" type="int">
@@ -55229,7 +60909,7 @@
  synchronized="false"
  static="false"
  final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
  visibility="public"
 >
 <parameter name="row" type="int">
@@ -55640,6 +61320,19 @@
 <parameter name="columnIndex" type="int">
 </parameter>
 </method>
+<method name="getType"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="columnIndex" type="int">
+</parameter>
+</method>
 <method name="getWantsAllOnMoveCalls"
  return="boolean"
  abstract="false"
@@ -55651,6 +61344,17 @@
  visibility="public"
 >
 </method>
+<method name="getWrappedCursor"
+ return="android.database.Cursor"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
 <method name="isAfterLast"
  return="boolean"
  abstract="false"
@@ -55959,6 +61663,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"
@@ -56459,6 +62184,19 @@
 <parameter name="name" type="java.lang.String">
 </parameter>
 </method>
+<method name="getSqlStatementType"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="sql" type="java.lang.String">
+</parameter>
+</method>
 <method name="longForQuery"
  return="long"
  abstract="false"
@@ -56609,6 +62347,83 @@
 <parameter name="e" type="java.lang.Exception">
 </parameter>
 </method>
+<field name="STATEMENT_ABORT"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="6"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="STATEMENT_ATTACH"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="3"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="STATEMENT_BEGIN"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="4"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="STATEMENT_COMMIT"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="5"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="STATEMENT_OTHER"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="7"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="STATEMENT_SELECT"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="1"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="STATEMENT_UPDATE"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="2"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 </class>
 <class name="DatabaseUtils.InsertHelper"
  extends="java.lang.Object"
@@ -56843,6 +62658,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"
@@ -57326,6 +63173,114 @@
 </parameter>
 </constructor>
 </class>
+<class name="SQLiteAccessPermException"
+ extends="android.database.sqlite.SQLiteException"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="SQLiteAccessPermException"
+ type="android.database.sqlite.SQLiteAccessPermException"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</constructor>
+<constructor name="SQLiteAccessPermException"
+ type="android.database.sqlite.SQLiteAccessPermException"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="error" type="java.lang.String">
+</parameter>
+</constructor>
+</class>
+<class name="SQLiteBindOrColumnIndexOutOfRangeException"
+ extends="android.database.sqlite.SQLiteException"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="SQLiteBindOrColumnIndexOutOfRangeException"
+ type="android.database.sqlite.SQLiteBindOrColumnIndexOutOfRangeException"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</constructor>
+<constructor name="SQLiteBindOrColumnIndexOutOfRangeException"
+ type="android.database.sqlite.SQLiteBindOrColumnIndexOutOfRangeException"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="error" type="java.lang.String">
+</parameter>
+</constructor>
+</class>
+<class name="SQLiteBlobTooBigException"
+ extends="android.database.sqlite.SQLiteException"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="SQLiteBlobTooBigException"
+ type="android.database.sqlite.SQLiteBlobTooBigException"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</constructor>
+<constructor name="SQLiteBlobTooBigException"
+ type="android.database.sqlite.SQLiteBlobTooBigException"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="error" type="java.lang.String">
+</parameter>
+</constructor>
+</class>
+<class name="SQLiteCantOpenDatabaseException"
+ extends="android.database.sqlite.SQLiteException"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="SQLiteCantOpenDatabaseException"
+ type="android.database.sqlite.SQLiteCantOpenDatabaseException"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</constructor>
+<constructor name="SQLiteCantOpenDatabaseException"
+ type="android.database.sqlite.SQLiteCantOpenDatabaseException"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="error" type="java.lang.String">
+</parameter>
+</constructor>
+</class>
 <class name="SQLiteClosable"
  extends="java.lang.Object"
  abstract="true"
@@ -57437,7 +63392,7 @@
  type="android.database.sqlite.SQLiteCursor"
  static="false"
  final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
  visibility="public"
 >
 <parameter name="db" type="android.database.sqlite.SQLiteDatabase">
@@ -57449,6 +63404,20 @@
 <parameter name="query" type="android.database.sqlite.SQLiteQuery">
 </parameter>
 </constructor>
+<constructor name="SQLiteCursor"
+ type="android.database.sqlite.SQLiteCursor"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="driver" type="android.database.sqlite.SQLiteCursorDriver">
+</parameter>
+<parameter name="editTable" type="java.lang.String">
+</parameter>
+<parameter name="query" type="android.database.sqlite.SQLiteQuery">
+</parameter>
+</constructor>
 <method name="getColumnNames"
  return="java.lang.String[]"
  abstract="false"
@@ -57586,6 +63555,17 @@
  visibility="public"
 >
 </method>
+<method name="beginTransactionNonExclusive"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
 <method name="beginTransactionWithListener"
  return="void"
  abstract="false"
@@ -57599,6 +63579,19 @@
 <parameter name="transactionListener" type="android.database.sqlite.SQLiteTransactionListener">
 </parameter>
 </method>
+<method name="beginTransactionWithListenerNonExclusive"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="transactionListener" type="android.database.sqlite.SQLiteTransactionListener">
+</parameter>
+</method>
 <method name="close"
  return="void"
  abstract="false"
@@ -57655,6 +63648,17 @@
 <parameter name="whereArgs" type="java.lang.String[]">
 </parameter>
 </method>
+<method name="enableWriteAheadLogging"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="true"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
 <method name="endTransaction"
  return="void"
  abstract="false"
@@ -57711,6 +63715,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"
@@ -57751,7 +63766,7 @@
  synchronized="false"
  static="false"
  final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
  visibility="public"
 >
 </method>
@@ -57832,6 +63847,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"
@@ -57883,7 +63909,7 @@
  synchronized="false"
  static="false"
  final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
  visibility="public"
 >
 <parameter name="table" type="java.lang.String">
@@ -57898,7 +63924,7 @@
  synchronized="false"
  static="false"
  final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
  visibility="public"
 >
 <parameter name="table" type="java.lang.String">
@@ -57949,6 +63975,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"
@@ -57979,6 +64024,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"
@@ -58172,6 +64234,19 @@
 <exception name="SQLException" type="android.database.SQLException">
 </exception>
 </method>
+<method name="setConnectionPoolSize"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="true"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="size" type="int">
+</parameter>
+</method>
 <method name="setLocale"
  return="void"
  abstract="false"
@@ -58198,6 +64273,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"
@@ -58400,6 +64488,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"
@@ -58499,6 +64598,60 @@
 </parameter>
 </constructor>
 </class>
+<class name="SQLiteDatabaseLockedException"
+ extends="android.database.sqlite.SQLiteException"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="SQLiteDatabaseLockedException"
+ type="android.database.sqlite.SQLiteDatabaseLockedException"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</constructor>
+<constructor name="SQLiteDatabaseLockedException"
+ type="android.database.sqlite.SQLiteDatabaseLockedException"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="error" type="java.lang.String">
+</parameter>
+</constructor>
+</class>
+<class name="SQLiteDatatypeMismatchException"
+ extends="android.database.sqlite.SQLiteException"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="SQLiteDatatypeMismatchException"
+ type="android.database.sqlite.SQLiteDatatypeMismatchException"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</constructor>
+<constructor name="SQLiteDatatypeMismatchException"
+ type="android.database.sqlite.SQLiteDatatypeMismatchException"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="error" type="java.lang.String">
+</parameter>
+</constructor>
+</class>
 <class name="SQLiteDiskIOException"
  extends="android.database.sqlite.SQLiteException"
  abstract="false"
@@ -58658,6 +64811,24 @@
 <parameter name="version" type="int">
 </parameter>
 </constructor>
+<constructor name="SQLiteOpenHelper"
+ type="android.database.sqlite.SQLiteOpenHelper"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="context" type="android.content.Context">
+</parameter>
+<parameter name="name" type="java.lang.String">
+</parameter>
+<parameter name="factory" type="android.database.sqlite.SQLiteDatabase.CursorFactory">
+</parameter>
+<parameter name="version" type="int">
+</parameter>
+<parameter name="errorHandler" type="android.database.DatabaseErrorHandler">
+</parameter>
+</constructor>
 <method name="close"
  return="void"
  abstract="false"
@@ -58735,6 +64906,33 @@
 </parameter>
 </method>
 </class>
+<class name="SQLiteOutOfMemoryException"
+ extends="android.database.sqlite.SQLiteException"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="SQLiteOutOfMemoryException"
+ type="android.database.sqlite.SQLiteOutOfMemoryException"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</constructor>
+<constructor name="SQLiteOutOfMemoryException"
+ type="android.database.sqlite.SQLiteOutOfMemoryException"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="error" type="java.lang.String">
+</parameter>
+</constructor>
+</class>
 <class name="SQLiteProgram"
  extends="android.database.sqlite.SQLiteClosable"
  abstract="true"
@@ -58743,6 +64941,19 @@
  deprecated="not deprecated"
  visibility="public"
 >
+<method name="bindAllArgsAsStrings"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="bindArgs" type="java.lang.String[]">
+</parameter>
+</method>
 <method name="bindBlob"
  return="void"
  abstract="false"
@@ -58860,7 +65071,7 @@
  synchronized="false"
  static="false"
  final="true"
- deprecated="not deprecated"
+ deprecated="deprecated"
  visibility="public"
 >
 </method>
@@ -59283,6 +65494,33 @@
 </parameter>
 </method>
 </class>
+<class name="SQLiteReadOnlyDatabaseException"
+ extends="android.database.sqlite.SQLiteException"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="SQLiteReadOnlyDatabaseException"
+ type="android.database.sqlite.SQLiteReadOnlyDatabaseException"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</constructor>
+<constructor name="SQLiteReadOnlyDatabaseException"
+ type="android.database.sqlite.SQLiteReadOnlyDatabaseException"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="error" type="java.lang.String">
+</parameter>
+</constructor>
+</class>
 <class name="SQLiteStatement"
  extends="android.database.sqlite.SQLiteProgram"
  abstract="false"
@@ -59313,6 +65551,17 @@
  visibility="public"
 >
 </method>
+<method name="executeUpdateDelete"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
 <method name="simpleQueryForLong"
  return="long"
  abstract="false"
@@ -59336,6 +65585,33 @@
 >
 </method>
 </class>
+<class name="SQLiteTableLockedException"
+ extends="android.database.sqlite.SQLiteException"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="SQLiteTableLockedException"
+ type="android.database.sqlite.SQLiteTableLockedException"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</constructor>
+<constructor name="SQLiteTableLockedException"
+ type="android.database.sqlite.SQLiteTableLockedException"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="error" type="java.lang.String">
+</parameter>
+</constructor>
+</class>
 <interface name="SQLiteTransactionListener"
  abstract="true"
  static="false"
@@ -62537,7 +68813,7 @@
  type="android.graphics.Canvas"
  static="false"
  final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
  visibility="public"
 >
 <parameter name="gl" type="javax.microedition.khronos.opengles.GL">
@@ -63316,7 +69592,7 @@
 <method name="drawText"
  return="void"
  abstract="false"
- native="true"
+ native="false"
  synchronized="false"
  static="false"
  final="false"
@@ -63466,7 +69742,7 @@
  synchronized="false"
  static="true"
  final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
  visibility="public"
 >
 </method>
@@ -63523,7 +69799,7 @@
  synchronized="false"
  static="false"
  final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
  visibility="public"
 >
 </method>
@@ -63584,6 +69860,17 @@
  visibility="public"
 >
 </method>
+<method name="isHardwareAccelerated"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
 <method name="isOpaque"
  return="boolean"
  abstract="false"
@@ -63897,7 +70184,7 @@
  synchronized="false"
  static="false"
  final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
  visibility="public"
 >
 <parameter name="width" type="int">
@@ -73269,6 +79556,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"
@@ -74163,6 +80461,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"
@@ -92082,6 +98410,25 @@
 <parameter name="profile" type="android.media.CamcorderProfile">
 </parameter>
 </method>
+<method name="setTimeLapseParameters"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="enableTimeLapse" type="boolean">
+</parameter>
+<parameter name="useStillCameraForTimeLapse" type="boolean">
+</parameter>
+<parameter name="timeBetweenTimeLapseFrameCaptureMs" type="int">
+</parameter>
+<parameter name="encoderLevel" type="int">
+</parameter>
+</method>
 <method name="setVideoEncoder"
  return="void"
  abstract="false"
@@ -121600,6 +127947,17 @@
  visibility="public"
 >
 </field>
+<field name="HONEYCOMB"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="10000"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 </class>
 <class name="Bundle"
  extends="java.lang.Object"
@@ -129523,6 +135881,53 @@
 </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"
@@ -129531,6 +135936,28 @@
  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="getMountedObbPath"
  return="java.lang.String"
  abstract="false"
@@ -129557,6 +135984,28 @@
 <parameter name="filename" type="java.lang.String">
 </parameter>
 </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="mountObb"
  return="boolean"
  abstract="false"
@@ -129572,6 +136021,19 @@
 <parameter name="key" type="java.lang.String">
 </parameter>
 </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="unmountObb"
  return="boolean"
  abstract="false"
@@ -129587,6 +136049,124 @@
 <parameter name="force" type="boolean">
 </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"
@@ -130382,6 +136962,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"
@@ -130500,6 +137222,17 @@
  visibility="public"
 >
 </method>
+<method name="getFragment"
+ return="java.lang.String"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
 <method name="getIntent"
  return="android.content.Intent"
  abstract="false"
@@ -131062,6 +137795,19 @@
 <parameter name="enabled" type="boolean">
 </parameter>
 </method>
+<method name="setFragment"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="fragment" type="java.lang.String">
+</parameter>
+</method>
 <method name="setIntent"
  return="void"
  abstract="false"
@@ -131380,6 +138126,8 @@
  deprecated="not deprecated"
  visibility="public"
 >
+<implements name="android.preference.PreferenceFragment.OnPreferenceStartFragmentCallback">
+</implements>
 <constructor name="PreferenceActivity"
  type="android.preference.PreferenceActivity"
  static="false"
@@ -131395,6 +138143,291 @@
  synchronized="false"
  static="false"
  final="false"
+ deprecated="deprecated"
+ visibility="public"
+>
+<parameter name="intent" type="android.content.Intent">
+</parameter>
+</method>
+<method name="addPreferencesFromResource"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="deprecated"
+ visibility="public"
+>
+<parameter name="preferencesResId" type="int">
+</parameter>
+</method>
+<method name="findPreference"
+ return="android.preference.Preference"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="deprecated"
+ visibility="public"
+>
+<parameter name="key" type="java.lang.CharSequence">
+</parameter>
+</method>
+<method name="getPreferenceManager"
+ return="android.preference.PreferenceManager"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="deprecated"
+ visibility="public"
+>
+</method>
+<method name="getPreferenceScreen"
+ return="android.preference.PreferenceScreen"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="deprecated"
+ visibility="public"
+>
+</method>
+<method name="loadHeadersFromResource"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="resid" type="int">
+</parameter>
+<parameter name="target" type="java.util.List&lt;android.preference.PreferenceActivity.Header&gt;">
+</parameter>
+</method>
+<method name="onBuildHeaders"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="target" type="java.util.List&lt;android.preference.PreferenceActivity.Header&gt;">
+</parameter>
+</method>
+<method name="onGetInitialFragment"
+ return="java.lang.String"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="onHeaderClick"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="header" type="android.preference.PreferenceActivity.Header">
+</parameter>
+<parameter name="position" type="int">
+</parameter>
+</method>
+<method name="onIsHidingHeaders"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="onIsMultiPane"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="onPreferenceStartFragment"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="caller" type="android.preference.PreferenceFragment">
+</parameter>
+<parameter name="pref" type="android.preference.Preference">
+</parameter>
+</method>
+<method name="onPreferenceTreeClick"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="deprecated"
+ visibility="public"
+>
+<parameter name="preferenceScreen" type="android.preference.PreferenceScreen">
+</parameter>
+<parameter name="preference" type="android.preference.Preference">
+</parameter>
+</method>
+<method name="setPreferenceScreen"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="deprecated"
+ visibility="public"
+>
+<parameter name="preferenceScreen" type="android.preference.PreferenceScreen">
+</parameter>
+</method>
+<method name="startWithFragment"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="fragmentName" type="java.lang.String">
+</parameter>
+</method>
+<method name="switchToHeader"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="fragmentName" type="java.lang.String">
+</parameter>
+</method>
+</class>
+<class name="PreferenceActivity.Header"
+ extends="java.lang.Object"
+ abstract="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="PreferenceActivity.Header"
+ type="android.preference.PreferenceActivity.Header"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</constructor>
+</class>
+<class name="PreferenceCategory"
+ extends="android.preference.PreferenceGroup"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="PreferenceCategory"
+ type="android.preference.PreferenceCategory"
+ 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>
+<parameter name="defStyle" type="int">
+</parameter>
+</constructor>
+<constructor name="PreferenceCategory"
+ type="android.preference.PreferenceCategory"
+ 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="PreferenceCategory"
+ type="android.preference.PreferenceCategory"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="context" type="android.content.Context">
+</parameter>
+</constructor>
+</class>
+<class name="PreferenceFragment"
+ extends="android.app.Fragment"
+ abstract="true"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="PreferenceFragment"
+ type="android.preference.PreferenceFragment"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</constructor>
+<method name="addPreferencesFromIntent"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
  deprecated="not deprecated"
  visibility="public"
 >
@@ -131478,51 +138511,29 @@
 </parameter>
 </method>
 </class>
-<class name="PreferenceCategory"
- extends="android.preference.PreferenceGroup"
- abstract="false"
+<interface name="PreferenceFragment.OnPreferenceStartFragmentCallback"
+ abstract="true"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<method name="onPreferenceStartFragment"
+ return="boolean"
+ abstract="true"
+ native="false"
+ synchronized="false"
  static="false"
  final="false"
  deprecated="not deprecated"
  visibility="public"
 >
-<constructor name="PreferenceCategory"
- type="android.preference.PreferenceCategory"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="context" type="android.content.Context">
+<parameter name="caller" type="android.preference.PreferenceFragment">
 </parameter>
-<parameter name="attrs" type="android.util.AttributeSet">
+<parameter name="pref" type="android.preference.Preference">
 </parameter>
-<parameter name="defStyle" type="int">
-</parameter>
-</constructor>
-<constructor name="PreferenceCategory"
- type="android.preference.PreferenceCategory"
- 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="PreferenceCategory"
- type="android.preference.PreferenceCategory"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="context" type="android.content.Context">
-</parameter>
-</constructor>
-</class>
+</method>
+</interface>
 <class name="PreferenceGroup"
  extends="android.preference.Preference"
  abstract="true"
@@ -132422,9 +139433,9 @@
  deprecated="not deprecated"
  visibility="public"
 >
-<parameter name="c" type="android.content.Context">
+<parameter name="context" type="android.content.Context">
 </parameter>
-<parameter name="s" type="java.lang.String">
+<parameter name="string" type="java.lang.String">
 </parameter>
 </method>
 <method name="truncateHistory"
@@ -132808,7 +139819,7 @@
  value="&quot;url&quot;"
  static="true"
  final="true"
- deprecated="not deprecated"
+ deprecated="deprecated"
  visibility="public"
 >
 </field>
@@ -138763,6 +145774,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"
@@ -138774,6 +145796,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"
@@ -148856,7 +155889,7 @@
  deprecated="not deprecated"
  visibility="public"
 >
-<parameter name="text" type="android.text.Editable">
+<parameter name="s" type="android.text.Editable">
 </parameter>
 </method>
 <method name="beforeTextChanged"
@@ -153687,6 +160720,36 @@
 >
 </method>
 </class>
+<class name="LoaderTestCase"
+ extends="android.test.AndroidTestCase"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="LoaderTestCase"
+ type="android.test.LoaderTestCase"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</constructor>
+<method name="getLoaderResultSynchronously"
+ return="T"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="loader" type="android.content.Loader&lt;T&gt;">
+</parameter>
+</method>
+</class>
 <class name="MoreAsserts"
  extends="java.lang.Object"
  abstract="false"
@@ -156607,6 +163670,25 @@
 <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="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="file" type="java.lang.String">
+</parameter>
+<parameter name="mode" type="int">
+</parameter>
+<parameter name="factory" type="android.database.sqlite.SQLiteDatabase.CursorFactory">
+</parameter>
+<parameter name="errorHandler" type="android.database.DatabaseErrorHandler">
+</parameter>
+</method>
 <method name="peekWallpaper"
  return="android.graphics.drawable.Drawable"
  abstract="false"
@@ -156951,17 +164033,6 @@
  visibility="public"
 >
 </constructor>
-<method name="abortUpdates"
- return="void"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-</method>
 <method name="close"
  return="void"
  abstract="false"
@@ -156973,30 +164044,6 @@
  visibility="public"
 >
 </method>
-<method name="commitUpdates"
- return="boolean"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-</method>
-<method name="commitUpdates"
- return="boolean"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="values" type="java.util.Map&lt;? extends java.lang.Long, ? extends java.util.Map&lt;java.lang.String, java.lang.Object&gt;&gt;">
-</parameter>
-</method>
 <method name="copyStringToBuffer"
  return="void"
  abstract="false"
@@ -157023,17 +164070,6 @@
  visibility="public"
 >
 </method>
-<method name="deleteRow"
- return="boolean"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-</method>
 <method name="getBlob"
  return="byte[]"
  abstract="false"
@@ -157219,8 +164255,8 @@
 <parameter name="columnIndex" type="int">
 </parameter>
 </method>
-<method name="getWantsAllOnMoveCalls"
- return="boolean"
+<method name="getType"
+ return="int"
  abstract="false"
  native="false"
  synchronized="false"
@@ -157229,8 +164265,10 @@
  deprecated="not deprecated"
  visibility="public"
 >
+<parameter name="columnIndex" type="int">
+</parameter>
 </method>
-<method name="hasUpdates"
+<method name="getWantsAllOnMoveCalls"
  return="boolean"
  abstract="false"
  native="false"
@@ -157444,17 +164482,6 @@
 <parameter name="uri" type="android.net.Uri">
 </parameter>
 </method>
-<method name="supportsUpdates"
- return="boolean"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-</method>
 <method name="unregisterContentObserver"
  return="void"
  abstract="false"
@@ -157481,124 +164508,6 @@
 <parameter name="observer" type="android.database.DataSetObserver">
 </parameter>
 </method>
-<method name="updateBlob"
- return="boolean"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="columnIndex" type="int">
-</parameter>
-<parameter name="value" type="byte[]">
-</parameter>
-</method>
-<method name="updateDouble"
- return="boolean"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="columnIndex" type="int">
-</parameter>
-<parameter name="value" type="double">
-</parameter>
-</method>
-<method name="updateFloat"
- return="boolean"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="columnIndex" type="int">
-</parameter>
-<parameter name="value" type="float">
-</parameter>
-</method>
-<method name="updateInt"
- return="boolean"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="columnIndex" type="int">
-</parameter>
-<parameter name="value" type="int">
-</parameter>
-</method>
-<method name="updateLong"
- return="boolean"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="columnIndex" type="int">
-</parameter>
-<parameter name="value" type="long">
-</parameter>
-</method>
-<method name="updateShort"
- return="boolean"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="columnIndex" type="int">
-</parameter>
-<parameter name="value" type="short">
-</parameter>
-</method>
-<method name="updateString"
- return="boolean"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="columnIndex" type="int">
-</parameter>
-<parameter name="value" type="java.lang.String">
-</parameter>
-</method>
-<method name="updateToNull"
- return="boolean"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="columnIndex" type="int">
-</parameter>
-</method>
 </class>
 <class name="MockDialogInterface"
  extends="java.lang.Object"
@@ -159734,15 +166643,23 @@
 </class>
 <class name="ClipboardManager"
  extends="java.lang.Object"
- abstract="false"
+ abstract="true"
+ static="false"
+ final="false"
+ deprecated="deprecated"
+ visibility="public"
+>
+<constructor name="ClipboardManager"
+ type="android.text.ClipboardManager"
  static="false"
  final="false"
  deprecated="not deprecated"
  visibility="public"
 >
+</constructor>
 <method name="getText"
  return="java.lang.CharSequence"
- abstract="false"
+ abstract="true"
  native="false"
  synchronized="false"
  static="false"
@@ -159753,7 +166670,7 @@
 </method>
 <method name="hasText"
  return="boolean"
- abstract="false"
+ abstract="true"
  native="false"
  synchronized="false"
  static="false"
@@ -159764,7 +166681,7 @@
 </method>
 <method name="setText"
  return="void"
- abstract="false"
+ abstract="true"
  native="false"
  synchronized="false"
  static="false"
@@ -162589,6 +169506,29 @@
 <parameter name="kind" type="java.lang.Class&lt;T&gt;">
 </parameter>
 </method>
+<method name="getTextRunCursor"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="contextStart" type="int">
+</parameter>
+<parameter name="contextEnd" type="int">
+</parameter>
+<parameter name="flags" type="int">
+</parameter>
+<parameter name="offset" type="int">
+</parameter>
+<parameter name="cursorOpt" type="int">
+</parameter>
+<parameter name="p" type="android.graphics.Paint">
+</parameter>
+</method>
 <method name="insert"
  return="android.text.SpannableStringBuilder"
  abstract="false"
@@ -163649,7 +170589,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>
@@ -166341,6 +173281,29 @@
 <parameter name="event" type="android.view.MotionEvent">
 </parameter>
 </method>
+<method name="setCursorController"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="cursorController" type="android.widget.TextView.CursorController">
+</parameter>
+</method>
+<field name="mCursorController"
+ type="android.widget.TextView.CursorController"
+ transient="false"
+ volatile="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="protected"
+>
+</field>
 </class>
 <class name="BaseKeyListener"
  extends="android.text.method.MetaKeyKeyListener"
@@ -173938,7 +180901,7 @@
  type="java.lang.String"
  transient="false"
  volatile="false"
- value="&quot;((aero|arpa|asia|a[cdefgilmnoqrstuwxz])|(biz|b[abdefghijmnorstvwyz])|(cat|com|coop|c[acdfghiklmnoruvxyz])|d[ejkmoz]|(edu|e[cegrstu])|f[ijkmor]|(gov|g[abdefghilmnpqrstuwy])|h[kmnrtu]|(info|int|i[delmnoqrst])|(jobs|j[emop])|k[eghimnprwyz]|l[abcikrstuvy]|(mil|mobi|museum|m[acdeghklmnopqrstuvwxyz])|(name|net|n[acefgilopruz])|(org|om)|(pro|p[aefghklmnrstwy])|qa|r[eosuw]|s[abcdeghijklmnortuvyz]|(tel|travel|t[cdfghjklmnoprtvwz])|u[agksyz]|v[aceginu]|w[fs]|(xn\\-\\-0zwm56d|xn\\-\\-11b5bs3a9aj6g|xn\\-\\-80akhbyknj4f|xn\\-\\-9t4b11yi5a|xn\\-\\-deba0ad|xn\\-\\-g6w251d|xn\\-\\-hgbk6aj7f53bba|xn\\-\\-hlcj6aya9esc7a|xn\\-\\-jxalpdlp|xn\\-\\-kgbechtv|xn\\-\\-zckzah)|y[etu]|z[amw])&quot;"
+ value="&quot;((aero|arpa|asia|a[cdefgilmnoqrstuwxz])|(biz|b[abdefghijmnorstvwyz])|(cat|com|coop|c[acdfghiklmnoruvxyz])|d[ejkmoz]|(edu|e[cegrstu])|f[ijkmor]|(gov|g[abdefghilmnpqrstuwy])|h[kmnrtu]|(info|int|i[delmnoqrst])|(jobs|j[emop])|k[eghimnprwyz]|l[abcikrstuvy]|(mil|mobi|museum|m[acdeghklmnopqrstuvwxyz])|(name|net|n[acefgilopruz])|(org|om)|(pro|p[aefghklmnrstwy])|qa|r[eosuw]|s[abcdeghijklmnortuvyz]|(tel|travel|t[cdfghjklmnoprtvwz])|u[agksyz]|v[aceginu]|w[fs]|(xn\\-\\-0zwm56d|xn\\-\\-11b5bs3a9aj6g|xn\\-\\-80akhbyknj4f|xn\\-\\-9t4b11yi5a|xn\\-\\-deba0ad|xn\\-\\-g6w251d|xn\\-\\-hgbk6aj7f53bba|xn\\-\\-hlcj6aya9esc7a|xn\\-\\-jxalpdlp|xn\\-\\-kgbechtv|xn\\-\\-mgbaam7a8h|xn\\-\\-mgberp4a5d4ar|xn\\-\\-wgbh1c|xn\\-\\-zckzah)|y[et]|z[amw])&quot;"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -173949,7 +180912,7 @@
  type="java.lang.String"
  transient="false"
  volatile="false"
- value="&quot;(?:(?:aero|arpa|asia|a[cdefgilmnoqrstuwxz])|(?:biz|b[abdefghijmnorstvwyz])|(?:cat|com|coop|c[acdfghiklmnoruvxyz])|d[ejkmoz]|(?:edu|e[cegrstu])|f[ijkmor]|(?:gov|g[abdefghilmnpqrstuwy])|h[kmnrtu]|(?:info|int|i[delmnoqrst])|(?:jobs|j[emop])|k[eghimnprwyz]|l[abcikrstuvy]|(?:mil|mobi|museum|m[acdeghklmnopqrstuvwxyz])|(?:name|net|n[acefgilopruz])|(?:org|om)|(?:pro|p[aefghklmnrstwy])|qa|r[eosuw]|s[abcdeghijklmnortuvyz]|(?:tel|travel|t[cdfghjklmnoprtvwz])|u[agksyz]|v[aceginu]|w[fs]|(?:xn\\-\\-0zwm56d|xn\\-\\-11b5bs3a9aj6g|xn\\-\\-80akhbyknj4f|xn\\-\\-9t4b11yi5a|xn\\-\\-deba0ad|xn\\-\\-g6w251d|xn\\-\\-hgbk6aj7f53bba|xn\\-\\-hlcj6aya9esc7a|xn\\-\\-jxalpdlp|xn\\-\\-kgbechtv|xn\\-\\-zckzah)|y[etu]|z[amw]))&quot;"
+ value="&quot;(?:(?:aero|arpa|asia|a[cdefgilmnoqrstuwxz])|(?:biz|b[abdefghijmnorstvwyz])|(?:cat|com|coop|c[acdfghiklmnoruvxyz])|d[ejkmoz]|(?:edu|e[cegrstu])|f[ijkmor]|(?:gov|g[abdefghilmnpqrstuwy])|h[kmnrtu]|(?:info|int|i[delmnoqrst])|(?:jobs|j[emop])|k[eghimnprwyz]|l[abcikrstuvy]|(?:mil|mobi|museum|m[acdeghklmnopqrstuvwxyz])|(?:name|net|n[acefgilopruz])|(?:org|om)|(?:pro|p[aefghklmnrstwy])|qa|r[eosuw]|s[abcdeghijklmnortuvyz]|(?:tel|travel|t[cdfghjklmnoprtvwz])|u[agksyz]|v[aceginu]|w[fs]|(?:xn\\-\\-0zwm56d|xn\\-\\-11b5bs3a9aj6g|xn\\-\\-80akhbyknj4f|xn\\-\\-9t4b11yi5a|xn\\-\\-deba0ad|xn\\-\\-g6w251d|xn\\-\\-hgbk6aj7f53bba|xn\\-\\-hlcj6aya9esc7a|xn\\-\\-jxalpdlp|xn\\-\\-kgbechtv|xn\\-\\-mgbaam7a8h|xn\\-\\-mgberp4a5d4ar|xn\\-\\-wgbh1c|xn\\-\\-zckzah)|y[et]|z[amw]))&quot;"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -174216,6 +181179,19 @@
 <parameter name="key" type="int">
 </parameter>
 </method>
+<method name="removeAt"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="index" type="int">
+</parameter>
+</method>
 <method name="setValueAt"
  return="void"
  abstract="false"
@@ -175821,6 +182797,231 @@
 >
 </field>
 </class>
+<class name="ActionMode"
+ extends="java.lang.Object"
+ abstract="true"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="ActionMode"
+ type="android.view.ActionMode"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</constructor>
+<method name="finish"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getCustomView"
+ return="android.view.View"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getMenu"
+ return="android.view.Menu"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getMenuInflater"
+ return="android.view.MenuInflater"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getSubtitle"
+ return="java.lang.CharSequence"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getTitle"
+ return="java.lang.CharSequence"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="invalidate"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="setCustomView"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="view" type="android.view.View">
+</parameter>
+</method>
+<method name="setSubtitle"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="subtitle" type="java.lang.CharSequence">
+</parameter>
+</method>
+<method name="setSubtitle"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="resId" type="int">
+</parameter>
+</method>
+<method name="setTitle"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="title" type="java.lang.CharSequence">
+</parameter>
+</method>
+<method name="setTitle"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="resId" type="int">
+</parameter>
+</method>
+</class>
+<interface name="ActionMode.Callback"
+ abstract="true"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<method name="onActionItemClicked"
+ return="boolean"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="mode" type="android.view.ActionMode">
+</parameter>
+<parameter name="item" type="android.view.MenuItem">
+</parameter>
+</method>
+<method name="onCreateActionMode"
+ return="boolean"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="mode" type="android.view.ActionMode">
+</parameter>
+<parameter name="menu" type="android.view.Menu">
+</parameter>
+</method>
+<method name="onDestroyActionMode"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="mode" type="android.view.ActionMode">
+</parameter>
+</method>
+<method name="onPrepareActionMode"
+ return="boolean"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="mode" type="android.view.ActionMode">
+</parameter>
+<parameter name="menu" type="android.view.Menu">
+</parameter>
+</method>
+</interface>
 <interface name="ContextMenu"
  abstract="true"
  static="false"
@@ -181309,6 +188510,19 @@
 <parameter name="alphaChar" type="char">
 </parameter>
 </method>
+<method name="setShowAsAction"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="actionEnum" type="int">
+</parameter>
+</method>
 <method name="setTitle"
  return="android.view.MenuItem"
  abstract="true"
@@ -181361,6 +188575,39 @@
 <parameter name="visible" type="boolean">
 </parameter>
 </method>
+<field name="SHOW_AS_ACTION_ALWAYS"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="2"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="SHOW_AS_ACTION_IF_ROOM"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="1"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="SHOW_AS_ACTION_NEVER"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="0"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 </interface>
 <interface name="MenuItem.OnMenuItemClickListener"
  abstract="true"
@@ -185121,6 +192368,17 @@
  visibility="public"
 >
 </method>
+<method name="getAlpha"
+ return="float"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
 <method name="getAnimation"
  return="android.view.animation.Animation"
  abstract="false"
@@ -185554,6 +192812,17 @@
 <parameter name="location" type="int[]">
 </parameter>
 </method>
+<method name="getMatrix"
+ return="android.graphics.Matrix"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
 <method name="getMeasuredHeight"
  return="int"
  abstract="false"
@@ -185686,6 +192955,28 @@
  visibility="public"
 >
 </method>
+<method name="getPivotX"
+ return="float"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getPivotY"
+ return="float"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
 <method name="getResources"
  return="android.content.res.Resources"
  abstract="false"
@@ -185741,6 +193032,39 @@
  visibility="public"
 >
 </method>
+<method name="getRotation"
+ return="float"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getScaleX"
+ return="float"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getScaleY"
+ return="float"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
 <method name="getScrollBarStyle"
  return="int"
  abstract="false"
@@ -185886,6 +193210,28 @@
  visibility="public"
 >
 </method>
+<method name="getTranslationX"
+ return="float"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getTranslationY"
+ return="float"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
 <method name="getVerticalFadingEdgeLength"
  return="int"
  abstract="false"
@@ -185987,6 +193333,28 @@
 <parameter name="outRect" type="android.graphics.Rect">
 </parameter>
 </method>
+<method name="getX"
+ return="float"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getY"
+ return="float"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
 <method name="hasFocus"
  return="boolean"
  abstract="false"
@@ -186317,6 +193685,17 @@
  visibility="public"
 >
 </method>
+<method name="isSaveFromParentEnabled"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
 <method name="isScrollbarFadingEnabled"
  return="boolean"
  abstract="false"
@@ -187306,6 +194685,19 @@
 <parameter name="event" type="android.view.accessibility.AccessibilityEvent">
 </parameter>
 </method>
+<method name="setAlpha"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="alpha" type="float">
+</parameter>
+</method>
 <method name="setAnimation"
  return="void"
  abstract="false"
@@ -187769,6 +195161,32 @@
 <parameter name="bottom" type="int">
 </parameter>
 </method>
+<method name="setPivotX"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="pivotX" type="float">
+</parameter>
+</method>
+<method name="setPivotY"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="pivotY" type="float">
+</parameter>
+</method>
 <method name="setPressed"
  return="void"
  abstract="false"
@@ -187782,6 +195200,19 @@
 <parameter name="pressed" type="boolean">
 </parameter>
 </method>
+<method name="setRotation"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="rotation" type="float">
+</parameter>
+</method>
 <method name="setSaveEnabled"
  return="void"
  abstract="false"
@@ -187795,6 +195226,45 @@
 <parameter name="enabled" type="boolean">
 </parameter>
 </method>
+<method name="setSaveFromParentEnabled"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="enabled" type="boolean">
+</parameter>
+</method>
+<method name="setScaleX"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="scaleX" type="float">
+</parameter>
+</method>
+<method name="setScaleY"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="scaleY" type="float">
+</parameter>
+</method>
 <method name="setScrollBarStyle"
  return="void"
  abstract="false"
@@ -187901,6 +195371,32 @@
 <parameter name="delegate" type="android.view.TouchDelegate">
 </parameter>
 </method>
+<method name="setTranslationX"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="translationX" type="float">
+</parameter>
+</method>
+<method name="setTranslationY"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="translationY" type="float">
+</parameter>
+</method>
 <method name="setVerticalFadingEdgeEnabled"
  return="void"
  abstract="false"
@@ -187966,6 +195462,32 @@
 <parameter name="willNotDraw" type="boolean">
 </parameter>
 </method>
+<method name="setX"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="x" type="float">
+</parameter>
+</method>
+<method name="setY"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="y" type="float">
+</parameter>
+</method>
 <method name="showContextMenu"
  return="boolean"
  abstract="false"
@@ -187977,6 +195499,19 @@
  visibility="public"
 >
 </method>
+<method name="startActionMode"
+ return="android.view.ActionMode"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="callback" type="android.view.ActionMode.Callback">
+</parameter>
+</method>
 <method name="startAnimation"
  return="void"
  abstract="false"
@@ -190754,6 +198289,21 @@
 <parameter name="originalView" type="android.view.View">
 </parameter>
 </method>
+<method name="startActionModeForChild"
+ return="android.view.ActionMode"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="originalView" type="android.view.View">
+</parameter>
+<parameter name="callback" type="android.view.ActionMode.Callback">
+</parameter>
+</method>
 <method name="startLayoutAnimation"
  return="void"
  abstract="false"
@@ -191444,6 +198994,21 @@
 <parameter name="originalView" type="android.view.View">
 </parameter>
 </method>
+<method name="startActionModeForChild"
+ return="android.view.ActionMode"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="originalView" type="android.view.View">
+</parameter>
+<parameter name="callback" type="android.view.ActionMode.Callback">
+</parameter>
+</method>
 </interface>
 <class name="ViewStub"
  extends="android.view.View"
@@ -192120,6 +199685,19 @@
  visibility="public"
 >
 </method>
+<method name="hasFeature"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="feature" type="int">
+</parameter>
+</method>
 <method name="hasSoftInputMode"
  return="boolean"
  abstract="false"
@@ -192131,6 +199709,19 @@
  visibility="protected"
 >
 </method>
+<method name="invalidatePanelMenu"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="featureId" type="int">
+</parameter>
+</method>
 <method name="isActive"
  return="boolean"
  abstract="false"
@@ -192692,6 +200283,25 @@
 <parameter name="appName" type="java.lang.String">
 </parameter>
 </method>
+<method name="setWindowManager"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="wm" type="android.view.WindowManager">
+</parameter>
+<parameter name="appToken" type="android.os.IBinder">
+</parameter>
+<parameter name="appName" type="java.lang.String">
+</parameter>
+<parameter name="hardwareAccelerated" type="boolean">
+</parameter>
+</method>
 <method name="superDispatchKeyEvent"
  return="boolean"
  abstract="true"
@@ -192796,6 +200406,28 @@
  visibility="protected"
 >
 </field>
+<field name="FEATURE_ACTION_BAR"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="8"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="FEATURE_ACTION_MODE_OVERLAY"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="9"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="FEATURE_CONTEXT_MENU"
  type="int"
  transient="false"
@@ -192818,6 +200450,17 @@
  visibility="public"
 >
 </field>
+<field name="FEATURE_HARDWARE_ACCELERATED"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="10"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="FEATURE_INDETERMINATE_PROGRESS"
  type="int"
  transient="false"
@@ -193177,6 +200820,19 @@
  visibility="public"
 >
 </method>
+<method name="onStartActionMode"
+ return="android.view.ActionMode"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="callback" type="android.view.ActionMode.Callback">
+</parameter>
+</method>
 <method name="onWindowAttributesChanged"
  return="void"
  abstract="true"
@@ -193666,6 +201322,17 @@
  visibility="public"
 >
 </field>
+<field name="FLAG_HARDWARE_ACCELERATED"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="-2147483648"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="FLAG_IGNORE_CHEEK_PRESSES"
  type="int"
  transient="false"
@@ -195552,6 +203219,17 @@
  visibility="public"
 >
 </method>
+<method name="getScaleFactor"
+ return="float"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="protected"
+>
+</method>
 <method name="getStartOffset"
  return="long"
  abstract="false"
@@ -195589,6 +203267,23 @@
 <parameter name="outTransformation" type="android.view.animation.Transformation">
 </parameter>
 </method>
+<method name="getTransformation"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="currentTime" type="long">
+</parameter>
+<parameter name="outTransformation" type="android.view.animation.Transformation">
+</parameter>
+<parameter name="scale" type="float">
+</parameter>
+</method>
 <method name="getZAdjustment"
  return="int"
  abstract="false"
@@ -196228,6 +203923,23 @@
 <exception name="Resources.NotFoundException" type="android.content.res.Resources.NotFoundException">
 </exception>
 </method>
+<method name="loadAnimator"
+ return="android.animation.Animatable"
+ 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>
+<exception name="Resources.NotFoundException" type="android.content.res.Resources.NotFoundException">
+</exception>
+</method>
 <method name="loadInterpolator"
  return="android.view.animation.Interpolator"
  abstract="false"
@@ -202868,6 +210580,17 @@
  deprecated="not deprecated"
  visibility="public"
 >
+<method name="enableSmoothTransition"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
 <method name="getAllowFileAccess"
  return="boolean"
  abstract="false"
@@ -203483,6 +211206,19 @@
 <parameter name="flag" type="boolean">
 </parameter>
 </method>
+<method name="setEnableSmoothTransition"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="enable" type="boolean">
+</parameter>
+</method>
 <method name="setFantasyFontFamily"
  return="void"
  abstract="false"
@@ -204465,6 +212201,28 @@
  visibility="public"
 >
 </method>
+<method name="canZoomIn"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="canZoomOut"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
 <method name="capturePicture"
  return="android.graphics.Picture"
  abstract="false"
@@ -205200,6 +212958,36 @@
 <parameter name="outState" type="android.os.Bundle">
 </parameter>
 </method>
+<method name="saveWebArchive"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="filename" type="java.lang.String">
+</parameter>
+</method>
+<method name="saveWebArchive"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="basename" type="java.lang.String">
+</parameter>
+<parameter name="autoname" type="boolean">
+</parameter>
+<parameter name="callback" type="android.webkit.ValueCallback&lt;java.lang.String&gt;">
+</parameter>
+</method>
 <method name="setCertificate"
  return="void"
  abstract="false"
@@ -206448,6 +214236,28 @@
  visibility="public"
 >
 </method>
+<method name="onRemoteAdapterConnected"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="onRemoteAdapterDisconnected"
+ 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"
@@ -206625,6 +214435,19 @@
 <parameter name="listener" type="android.widget.AbsListView.RecyclerListener">
 </parameter>
 </method>
+<method name="setRemoteViewsAdapter"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="intent" type="android.content.Intent">
+</parameter>
+</method>
 <method name="setScrollIndicators"
  return="void"
  abstract="false"
@@ -206774,6 +214597,21 @@
 <parameter name="boundPosition" type="int">
 </parameter>
 </method>
+<method name="smoothScrollToPositionFromTop"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="position" type="int">
+</parameter>
+<parameter name="offset" type="int">
+</parameter>
+</method>
 <method name="verifyDrawable"
  return="boolean"
  abstract="false"
@@ -208054,6 +215892,597 @@
 </parameter>
 </method>
 </interface>
+<class name="AdapterViewAnimator"
+ extends="android.widget.AdapterView"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="AdapterViewAnimator"
+ type="android.widget.AdapterViewAnimator"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="context" type="android.content.Context">
+</parameter>
+</constructor>
+<constructor name="AdapterViewAnimator"
+ type="android.widget.AdapterViewAnimator"
+ 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>
+<method name="getAdapter"
+ return="android.widget.Adapter"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getCurrentView"
+ return="android.view.View"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getDefaultInAnimation"
+ return="android.view.animation.Animation"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getDefaultOutAnimation"
+ return="android.view.animation.Animation"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getDisplayedChild"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getInAnimation"
+ return="android.view.animation.Animation"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getOutAnimation"
+ return="android.view.animation.Animation"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getSelectedView"
+ return="android.view.View"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="onRemoteAdapterConnected"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="onRemoteAdapterDisconnected"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="setAdapter"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="adapter" type="android.widget.Adapter">
+</parameter>
+</method>
+<method name="setAnimateFirstView"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="animate" type="boolean">
+</parameter>
+</method>
+<method name="setDisplayedChild"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="whichChild" type="int">
+</parameter>
+</method>
+<method name="setInAnimation"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="inAnimation" type="android.view.animation.Animation">
+</parameter>
+</method>
+<method name="setInAnimation"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="context" type="android.content.Context">
+</parameter>
+<parameter name="resourceID" type="int">
+</parameter>
+</method>
+<method name="setOutAnimation"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="outAnimation" type="android.view.animation.Animation">
+</parameter>
+</method>
+<method name="setOutAnimation"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="context" type="android.content.Context">
+</parameter>
+<parameter name="resourceID" type="int">
+</parameter>
+</method>
+<method name="setRemoteViewsAdapter"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="intent" type="android.content.Intent">
+</parameter>
+</method>
+<method name="setSelection"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="position" type="int">
+</parameter>
+</method>
+<method name="showNext"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="showOnly"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="protected"
+>
+<parameter name="childIndex" type="int">
+</parameter>
+<parameter name="animate" type="boolean">
+</parameter>
+<parameter name="onLayout" type="boolean">
+</parameter>
+</method>
+<method name="showPrevious"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+</class>
+<class name="AdapterViewFlipper"
+ extends="android.widget.AdapterViewAnimator"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="AdapterViewFlipper"
+ type="android.widget.AdapterViewFlipper"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="context" type="android.content.Context">
+</parameter>
+</constructor>
+<constructor name="AdapterViewFlipper"
+ type="android.widget.AdapterViewFlipper"
+ 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>
+<method name="isAutoStart"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="isFlipping"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="setAutoStart"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="autoStart" type="boolean">
+</parameter>
+</method>
+<method name="setFlipInterval"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="milliseconds" type="int">
+</parameter>
+</method>
+<method name="startFlipping"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="stopFlipping"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+</class>
+<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"
@@ -210041,6 +218470,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"
@@ -210188,6 +218631,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"
@@ -210259,6 +218719,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"
@@ -212698,6 +221180,17 @@
  visibility="public"
 >
 </method>
+<method name="getNumColumns"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
 <method name="getStretchMode"
  return="int"
  abstract="false"
@@ -212813,6 +221306,19 @@
 <parameter name="verticalSpacing" type="int">
 </parameter>
 </method>
+<method name="smoothScrollByOffset"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="offset" type="int">
+</parameter>
+</method>
 <field name="AUTO_FIT"
  type="int"
  transient="false"
@@ -213884,6 +222390,20 @@
 <parameter name="attrs" type="android.util.AttributeSet">
 </parameter>
 </constructor>
+<constructor name="LinearLayout"
+ type="android.widget.LinearLayout"
+ 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>
+<parameter name="defStyle" type="int">
+</parameter>
+</constructor>
 <method name="getBaselineAlignedChildIndex"
  return="int"
  abstract="false"
@@ -213928,6 +222448,17 @@
  visibility="public"
 >
 </method>
+<method name="isMeasureWithLargestChildEnabled"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
 <method name="onLayout"
  return="void"
  abstract="false"
@@ -214001,6 +222532,19 @@
 <parameter name="horizontalGravity" type="int">
 </parameter>
 </method>
+<method name="setMeasureWithLargestChildEnabled"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="enabled" type="boolean">
+</parameter>
+</method>
 <method name="setOrientation"
  return="void"
  abstract="false"
@@ -214197,6 +222741,678 @@
 </parameter>
 </method>
 </interface>
+<class name="ListPopupWindow"
+ extends="java.lang.Object"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="ListPopupWindow"
+ type="android.widget.ListPopupWindow"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="context" type="android.content.Context">
+</parameter>
+</constructor>
+<constructor name="ListPopupWindow"
+ type="android.widget.ListPopupWindow"
+ 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="ListPopupWindow"
+ type="android.widget.ListPopupWindow"
+ 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>
+<parameter name="defStyleAttr" type="int">
+</parameter>
+</constructor>
+<constructor name="ListPopupWindow"
+ type="android.widget.ListPopupWindow"
+ 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>
+<parameter name="defStyleAttr" type="int">
+</parameter>
+<parameter name="defStyleRes" type="int">
+</parameter>
+</constructor>
+<method name="clearListSelection"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="dismiss"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getAnchorView"
+ return="android.view.View"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getAnimationStyle"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getBackground"
+ return="android.graphics.drawable.Drawable"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getHeight"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getHorizontalOffset"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getInputMethodMode"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getListView"
+ return="android.widget.ListView"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getPromptPosition"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getSelectedItem"
+ return="java.lang.Object"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getSelectedItemId"
+ return="long"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getSelectedItemPosition"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getSelectedView"
+ return="android.view.View"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getSoftInputMode"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getVerticalOffset"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getWidth"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="isInputMethodNotNeeded"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="isModal"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="isShowing"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="onKeyDown"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="keyCode" type="int">
+</parameter>
+<parameter name="event" type="android.view.KeyEvent">
+</parameter>
+</method>
+<method name="onKeyPreIme"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="keyCode" type="int">
+</parameter>
+<parameter name="event" type="android.view.KeyEvent">
+</parameter>
+</method>
+<method name="onKeyUp"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="keyCode" type="int">
+</parameter>
+<parameter name="event" type="android.view.KeyEvent">
+</parameter>
+</method>
+<method name="performItemClick"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="position" type="int">
+</parameter>
+</method>
+<method name="postShow"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="setAdapter"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="adapter" type="android.widget.ListAdapter">
+</parameter>
+</method>
+<method name="setAnchorView"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="anchor" type="android.view.View">
+</parameter>
+</method>
+<method name="setAnimationStyle"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="animationStyle" type="int">
+</parameter>
+</method>
+<method name="setBackgroundDrawable"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="d" type="android.graphics.drawable.Drawable">
+</parameter>
+</method>
+<method name="setContentWidth"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="width" type="int">
+</parameter>
+</method>
+<method name="setHeight"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="height" type="int">
+</parameter>
+</method>
+<method name="setHorizontalOffset"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="offset" type="int">
+</parameter>
+</method>
+<method name="setInputMethodMode"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="mode" type="int">
+</parameter>
+</method>
+<method name="setListSelector"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="selector" type="android.graphics.drawable.Drawable">
+</parameter>
+</method>
+<method name="setModal"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="modal" type="boolean">
+</parameter>
+</method>
+<method name="setOnItemClickListener"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="clickListener" type="android.widget.AdapterView.OnItemClickListener">
+</parameter>
+</method>
+<method name="setOnItemSelectedListener"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="selectedListener" type="android.widget.AdapterView.OnItemSelectedListener">
+</parameter>
+</method>
+<method name="setPromptPosition"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="position" type="int">
+</parameter>
+</method>
+<method name="setPromptView"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="prompt" type="android.view.View">
+</parameter>
+</method>
+<method name="setSelection"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="position" type="int">
+</parameter>
+</method>
+<method name="setSoftInputMode"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="mode" type="int">
+</parameter>
+</method>
+<method name="setVerticalOffset"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="offset" type="int">
+</parameter>
+</method>
+<method name="setWidth"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="width" type="int">
+</parameter>
+</method>
+<method name="show"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<field name="INPUT_METHOD_FROM_FOCUSABLE"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="0"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="INPUT_METHOD_NEEDED"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="1"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="INPUT_METHOD_NOT_NEEDED"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="2"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="MATCH_PARENT"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="-1"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="POSITION_PROMPT_ABOVE"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="0"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="POSITION_PROMPT_BELOW"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="1"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="WRAP_CONTENT"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="-2"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+</class>
 <class name="ListView"
  extends="android.widget.AbsListView"
  abstract="false"
@@ -214360,6 +223576,17 @@
  visibility="public"
 >
 </method>
+<method name="getCheckedItemCount"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
 <method name="getCheckedItemIds"
  return="long[]"
  abstract="false"
@@ -214615,6 +223842,19 @@
 <parameter name="itemsCanFocus" type="boolean">
 </parameter>
 </method>
+<method name="setMultiChoiceModeListener"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="listener" type="android.widget.ListView.MultiChoiceModeListener">
+</parameter>
+</method>
 <method name="setSelection"
  return="void"
  abstract="false"
@@ -214654,6 +223894,19 @@
 <parameter name="y" type="int">
 </parameter>
 </method>
+<method name="smoothScrollByOffset"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="offset" type="int">
+</parameter>
+</method>
 <field name="CHOICE_MODE_MULTIPLE"
  type="int"
  transient="false"
@@ -214665,6 +223918,17 @@
  visibility="public"
 >
 </field>
+<field name="CHOICE_MODE_MULTIPLE_MODAL"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="3"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="CHOICE_MODE_NONE"
  type="int"
  transient="false"
@@ -214735,6 +223999,35 @@
 >
 </field>
 </class>
+<interface name="ListView.MultiChoiceModeListener"
+ abstract="true"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<implements name="android.view.ActionMode.Callback">
+</implements>
+<method name="onItemCheckedStateChanged"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="mode" type="android.view.ActionMode">
+</parameter>
+<parameter name="position" type="int">
+</parameter>
+<parameter name="id" type="long">
+</parameter>
+<parameter name="checked" type="boolean">
+</parameter>
+</method>
+</interface>
 <class name="MediaController"
  extends="android.widget.FrameLayout"
  abstract="false"
@@ -215186,6 +224479,105 @@
 </parameter>
 </method>
 </interface>
+<class name="PopupMenu"
+ extends="java.lang.Object"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="PopupMenu"
+ type="android.widget.PopupMenu"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="context" type="android.content.Context">
+</parameter>
+<parameter name="anchor" type="android.view.View">
+</parameter>
+</constructor>
+<method name="dismiss"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getMenu"
+ return="android.view.Menu"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getMenuInflater"
+ return="android.view.MenuInflater"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="setOnMenuItemClickListener"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="listener" type="android.widget.PopupMenu.OnMenuItemClickListener">
+</parameter>
+</method>
+<method name="show"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+</class>
+<interface name="PopupMenu.OnMenuItemClickListener"
+ abstract="true"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<method name="onMenuItemClick"
+ return="boolean"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="item" type="android.view.MenuItem">
+</parameter>
+</method>
+</interface>
 <class name="PopupWindow"
  extends="java.lang.Object"
  abstract="false"
@@ -215237,6 +224629,22 @@
  deprecated="not deprecated"
  visibility="public"
 >
+<parameter name="context" type="android.content.Context">
+</parameter>
+<parameter name="attrs" type="android.util.AttributeSet">
+</parameter>
+<parameter name="defStyleAttr" type="int">
+</parameter>
+<parameter name="defStyleRes" type="int">
+</parameter>
+</constructor>
+<constructor name="PopupWindow"
+ type="android.widget.PopupWindow"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
 </constructor>
 <constructor name="PopupWindow"
  type="android.widget.PopupWindow"
@@ -216266,6 +225674,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"
@@ -217481,6 +226900,23 @@
 <parameter name="value" type="int">
 </parameter>
 </method>
+<method name="setIntent"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="viewId" type="int">
+</parameter>
+<parameter name="methodName" type="java.lang.String">
+</parameter>
+<parameter name="value" type="android.content.Intent">
+</parameter>
+</method>
 <method name="setLong"
  return="void"
  abstract="false"
@@ -217532,6 +226968,51 @@
 <parameter name="indeterminate" type="boolean">
 </parameter>
 </method>
+<method name="setRelativeScrollPosition"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="viewId" type="int">
+</parameter>
+<parameter name="offset" type="int">
+</parameter>
+</method>
+<method name="setRemoteAdapter"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="viewId" type="int">
+</parameter>
+<parameter name="intent" type="android.content.Intent">
+</parameter>
+</method>
+<method name="setScrollPosition"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="viewId" type="int">
+</parameter>
+<parameter name="position" type="int">
+</parameter>
+</method>
 <method name="setShort"
  return="void"
  abstract="false"
@@ -217694,6 +227175,149 @@
 <implements name="java.lang.annotation.Annotation">
 </implements>
 </class>
+<class name="RemoteViewsService"
+ extends="android.app.Service"
+ abstract="true"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="RemoteViewsService"
+ type="android.widget.RemoteViewsService"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</constructor>
+<method name="onBind"
+ return="android.os.IBinder"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="intent" type="android.content.Intent">
+</parameter>
+</method>
+<method name="onGetViewFactory"
+ return="android.widget.RemoteViewsService.RemoteViewsFactory"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="intent" type="android.content.Intent">
+</parameter>
+</method>
+</class>
+<interface name="RemoteViewsService.RemoteViewsFactory"
+ abstract="true"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<method name="getCount"
+ return="int"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getItemId"
+ return="long"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="position" type="int">
+</parameter>
+</method>
+<method name="getLoadingView"
+ return="android.widget.RemoteViews"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getViewAt"
+ return="android.widget.RemoteViews"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="position" type="int">
+</parameter>
+</method>
+<method name="getViewTypeCount"
+ return="int"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="hasStableIds"
+ return="boolean"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="onCreate"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="onDestroy"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+</interface>
 <class name="ResourceCursorAdapter"
  extends="android.widget.CursorAdapter"
  abstract="true"
@@ -219812,6 +229436,28 @@
 <parameter name="promptId" type="int">
 </parameter>
 </method>
+<field name="MODE_DIALOG"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="0"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="MODE_DROPDOWN"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="1"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 </class>
 <interface name="SpinnerAdapter"
  abstract="true"
@@ -222851,6 +232497,108 @@
 >
 </method>
 </class>
+<interface name="TextView.CursorController"
+ abstract="true"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<method name="draw"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="canvas" type="android.graphics.Canvas">
+</parameter>
+</method>
+<method name="getOffsetX"
+ return="float"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getOffsetY"
+ return="float"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="hide"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="onTouchEvent"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="event" type="android.view.MotionEvent">
+</parameter>
+</method>
+<method name="show"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="updatePosition"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="offset" type="int">
+</parameter>
+</method>
+<field name="FADE_OUT_DURATION"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="400"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+</interface>
 <interface name="TextView.OnEditorActionListener"
  abstract="true"
  static="true"
diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java
index 301883f..fb60fdf 100644
--- a/cmds/am/src/com/android/commands/am/Am.java
+++ b/cmds/am/src/com/android/commands/am/Am.java
@@ -98,6 +98,8 @@
             sendBroadcast();
         } else if (op.equals("profile")) {
             runProfile();
+        } else if (op.equals("dumpheap")) {
+            runDumpHeap();
         } else {
             throw new IllegalArgumentException("Unknown command: " + op);
         }
@@ -424,6 +426,28 @@
         }
     }
 
+    private void runDumpHeap() throws Exception {
+        boolean managed = !"-n".equals(nextOption());
+        String process = nextArgRequired();
+        String heapFile = nextArgRequired();
+        ParcelFileDescriptor fd = null;
+
+        try {
+            fd = ParcelFileDescriptor.open(
+                    new File(heapFile),
+                    ParcelFileDescriptor.MODE_CREATE |
+                    ParcelFileDescriptor.MODE_TRUNCATE |
+                    ParcelFileDescriptor.MODE_READ_WRITE);
+        } catch (FileNotFoundException e) {
+            System.err.println("Error: Unable to open file: " + heapFile);
+            return;
+        }
+
+        if (!mAm.dumpHeap(process, managed, heapFile, fd)) {
+            throw new AndroidException("HEAP DUMP FAILED on process " + process);
+        }
+    }
+
     private class IntentReceiver extends IIntentReceiver.Stub {
         private boolean mFinished = false;
 
@@ -593,6 +617,8 @@
                 "\n" +
                 "    start profiling: am profile <PROCESS> start <FILE>\n" +
                 "    stop profiling: am profile <PROCESS> stop\n" +
+                "    dump heap: am dumpheap [flags] <PROCESS> <FILE>\n" +
+                "        -n: dump native heap instead of managed heap\n" +
                 "\n" +
                 "    <INTENT> specifications include these flags:\n" +
                 "        [-a <ACTION>] [-d <DATA_URI>] [-t <MIME_TYPE>]\n" +
diff --git a/cmds/dumpstate/dumpstate.c b/cmds/dumpstate/dumpstate.c
index 082e704..1fde437 100644
--- a/cmds/dumpstate/dumpstate.c
+++ b/cmds/dumpstate/dumpstate.c
@@ -103,6 +103,9 @@
     dump_file("NETWORK ROUTES", "/proc/net/route");
     dump_file("ARP CACHE", "/proc/net/arp");
 
+    run_command("WIFI NETWORKS", 20,
+            "su", "root", "wpa_cli", "list_networks", NULL);
+
 #ifdef FWDUMP_bcm4329
     run_command("DUMP WIFI FIRMWARE LOG", 60,
             "su", "root", "dhdutil", "-i", "eth0", "upload", "/data/local/tmp/wlan_crash.dump", NULL);
diff --git a/core/java/android/accounts/AccountManagerService.java b/core/java/android/accounts/AccountManagerService.java
index 1d9e0f1..e6b1c08 100644
--- a/core/java/android/accounts/AccountManagerService.java
+++ b/core/java/android/accounts/AccountManagerService.java
@@ -399,19 +399,19 @@
 
     private boolean insertAccountIntoDatabase(Account account, String password, Bundle extras) {
         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+        if (account == null) {
+            return false;
+        }
+        final boolean noBroadcast = account.type.equals(GOOGLE_ACCOUNT_TYPE)
+                && extras != null && extras.getBoolean(NO_BROADCAST_FLAG, false);
+        // Remove the 'nobroadcast' flag since we don't want it to persist in the db. It is instead
+        // used as a control signal to indicate whether or not this insertion should result in
+        // an accounts changed broadcast being sent.
+        if (extras != null) {
+            extras.remove(NO_BROADCAST_FLAG);
+        }
         db.beginTransaction();
         try {
-            if (account == null) {
-                return false;
-            }
-            boolean noBroadcast = false;
-            if (account.type.equals(GOOGLE_ACCOUNT_TYPE)) {
-                // Look for the 'nobroadcast' flag and remove it since we don't want it to persist
-                // in the db.
-                noBroadcast = extras.getBoolean(NO_BROADCAST_FLAG, false);
-                extras.remove(NO_BROADCAST_FLAG);
-            }
-
             long numMatches = DatabaseUtils.longForQuery(db,
                     "select count(*) from " + TABLE_ACCOUNTS
                             + " WHERE " + ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE+ "=?",
@@ -436,13 +436,13 @@
                 }
             }
             db.setTransactionSuccessful();
-            if (!noBroadcast) {
-                sendAccountsChangedBroadcast();
-            }
-            return true;
         } finally {
             db.endTransaction();
         }
+        if (!noBroadcast) {
+            sendAccountsChangedBroadcast();
+        }
+        return true;
     }
 
     private long insertExtra(SQLiteDatabase db, long accountId, String key, String value) {
@@ -1657,7 +1657,7 @@
                 }
                 boolean needsProvisioning;
                 try {
-                    needsProvisioning = telephony.getCdmaNeedsProvisioning();
+                    needsProvisioning = telephony.needsOtaServiceProvisioning();
                 } catch (RemoteException e) {
                     Log.w(TAG, "exception while checking provisioning", e);
                     // default to NOT wiping out the passwords
@@ -1681,11 +1681,11 @@
                 try {
                     db.execSQL("DELETE from " + TABLE_AUTHTOKENS);
                     db.execSQL("UPDATE " + TABLE_ACCOUNTS + " SET " + ACCOUNTS_PASSWORD + " = ''");
-                    sendAccountsChangedBroadcast();
                     db.setTransactionSuccessful();
                 } finally {
                     db.endTransaction();
                 }
+                sendAccountsChangedBroadcast();
             }
             setMetaValue("imsi", imsi);
         }
diff --git a/core/java/android/animation/Animatable.java b/core/java/android/animation/Animatable.java
new file mode 100644
index 0000000..68415f0
--- /dev/null
+++ b/core/java/android/animation/Animatable.java
@@ -0,0 +1,146 @@
+/*
+ * 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.animation;
+
+import java.util.ArrayList;
+
+/**
+ * This is the superclass for classes which provide basic support for animations which can be
+ * started, ended, and have <code>AnimatableListeners</code> added to them.
+ */
+public abstract class Animatable {
+
+
+    /**
+     * The set of listeners to be sent events through the life of an animation.
+     */
+    ArrayList<AnimatableListener> mListeners = null;
+
+    /**
+     * Starts this animation. If the animation has a nonzero startDelay, the animation will start
+     * running after that delay elapses. Note that the animation does not start synchronously with
+     * this call, because all animation events are posted to a central timing loop so that animation
+     * times are all synchronized on a single timing pulse on the UI thread. So the animation will
+     * start the next time that event handler processes events.
+     */
+    public void start() {
+    }
+
+    /**
+     * Cancels the animation. Unlike {@link #end()}, <code>cancel()</code> causes the animation to
+     * stop in its tracks, sending an {@link AnimatableListener#onAnimationCancel(Animatable)} to
+     * its listeners, followed by an {@link AnimatableListener#onAnimationEnd(Animatable)} message.
+     */
+    public void cancel() {
+    }
+
+    /**
+     * Ends the animation. This causes the animation to assign the end value of the property being
+     * animated, then calling the {@link AnimatableListener#onAnimationEnd(Animatable)} method on
+     * its listeners.
+     */
+    public void end() {
+    }
+
+    /**
+     * Adds a listener to the set of listeners that are sent events through the life of an
+     * animation, such as start, repeat, and end.
+     *
+     * @param listener the listener to be added to the current set of listeners for this animation.
+     */
+    public void addListener(AnimatableListener listener) {
+        if (mListeners == null) {
+            mListeners = new ArrayList<AnimatableListener>();
+        }
+        mListeners.add(listener);
+    }
+
+    /**
+     * Removes a listener from the set listening to this animation.
+     *
+     * @param listener the listener to be removed from the current set of listeners for this
+     *                 animation.
+     */
+    public void removeListener(AnimatableListener listener) {
+        if (mListeners == null) {
+            return;
+        }
+        mListeners.remove(listener);
+        if (mListeners.size() == 0) {
+            mListeners = null;
+        }
+    }
+
+    /**
+     * Gets the set of {@link AnimatableListener} objects that are currently
+     * listening for events on this <code>Animatable</code> object.
+     *
+     * @return ArrayList<AnimatableListener> The set of listeners.
+     */
+    public ArrayList<AnimatableListener> getListeners() {
+        return mListeners;
+    }
+
+    /**
+     * Removes all listeners from this object. This is equivalent to calling
+     * <code>getListeners()</code> followed by calling <code>clear()</code> on the
+     * returned list of listeners.
+     */
+    public void removeAllListeners() {
+        if (mListeners != null) {
+            mListeners.clear();
+            mListeners = null;
+        }
+    }
+
+    /**
+     * <p>An animation listener receives notifications from an animation.
+     * Notifications indicate animation related events, such as the end or the
+     * repetition of the animation.</p>
+     */
+    public static interface AnimatableListener {
+        /**
+         * <p>Notifies the start of the animation.</p>
+         *
+         * @param animation The started animation.
+         */
+        void onAnimationStart(Animatable animation);
+
+        /**
+         * <p>Notifies the end of the animation. This callback is not invoked
+         * for animations with repeat count set to INFINITE.</p>
+         *
+         * @param animation The animation which reached its end.
+         */
+        void onAnimationEnd(Animatable animation);
+
+        /**
+         * <p>Notifies the cancellation of the animation. This callback is not invoked
+         * for animations with repeat count set to INFINITE.</p>
+         *
+         * @param animation The animation which was canceled.
+         */
+        void onAnimationCancel(Animatable animation);
+
+        /**
+         * <p>Notifies the repetition of the animation.</p>
+         *
+         * @param animation The animation which was repeated.
+         */
+        void onAnimationRepeat(Animatable animation);
+    }
+}
diff --git a/core/java/android/animation/AnimatableListenerAdapter.java b/core/java/android/animation/AnimatableListenerAdapter.java
new file mode 100644
index 0000000..25a842b
--- /dev/null
+++ b/core/java/android/animation/AnimatableListenerAdapter.java
@@ -0,0 +1,56 @@
+/*
+ * 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.animation;
+
+import android.animation.Animatable.AnimatableListener;
+
+/**
+ * This adapter class provides empty implementations of the methods from {@link AnimatableListener}.
+ * Any custom listener that cares only about a subset of the methods of this listener can
+ * simply subclass this adapter class instead of implementing the interface directly.
+ */
+public abstract class AnimatableListenerAdapter implements AnimatableListener {
+
+    /**
+     * @{inheritdoc}
+     */
+    @Override
+    public void onAnimationCancel(Animatable animation) {
+    }
+
+    /**
+     * @{inheritdoc}
+     */
+    @Override
+    public void onAnimationEnd(Animatable animation) {
+    }
+
+    /**
+     * @{inheritdoc}
+     */
+    @Override
+    public void onAnimationRepeat(Animatable animation) {
+    }
+
+    /**
+     * @{inheritdoc}
+     */
+    @Override
+    public void onAnimationStart(Animatable animation) {
+    }
+
+}
diff --git a/core/java/android/animation/Animator.java b/core/java/android/animation/Animator.java
new file mode 100755
index 0000000..1013e01
--- /dev/null
+++ b/core/java/android/animation/Animator.java
@@ -0,0 +1,996 @@
+/*
+ * 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.animation;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.os.Handler;
+import android.os.Message;
+import android.util.AttributeSet;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
+
+import java.util.ArrayList;
+
+/**
+ * This class provides a simple timing engine for running animations
+ * which calculate animated values and set them on target objects.
+ *
+ * <p>There is a single timing pulse that all animations use. It runs in a
+ * custom handler to ensure that property changes happen on the UI thread.</p>
+ *
+ * <p>By default, Animator uses non-linear time interpolation, via the
+ * {@link AccelerateDecelerateInterpolator} class, which accelerates into and decelerates
+ * out of an animation. This behavior can be changed by calling
+ * {@link Animator#setInterpolator(Interpolator)}.</p>
+ */
+public class Animator extends Animatable {
+
+    /**
+     * Internal constants
+     */
+
+    /*
+     * The default amount of time in ms between animation frames
+     */
+    private static final long DEFAULT_FRAME_DELAY = 30;
+
+    /**
+     * Messages sent to timing handler: START is sent when an animation first begins, FRAME is sent
+     * by the handler to itself to process the next animation frame
+     */
+    private static final int ANIMATION_START = 0;
+    private static final int ANIMATION_FRAME = 1;
+
+    /**
+     * Values used with internal variable mPlayingState to indicate the current state of an
+     * animation.
+     */
+    private static final int STOPPED    = 0; // Not yet playing
+    private static final int RUNNING    = 1; // Playing normally
+    private static final int CANCELED   = 2; // cancel() called - need to end it
+    private static final int ENDED      = 3; // end() called - need to end it
+    private static final int SEEKED     = 4; // Seeked to some time value
+
+    /**
+     * Enum values used in XML attributes to indicate the value for mValueType
+     */
+    private static final int VALUE_TYPE_FLOAT       = 0;
+    private static final int VALUE_TYPE_INT         = 1;
+    private static final int VALUE_TYPE_DOUBLE      = 2;
+    private static final int VALUE_TYPE_COLOR       = 3;
+    private static final int VALUE_TYPE_CUSTOM      = 4;
+
+    /**
+     * Internal variables
+     */
+
+
+    // The first time that the animation's animateFrame() method is called. This time is used to
+    // determine elapsed time (and therefore the elapsed fraction) in subsequent calls
+    // to animateFrame()
+    private long mStartTime;
+
+    /**
+     * Set when setCurrentPlayTime() is called. If negative, animation is not currently seeked
+     * to a value.
+     */
+    private long mSeekTime = -1;
+
+    // The static sAnimationHandler processes the internal timing loop on which all animations
+    // are based
+    private static AnimationHandler sAnimationHandler;
+
+    // The static list of all active animations
+    private static final ArrayList<Animator> sAnimations = new ArrayList<Animator>();
+
+    // The set of animations to be started on the next animation frame
+    private static final ArrayList<Animator> sPendingAnimations = new ArrayList<Animator>();
+
+    // The time interpolator to be used if none is set on the animation
+    private static final Interpolator sDefaultInterpolator = new AccelerateDecelerateInterpolator();
+
+    // type evaluators for the three primitive types handled by this implementation
+    private static final TypeEvaluator sIntEvaluator = new IntEvaluator();
+    private static final TypeEvaluator sFloatEvaluator = new FloatEvaluator();
+    private static final TypeEvaluator sDoubleEvaluator = new DoubleEvaluator();
+
+    /**
+     * Used to indicate whether the animation is currently playing in reverse. This causes the
+     * elapsed fraction to be inverted to calculate the appropriate values.
+     */
+    private boolean mPlayingBackwards = false;
+
+    /**
+     * This variable tracks the current iteration that is playing. When mCurrentIteration exceeds the
+     * repeatCount (if repeatCount!=INFINITE), the animation ends
+     */
+    private int mCurrentIteration = 0;
+
+    /**
+     * Tracks whether a startDelay'd animation has begun playing through the startDelay.
+     */
+    private boolean mStartedDelay = false;
+
+    /**
+     * Tracks the time at which the animation began playing through its startDelay. This is
+     * different from the mStartTime variable, which is used to track when the animation became
+     * active (which is when the startDelay expired and the animation was added to the active
+     * animations list).
+     */
+    private long mDelayStartTime;
+
+    /**
+     * Flag that represents the current state of the animation. Used to figure out when to start
+     * an animation (if state == STOPPED). Also used to end an animation that
+     * has been cancel()'d or end()'d since the last animation frame. Possible values are
+     * STOPPED, RUNNING, ENDED, CANCELED.
+     */
+    private int mPlayingState = STOPPED;
+
+    /**
+     * Internal collections used to avoid set collisions as animations start and end while being
+     * processed.
+     */
+    private static final ArrayList<Animator> sEndingAnims = new ArrayList<Animator>();
+    private static final ArrayList<Animator> sDelayedAnims = new ArrayList<Animator>();
+    private static final ArrayList<Animator> sReadyAnims = new ArrayList<Animator>();
+
+    /**
+     * Flag that denotes whether the animation is set up and ready to go. Used by seek() to
+     * set up animation that has not yet been started.
+     */
+    private boolean mInitialized = false;
+
+    //
+    // Backing variables
+    //
+
+    // How long the animation should last in ms
+    private long mDuration;
+
+    // The value that the animation should start from, set in the constructor
+    private Object mValueFrom;
+
+    // The value that the animation should animate to, set in the constructor
+    private Object mValueTo;
+
+    // The amount of time in ms to delay starting the animation after start() is called
+    private long mStartDelay = 0;
+
+    // The number of milliseconds between animation frames
+    private static long sFrameDelay = DEFAULT_FRAME_DELAY;
+
+    // The number of times the animation will repeat. The default is 0, which means the animation
+    // will play only once
+    private int mRepeatCount = 0;
+
+    /**
+     * The type of repetition that will occur when repeatMode is nonzero. RESTART means the
+     * animation will start from the beginning on every new cycle. REVERSE means the animation
+     * will reverse directions on each iteration.
+     */
+    private int mRepeatMode = RESTART;
+
+    /**
+     * The time interpolator to be used. The elapsed fraction of the animation will be passed
+     * through this interpolator to calculate the interpolated fraction, which is then used to
+     * calculate the animated values.
+     */
+    private Interpolator mInterpolator = sDefaultInterpolator;
+
+    /**
+     * The type evaluator used to calculate the animated values. This evaluator is determined
+     * automatically based on the type of the start/end objects passed into the constructor,
+     * but the system only knows about the primitive types int, double, and float. Any other
+     * type will need to set the evaluator to a custom evaluator for that type.
+     */
+    private TypeEvaluator mEvaluator;
+
+    /**
+     * The set of listeners to be sent events through the life of an animation.
+     */
+    private ArrayList<AnimatorUpdateListener> mUpdateListeners = null;
+
+    /**
+     * The current value calculated by the animation. The value is calculated in animateFraction(),
+     * prior to calling the setter (if set) and sending out the onAnimationUpdate() callback
+     * to the update listeners.
+     */
+    private Object mAnimatedValue = null;
+
+    /**
+     * The set of keyframes (time/value pairs) that define this animation.
+     */
+    private KeyframeSet mKeyframeSet = null;
+
+    /**
+     * The type of the values, as determined by the valueFrom/valueTo properties.
+     */
+    Class mValueType;
+
+    /**
+     * Public constants
+     */
+
+    /**
+     * When the animation reaches the end and <code>repeatCount</code> is INFINITE
+     * or a positive value, the animation restarts from the beginning.
+     */
+    public static final int RESTART = 1;
+    /**
+     * When the animation reaches the end and <code>repeatCount</code> is INFINITE
+     * or a positive value, the animation reverses direction on every iteration.
+     */
+    public static final int REVERSE = 2;
+    /**
+     * This value used used with the {@link #setRepeatCount(int)} property to repeat
+     * the animation indefinitely.
+     */
+    public static final int INFINITE = -1;
+
+    /**
+     * Creates a new animation whose parameters come from the specified context and
+     * attributes set.
+     *
+     * @param context the application environment
+     * @param attrs the set of attributes holding the animation parameters
+     */
+    public Animator(Context context, AttributeSet attrs) {
+        TypedArray a =
+                context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.Animator);
+
+        mDuration = (long) a.getInt(com.android.internal.R.styleable.Animator_duration, 0);
+
+        mStartDelay = (long) a.getInt(com.android.internal.R.styleable.Animator_startOffset, 0);
+
+        final int resID =
+                a.getResourceId(com.android.internal.R.styleable.Animator_interpolator, 0);
+        if (resID > 0) {
+            setInterpolator(AnimationUtils.loadInterpolator(context, resID));
+        }
+        int valueType = a.getInt(com.android.internal.R.styleable.Animator_valueType,
+                VALUE_TYPE_FLOAT);
+
+        switch (valueType) {
+            case VALUE_TYPE_FLOAT:
+                mValueFrom = a.getFloat(com.android.internal.R.styleable.Animator_valueFrom, 0f);
+                mValueTo = a.getFloat(com.android.internal.R.styleable.Animator_valueTo, 0f);
+                mValueType = float.class;
+                break;
+            case VALUE_TYPE_INT:
+                mValueFrom = a.getInt(com.android.internal.R.styleable.Animator_valueFrom, 0);
+                mValueTo = a.getInt(com.android.internal.R.styleable.Animator_valueTo, 0);
+                mValueType = int.class;
+                break;
+            case VALUE_TYPE_DOUBLE:
+                mValueFrom = (double)
+                        a.getFloat(com.android.internal.R.styleable.Animator_valueFrom, 0f);
+                mValueTo = (double)
+                        a.getFloat(com.android.internal.R.styleable.Animator_valueTo, 0f);
+                mValueType = double.class;
+                break;
+            case VALUE_TYPE_COLOR:
+                mValueFrom = a.getInt(com.android.internal.R.styleable.Animator_valueFrom, 0);
+                mValueTo = a.getInt(com.android.internal.R.styleable.Animator_valueTo, 0);
+                mEvaluator = new RGBEvaluator();
+                mValueType = int.class;
+                break;
+            case VALUE_TYPE_CUSTOM:
+                // TODO: How to get an 'Object' value?
+                mValueFrom = a.getFloat(com.android.internal.R.styleable.Animator_valueFrom, 0f);
+                mValueTo = a.getFloat(com.android.internal.R.styleable.Animator_valueTo, 0f);
+                mValueType = Object.class;
+                break;
+        }
+
+        mRepeatCount = a.getInt(com.android.internal.R.styleable.Animator_repeatCount, mRepeatCount);
+        mRepeatMode = a.getInt(com.android.internal.R.styleable.Animator_repeatMode, RESTART);
+
+        a.recycle();
+    }
+
+    private Animator(long duration, Object valueFrom, Object valueTo, Class valueType) {
+        mDuration = duration;
+        mValueFrom = valueFrom;
+        mValueTo= valueTo;
+        this.mValueType = valueType;
+    }
+
+    /**
+     * This constructor takes a set of {@link Keyframe} objects that define the values
+     * for the animation, along with the times at which those values will hold true during
+     * the animation.
+     *
+     * @param duration The length of the animation, in milliseconds.
+     * @param keyframes The set of keyframes that define the time/value pairs for the animation.
+     */
+    public Animator(long duration, Keyframe...keyframes) {
+        mDuration = duration;
+        mKeyframeSet = new KeyframeSet(keyframes);
+        mValueType = keyframes[0].getType();
+    }
+
+    /**
+     * A constructor that takes <code>float</code> values.
+     *
+     * @param duration The length of the animation, in milliseconds.
+     * @param valueFrom The initial value of the property when the animation begins.
+     * @param valueTo The value to which the property will animate.
+     */
+    public Animator(long duration, float valueFrom, float valueTo) {
+        this(duration, valueFrom, valueTo, float.class);
+    }
+
+    /**
+     * A constructor that takes <code>int</code> values.
+     *
+     * @param duration The length of the animation, in milliseconds.
+     * @param valueFrom The initial value of the property when the animation begins.
+     * @param valueTo The value to which the property will animate.
+     */
+    public Animator(long duration, int valueFrom, int valueTo) {
+        this(duration, valueFrom, valueTo, int.class);
+    }
+
+    /**
+     * A constructor that takes <code>double</code> values.
+     *
+     * @param duration The length of the animation, in milliseconds.
+     * @param valueFrom The initial value of the property when the animation begins.
+     * @param valueTo The value to which the property will animate.
+     */
+    public Animator(long duration, double valueFrom, double valueTo) {
+        this(duration, valueFrom, valueTo, double.class);
+    }
+
+    /**
+     * A constructor that takes <code>Object</code> values.
+     *
+     * @param duration The length of the animation, in milliseconds.
+     * @param valueFrom The initial value of the property when the animation begins.
+     * @param valueTo The value to which the property will animate.
+     */
+    public Animator(long duration, Object valueFrom, Object valueTo) {
+        this(duration, valueFrom, valueTo,
+                (valueFrom != null) ? valueFrom.getClass() : valueTo.getClass());
+    }
+
+    /**
+     * Internal constructor that takes a single <code>float</code> value.
+     * This constructor is called by PropertyAnimator.
+     *
+     * @param duration The length of the animation, in milliseconds.
+     * @param valueFrom The initial value of the property when the animation begins.
+     * @param valueTo The value to which the property will animate.
+     */
+    Animator(long duration, float valueTo) {
+        this(duration, null, valueTo, float.class);
+    }
+
+    /**
+     * Internal constructor that takes a single <code>int</code> value.
+     * This constructor is called by PropertyAnimator.
+     *
+     * @param duration The length of the animation, in milliseconds.
+     * @param valueFrom The initial value of the property when the animation begins.
+     * @param valueTo The value to which the property will animate.
+     */
+    Animator(long duration, int valueTo) {
+        this(duration, null, valueTo, int.class);
+    }
+
+    /**
+     * Internal constructor that takes a single <code>double</code> value.
+     * This constructor is called by PropertyAnimator.
+     *
+     * @param duration The length of the animation, in milliseconds.
+     * @param valueFrom The initial value of the property when the animation begins.
+     * @param valueTo The value to which the property will animate.
+     */
+    Animator(long duration, double valueTo) {
+        this(duration, null, valueTo, double.class);
+    }
+
+    /**
+     * This function is called immediately before processing the first animation
+     * frame of an animation. If there is a nonzero <code>startDelay</code>, the
+     * function is called after that delay ends.
+     * It takes care of the final initialization steps for the
+     * animation.
+     *
+     *  <p>Overrides of this method should call the superclass method to ensure
+     *  that internal mechanisms for the animation are set up correctly.</p>
+     */
+    void initAnimation() {
+        if (mEvaluator == null) {
+            mEvaluator = (mValueType == int.class) ? sIntEvaluator :
+                (mValueType == double.class) ? sDoubleEvaluator : sFloatEvaluator;
+        }
+        mPlayingBackwards = false;
+        mCurrentIteration = 0;
+        mInitialized = true;
+    }
+
+    /**
+     * Sets the position of the animation to the specified point in time. This time should
+     * be between 0 and the total duration of the animation, including any repetition. If
+     * the animation has not yet been started, then it will not advance forward after it is
+     * set to this time; it will simply set the time to this value and perform any appropriate
+     * actions based on that time. If the animation is already running, then seek() will
+     * set the current playing time to this value and continue playing from that point.
+     *
+     * @param playTime The time, in milliseconds, to which the animation is advanced or rewound.
+     */
+    public void setCurrentPlayTime(long playTime) {
+        if (!mInitialized) {
+            initAnimation();
+        }
+        long currentTime = AnimationUtils.currentAnimationTimeMillis();
+        if (mPlayingState != RUNNING) {
+            mSeekTime = playTime;
+            mPlayingState = SEEKED;
+        }
+        mStartTime = currentTime - playTime;
+        animationFrame(currentTime);
+    }
+
+    /**
+     * Gets the current position of the animation in time, which is equal to the current
+     * time minus the time that the animation started. An animation that is not yet started will
+     * return a value of zero.
+     *
+     * @return The current position in time of the animation.
+     */
+    public long getCurrentPlayTime() {
+        if (!mInitialized) {
+            return 0;
+        }
+        return AnimationUtils.currentAnimationTimeMillis() - mStartTime;
+    }
+
+    /**
+     * This custom, static handler handles the timing pulse that is shared by
+     * all active animations. This approach ensures that the setting of animation
+     * values will happen on the UI thread and that all animations will share
+     * the same times for calculating their values, which makes synchronizing
+     * animations possible.
+     *
+     */
+    private static class AnimationHandler extends Handler {
+        /**
+         * There are only two messages that we care about: ANIMATION_START and
+         * ANIMATION_FRAME. The START message is sent when an animation's start()
+         * method is called. It cannot start synchronously when start() is called
+         * because the call may be on the wrong thread, and it would also not be
+         * synchronized with other animations because it would not start on a common
+         * timing pulse. So each animation sends a START message to the handler, which
+         * causes the handler to place the animation on the active animations queue and
+         * start processing frames for that animation.
+         * The FRAME message is the one that is sent over and over while there are any
+         * active animations to process.
+         */
+        @Override
+        public void handleMessage(Message msg) {
+            boolean callAgain = true;
+            switch (msg.what) {
+                // TODO: should we avoid sending frame message when starting if we
+                // were already running?
+                case ANIMATION_START:
+                    if (sAnimations.size() > 0 || sDelayedAnims.size() > 0) {
+                        callAgain = false;
+                    }
+                    // pendingAnims holds any animations that have requested to be started
+                    // We're going to clear sPendingAnimations, but starting animation may
+                    // cause more to be added to the pending list (for example, if one animation
+                    // starting triggers another starting). So we loop until sPendingAnimations
+                    // is empty.
+                    while (sPendingAnimations.size() > 0) {
+                        ArrayList<Animator> pendingCopy =
+                                (ArrayList<Animator>) sPendingAnimations.clone();
+                        sPendingAnimations.clear();
+                        int count = pendingCopy.size();
+                        for (int i = 0; i < count; ++i) {
+                            Animator anim = pendingCopy.get(i);
+                            // If the animation has a startDelay, place it on the delayed list
+                            if (anim.mStartDelay == 0) {
+                                anim.startAnimation();
+                            } else {
+                                sDelayedAnims.add(anim);
+                            }
+                        }
+                    }
+                    // fall through to process first frame of new animations
+                case ANIMATION_FRAME:
+                    // currentTime holds the common time for all animations processed
+                    // during this frame
+                    long currentTime = AnimationUtils.currentAnimationTimeMillis();
+
+                    // First, process animations currently sitting on the delayed queue, adding
+                    // them to the active animations if they are ready
+                    int numDelayedAnims = sDelayedAnims.size();
+                    for (int i = 0; i < numDelayedAnims; ++i) {
+                        Animator anim = sDelayedAnims.get(i);
+                        if (anim.delayedAnimationFrame(currentTime)) {
+                            sReadyAnims.add(anim);
+                        }
+                    }
+                    int numReadyAnims = sReadyAnims.size();
+                    if (numReadyAnims > 0) {
+                        for (int i = 0; i < numReadyAnims; ++i) {
+                            Animator anim = sReadyAnims.get(i);
+                            anim.startAnimation();
+                            sDelayedAnims.remove(anim);
+                        }
+                        sReadyAnims.clear();
+                    }
+
+                    // Now process all active animations. The return value from animationFrame()
+                    // tells the handler whether it should now be ended
+                    int numAnims = sAnimations.size();
+                    for (int i = 0; i < numAnims; ++i) {
+                        Animator anim = sAnimations.get(i);
+                        if (anim.animationFrame(currentTime)) {
+                            sEndingAnims.add(anim);
+                        }
+                    }
+                    if (sEndingAnims.size() > 0) {
+                        for (int i = 0; i < sEndingAnims.size(); ++i) {
+                            sEndingAnims.get(i).endAnimation();
+                        }
+                        sEndingAnims.clear();
+                    }
+
+                    // If there are still active or delayed animations, call the handler again
+                    // after the frameDelay
+                    if (callAgain && (!sAnimations.isEmpty() || !sDelayedAnims.isEmpty())) {
+                        sendEmptyMessageDelayed(ANIMATION_FRAME, sFrameDelay);
+                    }
+                    break;
+            }
+        }
+    }
+
+    /**
+     * The amount of time, in milliseconds, to delay starting the animation after
+     * {@link #start()} is called.
+     *
+     * @return the number of milliseconds to delay running the animation
+     */
+    public long getStartDelay() {
+        return mStartDelay;
+    }
+
+    /**
+     * The amount of time, in milliseconds, to delay starting the animation after
+     * {@link #start()} is called.
+
+     * @param startDelay The amount of the delay, in milliseconds
+     */
+    public void setStartDelay(long startDelay) {
+        this.mStartDelay = startDelay;
+    }
+
+    /**
+     * The amount of time, in milliseconds, between each frame of the animation. This is a
+     * requested time that the animation will attempt to honor, but the actual delay between
+     * frames may be different, depending on system load and capabilities. This is a static
+     * function because the same delay will be applied to all animations, since they are all
+     * run off of a single timing loop.
+     *
+     * @return the requested time between frames, in milliseconds
+     */
+    public static long getFrameDelay() {
+        return sFrameDelay;
+    }
+
+    /**
+     * Gets the set of keyframes that define this animation.
+     *
+     * @return KeyframeSet The set of keyframes for this animation.
+     */
+    KeyframeSet getKeyframes() {
+        return mKeyframeSet;
+    }
+
+    /**
+     * Gets the value that this animation will start from.
+     *
+     * @return Object The starting value for the animation.
+     */
+    public Object getValueFrom() {
+        if (mKeyframeSet != null) {
+            return mKeyframeSet.mKeyframes.get(0).getValue();
+        }
+        return mValueFrom;
+    }
+
+    /**
+     * Sets the value that this animation will start from.
+     */
+    public void setValueFrom(Object valueFrom) {
+        if (mKeyframeSet != null) {
+            Keyframe kf = mKeyframeSet.mKeyframes.get(0);
+            kf.setValue(valueFrom);
+        } else {
+            mValueFrom = valueFrom;
+        }
+    }
+
+    /**
+     * Gets the value that this animation will animate to.
+     *
+     * @return Object The ending value for the animation.
+     */
+    public Object getValueTo() {
+        if (mKeyframeSet != null) {
+            int numKeyframes = mKeyframeSet.mKeyframes.size();
+            return mKeyframeSet.mKeyframes.get(numKeyframes - 1).getValue();
+        }
+        return mValueTo;
+    }
+
+    /**
+     * Sets the value that this animation will animate to.
+     *
+     * @return Object The ending value for the animation.
+     */
+    public void setValueTo(Object valueTo) {
+        if (mKeyframeSet != null) {
+            int numKeyframes = mKeyframeSet.mKeyframes.size();
+            Keyframe kf = mKeyframeSet.mKeyframes.get(numKeyframes - 1);
+            kf.setValue(valueTo);
+        } else {
+            mValueTo = valueTo;
+        }
+    }
+
+    /**
+     * The amount of time, in milliseconds, between each frame of the animation. This is a
+     * requested time that the animation will attempt to honor, but the actual delay between
+     * frames may be different, depending on system load and capabilities. This is a static
+     * function because the same delay will be applied to all animations, since they are all
+     * run off of a single timing loop.
+     *
+     * @param frameDelay the requested time between frames, in milliseconds
+     */
+    public static void setFrameDelay(long frameDelay) {
+        sFrameDelay = frameDelay;
+    }
+
+    /**
+     * The most recent value calculated by this <code>Animator</code> for the property
+     * being animated. This value is only sensible while the animation is running. The main
+     * purpose for this read-only property is to retrieve the value from the <code>Animator</code>
+     * during a call to {@link AnimatorUpdateListener#onAnimationUpdate(Animator)}, which
+     * is called during each animation frame, immediately after the value is calculated.
+     *
+     * @return animatedValue The value most recently calculated by this <code>Animator</code> for
+     * the property specified in the constructor.
+     */
+    public Object getAnimatedValue() {
+        return mAnimatedValue;
+    }
+
+    /**
+     * Sets how many times the animation should be repeated. If the repeat
+     * count is 0, the animation is never repeated. If the repeat count is
+     * greater than 0 or {@link #INFINITE}, the repeat mode will be taken
+     * into account. The repeat count is 0 by default.
+     *
+     * @param value the number of times the animation should be repeated
+     */
+    public void setRepeatCount(int value) {
+        mRepeatCount = value;
+    }
+    /**
+     * Defines how many times the animation should repeat. The default value
+     * is 0.
+     *
+     * @return the number of times the animation should repeat, or {@link #INFINITE}
+     */
+    public int getRepeatCount() {
+        return mRepeatCount;
+    }
+
+    /**
+     * Defines what this animation should do when it reaches the end. This
+     * setting is applied only when the repeat count is either greater than
+     * 0 or {@link #INFINITE}. Defaults to {@link #RESTART}.
+     *
+     * @param value {@link #RESTART} or {@link #REVERSE}
+     */
+    public void setRepeatMode(int value) {
+        mRepeatMode = value;
+    }
+
+    /**
+     * Defines what this animation should do when it reaches the end.
+     *
+     * @return either one of {@link #REVERSE} or {@link #RESTART}
+     */
+    public int getRepeatMode() {
+        return mRepeatMode;
+    }
+
+    /**
+     * Adds a listener to the set of listeners that are sent update events through the life of
+     * an animation. This method is called on all listeners for every frame of the animation,
+     * after the values for the animation have been calculated.
+     *
+     * @param listener the listener to be added to the current set of listeners for this animation.
+     */
+    public void addUpdateListener(AnimatorUpdateListener listener) {
+        if (mUpdateListeners == null) {
+            mUpdateListeners = new ArrayList<AnimatorUpdateListener>();
+        }
+        mUpdateListeners.add(listener);
+    }
+
+    /**
+     * Removes a listener from the set listening to frame updates for this animation.
+     *
+     * @param listener the listener to be removed from the current set of update listeners
+     * for this animation.
+     */
+    public void removeUpdateListener(AnimatorUpdateListener listener) {
+        if (mUpdateListeners == null) {
+            return;
+        }
+        mUpdateListeners.remove(listener);
+        if (mUpdateListeners.size() == 0) {
+            mUpdateListeners = null;
+        }
+    }
+
+
+    /**
+     * The time interpolator used in calculating the elapsed fraction of this animation. The
+     * interpolator determines whether the animation runs with linear or non-linear motion,
+     * such as acceleration and deceleration. The default value is
+     * {@link android.view.animation.AccelerateDecelerateInterpolator}
+     *
+     * @param value the interpolator to be used by this animation
+     */
+    public void setInterpolator(Interpolator value) {
+        if (value != null) {
+            mInterpolator = value;
+        }
+    }
+
+    /**
+     * The type evaluator to be used when calculating the animated values of this animation.
+     * The system will automatically assign a float, int, or double evaluator based on the type
+     * of <code>startValue</code> and <code>endValue</code> in the constructor. But if these values
+     * are not one of these primitive types, or if different evaluation is desired (such as is
+     * necessary with int values that represent colors), a custom evaluator needs to be assigned.
+     * For example, when running an animation on color values, the {@link RGBEvaluator}
+     * should be used to get correct RGB color interpolation.
+     *
+     * @param value the evaluator to be used this animation
+     */
+    public void setEvaluator(TypeEvaluator value) {
+        if (value != null) {
+            mEvaluator = value;
+        }
+    }
+
+    public void start() {
+        mPlayingState = STOPPED;
+        sPendingAnimations.add(this);
+        if (sAnimationHandler == null) {
+            sAnimationHandler = new AnimationHandler();
+        }
+        // TODO: does this put too many messages on the queue if the handler
+        // is already running?
+        sAnimationHandler.sendEmptyMessage(ANIMATION_START);
+    }
+
+    public void cancel() {
+        if (mListeners != null) {
+            ArrayList<AnimatableListener> tmpListeners =
+                    (ArrayList<AnimatableListener>) mListeners.clone();
+            for (AnimatableListener listener : tmpListeners) {
+                listener.onAnimationCancel(this);
+            }
+        }
+        // Just set the CANCELED flag - this causes the animation to end the next time a frame
+        // is processed.
+        mPlayingState = CANCELED;
+    }
+
+    public void end() {
+        // Just set the ENDED flag - this causes the animation to end the next time a frame
+        // is processed.
+        mPlayingState = ENDED;
+    }
+
+    /**
+     * Called internally to end an animation by removing it from the animations list. Must be
+     * called on the UI thread.
+     */
+    private void endAnimation() {
+        sAnimations.remove(this);
+        if (mListeners != null) {
+            ArrayList<AnimatableListener> tmpListeners =
+                    (ArrayList<AnimatableListener>) mListeners.clone();
+            for (AnimatableListener listener : tmpListeners) {
+                listener.onAnimationEnd(this);
+            }
+        }
+        mPlayingState = STOPPED;
+    }
+
+    /**
+     * Called internally to start an animation by adding it to the active animations list. Must be
+     * called on the UI thread.
+     */
+    private void startAnimation() {
+        initAnimation();
+        sAnimations.add(this);
+        if (mListeners != null) {
+            ArrayList<AnimatableListener> tmpListeners =
+                    (ArrayList<AnimatableListener>) mListeners.clone();
+            for (AnimatableListener listener : tmpListeners) {
+                listener.onAnimationStart(this);
+            }
+        }
+    }
+
+    /**
+     * Internal function called to process an animation frame on an animation that is currently
+     * sleeping through its <code>startDelay</code> phase. The return value indicates whether it
+     * should be woken up and put on the active animations queue.
+     *
+     * @param currentTime The current animation time, used to calculate whether the animation
+     * has exceeded its <code>startDelay</code> and should be started.
+     * @return True if the animation's <code>startDelay</code> has been exceeded and the animation
+     * should be added to the set of active animations.
+     */
+    private boolean delayedAnimationFrame(long currentTime) {
+        if (!mStartedDelay) {
+            mStartedDelay = true;
+            mDelayStartTime = currentTime;
+        } else {
+            long deltaTime = currentTime - mDelayStartTime;
+            if (deltaTime > mStartDelay) {
+                // startDelay ended - start the anim and record the
+                // mStartTime appropriately
+                mStartTime = currentTime - (deltaTime - mStartDelay);
+                mPlayingState = RUNNING;
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * This internal function processes a single animation frame for a given animation. The
+     * currentTime parameter is the timing pulse sent by the handler, used to calculate the
+     * elapsed duration, and therefore
+     * the elapsed fraction, of the animation. The return value indicates whether the animation
+     * should be ended (which happens when the elapsed time of the animation exceeds the
+     * animation's duration, including the repeatCount).
+     *
+     * @param currentTime The current time, as tracked by the static timing handler
+     * @return true if the animation's duration, including any repetitions due to
+     * <code>repeatCount</code> has been exceeded and the animation should be ended.
+     */
+    private boolean animationFrame(long currentTime) {
+
+        boolean done = false;
+
+        if (mPlayingState == STOPPED) {
+            mPlayingState = RUNNING;
+            if (mSeekTime < 0) {
+                mStartTime = currentTime;
+            } else {
+                mStartTime = currentTime - mSeekTime;
+                // Now that we're playing, reset the seek time
+                mSeekTime = -1;
+            }
+        }
+        switch (mPlayingState) {
+        case RUNNING:
+        case SEEKED:
+            float fraction = (float)(currentTime - mStartTime) / mDuration;
+            if (fraction >= 1f) {
+                if (mCurrentIteration < mRepeatCount || mRepeatCount == INFINITE) {
+                    // Time to repeat
+                    if (mListeners != null) {
+                        for (AnimatableListener listener : mListeners) {
+                            listener.onAnimationRepeat(this);
+                        }
+                    }
+                    ++mCurrentIteration;
+                    if (mRepeatMode == REVERSE) {
+                        mPlayingBackwards = mPlayingBackwards ? false : true;
+                    }
+                    // TODO: doesn't account for fraction going Wayyyyy over 1, like 2+
+                    fraction = fraction - 1f;
+                    mStartTime += mDuration;
+                } else {
+                    done = true;
+                    fraction = Math.min(fraction, 1.0f);
+                }
+            }
+            if (mPlayingBackwards) {
+                fraction = 1f - fraction;
+            }
+            animateValue(fraction);
+            break;
+        case ENDED:
+            // The final value set on the target varies, depending on whether the animation
+            // was supposed to repeat an odd number of times
+            if (mRepeatCount > 0 && (mRepeatCount & 0x01) == 1) {
+                animateValue(0f);
+            } else {
+                animateValue(1f);
+            }
+            // Fall through to set done flag
+        case CANCELED:
+            done = true;
+            break;
+        }
+
+        return done;
+    }
+
+    /**
+     * This method is called with the elapsed fraction of the animation during every
+     * animation frame. This function turns the elapsed fraction into an interpolated fraction
+     * and then into an animated value (from the evaluator. The function is called mostly during
+     * animation updates, but it is also called when the <code>end()</code>
+     * function is called, to set the final value on the property.
+     *
+     * <p>Overrides of this method must call the superclass to perform the calculation
+     * of the animated value.</p>
+     *
+     * @param fraction The elapsed fraction of the animation.
+     */
+    void animateValue(float fraction) {
+        fraction = mInterpolator.getInterpolation(fraction);
+        if (mKeyframeSet != null) {
+            mAnimatedValue = mKeyframeSet.getValue(fraction, mEvaluator);
+        } else {
+            mAnimatedValue = mEvaluator.evaluate(fraction, mValueFrom, mValueTo);
+        }
+        if (mUpdateListeners != null) {
+            int numListeners = mUpdateListeners.size();
+            for (int i = 0; i < numListeners; ++i) {
+                mUpdateListeners.get(i).onAnimationUpdate(this);
+            }
+        }
+    }
+
+    /**
+     * Implementors of this interface can add themselves as update listeners
+     * to an <code>Animator</code> instance to receive callbacks on every animation
+     * frame, after the current frame's values have been calculated for that
+     * <code>Animator</code>.
+     */
+    public static interface AnimatorUpdateListener {
+        /**
+         * <p>Notifies the occurrence of another frame of the animation.</p>
+         *
+         * @param animation The animation which was repeated.
+         */
+        void onAnimationUpdate(Animator animation);
+
+    }
+}
\ No newline at end of file
diff --git a/core/java/android/animation/DoubleEvaluator.java b/core/java/android/animation/DoubleEvaluator.java
new file mode 100644
index 0000000..86e3f22
--- /dev/null
+++ b/core/java/android/animation/DoubleEvaluator.java
@@ -0,0 +1,42 @@
+/*
+ * 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.animation;
+
+/**
+ * This evaluator can be used to perform type interpolation between <code>double</code> values.
+ */
+public class DoubleEvaluator implements TypeEvaluator {
+    /**
+     * This function returns the result of linearly interpolating the start and end values, with
+     * <code>fraction</code> representing the proportion between the start and end values. The
+     * calculation is a simple parametric calculation: <code>result = x0 + t * (v1 - v0)</code>,
+     * where <code>x0</code> is <code>startValue</code>, <code>x1</code> is <code>endValue</code>,
+     * and <code>t</code> is <code>fraction</code>.
+     *
+     * @param fraction   The fraction from the starting to the ending values
+     * @param startValue The start value; should be of type <code>double</code> or
+     *                   <code>Double</code>
+     * @param endValue   The end value; should be of type <code>double</code> or
+     *                   <code>Double</code>
+     * @return A linear interpolation between the start and end values, given the
+     *         <code>fraction</code> parameter.
+     */
+    public Object evaluate(float fraction, Object startValue, Object endValue) {
+        double startDouble = (Double) startValue;
+        return startDouble + fraction * ((Double) endValue - startDouble);
+    }
+}
\ No newline at end of file
diff --git a/core/java/android/animation/FloatEvaluator.java b/core/java/android/animation/FloatEvaluator.java
new file mode 100644
index 0000000..29a6f71
--- /dev/null
+++ b/core/java/android/animation/FloatEvaluator.java
@@ -0,0 +1,42 @@
+/*
+ * 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.animation;
+
+/**
+ * This evaluator can be used to perform type interpolation between <code>float</code> values.
+ */
+public class FloatEvaluator implements TypeEvaluator {
+
+    /**
+     * This function returns the result of linearly interpolating the start and end values, with
+     * <code>fraction</code> representing the proportion between the start and end values. The
+     * calculation is a simple parametric calculation: <code>result = x0 + t * (v1 - v0)</code>,
+     * where <code>x0</code> is <code>startValue</code>, <code>x1</code> is <code>endValue</code>,
+     * and <code>t</code> is <code>fraction</code>.
+     *
+     * @param fraction   The fraction from the starting to the ending values
+     * @param startValue The start value; should be of type <code>float</code> or
+     *                   <code>Float</code>
+     * @param endValue   The end value; should be of type <code>float</code> or <code>Float</code>
+     * @return A linear interpolation between the start and end values, given the
+     *         <code>fraction</code> parameter.
+     */
+    public Object evaluate(float fraction, Object startValue, Object endValue) {
+        float startFloat = (Float) startValue;
+        return startFloat + fraction * ((Float) endValue - startFloat);
+    }
+}
\ No newline at end of file
diff --git a/core/java/android/animation/IntEvaluator.java b/core/java/android/animation/IntEvaluator.java
new file mode 100644
index 0000000..7a2911a
--- /dev/null
+++ b/core/java/android/animation/IntEvaluator.java
@@ -0,0 +1,42 @@
+/*
+ * 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.animation;
+
+/**
+ * This evaluator can be used to perform type interpolation between <code>int</code> values.
+ */
+public class IntEvaluator implements TypeEvaluator {
+
+    /**
+     * This function returns the result of linearly interpolating the start and end values, with
+     * <code>fraction</code> representing the proportion between the start and end values. The
+     * calculation is a simple parametric calculation: <code>result = x0 + t * (v1 - v0)</code>,
+     * where <code>x0</code> is <code>startValue</code>, <code>x1</code> is <code>endValue</code>,
+     * and <code>t</code> is <code>fraction</code>.
+     *
+     * @param fraction   The fraction from the starting to the ending values
+     * @param startValue The start value; should be of type <code>int</code> or
+     *                   <code>Integer</code>
+     * @param endValue   The end value; should be of type <code>int</code> or <code>Integer</code>
+     * @return A linear interpolation between the start and end values, given the
+     *         <code>fraction</code> parameter.
+     */
+    public Object evaluate(float fraction, Object startValue, Object endValue) {
+        int startInt = (Integer) startValue;
+        return (int) (startInt + fraction * ((Integer) endValue - startInt));
+    }
+}
\ No newline at end of file
diff --git a/core/java/android/animation/Keyframe.java b/core/java/android/animation/Keyframe.java
new file mode 100644
index 0000000..e2800b3
--- /dev/null
+++ b/core/java/android/animation/Keyframe.java
@@ -0,0 +1,203 @@
+/*
+ * 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.animation;
+
+import android.view.animation.Interpolator;
+
+/**
+ * This class holds a time/value pair for an animation. The Keyframe class is used
+ * by {@link Animator} to define the values that the animation target will have over the course
+ * of the animation. As the time proceeds from one keyframe to the other, the value of the
+ * target object will animate between the value at the previous keyframe and the value at the
+ * next keyframe. Each keyframe also holds an option {@link android.view.animation.Interpolator}
+ * object, which defines the time interpolation over the intervalue preceding the keyframe.
+ */
+public class Keyframe {
+    /**
+     * The time at which mValue will hold true.
+     */
+    private float mFraction;
+
+    /**
+     * The value of the animation at the time mFraction.
+     */
+    private Object mValue;
+
+    /**
+     * The type of the value in this Keyframe. This type is determined at construction time,
+     * based on the type of the <code>value</code> object passed into the constructor.
+     */
+    private Class mValueType;
+
+    /**
+     * The optional time interpolator for the interval preceding this keyframe. A null interpolator
+     * (the default) results in linear interpolation over the interval.
+     */
+    private Interpolator mInterpolator = null;
+
+    /**
+     * Private constructor, called from the public constructors with the additional
+     * <code>valueType</code> parameter.
+     *
+     * @param fraction The time, expressed as a value between 0 and 1, representing the fraction
+     * of time elapsed of the overall animation duration.
+     * @param value The value that the object will animate to as the animation time approaches
+     * the time in this keyframe, and the the value animated from as the time passes the time in
+     * this keyframe.
+     * @param valueType The type of the <code>value</code> object. This is used by the
+     * {@link #getValue()} functionm, which is queried by {@link Animator} to determine
+     * the type of {@link TypeEvaluator} to use to interpolate between values.
+     */
+    private Keyframe(float fraction, Object value, Class valueType) {
+        mFraction = fraction;
+        mValue = value;
+        mValueType = valueType;
+    }
+
+    /**
+     * Constructs a Keyframe object with the given time and value. The time defines the
+     * time, as a proportion of an overall animation's duration, at which the value will hold true
+     * for the animation. The value for the animation between keyframes will be calculated as
+     * an interpolation between the values at those keyframes.
+     *
+     * @param fraction The time, expressed as a value between 0 and 1, representing the fraction
+     * of time elapsed of the overall animation duration.
+     * @param value The value that the object will animate to as the animation time approaches
+     * the time in this keyframe, and the the value animated from as the time passes the time in
+     * this keyframe.
+     */
+    public Keyframe(float fraction, Object value) {
+        this(fraction, value, Object.class);
+    }
+
+    /**
+     * Constructs a Keyframe object with the given time and integer value. The time defines the
+     * time, as a proportion of an overall animation's duration, at which the value will hold true
+     * for the animation. The value for the animation between keyframes will be calculated as
+     * an interpolation between the values at those keyframes.
+     *
+     * @param fraction The time, expressed as a value between 0 and 1, representing the fraction
+     * of time elapsed of the overall animation duration.
+     * @param value The value that the object will animate to as the animation time approaches
+     * the time in this keyframe, and the the value animated from as the time passes the time in
+     * this keyframe.
+     */
+    public Keyframe(float fraction, int value) {
+        this(fraction, value, int.class);
+    }
+
+    /**
+     * Constructs a Keyframe object with the given time and float value. The time defines the
+     * time, as a proportion of an overall animation's duration, at which the value will hold true
+     * for the animation. The value for the animation between keyframes will be calculated as
+     * an interpolation between the values at those keyframes.
+     *
+     * @param fraction The time, expressed as a value between 0 and 1, representing the fraction
+     * of time elapsed of the overall animation duration.
+     * @param value The value that the object will animate to as the animation time approaches
+     * the time in this keyframe, and the the value animated from as the time passes the time in
+     * this keyframe.
+     */
+    public Keyframe(float fraction, float value) {
+        this(fraction, value, float.class);
+    }
+
+    /**
+     * Constructs a Keyframe object with the given time and double value. The time defines the
+     * time, as a proportion of an overall animation's duration, at which the value will hold true
+     * for the animation. The value for the animation between keyframes will be calculated as
+     * an interpolation between the values at those keyframes.
+     *
+     * @param fraction The time, expressed as a value between 0 and 1, representing the fraction
+     * of time elapsed of the overall animation duration.
+     * @param value The value that the object will animate to as the animation time approaches
+     * the time in this keyframe, and the the value animated from as the time passes the time in
+     * this keyframe.
+     */
+    public Keyframe(float fraction, double value) {
+        this(fraction, value, double.class);
+    }
+
+    /**
+     * Gets the value for this Keyframe.
+     *
+     * @return The value for this Keyframe.
+     */
+    public Object getValue() {
+        return mValue;
+    }
+
+    /**
+     * Sets the value for this Keyframe.
+     *
+     * @param value value for this Keyframe.
+     */
+    public void setValue(Object value) {
+        mValue = value;
+    }
+
+    /**
+     * Gets the time for this keyframe, as a fraction of the overall animation duration.
+     *
+     * @return The time associated with this keyframe, as a fraction of the overall animation
+     * duration. This should be a value between 0 and 1.
+     */
+    public float getFraction() {
+        return mFraction;
+    }
+
+    /**
+     * Sets the time for this keyframe, as a fraction of the overall animation duration.
+     *
+     * @param fraction time associated with this keyframe, as a fraction of the overall animation
+     * duration. This should be a value between 0 and 1.
+     */
+    public void setFraction(float fraction) {
+        mFraction = fraction;
+    }
+
+    /**
+     * Gets the optional interpolator for this Keyframe. A value of <code>null</code> indicates
+     * that there is no interpolation, which is the same as linear interpolation.
+     *
+     * @return The optional interpolator for this Keyframe.
+     */
+    public Interpolator getInterpolator() {
+        return mInterpolator;
+    }
+
+    /**
+     * Sets the optional interpolator for this Keyframe. A value of <code>null</code> indicates
+     * that there is no interpolation, which is the same as linear interpolation.
+     *
+     * @return The optional interpolator for this Keyframe.
+     */
+    public void setInterpolator(Interpolator interpolator) {
+        mInterpolator = interpolator;
+    }
+
+    /**
+     * Gets the type of keyframe. This information is used by Animator to determine the type of
+     * {@link TypeEvaluator} to use when calculating values between keyframes. The type is based
+     * on the type of Keyframe created.
+     *
+     * @return The type of the value stored in the Keyframe.
+     */
+    public Class getType() {
+        return mValueType;
+    }
+}
diff --git a/core/java/android/animation/KeyframeSet.java b/core/java/android/animation/KeyframeSet.java
new file mode 100644
index 0000000..d144b9c
--- /dev/null
+++ b/core/java/android/animation/KeyframeSet.java
@@ -0,0 +1,99 @@
+/*
+ * 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.animation;
+
+import java.util.ArrayList;
+
+import android.view.animation.Interpolator;
+
+/**
+ * This class holds a collection of Keyframe objects and is called by Animator to calculate
+ * values between those keyframes for a given animation. The class internal to the animation
+ * package because it is an implementation detail of how Keyframes are stored and used.
+ */
+class KeyframeSet {
+
+    private int mNumKeyframes;
+
+    ArrayList<Keyframe> mKeyframes;
+
+    public KeyframeSet(Keyframe... keyframes) {
+        mKeyframes = new ArrayList<Keyframe>();
+        for (Keyframe keyframe : keyframes) {
+            mKeyframes.add(keyframe);
+        }
+        mNumKeyframes = mKeyframes.size();
+    }
+
+    /**
+     * Gets the animated value, given the elapsed fraction of the animation (interpolated by the
+     * animation's interpolator) and the evaluator used to calculate in-between values. This
+     * function maps the input fraction to the appropriate keyframe interval and a fraction
+     * between them and returns the interpolated value. Note that the input fraction may fall
+     * outside the [0-1] bounds, if the animation's interpolator made that happen (e.g., a
+     * spring interpolation that might send the fraction past 1.0). We handle this situation by
+     * just using the two keyframes at the appropriate end when the value is outside those bounds.
+     *
+     * @param fraction The elapsed fraction of the animation
+     * @param evaluator The type evaluator to use when calculating the interpolated values.
+     * @return The animated value.
+     */
+    public Object getValue(float fraction, TypeEvaluator evaluator) {
+        // TODO: special-case 2-keyframe common case
+
+        if (fraction <= 0f) {
+            final Keyframe prevKeyframe = mKeyframes.get(0);
+            final Keyframe nextKeyframe = mKeyframes.get(1);
+            final Interpolator interpolator = nextKeyframe.getInterpolator();
+            if (interpolator != null) {
+                fraction = interpolator.getInterpolation(fraction);
+            }
+            float intervalFraction = (fraction - prevKeyframe.getFraction()) /
+                (nextKeyframe.getFraction() - prevKeyframe.getFraction());
+            return evaluator.evaluate(intervalFraction, prevKeyframe.getValue(),
+                    nextKeyframe.getValue());
+        } else if (fraction >= 1f) {
+            final Keyframe prevKeyframe = mKeyframes.get(mNumKeyframes - 2);
+            final Keyframe nextKeyframe = mKeyframes.get(mNumKeyframes - 1);
+            final Interpolator interpolator = nextKeyframe.getInterpolator();
+            if (interpolator != null) {
+                fraction = interpolator.getInterpolation(fraction);
+            }
+            float intervalFraction = (fraction - prevKeyframe.getFraction()) /
+                (nextKeyframe.getFraction() - prevKeyframe.getFraction());
+            return evaluator.evaluate(intervalFraction, prevKeyframe.getValue(),
+                    nextKeyframe.getValue());
+        }
+        Keyframe prevKeyframe = mKeyframes.get(0);
+        for (int i = 1; i < mNumKeyframes; ++i) {
+            Keyframe nextKeyframe = mKeyframes.get(i);
+            if (fraction < nextKeyframe.getFraction()) {
+                final Interpolator interpolator = nextKeyframe.getInterpolator();
+                if (interpolator != null) {
+                    fraction = interpolator.getInterpolation(fraction);
+                }
+                float intervalFraction = (fraction - prevKeyframe.getFraction()) /
+                    (nextKeyframe.getFraction() - prevKeyframe.getFraction());
+                return evaluator.evaluate(intervalFraction, prevKeyframe.getValue(),
+                        nextKeyframe.getValue());
+            }
+            prevKeyframe = nextKeyframe;
+        }
+        // shouldn't get here
+        return mKeyframes.get(mNumKeyframes - 1).getValue();
+    }
+}
diff --git a/core/java/android/animation/PropertyAnimator.java b/core/java/android/animation/PropertyAnimator.java
new file mode 100644
index 0000000..eac9798
--- /dev/null
+++ b/core/java/android/animation/PropertyAnimator.java
@@ -0,0 +1,555 @@
+/*
+ * 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.animation;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+import android.util.Log;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+/**
+ * This subclass of {@link Animator} provides support for animating properties on target objects.
+ * The constructors of this class take parameters to define the target object that will be animated
+ * as well as the name of the property that will be animated. Appropriate set/get functions
+ * are then determined internally and the animation will call these functions as necessary to
+ * animate the property.
+ */
+public final class PropertyAnimator extends Animator {
+
+    // The target object on which the property exists, set in the constructor
+    private Object mTarget;
+
+    private String mPropertyName;
+
+    private Method mGetter = null;
+
+    // The property setter that is assigned internally, based on the propertyName passed into
+    // the constructor
+    private Method mSetter;
+
+    // These maps hold all property entries for a particular class. This map
+    // is used to speed up property/setter/getter lookups for a given class/property
+    // combination. No need to use reflection on the combination more than once.
+    private static final HashMap<Object, HashMap<String, Method>> sSetterPropertyMap =
+            new HashMap<Object, HashMap<String, Method>>();
+    private static final HashMap<Object, HashMap<String, Method>> sGetterPropertyMap =
+            new HashMap<Object, HashMap<String, Method>>();
+
+    // This lock is used to ensure that only one thread is accessing the property maps
+    // at a time.
+    private ReentrantReadWriteLock propertyMapLock = new ReentrantReadWriteLock();
+
+    // Used to pass single value to varargs parameter in setter invocation
+    private Object[] mTmpValueArray = new Object[1];
+
+
+    /**
+     * Sets the name of the property that will be animated. This name is used to derive
+     * a setter function that will be called to set animated values.
+     * For example, a property name of <code>foo</code> will result
+     * in a call to the function <code>setFoo()</code> on the target object. If either
+     * <code>valueFrom</code> or <code>valueTo</code> is null, then a getter function will
+     * also be derived and called.
+     *
+     * <p>Note that the setter function derived from this property name
+     * must take the same parameter type as the
+     * <code>valueFrom</code> and <code>valueTo</code> properties, otherwise the call to
+     * the setter function will fail.</p>
+     *
+     * @param propertyName The name of the property being animated.
+     */
+    public void setPropertyName(String propertyName) {
+        mPropertyName = propertyName;
+    }
+
+    /**
+     * Gets the name of the property that will be animated. This name will be used to derive
+     * a setter function that will be called to set animated values.
+     * For example, a property name of <code>foo</code> will result
+     * in a call to the function <code>setFoo()</code> on the target object. If either
+     * <code>valueFrom</code> or <code>valueTo</code> is null, then a getter function will
+     * also be derived and called.
+     */
+    public String getPropertyName() {
+        return mPropertyName;
+    }
+
+    /**
+     * Sets the <code>Method</code> that is called with the animated values calculated
+     * during the animation. Setting the setter method is an alternative to supplying a
+     * {@link #setPropertyName(String) propertyName} from which the method is derived. This
+     * approach is more direct, and is especially useful when a function must be called that does
+     * not correspond to the convention of <code>setName()</code>. For example, if a function
+     * called <code>offset()</code> is to be called with the animated values, there is no way
+     * to tell <code>PropertyAnimator</code> how to call that function simply through a property
+     * name, so a setter method should be supplied instead.
+     *
+     * <p>Note that the setter function must take the same parameter type as the
+     * <code>valueFrom</code> and <code>valueTo</code> properties, otherwise the call to
+     * the setter function will fail.</p>
+     *
+     * @param setter The setter method that should be called with the animated values.
+     */
+    public void setSetter(Method setter) {
+        mSetter = setter;
+    }
+
+    /**
+     * Gets the <code>Method</code> that is called with the animated values calculated
+     * during the animation.
+     */
+    public Method getSetter() {
+        return mSetter;
+    }
+
+    /**
+     * Sets the <code>Method</code> that is called to get unsupplied <code>valueFrom</code> or
+     * <code>valueTo</code> properties. Setting the getter method is an alternative to supplying a
+     * {@link #setPropertyName(String) propertyName} from which the method is derived. This
+     * approach is more direct, and is especially useful when a function must be called that does
+     * not correspond to the convention of <code>setName()</code>. For example, if a function
+     * called <code>offset()</code> is to be called to get an initial value, there is no way
+     * to tell <code>PropertyAnimator</code> how to call that function simply through a property
+     * name, so a getter method should be supplied instead.
+     *
+     * <p>Note that the getter method is only called whether supplied here or derived
+     * from the property name, if one of <code>valueFrom</code> or <code>valueTo</code> are
+     * null. If both of those values are non-null, then there is no need to get one of the
+     * values and the getter is not called.
+     *
+     * <p>Note that the getter function must return the same parameter type as the
+     * <code>valueFrom</code> and <code>valueTo</code> properties (whichever of them are
+     * non-null), otherwise the call to the getter function will fail.</p>
+     *
+     * @param getter The getter method that should be called to get initial animation values.
+     */
+    public void setGetter(Method getter) {
+        mGetter = getter;
+    }
+
+    /**
+     * Gets the <code>Method</code> that is called to get unsupplied <code>valueFrom</code> or
+     * <code>valueTo</code> properties.
+     */
+    public Method getGetter() {
+        return mGetter;
+    }
+
+    /**
+     * Creates a new animation whose parameters come from the specified context and
+     * attributes set.
+     *
+     * @param context the application environment
+     * @param attrs the set of attributes holding the animation parameters
+     */
+    public PropertyAnimator(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        TypedArray a = context.obtainStyledAttributes(attrs,
+                com.android.internal.R.styleable.PropertyAnimator);
+
+        mPropertyName = a.getString(com.android.internal.R.styleable.PropertyAnimator_propertyName);
+
+
+        a.recycle();
+    }
+    /**
+     * Determine the setter or getter function using the JavaBeans convention of setFoo or
+     * getFoo for a property named 'foo'. This function figures out what the name of the
+     * function should be and uses reflection to find the Method with that name on the
+     * target object.
+     *
+     * @param prefix "set" or "get", depending on whether we need a setter or getter.
+     * @return Method the method associated with mPropertyName.
+     */
+    private Method getPropertyFunction(String prefix, Class valueType) {
+        // TODO: faster implementation...
+        Method returnVal = null;
+        String firstLetter = mPropertyName.substring(0, 1);
+        String theRest = mPropertyName.substring(1);
+        firstLetter = firstLetter.toUpperCase();
+        String setterName = prefix + firstLetter + theRest;
+        Class args[] = null;
+        if (valueType != null) {
+            args = new Class[1];
+            args[0] = valueType;
+        }
+        try {
+            returnVal = mTarget.getClass().getMethod(setterName, args);
+        } catch (NoSuchMethodException e) {
+            Log.e("PropertyAnimator",
+                    "Couldn't find setter/getter for property " + mPropertyName + ": " + e);
+        }
+        return returnVal;
+    }
+
+    /**
+     * A constructor that takes <code>float</code> values. When this constructor
+     * is called, the system expects to find a setter for <code>propertyName</code> on
+     * the target object that takes a <code>float</code> value.
+     *
+     * @param duration The length of the animation, in milliseconds.
+     * @param target The object whose property is to be animated. This object should
+     * have a public function on it called <code>setName()</code>, where <code>name</code> is
+     * the name of the property passed in as the <code>propertyName</code> parameter.
+     * @param propertyName The name of the property on the <code>target</code> object
+     * that will be animated. Given this name, the constructor will search for a
+     * setter on the target object with the name <code>setPropertyName</code>. For example,
+     * if the constructor is called with <code>propertyName = "foo"</code>, then the
+     * target object should have a setter function with the name <code>setFoo()</code>.
+     * @param valueFrom The initial value of the property when the animation begins.
+     * @param valueTo The value to which the property will animate.
+     */
+    public PropertyAnimator(int duration, Object target, String propertyName,
+            float valueFrom, float valueTo) {
+        super(duration, valueFrom, valueTo);
+        mTarget = target;
+        mPropertyName = propertyName;
+    }
+
+    /**
+     * A constructor that takes a single <code>float</code> value, which is the value that the
+     * target object will animate to. When this constructor
+     * is called, the system expects to find a setter for <code>propertyName</code> on
+     * the target object that takes a value of the same type as the <code>Object</code>s. The
+     * system also expects to find a similar getter function with which to derive the starting
+     * value for the animation.
+     *
+     * @param duration The length of the animation, in milliseconds.
+     * @param target The object whose property is to be animated. This object should
+     * have a public function on it called <code>setName()</code>, where <code>name</code> is
+     * the name of the property passed in as the <code>propertyName</code> parameter.
+     * @param propertyName The name of the property on the <code>target</code> object
+     * that will be animated. Given this name, the constructor will search for a
+     * setter on the target object with the name <code>setPropertyName</code>. For example,
+     * if the constructor is called with <code>propertyName = "foo"</code>, then the
+     * target object should have a setter function with the name <code>setFoo()</code>.
+     * @param valueTo The value to which the property will animate.
+     */
+    public PropertyAnimator(int duration, Object target, String propertyName, float valueTo) {
+        super(duration, valueTo);
+        mTarget = target;
+        mPropertyName = propertyName;
+    }
+
+    /**
+     * A constructor that takes <code>int</code> values. When this constructor
+     * is called, the system expects to find a setter for <code>propertyName</code> on
+     * the target object that takes a <code>int</code> value.
+     *
+     * @param duration The length of the animation, in milliseconds.
+     * @param target The object whose property is to be animated. This object should
+     * have a public function on it called <code>setName()</code>, where <code>name</code> is
+     * the name of the property passed in as the <code>propertyName</code> parameter.
+     * @param propertyName The name of the property on the <code>target</code> object
+     * that will be animated. Given this name, the constructor will search for a
+     * setter on the target object with the name <code>setPropertyName</code>. For example,
+     * if the constructor is called with <code>propertyName = "foo"</code>, then the
+     * target object should have a setter function with the name <code>setFoo()</code>.
+     * @param valueFrom The initial value of the property when the animation begins.
+     * @param valueTo The value to which the property will animate.
+     */
+    public PropertyAnimator(int duration, Object target, String propertyName,
+            int valueFrom, int valueTo) {
+        super(duration, valueFrom, valueTo);
+        mTarget = target;
+        mPropertyName = propertyName;
+    }
+
+    /**
+     * A constructor that takes a single <code>int</code> value, which is the value that the
+     * target object will animate to. When this constructor
+     * is called, the system expects to find a setter for <code>propertyName</code> on
+     * the target object that takes a value of the same type as the <code>Object</code>s. The
+     * system also expects to find a similar getter function with which to derive the starting
+     * value for the animation.
+     *
+     * @param duration The length of the animation, in milliseconds.
+     * @param target The object whose property is to be animated. This object should
+     * have a public function on it called <code>setName()</code>, where <code>name</code> is
+     * the name of the property passed in as the <code>propertyName</code> parameter.
+     * @param propertyName The name of the property on the <code>target</code> object
+     * that will be animated. Given this name, the constructor will search for a
+     * setter on the target object with the name <code>setPropertyName</code>. For example,
+     * if the constructor is called with <code>propertyName = "foo"</code>, then the
+     * target object should have a setter function with the name <code>setFoo()</code>.
+     * @param valueTo The value to which the property will animate.
+     */
+    public PropertyAnimator(int duration, Object target, String propertyName, int valueTo) {
+        super(duration, valueTo);
+        mTarget = target;
+        mPropertyName = propertyName;
+    }
+
+    /**
+     * A constructor that takes <code>double</code> values. When this constructor
+     * is called, the system expects to find a setter for <code>propertyName</code> on
+     * the target object that takes a <code>double</code> value.
+     *
+     * @param duration The length of the animation, in milliseconds.
+     * @param target The object whose property is to be animated. This object should
+     * have a public function on it called <code>setName()</code>, where <code>name</code> is
+     * the name of the property passed in as the <code>propertyName</code> parameter.
+     * @param propertyName The name of the property on the <code>target</code> object
+     * that will be animated. Given this name, the constructor will search for a
+     * setter on the target object with the name <code>setPropertyName</code>. For example,
+     * if the constructor is called with <code>propertyName = "foo"</code>, then the
+     * target object should have a setter function with the name <code>setFoo()</code>.
+     * @param valueFrom The initial value of the property when the animation begins.
+     * @param valueTo The value to which the property will animate.
+     */
+    public PropertyAnimator(int duration, Object target, String propertyName,
+            double valueFrom, double valueTo) {
+        super(duration, valueFrom, valueTo);
+        mTarget = target;
+        mPropertyName = propertyName;
+    }
+
+    /**
+     * A constructor that takes a single <code>double</code> value, which is the value that the
+     * target object will animate to. When this constructor
+     * is called, the system expects to find a setter for <code>propertyName</code> on
+     * the target object that takes a value of the same type as the <code>Object</code>s. The
+     * system also expects to find a similar getter function with which to derive the starting
+     * value for the animation.
+     *
+     * @param duration The length of the animation, in milliseconds.
+     * @param target The object whose property is to be animated. This object should
+     * have a public function on it called <code>setName()</code>, where <code>name</code> is
+     * the name of the property passed in as the <code>propertyName</code> parameter.
+     * @param propertyName The name of the property on the <code>target</code> object
+     * that will be animated. Given this name, the constructor will search for a
+     * setter on the target object with the name <code>setPropertyName</code>. For example,
+     * if the constructor is called with <code>propertyName = "foo"</code>, then the
+     * target object should have a setter function with the name <code>setFoo()</code>.
+     * @param valueTo The value to which the property will animate.
+     */
+    public PropertyAnimator(int duration, Object target, String propertyName, double valueTo) {
+        super(duration, valueTo);
+        mTarget = target;
+        mPropertyName = propertyName;
+    }
+
+    /**
+     * A constructor that takes <code>Object</code> values. When this constructor
+     * is called, the system expects to find a setter for <code>propertyName</code> on
+     * the target object that takes a value of the same type as the <code>Object</code>s.
+     *
+     * @param duration The length of the animation, in milliseconds.
+     * @param target The object whose property is to be animated. This object should
+     * have a public function on it called <code>setName()</code>, where <code>name</code> is
+     * the name of the property passed in as the <code>propertyName</code> parameter.
+     * @param propertyName The name of the property on the <code>target</code> object
+     * that will be animated. Given this name, the constructor will search for a
+     * setter on the target object with the name <code>setPropertyName</code>. For example,
+     * if the constructor is called with <code>propertyName = "foo"</code>, then the
+     * target object should have a setter function with the name <code>setFoo()</code>.
+     * @param valueFrom The initial value of the property when the animation begins.
+     * @param valueTo The value to which the property will animate.
+     */
+    public PropertyAnimator(int duration, Object target, String propertyName,
+            Object valueFrom, Object valueTo) {
+        super(duration, valueFrom, valueTo);
+        mTarget = target;
+        mPropertyName = propertyName;
+    }
+
+    /**
+     * A constructor that takes a single <code>Object</code> value, which is the value that the
+     * target object will animate to. When this constructor
+     * is called, the system expects to find a setter for <code>propertyName</code> on
+     * the target object that takes a value of the same type as the <code>Object</code>s. The
+     * system also expects to find a similar getter function with which to derive the starting
+     * value for the animation.
+     *
+     * @param duration The length of the animation, in milliseconds.
+     * @param target The object whose property is to be animated. This object should
+     * have a public function on it called <code>setName()</code>, where <code>name</code> is
+     * the name of the property passed in as the <code>propertyName</code> parameter.
+     * @param propertyName The name of the property on the <code>target</code> object
+     * that will be animated. Given this name, the constructor will search for a
+     * setter on the target object with the name <code>setPropertyName</code>. For example,
+     * if the constructor is called with <code>propertyName = "foo"</code>, then the
+     * target object should have a setter function with the name <code>setFoo()</code>.
+     * @param valueTo The value to which the property will animate.
+     */
+    public PropertyAnimator(int duration, Object target, String propertyName, Object valueTo) {
+        this(duration, target, propertyName, null, valueTo);
+    }
+
+    /**
+     * A constructor that takes <code>Keyframe</code>s. When this constructor
+     * is called, the system expects to find a setter for <code>propertyName</code> on
+     * the target object that takes a value of the same type as that returned from
+     * {@link Keyframe#getType()}.
+     * .
+     *
+     * @param duration The length of the animation, in milliseconds.
+     * @param target The object whose property is to be animated. This object should
+     * have a public function on it called <code>setName()</code>, where <code>name</code> is
+     * the name of the property passed in as the <code>propertyName</code> parameter.
+     * @param propertyName The name of the property on the <code>target</code> object
+     * that will be animated. Given this name, the constructor will search for a
+     * setter on the target object with the name <code>setPropertyName</code>. For example,
+     * if the constructor is called with <code>propertyName = "foo"</code>, then the
+     * target object should have a setter function with the name <code>setFoo()</code>.
+     * @param keyframes The set of keyframes that define the times and values for the animation.
+     * These keyframes should be ordered in increasing time value, should have a starting
+     * keyframe with a fraction of 0 and and ending keyframe with a fraction of 1.
+     */
+    public PropertyAnimator(int duration, Object target, String propertyName,
+            Keyframe...keyframes) {
+        super(duration, keyframes);
+        mTarget = target;
+        mPropertyName = propertyName;
+    }
+
+    /**
+     * This function is called immediately before processing the first animation
+     * frame of an animation. If there is a nonzero <code>startDelay</code>, the
+     * function is called after that delay ends.
+     * It takes care of the final initialization steps for the
+     * animation. This includes setting mEvaluator, if the user has not yet
+     * set it up, and the setter/getter methods, if the user did not supply
+     * them.
+     *
+     *  <p>Overriders of this method should call the superclass method to cause
+     *  internal mechanisms to be set up correctly.</p>
+     */
+    @Override
+    void initAnimation() {
+        super.initAnimation();
+        if (mSetter == null) {
+            try {
+                // Have to lock property map prior to reading it, to guard against
+                // another thread putting something in there after we've checked it
+                // but before we've added an entry to it
+                propertyMapLock.writeLock().lock();
+                HashMap<String, Method> propertyMap = sSetterPropertyMap.get(mTarget);
+                if (propertyMap != null) {
+                    mSetter = propertyMap.get(mPropertyName);
+                }
+                if (mSetter == null) {
+                    mSetter = getPropertyFunction("set", mValueType);
+                    if (propertyMap == null) {
+                        propertyMap = new HashMap<String, Method>();
+                        sSetterPropertyMap.put(mTarget, propertyMap);
+                    }
+                    propertyMap.put(mPropertyName, mSetter);
+                }
+            } finally {
+                propertyMapLock.writeLock().unlock();
+            }
+        }
+        if (getKeyframes() == null && (getValueFrom() == null || getValueTo() == null)) {
+            // Need to set up the getter if not set by the user, then call it
+            // to get the initial values
+            if (mGetter == null) {
+                try {
+                    propertyMapLock.writeLock().lock();
+                    HashMap<String, Method> propertyMap = sGetterPropertyMap.get(mTarget);
+                    if (propertyMap != null) {
+                        mGetter = propertyMap.get(mPropertyName);
+                    }
+                    if (mGetter == null) {
+                        mGetter = getPropertyFunction("get", null);
+                        if (propertyMap == null) {
+                            propertyMap = new HashMap<String, Method>();
+                            sGetterPropertyMap.put(mTarget, propertyMap);
+                        }
+                        propertyMap.put(mPropertyName, mGetter);
+                    }
+                } finally {
+                    propertyMapLock.writeLock().unlock();
+                }
+            }
+            try {
+                if (getValueFrom() == null) {
+                    setValueFrom(mGetter.invoke(mTarget));
+                }
+                if (getValueTo() == null) {
+                    setValueTo(mGetter.invoke(mTarget));
+                }
+            } catch (IllegalArgumentException e) {
+                Log.e("PropertyAnimator", e.toString());
+            } catch (IllegalAccessException e) {
+                Log.e("PropertyAnimator", e.toString());
+            } catch (InvocationTargetException e) {
+                Log.e("PropertyAnimator", e.toString());
+            }
+        }
+    }
+
+
+    /**
+     * The target object whose property will be animated by this animation
+     *
+     * @return The object being animated
+     */
+    public Object getTarget() {
+        return mTarget;
+    }
+
+    /**
+     * Sets the target object whose property will be animated by this animation
+     *
+     * @param target The object being animated
+     */
+    public void setTarget(Object target) {
+        mTarget = target;
+    }
+
+    /**
+     * This method is called with the elapsed fraction of the animation during every
+     * animation frame. This function turns the elapsed fraction into an interpolated fraction
+     * and then into an animated value (from the evaluator. The function is called mostly during
+     * animation updates, but it is also called when the <code>end()</code>
+     * function is called, to set the final value on the property.
+     *
+     * <p>Overrides of this method must call the superclass to perform the calculation
+     * of the animated value.</p>
+     *
+     * @param fraction The elapsed fraction of the animation.
+     */
+    @Override
+    void animateValue(float fraction) {
+        super.animateValue(fraction);
+        if (mSetter != null) {
+            try {
+                mTmpValueArray[0] = getAnimatedValue();
+                mSetter.invoke(mTarget, mTmpValueArray);
+            } catch (InvocationTargetException e) {
+                Log.e("PropertyAnimator", e.toString());
+            } catch (IllegalAccessException e) {
+                Log.e("PropertyAnimator", e.toString());
+            }
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "Animator: target: " + this.mTarget + "\n" +
+                "    property: " + mPropertyName + "\n" +
+                "    from: " + getValueFrom() + "\n" +
+                "    to: " + getValueTo();
+    }
+}
diff --git a/core/java/android/animation/RGBEvaluator.java b/core/java/android/animation/RGBEvaluator.java
new file mode 100644
index 0000000..bae0af0
--- /dev/null
+++ b/core/java/android/animation/RGBEvaluator.java
@@ -0,0 +1,59 @@
+/*
+ * 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.animation;
+
+/**
+ * This evaluator can be used to perform type interpolation between integer
+ * values that represent ARGB colors.
+ */
+public class RGBEvaluator implements TypeEvaluator {
+
+    /**
+     * This function returns the calculated in-between value for a color
+     * given integers that represent the start and end values in the four
+     * bytes of the 32-bit int. Each channel is separately linearly interpolated
+     * and the resulting calculated values are recombined into the return value.
+     *
+     * @param fraction The fraction from the starting to the ending values
+     * @param startValue A 32-bit int value representing colors in the
+     * separate bytes of the parameter
+     * @param endValue A 32-bit int value representing colors in the
+     * separate bytes of the parameter
+     * @return A value that is calculated to be the linearly interpolated
+     * result, derived by separating the start and end values into separate
+     * color channels and interpolating each one separately, recombining the
+     * resulting values in the same way.
+     */
+    public Object evaluate(float fraction, Object startValue, Object endValue) {
+        int startInt = (Integer) startValue;
+        int startA = (startInt >> 24);
+        int startR = (startInt >> 16) & 0xff;
+        int startG = (startInt >> 8) & 0xff;
+        int startB = startInt & 0xff;
+
+        int endInt = (Integer) endValue;
+        int endA = (endInt >> 24);
+        int endR = (endInt >> 16) & 0xff;
+        int endG = (endInt >> 8) & 0xff;
+        int endB = endInt & 0xff;
+
+        return (int)((startA + (int)(fraction * (endA - startA))) << 24) |
+                (int)((startR + (int)(fraction * (endR - startR))) << 16) |
+                (int)((startG + (int)(fraction * (endG - startG))) << 8) |
+                (int)((startB + (int)(fraction * (endB - startB))));
+    }
+}
\ No newline at end of file
diff --git a/core/java/android/animation/Sequencer.java b/core/java/android/animation/Sequencer.java
new file mode 100644
index 0000000..3278a3e
--- /dev/null
+++ b/core/java/android/animation/Sequencer.java
@@ -0,0 +1,721 @@
+/*
+ * 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.animation;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+import android.view.animation.AnimationUtils;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+/**
+ * This class plays a set of {@link Animatable} objects in the specified order. Animations
+ * can be set up to play together, in sequence, or after a specified delay.
+ *
+ * <p>There are two different approaches to adding animations to a <code>Sequencer</code>:
+ * either the {@link Sequencer#playTogether(Animatable[]) playTogether()} or
+ * {@link Sequencer#playSequentially(Animatable[]) playSequentially()} methods can be called to add
+ * a set of animations all at once, or the {@link Sequencer#play(Animatable)} can be
+ * used in conjunction with methods in the {@link android.animation.Sequencer.Builder Builder}
+ * class to add animations
+ * one by one.</p>
+ *
+ * <p>It is possible to set up a <code>Sequencer</code> with circular dependencies between
+ * its animations. For example, an animation a1 could be set up to start before animation a2, a2
+ * before a3, and a3 before a1. The results of this configuration are undefined, but will typically
+ * result in none of the affected animations being played. Because of this (and because
+ * circular dependencies do not make logical sense anyway), circular dependencies
+ * should be avoided, and the dependency flow of animations should only be in one direction.
+ */
+public final class Sequencer extends Animatable {
+
+    /**
+     * Tracks animations currently being played, so that we know what to
+     * cancel or end when cancel() or end() is called on this Sequencer
+     */
+    private final ArrayList<Animatable> mPlayingSet = new ArrayList<Animatable>();
+
+    /**
+     * Contains all nodes, mapped to their respective Animatables. When new
+     * dependency information is added for an Animatable, we want to add it
+     * to a single node representing that Animatable, not create a new Node
+     * if one already exists.
+     */
+    private final HashMap<Animatable, Node> mNodeMap = new HashMap<Animatable, Node>();
+
+    /**
+     * Set of all nodes created for this Sequencer. This list is used upon
+     * starting the sequencer, and the nodes are placed in sorted order into the
+     * sortedNodes collection.
+     */
+    private final ArrayList<Node> mNodes = new ArrayList<Node>();
+
+    /**
+     * The sorted list of nodes. This is the order in which the animations will
+     * be played. The details about when exactly they will be played depend
+     * on the dependency relationships of the nodes.
+     */
+    private final ArrayList<Node> mSortedNodes = new ArrayList<Node>();
+
+    /**
+     * Flag indicating whether the nodes should be sorted prior to playing. This
+     * flag allows us to cache the previous sorted nodes so that if the sequence
+     * is replayed with no changes, it does not have to re-sort the nodes again.
+     */
+    private boolean mNeedsSort = true;
+
+    private SequencerAnimatableListener mSequenceListener = null;
+
+    /**
+     * Flag indicating that the Sequencer has been canceled (by calling cancel() or end()).
+     * This flag is used to avoid starting other animations when currently-playing
+     * child animations of this Sequencer end.
+     */
+    boolean mCanceled = false;
+
+    /**
+     * Sets up this Sequencer to play all of the supplied animations at the same time.
+     *
+     * @param sequenceItems The animations that will be started simultaneously.
+     */
+    public void playTogether(Animatable... sequenceItems) {
+        if (sequenceItems != null) {
+            mNeedsSort = true;
+            Builder builder = play(sequenceItems[0]);
+            for (int i = 1; i < sequenceItems.length; ++i) {
+                builder.with(sequenceItems[i]);
+            }
+        }
+    }
+
+    /**
+     * Sets up this Sequencer to play each of the supplied animations when the
+     * previous animation ends.
+     *
+     * @param sequenceItems The aniamtions that will be started one after another.
+     */
+    public void playSequentially(Animatable... sequenceItems) {
+        if (sequenceItems != null) {
+            mNeedsSort = true;
+            if (sequenceItems.length == 1) {
+                play(sequenceItems[0]);
+            } else {
+                for (int i = 0; i < sequenceItems.length - 1; ++i) {
+                    play(sequenceItems[i]).before(sequenceItems[i+1]);
+                }
+            }
+        }
+    }
+
+    /**
+     * Returns the current list of child Animatable objects controlled by this
+     * Sequencer. This is a copy of the internal list; modifications to the returned list
+     * will not affect the Sequencer, although changes to the underlying Animatable objects
+     * will affect those objects being managed by the Sequencer.
+     *
+     * @return ArrayList<Animatable> The list of child animations of this Sequencer.
+     */
+    public ArrayList<Animatable> getChildAnimations() {
+        ArrayList<Animatable> childList = new ArrayList<Animatable>();
+        for (Node node : mNodes) {
+            childList.add(node.animation);
+        }
+        return childList;
+    }
+
+    /**
+     * This method creates a <code>Builder</code> object, which is used to
+     * set up playing constraints. This initial <code>play()</code> method
+     * tells the <code>Builder</code> the animation that is the dependency for
+     * the succeeding commands to the <code>Builder</code>. For example,
+     * calling <code>play(a1).with(a2)</code> sets up the Sequence to play
+     * <code>a1</code> and <code>a2</code> at the same time,
+     * <code>play(a1).before(a2)</code> sets up the Sequence to play
+     * <code>a1</code> first, followed by <code>a2</code>, and
+     * <code>play(a1).after(a2)</code> sets up the Sequence to play
+     * <code>a2</code> first, followed by <code>a1</code>.
+     *
+     * <p>Note that <code>play()</code> is the only way to tell the
+     * <code>Builder</code> the animation upon which the dependency is created,
+     * so successive calls to the various functions in <code>Builder</code>
+     * will all refer to the initial parameter supplied in <code>play()</code>
+     * as the dependency of the other animations. For example, calling
+     * <code>play(a1).before(a2).before(a3)</code> will play both <code>a2</code>
+     * and <code>a3</code> when a1 ends; it does not set up a dependency between
+     * <code>a2</code> and <code>a3</code>.</p>
+     *
+     * @param anim The animation that is the dependency used in later calls to the
+     * methods in the returned <code>Builder</code> object. A null parameter will result
+     * in a null <code>Builder</code> return value.
+     * @return Builder The object that constructs the sequence based on the dependencies
+     * outlined in the calls to <code>play</code> and the other methods in the
+     * <code>Builder</code object.
+     */
+    public Builder play(Animatable anim) {
+        if (anim != null) {
+            mNeedsSort = true;
+            return new Builder(anim);
+        }
+        return null;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>Note that canceling a <code>Sequencer</code> also cancels all of the animations that it is
+     * responsible for.</p>
+     */
+    @SuppressWarnings("unchecked")
+    @Override
+    public void cancel() {
+        mCanceled = true;
+        if (mListeners != null) {
+            ArrayList<AnimatableListener> tmpListeners =
+                    (ArrayList<AnimatableListener>) mListeners.clone();
+            for (AnimatableListener listener : tmpListeners) {
+                listener.onAnimationCancel(this);
+            }
+        }
+        if (mSortedNodes.size() > 0) {
+            for (Node node : mSortedNodes) {
+                node.animation.cancel();
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>Note that ending a <code>Sequencer</code> also ends all of the animations that it is
+     * responsible for.</p>
+     */
+    @Override
+    public void end() {
+        mCanceled = true;
+        if (mSortedNodes.size() > 0) {
+            for (Node node : mSortedNodes) {
+                node.animation.end();
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>Starting this <code>Sequencer</code> will, in turn, start the animations for which
+     * it is responsible. The details of when exactly those animations are started depends on
+     * the dependency relationships that have been set up between the animations.
+     */
+    @SuppressWarnings("unchecked")
+    @Override
+    public void start() {
+        mCanceled = false;
+
+        // First, sort the nodes (if necessary). This will ensure that sortedNodes
+        // contains the animation nodes in the correct order.
+        sortNodes();
+
+        // nodesToStart holds the list of nodes to be started immediately. We don't want to
+        // start the animations in the loop directly because we first need to set up
+        // dependencies on all of the nodes. For example, we don't want to start an animation
+        // when some other animation also wants to start when the first animation begins.
+        ArrayList<Node> nodesToStart = new ArrayList<Node>();
+        for (Node node : mSortedNodes) {
+            if (mSequenceListener == null) {
+                mSequenceListener = new SequencerAnimatableListener(this);
+            }
+            if (node.dependencies == null || node.dependencies.size() == 0) {
+                nodesToStart.add(node);
+            } else {
+                for (Dependency dependency : node.dependencies) {
+                    dependency.node.animation.addListener(
+                            new DependencyListener(this, node, dependency.rule));
+                }
+                node.tmpDependencies = (ArrayList<Dependency>) node.dependencies.clone();
+            }
+            node.animation.addListener(mSequenceListener);
+        }
+        // Now that all dependencies are set up, start the animations that should be started.
+        for (Node node : nodesToStart) {
+            node.animation.start();
+            mPlayingSet.add(node.animation);
+        }
+        if (mListeners != null) {
+            ArrayList<AnimatableListener> tmpListeners =
+                    (ArrayList<AnimatableListener>) mListeners.clone();
+            for (AnimatableListener listener : tmpListeners) {
+                listener.onAnimationStart(this);
+            }
+        }
+    }
+
+    /**
+     * This class is the mechanism by which animations are started based on events in other
+     * animations. If an animation has multiple dependencies on other animations, then
+     * all dependencies must be satisfied before the animation is started.
+     */
+    private static class DependencyListener implements AnimatableListener {
+
+        private Sequencer mSequencer;
+
+        // The node upon which the dependency is based.
+        private Node mNode;
+
+        // The Dependency rule (WITH or AFTER) that the listener should wait for on
+        // the node
+        private int mRule;
+
+        public DependencyListener(Sequencer sequencer, Node node, int rule) {
+            this.mSequencer = sequencer;
+            this.mNode = node;
+            this.mRule = rule;
+        }
+
+        /**
+         * Ignore cancel events for now. We may want to handle this eventually,
+         * to prevent follow-on animations from running when some dependency
+         * animation is canceled.
+         */
+        public void onAnimationCancel(Animatable animation) {
+        }
+
+        /**
+         * An end event is received - see if this is an event we are listening for
+         */
+        public void onAnimationEnd(Animatable animation) {
+            if (mRule == Dependency.AFTER) {
+                startIfReady(animation);
+            }
+        }
+
+        /**
+         * Ignore repeat events for now
+         */
+        public void onAnimationRepeat(Animatable animation) {
+        }
+
+        /**
+         * A start event is received - see if this is an event we are listening for
+         */
+        public void onAnimationStart(Animatable animation) {
+            if (mRule == Dependency.WITH) {
+                startIfReady(animation);
+            }
+        }
+
+        /**
+         * Check whether the event received is one that the node was waiting for.
+         * If so, mark it as complete and see whether it's time to start
+         * the animation.
+         * @param dependencyAnimation the animation that sent the event.
+         */
+        private void startIfReady(Animatable dependencyAnimation) {
+            if (mSequencer.mCanceled) {
+                // if the parent Sequencer was canceled, then don't start any dependent anims
+                return;
+            }
+            Dependency dependencyToRemove = null;
+            for (Dependency dependency : mNode.tmpDependencies) {
+                if (dependency.rule == mRule &&
+                        dependency.node.animation == dependencyAnimation) {
+                    // rule fired - remove the dependency and listener and check to
+                    // see whether it's time to start the animation
+                    dependencyToRemove = dependency;
+                    dependencyAnimation.removeListener(this);
+                    break;
+                }
+            }
+            mNode.tmpDependencies.remove(dependencyToRemove);
+            if (mNode.tmpDependencies.size() == 0) {
+                // all dependencies satisfied: start the animation
+                mNode.animation.start();
+                mSequencer.mPlayingSet.add(mNode.animation);
+            }
+        }
+
+    }
+
+    private class SequencerAnimatableListener implements AnimatableListener {
+
+        private Sequencer mSequencer;
+
+        SequencerAnimatableListener(Sequencer sequencer) {
+            mSequencer = sequencer;
+        }
+
+        public void onAnimationCancel(Animatable animation) {
+            if (mPlayingSet.size() == 0) {
+                if (mListeners != null) {
+                    for (AnimatableListener listener : mListeners) {
+                        listener.onAnimationCancel(mSequencer);
+                    }
+                }
+            }
+        }
+
+        @SuppressWarnings("unchecked")
+        public void onAnimationEnd(Animatable animation) {
+            animation.removeListener(this);
+            mPlayingSet.remove(animation);
+            Node animNode = mSequencer.mNodeMap.get(animation);
+            animNode.done = true;
+            ArrayList<Node> sortedNodes = mSequencer.mSortedNodes;
+            int numNodes = sortedNodes.size();
+            int nodeIndex = sortedNodes.indexOf(animNode);
+            boolean allDone = true;
+            for (int i = nodeIndex + 1; i < numNodes; ++i) {
+                if (!sortedNodes.get(i).done) {
+                    allDone = false;
+                    break;
+                }
+            }
+            if (allDone) {
+                // If this was the last child animation to end, then notify listeners that this
+                // sequencer has ended
+                if (mListeners != null) {
+                    ArrayList<AnimatableListener> tmpListeners =
+                            (ArrayList<AnimatableListener>) mListeners.clone();
+                    for (AnimatableListener listener : tmpListeners) {
+                        listener.onAnimationEnd(mSequencer);
+                    }
+                }
+            }
+        }
+
+        // Nothing to do
+        public void onAnimationRepeat(Animatable animation) {
+        }
+
+        // Nothing to do
+        public void onAnimationStart(Animatable animation) {
+        }
+
+    }
+
+    /**
+     * This method sorts the current set of nodes, if needed. The sort is a simple
+     * DependencyGraph sort, which goes like this:
+     * - All nodes without dependencies become 'roots'
+     * - while roots list is not null
+     * -   for each root r
+     * -     add r to sorted list
+     * -     remove r as a dependency from any other node
+     * -   any nodes with no dependencies are added to the roots list
+     */
+    private void sortNodes() {
+        if (mNeedsSort) {
+            mSortedNodes.clear();
+            ArrayList<Node> roots = new ArrayList<Node>();
+            for (Node node : mNodes) {
+                if (node.dependencies == null || node.dependencies.size() == 0) {
+                    roots.add(node);
+                }
+            }
+            ArrayList<Node> tmpRoots = new ArrayList<Node>();
+            while (roots.size() > 0) {
+                for (Node root : roots) {
+                    mSortedNodes.add(root);
+                    if (root.nodeDependents != null) {
+                        for (Node node : root.nodeDependents) {
+                            node.nodeDependencies.remove(root);
+                            if (node.nodeDependencies.size() == 0) {
+                                tmpRoots.add(node);
+                            }
+                        }
+                    }
+                }
+                roots.clear();
+                roots.addAll(tmpRoots);
+                tmpRoots.clear();
+            }
+            mNeedsSort = false;
+            if (mSortedNodes.size() != mNodes.size()) {
+                throw new IllegalStateException("Circular dependencies cannot exist"
+                        + " in Sequencer");
+            }
+        } else {
+            // Doesn't need sorting, but still need to add in the nodeDependencies list
+            // because these get removed as the event listeners fire and the dependencies
+            // are satisfied
+            for (Node node : mNodes) {
+                if (node.dependencies != null && node.dependencies.size() > 0) {
+                    for (Dependency dependency : node.dependencies) {
+                        if (node.nodeDependencies == null) {
+                            node.nodeDependencies = new ArrayList<Node>();
+                        }
+                        if (!node.nodeDependencies.contains(dependency.node)) {
+                            node.nodeDependencies.add(dependency.node);
+                        }
+                    }
+                }
+                node.done = false; // also reset done flag
+            }
+        }
+    }
+
+    /**
+     * Dependency holds information about the node that some other node is
+     * dependent upon and the nature of that dependency.
+     *
+     */
+    private static class Dependency {
+        static final int WITH = 0; // dependent node must start with this dependency node
+        static final int AFTER = 1; // dependent node must start when this dependency node finishes
+
+        // The node that the other node with this Dependency is dependent upon
+        public Node node;
+
+        // The nature of the dependency (WITH or AFTER)
+        public int rule;
+
+        public Dependency(Node node, int rule) {
+            this.node = node;
+            this.rule = rule;
+        }
+    }
+
+    /**
+     * A Node is an embodiment of both the Animatable that it wraps as well as
+     * any dependencies that are associated with that Animation. This includes
+     * both dependencies upon other nodes (in the dependencies list) as
+     * well as dependencies of other nodes upon this (in the nodeDependents list).
+     */
+    private static class Node {
+        public Animatable animation;
+
+        /**
+         *  These are the dependencies that this node's animation has on other
+         *  nodes. For example, if this node's animation should begin with some
+         *  other animation ends, then there will be an item in this node's
+         *  dependencies list for that other animation's node.
+         */
+        public ArrayList<Dependency> dependencies = null;
+
+        /**
+         * tmpDependencies is a runtime detail. We use the dependencies list for sorting.
+         * But we also use the list to keep track of when multiple dependencies are satisfied,
+         * but removing each dependency as it is satisfied. We do not want to remove
+         * the dependency itself from the list, because we need to retain that information
+         * if the sequencer is launched in the future. So we create a copy of the dependency
+         * list when the sequencer starts and use this tmpDependencies list to track the
+         * list of satisfied dependencies.
+         */
+        public ArrayList<Dependency> tmpDependencies = null;
+
+        /**
+         * nodeDependencies is just a list of the nodes that this Node is dependent upon.
+         * This information is used in sortNodes(), to determine when a node is a root.
+         */
+        public ArrayList<Node> nodeDependencies = null;
+
+        /**
+         * nodeDepdendents is the list of nodes that have this node as a dependency. This
+         * is a utility field used in sortNodes to facilitate removing this node as a
+         * dependency when it is a root node.
+         */
+        public ArrayList<Node> nodeDependents = null;
+
+        /**
+         * Flag indicating whether the animation in this node is finished. This flag
+         * is used by Sequencer to check, as each animation ends, whether all child animations
+         * are done and it's time to send out an end event for the entire Sequencer.
+         */
+        public boolean done = false;
+
+        /**
+         * Constructs the Node with the animation that it encapsulates. A Node has no
+         * dependencies by default; dependencies are added via the addDependency()
+         * method.
+         *
+         * @param animation The animation that the Node encapsulates.
+         */
+        public Node(Animatable animation) {
+            this.animation = animation;
+        }
+
+        /**
+         * Add a dependency to this Node. The dependency includes information about the
+         * node that this node is dependency upon and the nature of the dependency.
+         * @param dependency
+         */
+        public void addDependency(Dependency dependency) {
+            if (dependencies == null) {
+                dependencies = new ArrayList<Dependency>();
+                nodeDependencies = new ArrayList<Node>();
+            }
+            dependencies.add(dependency);
+            if (!nodeDependencies.contains(dependency.node)) {
+                nodeDependencies.add(dependency.node);
+            }
+            Node dependencyNode = dependency.node;
+            if (dependencyNode.nodeDependents == null) {
+                dependencyNode.nodeDependents = new ArrayList<Node>();
+            }
+            dependencyNode.nodeDependents.add(this);
+        }
+    }
+
+    /**
+     * The <code>Builder</code> object is a utility class to facilitate adding animations to a
+     * <code>Sequencer</code> along with the relationships between the various animations. The
+     * intention of the <code>Builder</code> methods, along with the {@link
+     * Sequencer#play(Animatable) play()} method of <code>Sequencer</code> is to make it possible to
+     * express the dependency relationships of animations in a natural way. Developers can also use
+     * the {@link Sequencer#playTogether(Animatable[]) playTogether()} and {@link
+     * Sequencer#playSequentially(Animatable[]) playSequentially()} methods if these suit the need,
+     * but it might be easier in some situations to express the sequence of animations in pairs.
+     * <p/>
+     * <p>The <code>Builder</code> object cannot be constructed directly, but is rather constructed
+     * internally via a call to {@link Sequencer#play(Animatable)}.</p>
+     * <p/>
+     * <p>For example, this sets up a Sequencer to play anim1 and anim2 at the same time, anim3 to
+     * play when anim2 finishes, and anim4 to play when anim3 finishes:</p>
+     * <pre>
+     *     Sequencer s = new Sequencer();
+     *     s.play(anim1).with(anim2);
+     *     s.play(anim2).before(anim3);
+     *     s.play(anim4).after(anim3);
+     * </pre>
+     * <p/>
+     * <p>Note in the example that both {@link Builder#before(Animatable)} and {@link
+     * Builder#after(Animatable)} are used. These are just different ways of expressing the same
+     * relationship and are provided to make it easier to say things in a way that is more natural,
+     * depending on the situation.</p>
+     * <p/>
+     * <p>It is possible to make several calls into the same <code>Builder</code> object to express
+     * multiple relationships. However, note that it is only the animation passed into the initial
+     * {@link Sequencer#play(Animatable)} method that is the dependency in any of the successive
+     * calls to the <code>Builder</code> object. For example, the following code starts both anim2
+     * and anim3 when anim1 ends; there is no direct dependency relationship between anim2 and
+     * anim3:
+     * <pre>
+     *   Sequencer s = new Sequencer();
+     *   s.play(anim1).before(anim2).before(anim3);
+     * </pre>
+     * If the desired result is to play anim1 then anim2 then anim3, this code expresses the
+     * relationship correctly:</p>
+     * <pre>
+     *   Sequencer s = new Sequencer();
+     *   s.play(anim1).before(anim2);
+     *   s.play(anim2).before(anim3);
+     * </pre>
+     * <p/>
+     * <p>Note that it is possible to express relationships that cannot be resolved and will not
+     * result in sensible results. For example, <code>play(anim1).after(anim1)</code> makes no
+     * sense. In general, circular dependencies like this one (or more indirect ones where a depends
+     * on b, which depends on c, which depends on a) should be avoided. Only create sequences that
+     * can boil down to a simple, one-way relationship of animations starting with, before, and
+     * after other, different, animations.</p>
+     */
+    public class Builder {
+
+        /**
+         * This tracks the current node being processed. It is supplied to the play() method
+         * of Sequencer and passed into the constructor of Builder.
+         */
+        private Node mCurrentNode;
+
+        /**
+         * package-private constructor. Builders are only constructed by Sequencer, when the
+         * play() method is called.
+         *
+         * @param anim The animation that is the dependency for the other animations passed into
+         * the other methods of this Builder object.
+         */
+        Builder(Animatable anim) {
+            mCurrentNode = mNodeMap.get(anim);
+            if (mCurrentNode == null) {
+                mCurrentNode = new Node(anim);
+                mNodeMap.put(anim, mCurrentNode);
+                mNodes.add(mCurrentNode);
+            }
+        }
+
+        /**
+         * Sets up the given animation to play at the same time as the animation supplied in the
+         * {@link Sequencer#play(Animatable)} call that created this <code>Builder</code> object.
+         *
+         * @param anim The animation that will play when the animation supplied to the
+         * {@link Sequencer#play(Animatable)} method starts.
+         */
+        public void with(Animatable anim) {
+            Node node = mNodeMap.get(anim);
+            if (node == null) {
+                node = new Node(anim);
+                mNodeMap.put(anim, node);
+                mNodes.add(node);
+            }
+            Dependency dependency = new Dependency(mCurrentNode, Dependency.WITH);
+            node.addDependency(dependency);
+        }
+
+        /**
+         * Sets up the given animation to play when the animation supplied in the
+         * {@link Sequencer#play(Animatable)} call that created this <code>Builder</code> object
+         * ends.
+         *
+         * @param anim The animation that will play when the animation supplied to the
+         * {@link Sequencer#play(Animatable)} method ends.
+         */
+        public void before(Animatable anim) {
+            Node node = mNodeMap.get(anim);
+            if (node == null) {
+                node = new Node(anim);
+                mNodeMap.put(anim, node);
+                mNodes.add(node);
+            }
+            Dependency dependency = new Dependency(mCurrentNode, Dependency.AFTER);
+            node.addDependency(dependency);
+        }
+
+        /**
+         * Sets up the given animation to play when the animation supplied in the
+         * {@link Sequencer#play(Animatable)} call that created this <code>Builder</code> object
+         * to start when the animation supplied in this method call ends.
+         *
+         * @param anim The animation whose end will cause the animation supplied to the
+         * {@link Sequencer#play(Animatable)} method to play.
+         */
+        public void after(Animatable anim) {
+            Node node = mNodeMap.get(anim);
+            if (node == null) {
+                node = new Node(anim);
+                mNodeMap.put(anim, node);
+                mNodes.add(node);
+            }
+            Dependency dependency = new Dependency(node, Dependency.AFTER);
+            mCurrentNode.addDependency(dependency);
+        }
+
+        /**
+         * Sets up the animation supplied in the
+         * {@link Sequencer#play(Animatable)} call that created this <code>Builder</code> object
+         * to play when the given amount of time elapses.
+         *
+         * @param delay The number of milliseconds that should elapse before the
+         * animation starts.
+         */
+        public void after(long delay) {
+            // setup dummy Animator just to run the clock
+            after(new Animator(delay, 0f, 1f));
+        }
+
+    }
+
+}
diff --git a/core/java/android/animation/TypeEvaluator.java b/core/java/android/animation/TypeEvaluator.java
new file mode 100644
index 0000000..6150e00
--- /dev/null
+++ b/core/java/android/animation/TypeEvaluator.java
@@ -0,0 +1,44 @@
+/*
+ * 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.animation;
+
+/**
+ * Interface for use with the {@link Animator#setEvaluator(TypeEvaluator)} function. Evaluators
+ * allow developers to create animations on arbitrary property types, by allowing them to supply
+ * custom evaulators for types that are not automatically understood and used by the animation
+ * system.
+ *
+ * @see Animator#setEvaluator(TypeEvaluator)
+ */
+public interface TypeEvaluator {
+
+    /**
+     * This function returns the result of linearly interpolating the start and end values, with
+     * <code>fraction</code> representing the proportion between the start and end values. The
+     * calculation is a simple parametric calculation: <code>result = x0 + t * (v1 - v0)</code>,
+     * where <code>x0</code> is <code>startValue</code>, <code>x1</code> is <code>endValue</code>,
+     * and <code>t</code> is <code>fraction</code>.
+     *
+     * @param fraction   The fraction from the starting to the ending values
+     * @param startValue The start value.
+     * @param endValue   The end value.
+     * @return A linear interpolation between the start and end values, given the
+     *         <code>fraction</code> parameter.
+     */
+    public Object evaluate(float fraction, Object startValue, Object endValue);
+
+}
\ No newline at end of file
diff --git a/core/java/android/animation/package.html b/core/java/android/animation/package.html
new file mode 100644
index 0000000..b66669b
--- /dev/null
+++ b/core/java/android/animation/package.html
@@ -0,0 +1,6 @@
+<html>
+<body>
+Provides classes for animating values over time, and setting those values on target
+objects.
+</body>
+</html>
diff --git a/core/java/android/app/ActionBar.java b/core/java/android/app/ActionBar.java
new file mode 100644
index 0000000..d33494b
--- /dev/null
+++ b/core/java/android/app/ActionBar.java
@@ -0,0 +1,469 @@
+/*
+ * 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.graphics.drawable.Drawable;
+import android.view.View;
+import android.widget.SpinnerAdapter;
+
+/**
+ * This is the public interface to the contextual ActionBar.
+ * The ActionBar acts as a replacement for the title bar in Activities.
+ * It provides facilities for creating toolbar actions as well as
+ * methods of navigating around an application. 
+ */
+public abstract class ActionBar {
+    /**
+     * Standard navigation mode. Consists of either a logo or icon
+     * and title text with an optional subtitle. Clicking any of these elements
+     * will dispatch onActionItemSelected to the registered Callback with
+     * a MenuItem with item ID android.R.id.home.
+     */
+    public static final int NAVIGATION_MODE_STANDARD = 0;
+    
+    /**
+     * Dropdown list navigation mode. Instead of static title text this mode
+     * presents a dropdown menu for navigation within the activity.
+     */
+    public static final int NAVIGATION_MODE_DROPDOWN_LIST = 1;
+    
+    /**
+     * Tab navigation mode. Instead of static title text this mode
+     * presents a series of tabs for navigation within the activity.
+     */
+    public static final int NAVIGATION_MODE_TABS = 2;
+    
+    /**
+     * Custom navigation mode. This navigation mode is set implicitly whenever
+     * a custom navigation view is set. See {@link #setCustomNavigationMode(View)}.
+     */
+    public static final int NAVIGATION_MODE_CUSTOM = 3;
+
+    /**
+     * Use logo instead of icon if available. This flag will cause appropriate
+     * navigation modes to use a wider logo in place of the standard icon.
+     */
+    public static final int DISPLAY_USE_LOGO = 0x1;
+    
+    /**
+     * Hide 'home' elements in this action bar, leaving more space for other
+     * navigation elements. This includes logo and icon.
+     */
+    public static final int DISPLAY_HIDE_HOME = 0x2;
+
+    /**
+     * Set the action bar into custom navigation mode, supplying a view
+     * for custom navigation.
+     * 
+     * Custom navigation views appear between the application icon and
+     * any action buttons and may use any space available there. Common
+     * use cases for custom navigation views might include an auto-suggesting
+     * address bar for a browser or other navigation mechanisms that do not
+     * translate well to provided navigation modes.
+     * 
+     * @param view Custom navigation view to place in the ActionBar.
+     */
+    public abstract void setCustomNavigationMode(View view);
+    
+    /**
+     * Set the action bar into dropdown navigation mode and supply an adapter
+     * that will provide views for navigation choices.
+     * 
+     * @param adapter An adapter that will provide views both to display
+     *                the current navigation selection and populate views
+     *                within the dropdown navigation menu.
+     * @param callback A NavigationCallback that will receive events when the user
+     *                 selects a navigation item.
+     */
+    public abstract void setDropdownNavigationMode(SpinnerAdapter adapter,
+            NavigationCallback callback);
+
+    /**
+     * Set the action bar into dropdown navigation mode and supply an adapter that will
+     * provide views for navigation choices.
+     *
+     * @param adapter An adapter that will provide views both to display the current
+     *                navigation selection and populate views within the dropdown
+     *                navigation menu.
+     * @param callback A NavigationCallback that will receive events when the user
+     *                 selects a navigation item.
+     * @param defaultSelectedPosition Position within the provided adapter that should be
+     *                                selected from the outset.
+     */
+    public abstract void setDropdownNavigationMode(SpinnerAdapter adapter,
+            NavigationCallback callback, int defaultSelectedPosition);
+
+    /**
+     * Set the selected navigation item in dropdown or tabbed navigation modes.
+     *
+     * @param position Position of the item to select.
+     */
+    public abstract void setSelectedNavigationItem(int position);
+
+    /**
+     * Get the position of the selected navigation item in dropdown or tabbed navigation modes.
+     *
+     * @return Position of the selected item.
+     */
+    public abstract int getSelectedNavigationItem();
+
+    /**
+     * Set the action bar into standard navigation mode, supplying a title and subtitle.
+     * 
+     * Standard navigation mode is default. The title is automatically set to the
+     * name of your Activity. Subtitles are displayed underneath the title, usually
+     * in a smaller font or otherwise less prominently than the title. Subtitles are
+     * good for extended descriptions of activity state.
+     *
+     * @param title The action bar's title. null is treated as an empty string.
+     * @param subtitle The action bar's subtitle. null will remove the subtitle entirely.
+     *
+     * @see #setStandardNavigationMode()
+     * @see #setStandardNavigationMode(CharSequence)
+     * @see #setStandardNavigationMode(int)
+     * @see #setStandardNavigationMode(int, int)
+     */
+    public abstract void setStandardNavigationMode(CharSequence title, CharSequence subtitle);
+
+    /**
+     * Set the action bar into standard navigation mode, supplying a title and subtitle.
+     * 
+     * Standard navigation mode is default. The title is automatically set to the
+     * name of your Activity. Subtitles are displayed underneath the title, usually
+     * in a smaller font or otherwise less prominently than the title. Subtitles are
+     * good for extended descriptions of activity state.
+     *
+     * @param titleResId Resource ID of a title string
+     * @param subtitleResId Resource ID of a subtitle string
+     *
+     * @see #setStandardNavigationMode()
+     * @see #setStandardNavigationMode(CharSequence)
+     * @see #setStandardNavigationMode(CharSequence, CharSequence)
+     * @see #setStandardNavigationMode(int)
+     */
+    public abstract void setStandardNavigationMode(int titleResId, int subtitleResId);
+
+    /**
+     * Set the action bar into standard navigation mode, supplying a title and subtitle.
+     * 
+     * Standard navigation mode is default. The title is automatically set to the
+     * name of your Activity on startup if an action bar is present.
+     *
+     * @param title The action bar's title. null is treated as an empty string.
+     *
+     * @see #setStandardNavigationMode()
+     * @see #setStandardNavigationMode(CharSequence, CharSequence)
+     * @see #setStandardNavigationMode(int)
+     * @see #setStandardNavigationMode(int, int)
+     */
+    public abstract void setStandardNavigationMode(CharSequence title);
+
+    /**
+     * Set the action bar into standard navigation mode, supplying a title and subtitle.
+     * 
+     * Standard navigation mode is default. The title is automatically set to the
+     * name of your Activity on startup if an action bar is present.
+     *
+     * @param titleResId Resource ID of a title string
+     *
+     * @see #setStandardNavigationMode()
+     * @see #setStandardNavigationMode(CharSequence)
+     * @see #setStandardNavigationMode(CharSequence, CharSequence)
+     * @see #setStandardNavigationMode(int, int)
+     */
+    public abstract void setStandardNavigationMode(int titleResId);
+
+    /**
+     * Set the action bar into standard navigation mode, using the currently set title
+     * and/or subtitle.
+     *
+     * Standard navigation mode is default. The title is automatically set to the name of
+     * your Activity on startup if an action bar is present.
+     */
+    public abstract void setStandardNavigationMode();
+
+    /**
+     * Set the action bar's title. This will only be displayed in standard navigation mode.
+     *
+     * @param title Title to set
+     *
+     * @see #setTitle(int)
+     */
+    public abstract void setTitle(CharSequence title);
+
+    /**
+     * Set the action bar's title. This will only be displayed in standard navigation mode.
+     *
+     * @param resId Resource ID of title string to set
+     *
+     * @see #setTitle(CharSequence)
+     */
+    public abstract void setTitle(int resId);
+
+    /**
+     * Set the action bar's subtitle. This will only be displayed in standard navigation mode.
+     * Set to null to disable the subtitle entirely.
+     *
+     * @param subtitle Subtitle to set
+     *
+     * @see #setSubtitle(int)
+     */
+    public abstract void setSubtitle(CharSequence subtitle);
+
+    /**
+     * Set the action bar's subtitle. This will only be displayed in standard navigation mode.
+     *
+     * @param resId Resource ID of subtitle string to set
+     *
+     * @see #setSubtitle(CharSequence)
+     */
+    public abstract void setSubtitle(int resId);
+
+    /**
+     * Set display options. This changes all display option bits at once. To change
+     * a limited subset of display options, see {@link #setDisplayOptions(int, int)}.
+     * 
+     * @param options A combination of the bits defined by the DISPLAY_ constants
+     *                defined in ActionBar.
+     */
+    public abstract void setDisplayOptions(int options);
+    
+    /**
+     * Set selected display options. Only the options specified by mask will be changed.
+     * To change all display option bits at once, see {@link #setDisplayOptions(int)}.
+     * 
+     * <p>Example: setDisplayOptions(0, DISPLAY_HIDE_HOME) will disable the
+     * {@link #DISPLAY_HIDE_HOME} option.
+     * setDisplayOptions(DISPLAY_HIDE_HOME, DISPLAY_HIDE_HOME | DISPLAY_USE_LOGO)
+     * will enable {@link #DISPLAY_HIDE_HOME} and disable {@link #DISPLAY_USE_LOGO}.
+     * 
+     * @param options A combination of the bits defined by the DISPLAY_ constants
+     *                defined in ActionBar.
+     * @param mask A bit mask declaring which display options should be changed.
+     */
+    public abstract void setDisplayOptions(int options, int mask);
+    
+    /**
+     * Set the ActionBar's background.
+     * 
+     * @param d Background drawable
+     */
+    public abstract void setBackgroundDrawable(Drawable d);
+    
+    /**
+     * @return The current custom navigation view.
+     */
+    public abstract View getCustomNavigationView();
+    
+    /**
+     * Returns the current ActionBar title in standard mode.
+     * Returns null if {@link #getNavigationMode()} would not return
+     * {@link #NAVIGATION_MODE_STANDARD}. 
+     *
+     * @return The current ActionBar title or null.
+     */
+    public abstract CharSequence getTitle();
+    
+    /**
+     * Returns the current ActionBar subtitle in standard mode.
+     * Returns null if {@link #getNavigationMode()} would not return
+     * {@link #NAVIGATION_MODE_STANDARD}. 
+     *
+     * @return The current ActionBar subtitle or null.
+     */
+    public abstract CharSequence getSubtitle();
+    
+    /**
+     * Returns the current navigation mode. The result will be one of:
+     * <ul>
+     * <li>{@link #NAVIGATION_MODE_STANDARD}</li>
+     * <li>{@link #NAVIGATION_MODE_DROPDOWN_LIST}</li>
+     * <li>{@link #NAVIGATION_MODE_TABS}</li>
+     * <li>{@link #NAVIGATION_MODE_CUSTOM}</li>
+     * </ul>
+     *
+     * @return The current navigation mode.
+     * 
+     * @see #setStandardNavigationMode()
+     * @see #setStandardNavigationMode(CharSequence)
+     * @see #setStandardNavigationMode(CharSequence, CharSequence)
+     * @see #setDropdownNavigationMode(SpinnerAdapter)
+     * @see #setTabNavigationMode()
+     * @see #setCustomNavigationMode(View)
+     */
+    public abstract int getNavigationMode();
+    
+    /**
+     * @return The current set of display options. 
+     */
+    public abstract int getDisplayOptions();
+
+    /**
+     * Set the action bar into tabbed navigation mode.
+     *
+     * @see #addTab(Tab)
+     * @see #insertTab(Tab, int)
+     * @see #removeTab(Tab)
+     * @see #removeTabAt(int)
+     */
+    public abstract void setTabNavigationMode();
+
+    /**
+     * Set the action bar into tabbed navigation mode.
+     *
+     * @param containerViewId Id of the container view where tab content fragments should appear.
+     *
+     * @see #addTab(Tab)
+     * @see #insertTab(Tab, int)
+     * @see #removeTab(Tab)
+     * @see #removeTabAt(int)
+     */
+    public abstract void setTabNavigationMode(int containerViewId);
+
+    /**
+     * Create and return a new {@link Tab}.
+     * This tab will not be included in the action bar until it is added.
+     *
+     * @return A new Tab
+     *
+     * @see #addTab(Tab)
+     * @see #insertTab(Tab, int)
+     */
+    public abstract Tab newTab();
+
+    /**
+     * Add a tab for use in tabbed navigation mode. The tab will be added at the end of the list.
+     *
+     * @param tab Tab to add
+     */
+    public abstract void addTab(Tab tab);
+
+    /**
+     * Insert a tab for use in tabbed navigation mode. The tab will be inserted at
+     * <code>position</code>.
+     *
+     * @param tab The tab to add
+     * @param position The new position of the tab
+     */
+    public abstract void insertTab(Tab tab, int position);
+
+    /**
+     * Remove a tab from the action bar.
+     *
+     * @param tab The tab to remove
+     */
+    public abstract void removeTab(Tab tab);
+
+    /**
+     * Remove a tab from the action bar.
+     *
+     * @param position Position of the tab to remove
+     */
+    public abstract void removeTabAt(int position);
+
+    /**
+     * Select the specified tab. If it is not a child of this action bar it will be added.
+     *
+     * @param tab Tab to select
+     */
+    public abstract void selectTab(Tab tab);
+
+    /**
+     * Callback interface for ActionBar navigation events. 
+     */
+    public interface NavigationCallback {
+        /**
+         * This method is called whenever a navigation item in your action bar
+         * is selected.
+         *    
+         * @param itemPosition Position of the item clicked.
+         * @param itemId ID of the item clicked.
+         * @return True if the event was handled, false otherwise.
+         */
+        public boolean onNavigationItemSelected(int itemPosition, long itemId);
+    }
+
+    /**
+     * A tab in the action bar.
+     *
+     * <p>Tabs manage the hiding and showing of {@link Fragment}s.
+     */
+    public static abstract class Tab {
+        /**
+         * An invalid position for a tab.
+         *
+         * @see #getPosition()
+         */
+        public static final int INVALID_POSITION = -1;
+
+        /**
+         * Return the current position of this tab in the action bar.
+         *
+         * @return Current position, or {@link #INVALID_POSITION} if this tab is not currently in
+         *         the action bar.
+         */
+        public abstract int getPosition();
+
+        /**
+         * Return the icon associated with this tab.
+         *
+         * @return The tab's icon
+         */
+        public abstract Drawable getIcon();
+
+        /**
+         * Return the text of this tab.
+         *
+         * @return The tab's text
+         */
+        public abstract CharSequence getText();
+
+        /**
+         * Set the icon displayed on this tab.
+         *
+         * @param icon The drawable to use as an icon
+         */
+        public abstract void setIcon(Drawable icon);
+
+        /**
+         * Set the text displayed on this tab. Text may be truncated if there is not
+         * room to display the entire string.
+         *
+         * @param text The text to display
+         */
+        public abstract void setText(CharSequence text);
+
+        /**
+         * Returns the fragment that will be shown when this tab is selected.
+         *
+         * @return Fragment associated with this tab
+         */
+        public abstract Fragment getFragment();
+
+        /**
+         * Set the fragment that will be shown when this tab is selected.
+         *
+         * @param fragment Fragment to associate with this tab
+         */
+        public abstract void setFragment(Fragment fragment);
+
+        /**
+         * Select this tab. Only valid if the tab has been added to the action bar.
+         */
+        public abstract void select();
+    }
+}
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index f7ccc12..ee91318 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -16,19 +16,21 @@
 
 package android.app;
 
+import com.android.internal.app.ActionBarImpl;
 import com.android.internal.policy.PolicyManager;
 
 import android.content.ComponentCallbacks;
 import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.Context;
-import android.content.Intent;
 import android.content.IIntentSender;
+import android.content.Intent;
 import android.content.IntentSender;
 import android.content.SharedPreferences;
 import android.content.pm.ActivityInfo;
 import android.content.res.Configuration;
 import android.content.res.Resources;
+import android.content.res.TypedArray;
 import android.database.Cursor;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
@@ -39,6 +41,7 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.Parcelable;
 import android.os.RemoteException;
 import android.text.Selection;
 import android.text.SpannableStringBuilder;
@@ -49,8 +52,11 @@
 import android.util.EventLog;
 import android.util.Log;
 import android.util.SparseArray;
+import android.view.ActionMode;
 import android.view.ContextMenu;
+import android.view.ContextMenu.ContextMenuInfo;
 import android.view.ContextThemeWrapper;
+import android.view.InflateException;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
 import android.view.Menu;
@@ -58,17 +64,15 @@
 import android.view.MenuItem;
 import android.view.MotionEvent;
 import android.view.View;
+import android.view.View.OnCreateContextMenuListener;
 import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
 import android.view.ViewManager;
 import android.view.Window;
 import android.view.WindowManager;
-import android.view.ContextMenu.ContextMenuInfo;
-import android.view.View.OnCreateContextMenuListener;
-import android.view.ViewGroup.LayoutParams;
 import android.view.accessibility.AccessibilityEvent;
 import android.widget.AdapterView;
 import android.widget.FrameLayout;
-import android.widget.LinearLayout;
 
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -607,6 +611,7 @@
     private static long sInstanceCount = 0;
 
     private static final String WINDOW_HIERARCHY_TAG = "android:viewHierarchyState";
+    private static final String FRAGMENTS_TAG = "android:fragments";
     private static final String SAVED_DIALOG_IDS_KEY = "android:savedDialogIds";
     private static final String SAVED_DIALOGS_TAG = "android:savedDialogs";
     private static final String SAVED_DIALOG_KEY_PREFIX = "android:dialog_";
@@ -628,18 +633,27 @@
     private ComponentName mComponent;
     /*package*/ ActivityInfo mActivityInfo;
     /*package*/ ActivityThread mMainThread;
-    /*package*/ Object mLastNonConfigurationInstance;
-    /*package*/ HashMap<String,Object> mLastNonConfigurationChildInstances;
     Activity mParent;
     boolean mCalled;
+    boolean mStarted;
     private boolean mResumed;
     private boolean mStopped;
     boolean mFinished;
     boolean mStartedActivity;
+    /** true if the activity is being destroyed in order to recreate it with a new configuration */
+    /*package*/ boolean mChangingConfigurations = false;
     /*package*/ int mConfigChangeFlags;
     /*package*/ Configuration mCurrentConfig;
     private SearchManager mSearchManager;
 
+    static final class NonConfigurationInstances {
+        Object activity;
+        HashMap<String, Object> children;
+        ArrayList<Fragment> fragments;
+        SparseArray<LoaderManagerImpl> loaders;
+    }
+    /* package */ NonConfigurationInstances mLastNonConfigurationInstances;
+    
     private Window mWindow;
 
     private WindowManager mWindowManager;
@@ -647,10 +661,16 @@
     /*package*/ boolean mWindowAdded = false;
     /*package*/ boolean mVisibleFromServer = false;
     /*package*/ boolean mVisibleFromClient = true;
+    /*package*/ ActionBarImpl mActionBar = null;
 
     private CharSequence mTitle;
     private int mTitleColor = 0;
 
+    final FragmentManager mFragments = new FragmentManager();
+    
+    SparseArray<LoaderManagerImpl> mAllLoaderManagers;
+    LoaderManagerImpl mLoaderManager;
+    
     private static final class ManagedCursor {
         ManagedCursor(Cursor cursor) {
             mCursor = cursor;
@@ -677,7 +697,7 @@
     protected static final int[] FOCUSED_STATE_SET = {com.android.internal.R.attr.state_focused};
 
     private Thread mUiThread;
-    private final Handler mHandler = new Handler();
+    final Handler mHandler = new Handler();
 
     // Used for debug only
     /*
@@ -748,6 +768,29 @@
     }
 
     /**
+     * Return the LoaderManager for this fragment, creating it if needed.
+     */
+    public LoaderManager getLoaderManager() {
+        if (mLoaderManager != null) {
+            return mLoaderManager;
+        }
+        mLoaderManager = getLoaderManager(-1, mStarted);
+        return mLoaderManager;
+    }
+    
+    LoaderManagerImpl getLoaderManager(int index, boolean started) {
+        if (mAllLoaderManagers == null) {
+            mAllLoaderManagers = new SparseArray<LoaderManagerImpl>();
+        }
+        LoaderManagerImpl lm = mAllLoaderManagers.get(index);
+        if (lm == null) {
+            lm = new LoaderManagerImpl(started);
+            mAllLoaderManagers.put(index, lm);
+        }
+        return lm;
+    }
+    
+    /**
      * Calls {@link android.view.Window#getCurrentFocus} on the
      * Window of this Activity to return the currently focused view.
      * 
@@ -801,6 +844,15 @@
     protected void onCreate(Bundle savedInstanceState) {
         mVisibleFromClient = !mWindow.getWindowStyle().getBoolean(
                 com.android.internal.R.styleable.Window_windowNoDisplay, false);
+        if (mLastNonConfigurationInstances != null) {
+            mAllLoaderManagers = mLastNonConfigurationInstances.loaders;
+        }
+        if (savedInstanceState != null) {
+            Parcelable p = savedInstanceState.getParcelable(FRAGMENTS_TAG);
+            mFragments.restoreAllState(p, mLastNonConfigurationInstances != null
+                    ? mLastNonConfigurationInstances.fragments : null);
+        }
+        mFragments.dispatchCreate();
         mCalled = true;
     }
 
@@ -915,6 +967,10 @@
             mTitleReady = true;
             onTitleChanged(getTitle(), getTitleColor());
         }
+        if (mWindow != null && mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) {
+            // Invalidate the action bar menu so that it can initialize properly. 
+            mWindow.invalidatePanelMenu(Window.FEATURE_ACTION_BAR);
+        }
         mCalled = true;
     }
 
@@ -933,6 +989,10 @@
      */
     protected void onStart() {
         mCalled = true;
+        mStarted = true;
+        if (mLoaderManager != null) {
+            mLoaderManager.doStart();
+        }
     }
 
     /**
@@ -1085,6 +1145,10 @@
      */
     protected void onSaveInstanceState(Bundle outState) {
         outState.putBundle(WINDOW_HIERARCHY_TAG, mWindow.saveHierarchyState());
+        Parcelable p = mFragments.saveAllState();
+        if (p != null) {
+            outState.putParcelable(FRAGMENTS_TAG, p);
+        }
     }
 
     /**
@@ -1407,7 +1471,8 @@
      * {@link #onRetainNonConfigurationInstance()}.
      */
     public Object getLastNonConfigurationInstance() {
-        return mLastNonConfigurationInstance;
+        return mLastNonConfigurationInstances != null
+                ? mLastNonConfigurationInstances.activity : null;
     }
     
     /**
@@ -1463,8 +1528,9 @@
      * @return Returns the object previously returned by
      * {@link #onRetainNonConfigurationChildInstances()}
      */
-    HashMap<String,Object> getLastNonConfigurationChildInstances() {
-        return mLastNonConfigurationChildInstances;
+    HashMap<String, Object> getLastNonConfigurationChildInstances() {
+        return mLastNonConfigurationInstances != null
+                ? mLastNonConfigurationInstances.children : null;
     }
     
     /**
@@ -1478,11 +1544,62 @@
         return null;
     }
     
+    NonConfigurationInstances retainNonConfigurationInstances() {
+        Object activity = onRetainNonConfigurationInstance();
+        HashMap<String, Object> children = onRetainNonConfigurationChildInstances();
+        ArrayList<Fragment> fragments = mFragments.retainNonConfig();
+        boolean retainLoaders = false;
+        if (mAllLoaderManagers != null) {
+            // prune out any loader managers that were already stopped, so
+            // have nothing useful to retain.
+            for (int i=mAllLoaderManagers.size()-1; i>=0; i--) {
+                LoaderManagerImpl lm = mAllLoaderManagers.valueAt(i);
+                if (lm.mRetaining) {
+                    retainLoaders = true;
+                } else {
+                    mAllLoaderManagers.removeAt(i);
+                }
+            }
+        }
+        if (activity == null && children == null && fragments == null && !retainLoaders) {
+            return null;
+        }
+        
+        NonConfigurationInstances nci = new NonConfigurationInstances();
+        nci.activity = activity;
+        nci.children = children;
+        nci.fragments = fragments;
+        nci.loaders = mAllLoaderManagers;
+        return nci;
+    }
+    
     public void onLowMemory() {
         mCalled = true;
     }
     
     /**
+     * Start a series of edit operations on the Fragments associated with
+     * this activity.
+     */
+    public FragmentTransaction openFragmentTransaction() {
+        return new BackStackEntry(mFragments);
+    }
+    
+    void invalidateFragmentIndex(int index) {
+        if (mAllLoaderManagers != null) {
+            mAllLoaderManagers.remove(index);
+        }
+    }
+    
+    /**
+     * Called when a Fragment is being attached to this activity, immediately
+     * after the call to its {@link Fragment#onAttach Fragment.onAttach()}
+     * method and before {@link Fragment#onCreate Fragment.onCreate()}.
+     */
+    public void onAttachFragment(Fragment fragment) {
+    }
+    
+    /**
      * Wrapper around
      * {@link ContentResolver#query(android.net.Uri , String[], String, String[], String)}
      * that gives the resulting {@link Cursor} to call
@@ -1544,40 +1661,6 @@
     }
 
     /**
-     * Wrapper around {@link Cursor#commitUpdates()} that takes care of noting
-     * that the Cursor needs to be requeried.  You can call this method in
-     * {@link #onPause} or {@link #onStop} to have the system call
-     * {@link Cursor#requery} for you if the activity is later resumed.  This
-     * allows you to avoid determing when to do the requery yourself (which is
-     * required for the Cursor to see any data changes that were committed with
-     * it).
-     * 
-     * @param c The Cursor whose changes are to be committed.
-     * 
-     * @see #managedQuery(android.net.Uri , String[], String, String[], String)
-     * @see #startManagingCursor
-     * @see Cursor#commitUpdates()
-     * @see Cursor#requery
-     * @hide
-     */
-    @Deprecated
-    public void managedCommitUpdates(Cursor c) {
-        synchronized (mManagedCursors) {
-            final int N = mManagedCursors.size();
-            for (int i=0; i<N; i++) {
-                ManagedCursor mc = mManagedCursors.get(i);
-                if (mc.mCursor == c) {
-                    c.commitUpdates();
-                    mc.mUpdated = true;
-                    return;
-                }
-            }
-            throw new RuntimeException(
-                "Cursor " + c + " is not currently managed");
-        }
-    }
-
-    /**
      * This method allows the activity to take care of managing the given
      * {@link Cursor}'s lifecycle for you based on the activity's lifecycle.
      * That is, when the activity is stopped it will automatically call
@@ -1655,7 +1738,52 @@
     public View findViewById(int id) {
         return getWindow().findViewById(id);
     }
-
+    
+    /**
+     * Retrieve a reference to this activity's ActionBar.
+     * 
+     * <p><em>Note:</em> The ActionBar is initialized when a content view
+     * is set. This function will return null if called before {@link #setContentView}
+     * or {@link #addContentView}.
+     * @return The Activity's ActionBar, or null if it does not have one.
+     */
+    public ActionBar getActionBar() {
+        return mActionBar;
+    }
+    
+    /**
+     * Creates a new ActionBar, locates the inflated ActionBarView,
+     * initializes the ActionBar with the view, and sets mActionBar.
+     */
+    private void initActionBar() {
+        Window window = getWindow();
+        if (!window.hasFeature(Window.FEATURE_ACTION_BAR) || mActionBar != null) {
+            return;
+        }
+        
+        mActionBar = new ActionBarImpl(this);
+    }
+    
+    /**
+     * Finds a fragment that was identified by the given id either when inflated
+     * from XML or as the container ID when added in a transaction.  This only
+     * returns fragments that are currently added to the activity's content.
+     * @return The fragment if found or null otherwise.
+     */
+    public Fragment findFragmentById(int id) {
+        return mFragments.findFragmentById(id);
+    }
+    
+    /**
+     * Finds a fragment that was identified by the given tag either when inflated
+     * from XML or as supplied when added in a transaction.  This only
+     * returns fragments that are currently added to the activity's content.
+     * @return The fragment if found or null otherwise.
+     */
+    public Fragment findFragmentByTag(String tag) {
+        return mFragments.findFragmentByTag(tag);
+    }
+    
     /**
      * Set the activity content from a layout resource.  The resource will be
      * inflated, adding all top-level views to the activity.
@@ -1664,6 +1792,7 @@
      */
     public void setContentView(int layoutResID) {
         getWindow().setContentView(layoutResID);
+        initActionBar();
     }
 
     /**
@@ -1675,6 +1804,7 @@
      */
     public void setContentView(View view) {
         getWindow().setContentView(view);
+        initActionBar();
     }
 
     /**
@@ -1687,6 +1817,7 @@
      */
     public void setContentView(View view, ViewGroup.LayoutParams params) {
         getWindow().setContentView(view, params);
+        initActionBar();
     }
 
     /**
@@ -1698,6 +1829,7 @@
      */
     public void addContentView(View view, ViewGroup.LayoutParams params) {
         getWindow().addContentView(view, params);
+        initActionBar();
     }
 
     /**
@@ -1921,12 +2053,59 @@
     }
     
     /**
+     * Flag for {@link #popBackStack(String, int)}
+     * and {@link #popBackStack(int, int)}: If set, and the name or ID of
+     * a back stack entry has been supplied, then all matching entries will
+     * be consumed until one that doesn't match is found or the bottom of
+     * the stack is reached.  Otherwise, all entries up to but not including that entry
+     * will be removed.
+     */
+    public static final int POP_BACK_STACK_INCLUSIVE = 1<<0;
+
+    /**
+     * Pop the top state off the back stack.  Returns true if there was one
+     * to pop, else false.
+     */
+    public boolean popBackStack() {
+        return popBackStack(null, -1);
+    }
+
+    /**
+     * Pop the last fragment transition from the local activity's fragment
+     * back stack.  If there is nothing to pop, false is returned.
+     * @param name If non-null, this is the name of a previous back state
+     * to look for; if found, all states up to that state will be popped.  The
+     * {@link #POP_BACK_STACK_INCLUSIVE} flag can be used to control whether
+     * the named state itself is popped. If null, only the top state is popped.
+     * @param flags Either 0 or {@link #POP_BACK_STACK_INCLUSIVE}.
+     */
+    public boolean popBackStack(String name, int flags) {
+        return mFragments.popBackStackState(mHandler, name, flags);
+    }
+
+    /**
+     * Pop all back stack states up to the one with the given identifier.
+     * @param id Identifier of the stated to be popped. If no identifier exists,
+     * false is returned.
+     * The identifier is the number returned by
+     * {@link FragmentTransaction#commit() FragmentTransaction.commit()}.  The
+     * {@link #POP_BACK_STACK_INCLUSIVE} flag can be used to control whether
+     * the named state itself is popped.
+     * @param flags Either 0 or {@link #POP_BACK_STACK_INCLUSIVE}.
+     */
+    public boolean popBackStack(int id, int flags) {
+        return mFragments.popBackStackState(mHandler, id, flags);
+    }
+    
+    /**
      * Called when the activity has detected the user's press of the back
      * key.  The default implementation simply finishes the current activity,
      * but you can override this to do whatever you want.
      */
     public void onBackPressed() {
-        finish();
+        if (!popBackStack()) {
+            finish();
+        }
     }
     
     /**
@@ -2164,7 +2343,9 @@
      */
     public boolean onCreatePanelMenu(int featureId, Menu menu) {
         if (featureId == Window.FEATURE_OPTIONS_PANEL) {
-            return onCreateOptionsMenu(menu);
+            boolean show = onCreateOptionsMenu(menu);
+            show |= mFragments.dispatchCreateOptionsMenu(menu, getMenuInflater());
+            return show;
         }
         return false;
     }
@@ -2181,6 +2362,7 @@
     public boolean onPreparePanel(int featureId, View view, Menu menu) {
         if (featureId == Window.FEATURE_OPTIONS_PANEL && menu != null) {
             boolean goforit = onPrepareOptionsMenu(menu);
+            goforit |= mFragments.dispatchPrepareOptionsMenu(menu);
             return goforit && menu.hasVisibleItems();
         }
         return true;
@@ -2211,11 +2393,17 @@
                 // doesn't call through to superclass's implmeentation of each
                 // of these methods below
                 EventLog.writeEvent(50000, 0, item.getTitleCondensed());
-                return onOptionsItemSelected(item);
+                if (onOptionsItemSelected(item)) {
+                    return true;
+                }
+                return mFragments.dispatchOptionsItemSelected(item);
                 
             case Window.FEATURE_CONTEXT_MENU:
                 EventLog.writeEvent(50000, 1, item.getTitleCondensed());
-                return onContextItemSelected(item);
+                if (onContextItemSelected(item)) {
+                    return true;
+                }
+                return mFragments.dispatchContextItemSelected(item);
                 
             default:
                 return false;
@@ -2234,6 +2422,7 @@
     public void onPanelClosed(int featureId, Menu menu) {
         switch (featureId) {
             case Window.FEATURE_OPTIONS_PANEL:
+                mFragments.dispatchOptionsMenuClosed(menu);
                 onOptionsMenuClosed(menu);
                 break;
                 
@@ -2244,6 +2433,15 @@
     }
 
     /**
+     * Declare that the options menu has changed, so should be recreated.
+     * The {@link #onCreateOptionsMenu(Menu)} method will be called the next
+     * time it needs to be displayed.
+     */
+    public void invalidateOptionsMenu() {
+        mWindow.invalidatePanelMenu(Window.FEATURE_OPTIONS_PANEL);
+    }
+    
+    /**
      * Initialize the contents of the Activity's standard options menu.  You
      * should place your menu items in to <var>menu</var>.
      * 
@@ -3085,6 +3283,36 @@
     }
 
     /**
+     * This is called when a Fragment in this activity calls its 
+     * {@link Fragment#startActivity} or {@link Fragment#startActivityForResult}
+     * method.
+     * 
+     * <p>This method throws {@link android.content.ActivityNotFoundException}
+     * if there was no Activity found to run the given Intent.
+     * 
+     * @param fragment The fragment making the call.
+     * @param intent The intent to start.
+     * @param requestCode Reply request code.  < 0 if reply is not requested. 
+     * 
+     * @throws android.content.ActivityNotFoundException
+     * 
+     * @see Fragment#startActivity 
+     * @see Fragment#startActivityForResult 
+     */
+    public void startActivityFromFragment(Fragment fragment, Intent intent, 
+            int requestCode) {
+        Instrumentation.ActivityResult ar =
+            mInstrumentation.execStartActivity(
+                this, mMainThread.getApplicationThread(), mToken, fragment,
+                intent, requestCode);
+        if (ar != null) {
+            mMainThread.sendActivityResult(
+                mToken, fragment.mWho, requestCode,
+                ar.getResultCode(), ar.getResultData());
+        }
+    }
+
+    /**
      * Like {@link #startActivityFromChild(Activity, Intent, int)}, but
      * taking a IntentSender; see
      * {@link #startIntentSenderForResult(IntentSender, int, Intent, int, int, int)}
@@ -3243,6 +3471,19 @@
     }
 
     /**
+     * Check to see whether this activity is in the process of being destroyed in order to be
+     * recreated with a new configuration. This is often used in
+     * {@link #onStop} to determine whether the state needs to be cleaned up or will be passed
+     * on to the next instance of the activity via {@link #onRetainNonConfigurationInstance()}.
+     * 
+     * @return If the activity is being torn down in order to be recreated with a new configuration,
+     * returns true; else returns false.
+     */
+    public boolean isChangingConfigurations() {
+        return mChangingConfigurations;
+    }
+
+    /**
      * Call this when your activity is done and should be closed.  The
      * ActivityResult is propagated back to whoever launched you via
      * onActivityResult().
@@ -3343,8 +3584,7 @@
      * @see #createPendingResult
      * @see #setResult(int)
      */
-    protected void onActivityResult(int requestCode, int resultCode,
-            Intent data) {
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
     }
 
     /**
@@ -3728,15 +3968,69 @@
     }
 
     /**
-     * Stub implementation of {@link android.view.LayoutInflater.Factory#onCreateView} used when
-     * inflating with the LayoutInflater returned by {@link #getSystemService}.  This
-     * implementation simply returns null for all view names.
+     * Standard implementation of
+     * {@link android.view.LayoutInflater.Factory#onCreateView} used when
+     * inflating with the LayoutInflater returned by {@link #getSystemService}.
+     * This implementation handles <fragment> tags to embed fragments inside
+     * of the activity.
      *
      * @see android.view.LayoutInflater#createView
      * @see android.view.Window#getLayoutInflater
      */
     public View onCreateView(String name, Context context, AttributeSet attrs) {
-        return null;
+        if (!"fragment".equals(name)) {
+            return null;
+        }
+        
+        TypedArray a = 
+            context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.Fragment);
+        String fname = a.getString(com.android.internal.R.styleable.Fragment_name);
+        int id = a.getResourceId(com.android.internal.R.styleable.Fragment_id, 0);
+        String tag = a.getString(com.android.internal.R.styleable.Fragment_tag);
+        a.recycle();
+        
+        if (id == 0) {
+            throw new IllegalArgumentException(attrs.getPositionDescription()
+                    + ": Must specify unique android:id for " + fname);
+        }
+        
+        try {
+            // If we restored from a previous state, we may already have
+            // instantiated this fragment from the state and should use
+            // that instance instead of making a new one.
+            Fragment fragment = mFragments.findFragmentById(id);
+            if (FragmentManager.DEBUG) Log.v(TAG, "onCreateView: id=0x"
+                    + Integer.toHexString(id) + " fname=" + fname
+                    + " existing=" + fragment);
+            if (fragment == null) {
+                fragment = Fragment.instantiate(this, fname);
+                fragment.mFromLayout = true;
+                fragment.mFragmentId = id;
+                fragment.mTag = tag;
+                fragment.mImmediateActivity = this;
+                mFragments.addFragment(fragment, true);
+            }
+            // If this fragment is newly instantiated (either right now, or
+            // from last saved state), then give it the attributes to
+            // initialize itself.
+            if (!fragment.mRetaining) {
+                fragment.onInflate(this, attrs, fragment.mSavedFragmentState);
+            }
+            if (fragment.mView == null) {
+                throw new IllegalStateException("Fragment " + fname
+                        + " did not create a view.");
+            }
+            fragment.mView.setId(id);
+            if (fragment.mView.getTag() == null) {
+                fragment.mView.setTag(tag);
+            }
+            return fragment.mView;
+        } catch (Exception e) {
+            InflateException ie = new InflateException(attrs.getPositionDescription()
+                    + ": Error inflating fragment " + fname);
+            ie.initCause(e);
+            throw ie;
+        }
     }
 
     /**
@@ -3779,6 +4073,25 @@
         }
     }
 
+    /**
+     * Start a context mode.
+     *
+     * @param callback Callback that will manage lifecycle events for this context mode
+     * @return The ContextMode that was started, or null if it was canceled
+     *
+     * @see ActionMode
+     */
+    public ActionMode startActionMode(ActionMode.Callback callback) {
+        return mWindow.getDecorView().startActionMode(callback);
+    }
+
+    public ActionMode onStartActionMode(ActionMode.Callback callback) {
+        if (mActionBar != null) {
+            return mActionBar.startActionMode(callback);
+        }
+        return null;
+    }
+
     // ------------------ Internal API ------------------
     
     final void setParent(Activity parent) {
@@ -3787,28 +4100,30 @@
 
     final void attach(Context context, ActivityThread aThread, Instrumentation instr, IBinder token,
             Application application, Intent intent, ActivityInfo info, CharSequence title, 
-            Activity parent, String id, Object lastNonConfigurationInstance,
+            Activity parent, String id, NonConfigurationInstances lastNonConfigurationInstances,
             Configuration config) {
         attach(context, aThread, instr, token, 0, application, intent, info, title, parent, id,
-            lastNonConfigurationInstance, null, config);
+            lastNonConfigurationInstances, config);
     }
     
     final void attach(Context context, ActivityThread aThread,
             Instrumentation instr, IBinder token, int ident,
             Application application, Intent intent, ActivityInfo info,
             CharSequence title, Activity parent, String id,
-            Object lastNonConfigurationInstance,
-            HashMap<String,Object> lastNonConfigurationChildInstances,
+            NonConfigurationInstances lastNonConfigurationInstances,
             Configuration config) {
         attachBaseContext(context);
 
+        mFragments.attachActivity(this);
+        
         mWindow = PolicyManager.makeNewWindow(this);
         mWindow.setCallback(this);
+        mWindow.getLayoutInflater().setFactory(this);
         if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
             mWindow.setSoftInputMode(info.softInputMode);
         }
         mUiThread = Thread.currentThread();
-
+        
         mMainThread = aThread;
         mInstrumentation = instr;
         mToken = token;
@@ -3820,10 +4135,10 @@
         mTitle = title;
         mParent = parent;
         mEmbeddedID = id;
-        mLastNonConfigurationInstance = lastNonConfigurationInstance;
-        mLastNonConfigurationChildInstances = lastNonConfigurationChildInstances;
+        mLastNonConfigurationInstances = lastNonConfigurationInstances;
 
-        mWindow.setWindowManager(null, mToken, mComponent.flattenToString());
+        mWindow.setWindowManager(null, mToken, mComponent.flattenToString(),
+                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
         if (mParent != null) {
             mWindow.setContainer(mParent.getWindow());
         }
@@ -3835,14 +4150,26 @@
         return mParent != null ? mParent.getActivityToken() : mToken;
     }
 
+    final void performCreate(Bundle icicle) {
+        onCreate(icicle);
+        mFragments.dispatchActivityCreated();
+    }
+    
     final void performStart() {
         mCalled = false;
+        mFragments.execPendingActions();
         mInstrumentation.callActivityOnStart(this);
         if (!mCalled) {
             throw new SuperNotCalledException(
                 "Activity " + mComponent.toShortString() +
                 " did not call through to super.onStart()");
         }
+        mFragments.dispatchStart();
+        if (mAllLoaderManagers != null) {
+            for (int i=mAllLoaderManagers.size()-1; i>=0; i--) {
+                mAllLoaderManagers.valueAt(i).finishRetain();
+            }
+        }
     }
     
     final void performRestart() {
@@ -3851,7 +4178,10 @@
             for (int i=0; i<N; i++) {
                 ManagedCursor mc = mManagedCursors.get(i);
                 if (mc.mReleased || mc.mUpdated) {
-                    mc.mCursor.requery();
+                    if (!mc.mCursor.requery()) {
+                        throw new IllegalStateException(
+                                "trying to requery an already closed cursor");
+                    }
                     mc.mReleased = false;
                     mc.mUpdated = false;
                 }
@@ -3874,7 +4204,9 @@
     final void performResume() {
         performRestart();
         
-        mLastNonConfigurationInstance = null;
+        mFragments.execPendingActions();
+        
+        mLastNonConfigurationInstances = null;
         
         // First call onResume() -before- setting mResumed, so we don't
         // send out any status bar / menu notifications the client makes.
@@ -3889,6 +4221,10 @@
         // Now really resume, and install the current status bar and menu.
         mResumed = true;
         mCalled = false;
+        
+        mFragments.dispatchResume();
+        mFragments.execPendingActions();
+        
         onPostResume();
         if (!mCalled) {
             throw new SuperNotCalledException(
@@ -3898,6 +4234,7 @@
     }
 
     final void performPause() {
+        mFragments.dispatchPause();
         onPause();
     }
     
@@ -3907,11 +4244,24 @@
     }
     
     final void performStop() {
+        if (mStarted) {
+            mStarted = false;
+            if (mLoaderManager != null) {
+                if (!mChangingConfigurations) {
+                    mLoaderManager.doStop();
+                } else {
+                    mLoaderManager.doRetain();
+                }
+            }
+        }
+        
         if (!mStopped) {
             if (mWindow != null) {
                 mWindow.closeAllPanels();
             }
 
+            mFragments.dispatchStop();
+            
             mCalled = false;
             mInstrumentation.callActivityOnStop(this);
             if (!mCalled) {
@@ -3936,6 +4286,11 @@
         mResumed = false;
     }
 
+    final void performDestroy() {
+        mFragments.dispatchDestroy();
+        onDestroy();
+    }
+    
     final boolean isResumed() {
         return mResumed;
     }
@@ -3947,6 +4302,11 @@
             + ", resCode=" + resultCode + ", data=" + data);
         if (who == null) {
             onActivityResult(requestCode, resultCode, data);
+        } else {
+            Fragment frag = mFragments.findFragmentByWho(who);
+            if (frag != null) {
+                frag.onActivityResult(requestCode, resultCode, data);
+            }
         }
     }
 }
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index 9a55a6f..a93184d 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -1295,6 +1295,19 @@
             return true;
         }
 
+        case DUMP_HEAP_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            String process = data.readString();
+            boolean managed = data.readInt() != 0;
+            String path = data.readString();
+            ParcelFileDescriptor fd = data.readInt() != 0
+                    ? data.readFileDescriptor() : null;
+            boolean res = dumpHeap(process, managed, path, fd);
+            reply.writeNoException();
+            reply.writeInt(res ? 1 : 0);
+            return true;
+        }
+
         }
         
         return super.onTransact(code, data, reply, flags);
@@ -2875,6 +2888,28 @@
         data.recycle();
         reply.recycle();
     }
-    
+
+    public boolean dumpHeap(String process, boolean managed,
+            String path, ParcelFileDescriptor fd) throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeString(process);
+        data.writeInt(managed ? 1 : 0);
+        data.writeString(path);
+        if (fd != null) {
+            data.writeInt(1);
+            fd.writeToParcel(data, Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
+        } else {
+            data.writeInt(0);
+        }
+        mRemote.transact(DUMP_HEAP_TRANSACTION, data, reply, 0);
+        reply.readException();
+        boolean res = reply.readInt() != 0;
+        reply.recycle();
+        data.recycle();
+        return res;
+    }
+
     private IBinder mRemote;
 }
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index c88e086..1c8c73d 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -30,6 +30,7 @@
 import android.content.pm.IPackageManager;
 import android.content.pm.InstrumentationInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.ProviderInfo;
 import android.content.pm.ServiceInfo;
 import android.content.res.AssetManager;
@@ -195,8 +196,7 @@
         Window window;
         Activity parent;
         String embeddedID;
-        Object lastNonConfigurationInstance;
-        HashMap<String,Object> lastNonConfigurationChildInstances;
+        Activity.NonConfigurationInstances lastNonConfigurationInstances;
         boolean paused;
         boolean stopped;
         boolean hideForNow;
@@ -357,11 +357,16 @@
         ParcelFileDescriptor fd;
     }
 
+    private static final class DumpHeapData {
+        String path;
+        ParcelFileDescriptor fd;
+    }
+
     private final class ApplicationThread extends ApplicationThreadNative {
         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;
@@ -624,6 +629,13 @@
             queueOrSendMessage(H.PROFILER_CONTROL, pcd, start ? 1 : 0);
         }
 
+        public void dumpHeap(boolean managed, String path, ParcelFileDescriptor fd) {
+            DumpHeapData dhd = new DumpHeapData();
+            dhd.path = path;
+            dhd.fd = fd;
+            queueOrSendMessage(H.DUMP_HEAP, dhd, managed ? 1 : 0);
+        }
+
         public void setSchedulingGroup(int group) {
             // Note: do this immediately, since going into the foreground
             // should happen regardless of what pending work we have to do
@@ -761,7 +773,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(',');
                 }
 
@@ -812,11 +824,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);
                 }
             }
 
@@ -874,6 +887,7 @@
         public static final int ENABLE_JIT              = 132;
         public static final int DISPATCH_PACKAGE_BROADCAST = 133;
         public static final int SCHEDULE_CRASH          = 134;
+        public static final int DUMP_HEAP               = 135;
         String codeToString(int code) {
             if (localLOGV) {
                 switch (code) {
@@ -912,6 +926,7 @@
                     case ENABLE_JIT: return "ENABLE_JIT";
                     case DISPATCH_PACKAGE_BROADCAST: return "DISPATCH_PACKAGE_BROADCAST";
                     case SCHEDULE_CRASH: return "SCHEDULE_CRASH";
+                    case DUMP_HEAP: return "DUMP_HEAP";
                 }
             }
             return "(unknown)";
@@ -1037,13 +1052,35 @@
                     break;
                 case SCHEDULE_CRASH:
                     throw new RemoteServiceException((String)msg.obj);
+                case DUMP_HEAP:
+                    handleDumpHeap(msg.arg1 != 0, (DumpHeapData)msg.obj);
+                    break;
             }
         }
 
         void maybeSnapshot() {
             if (mBoundApplication != null) {
-                SamplingProfilerIntegration.writeSnapshot(
-                        mBoundApplication.processName);
+                // convert the *private* ActivityThread.PackageInfo to *public* known
+                // android.content.pm.PackageInfo
+                String packageName = mBoundApplication.info.mPackageName;
+                android.content.pm.PackageInfo packageInfo = null;
+                try {
+                    Context context = getSystemContext();
+                    if(context == null) {
+                        Log.e(TAG, "cannot get a valid context");
+                        return;
+                    }
+                    PackageManager pm = context.getPackageManager();
+                    if(pm == null) {
+                        Log.e(TAG, "cannot get a valid PackageManager");
+                        return;
+                    }
+                    packageInfo = pm.getPackageInfo(
+                            packageName, PackageManager.GET_ACTIVITIES);
+                } catch (NameNotFoundException e) {
+                    Log.e(TAG, "cannot get package info for " + packageName, e);
+                }
+                SamplingProfilerIntegration.writeSnapshot(mBoundApplication.processName, packageInfo);
             }
         }
     }
@@ -1434,7 +1471,7 @@
 
     public final Activity startActivityNow(Activity parent, String id,
         Intent intent, ActivityInfo activityInfo, IBinder token, Bundle state,
-        Object lastNonConfigurationInstance) {
+        Activity.NonConfigurationInstances lastNonConfigurationInstances) {
         ActivityClientRecord r = new ActivityClientRecord();
             r.token = token;
             r.ident = 0;
@@ -1443,7 +1480,7 @@
             r.parent = parent;
             r.embeddedID = id;
             r.activityInfo = activityInfo;
-            r.lastNonConfigurationInstance = lastNonConfigurationInstance;
+            r.lastNonConfigurationInstances = lastNonConfigurationInstances;
         if (localLOGV) {
             ComponentName compname = intent.getComponent();
             String name;
@@ -1565,14 +1602,12 @@
                         + r.activityInfo.name + " with config " + config);
                 activity.attach(appContext, this, getInstrumentation(), r.token,
                         r.ident, app, r.intent, r.activityInfo, title, r.parent,
-                        r.embeddedID, r.lastNonConfigurationInstance,
-                        r.lastNonConfigurationChildInstances, config);
+                        r.embeddedID, r.lastNonConfigurationInstances, config);
 
                 if (customIntent != null) {
                     activity.mIntent = customIntent;
                 }
-                r.lastNonConfigurationInstance = null;
-                r.lastNonConfigurationChildInstances = null;
+                r.lastNonConfigurationInstances = null;
                 activity.mStartedActivity = false;
                 int theme = r.activityInfo.getThemeResource();
                 if (theme != 0) {
@@ -2541,6 +2576,9 @@
             if (finishing) {
                 r.activity.mFinished = true;
             }
+            if (getNonConfigInstance) {
+                r.activity.mChangingConfigurations = true;
+            }
             if (!r.paused) {
                 try {
                     r.activity.mCalled = false;
@@ -2581,8 +2619,8 @@
             }
             if (getNonConfigInstance) {
                 try {
-                    r.lastNonConfigurationInstance
-                            = r.activity.onRetainNonConfigurationInstance();
+                    r.lastNonConfigurationInstances
+                            = r.activity.retainNonConfigurationInstances();
                 } catch (Exception e) {
                     if (!mInstrumentation.onException(r.activity, e)) {
                         throw new RuntimeException(
@@ -2591,22 +2629,10 @@
                                 + ": " + e.toString(), e);
                     }
                 }
-                try {
-                    r.lastNonConfigurationChildInstances
-                            = r.activity.onRetainNonConfigurationChildInstances();
-                } catch (Exception e) {
-                    if (!mInstrumentation.onException(r.activity, e)) {
-                        throw new RuntimeException(
-                                "Unable to retain child activities "
-                                + safeToComponentShortString(r.intent)
-                                + ": " + e.toString(), e);
-                    }
-                }
-
             }
             try {
                 r.activity.mCalled = false;
-                r.activity.onDestroy();
+                mInstrumentation.callActivityOnDestroy(r.activity);
                 if (!r.activity.mCalled) {
                     throw new SuperNotCalledException(
                         "Activity " + safeToComponentShortString(r.intent) +
@@ -3018,6 +3044,25 @@
         }
     }
 
+    final void handleDumpHeap(boolean managed, DumpHeapData dhd) {
+        if (managed) {
+            try {
+                Debug.dumpHprofData(dhd.path, dhd.fd.getFileDescriptor());
+            } catch (IOException e) {
+                Slog.w(TAG, "Managed heap dump failed on path " + dhd.path
+                        + " -- can the process access this path?");
+            } finally {
+                try {
+                    dhd.fd.close();
+                } catch (IOException e) {
+                    Slog.w(TAG, "Failure closing profile fd", e);
+                }
+            }
+        } else {
+            Debug.dumpNativeHeap(dhd.fd.getFileDescriptor());
+        }
+    }
+
     final void handleDispatchPackageBroadcast(int cmd, String[] packages) {
         boolean hasPkgInfo = false;
         if (packages != null) {
diff --git a/core/java/android/app/ApplicationErrorReport.java b/core/java/android/app/ApplicationErrorReport.java
index 2382596..1e012eb 100644
--- a/core/java/android/app/ApplicationErrorReport.java
+++ b/core/java/android/app/ApplicationErrorReport.java
@@ -184,7 +184,7 @@
         candidate = SystemProperties.get(DEFAULT_ERROR_RECEIVER_PROPERTY);
         return getErrorReportReceiver(pm, packageName, candidate);
     }
-    
+
     /**
      * Return activity in receiverPackage that handles ACTION_APP_ERROR.
      *
@@ -581,6 +581,9 @@
             case TYPE_BATTERY:
                 batteryInfo.dump(pw, prefix);
                 break;
+            case TYPE_RUNNING_SERVICE:
+                runningServiceInfo.dump(pw, prefix);
+                break;
         }
     }
 }
diff --git a/core/java/android/app/ApplicationThreadNative.java b/core/java/android/app/ApplicationThreadNative.java
index 1c20062..dc2145f 100644
--- a/core/java/android/app/ApplicationThreadNative.java
+++ b/core/java/android/app/ApplicationThreadNative.java
@@ -403,6 +403,17 @@
             scheduleCrash(msg);
             return true;
         }
+
+        case DUMP_HEAP_TRANSACTION:
+        {
+            data.enforceInterface(IApplicationThread.descriptor);
+            boolean managed = data.readInt() != 0;
+            String path = data.readString();
+            ParcelFileDescriptor fd = data.readInt() != 0
+                    ? data.readFileDescriptor() : null;
+            dumpHeap(managed, path, fd);
+            return true;
+        }
         }
 
         return super.onTransact(code, data, reply, flags);
@@ -829,5 +840,22 @@
         data.recycle();
         
     }
+
+    public void dumpHeap(boolean managed, String path,
+            ParcelFileDescriptor fd) throws RemoteException {
+        Parcel data = Parcel.obtain();
+        data.writeInterfaceToken(IApplicationThread.descriptor);
+        data.writeInt(managed ? 1 : 0);
+        data.writeString(path);
+        if (fd != null) {
+            data.writeInt(1);
+            fd.writeToParcel(data, Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
+        } else {
+            data.writeInt(0);
+        }
+        mRemote.transact(DUMP_HEAP_TRANSACTION, data, null,
+                IBinder.FLAG_ONEWAY);
+        data.recycle();
+    }
 }
 
diff --git a/core/java/android/app/BackStackEntry.java b/core/java/android/app/BackStackEntry.java
new file mode 100644
index 0000000..d63b862
--- /dev/null
+++ b/core/java/android/app/BackStackEntry.java
@@ -0,0 +1,493 @@
+/*
+ * 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.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+import java.util.ArrayList;
+
+final class BackStackState implements Parcelable {
+    final int[] mOps;
+    final int mTransition;
+    final int mTransitionStyle;
+    final String mName;
+    final int mIndex;
+    
+    public BackStackState(FragmentManager fm, BackStackEntry bse) {
+        int numRemoved = 0;
+        BackStackEntry.Op op = bse.mHead;
+        while (op != null) {
+            if (op.removed != null) numRemoved += op.removed.size();
+            op = op.next;
+        }
+        mOps = new int[bse.mNumOp*5 + numRemoved];
+        
+        op = bse.mHead;
+        int pos = 0;
+        while (op != null) {
+            mOps[pos++] = op.cmd;
+            mOps[pos++] = op.fragment.mIndex;
+            mOps[pos++] = op.enterAnim;
+            mOps[pos++] = op.exitAnim;
+            if (op.removed != null) {
+                final int N = op.removed.size();
+                mOps[pos++] = N;
+                for (int i=0; i<N; i++) {
+                    mOps[pos++] = op.removed.get(i).mIndex;
+                }
+            } else {
+                mOps[pos++] = 0;
+            }
+            op = op.next;
+        }
+        mTransition = bse.mTransition;
+        mTransitionStyle = bse.mTransitionStyle;
+        mName = bse.mName;
+        mIndex = bse.mIndex;
+    }
+    
+    public BackStackState(Parcel in) {
+        mOps = in.createIntArray();
+        mTransition = in.readInt();
+        mTransitionStyle = in.readInt();
+        mName = in.readString();
+        mIndex = in.readInt();
+    }
+    
+    public BackStackEntry instantiate(FragmentManager fm) {
+        BackStackEntry bse = new BackStackEntry(fm);
+        int pos = 0;
+        while (pos < mOps.length) {
+            BackStackEntry.Op op = new BackStackEntry.Op();
+            op.cmd = mOps[pos++];
+            Fragment f = fm.mActive.get(mOps[pos++]);
+            f.mBackStackNesting++;
+            op.fragment = f;
+            op.enterAnim = mOps[pos++];
+            op.exitAnim = mOps[pos++];
+            final int N = mOps[pos++];
+            if (N > 0) {
+                op.removed = new ArrayList<Fragment>(N);
+                for (int i=0; i<N; i++) {
+                    op.removed.add(fm.mActive.get(mOps[pos++]));
+                }
+            }
+            bse.addOp(op);
+        }
+        bse.mTransition = mTransition;
+        bse.mTransitionStyle = mTransitionStyle;
+        bse.mName = mName;
+        bse.mIndex = mIndex;
+        return bse;
+    }
+    
+    public int describeContents() {
+        return 0;
+    }
+
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeIntArray(mOps);
+        dest.writeInt(mTransition);
+        dest.writeInt(mTransitionStyle);
+        dest.writeString(mName);
+        dest.writeInt(mIndex);
+    }
+    
+    public static final Parcelable.Creator<BackStackState> CREATOR
+            = new Parcelable.Creator<BackStackState>() {
+        public BackStackState createFromParcel(Parcel in) {
+            return new BackStackState(in);
+        }
+        
+        public BackStackState[] newArray(int size) {
+            return new BackStackState[size];
+        }
+    };
+}
+
+/**
+ * @hide Entry of an operation on the fragment back stack.
+ */
+final class BackStackEntry implements FragmentTransaction, Runnable {
+    static final String TAG = "BackStackEntry";
+    
+    final FragmentManager mManager;
+    
+    static final int OP_NULL = 0;
+    static final int OP_ADD = 1;
+    static final int OP_REPLACE = 2;
+    static final int OP_REMOVE = 3;
+    static final int OP_HIDE = 4;
+    static final int OP_SHOW = 5;
+    
+    static final class Op {
+        Op next;
+        Op prev;
+        int cmd;
+        Fragment fragment;
+        int enterAnim;
+        int exitAnim;
+        ArrayList<Fragment> removed;
+    }
+    
+    Op mHead;
+    Op mTail;
+    int mNumOp;
+    int mEnterAnim;
+    int mExitAnim;
+    int mTransition;
+    int mTransitionStyle;
+    boolean mAddToBackStack;
+    String mName;
+    boolean mCommitted;
+    int mIndex;
+    
+    public BackStackEntry(FragmentManager manager) {
+        mManager = manager;
+    }
+    
+    void addOp(Op op) {
+        if (mHead == null) {
+            mHead = mTail = op;
+        } else {
+            op.prev = mTail;
+            mTail.next = op;
+            mTail = op;
+        }
+        op.enterAnim = mEnterAnim;
+        op.exitAnim = mExitAnim;
+        mNumOp++;
+    }
+        
+    public FragmentTransaction add(Fragment fragment, String tag) {
+        doAddOp(0, fragment, tag, OP_ADD);
+        return this;
+    }
+
+    public FragmentTransaction add(int containerViewId, Fragment fragment) {
+        doAddOp(containerViewId, fragment, null, OP_ADD);
+        return this;
+    }
+
+    public FragmentTransaction add(int containerViewId, Fragment fragment, String tag) {
+        doAddOp(containerViewId, fragment, tag, OP_ADD);
+        return this;
+    }
+
+    private void doAddOp(int containerViewId, Fragment fragment, String tag, int opcmd) {
+        if (fragment.mImmediateActivity != null) {
+            throw new IllegalStateException("Fragment already added: " + fragment);
+        }
+        fragment.mImmediateActivity = mManager.mActivity;
+        
+        if (tag != null) {
+            if (fragment.mTag != null && !tag.equals(fragment.mTag)) {
+                throw new IllegalStateException("Can't change tag of fragment "
+                        + fragment + ": was " + fragment.mTag
+                        + " now " + tag);
+            }
+            fragment.mTag = tag;
+        }
+        
+        if (containerViewId != 0) {
+            if (fragment.mFragmentId != 0 && fragment.mFragmentId != containerViewId) {
+                throw new IllegalStateException("Can't change container ID of fragment "
+                        + fragment + ": was " + fragment.mFragmentId
+                        + " now " + containerViewId);
+            }
+            fragment.mContainerId = fragment.mFragmentId = containerViewId;
+        }
+        
+        Op op = new Op();
+        op.cmd = opcmd;
+        op.fragment = fragment;
+        addOp(op);
+    }
+
+    public FragmentTransaction replace(int containerViewId, Fragment fragment) {
+        return replace(containerViewId, fragment, null);
+    }
+    
+    public FragmentTransaction replace(int containerViewId, Fragment fragment, String tag) {
+        if (containerViewId == 0) {
+            throw new IllegalArgumentException("Must use non-zero containerViewId");
+        }
+        
+        doAddOp(containerViewId, fragment, tag, OP_REPLACE);
+        return this;
+    }
+    
+    public FragmentTransaction remove(Fragment fragment) {
+        if (fragment.mImmediateActivity == null) {
+            throw new IllegalStateException("Fragment not added: " + fragment);
+        }
+        fragment.mImmediateActivity = null;
+        
+        Op op = new Op();
+        op.cmd = OP_REMOVE;
+        op.fragment = fragment;
+        addOp(op);
+        
+        return this;
+    }
+
+    public FragmentTransaction hide(Fragment fragment) {
+        if (fragment.mImmediateActivity == null) {
+            throw new IllegalStateException("Fragment not added: " + fragment);
+        }
+        
+        Op op = new Op();
+        op.cmd = OP_HIDE;
+        op.fragment = fragment;
+        addOp(op);
+        
+        return this;
+    }
+    
+    public FragmentTransaction show(Fragment fragment) {
+        if (fragment.mImmediateActivity == null) {
+            throw new IllegalStateException("Fragment not added: " + fragment);
+        }
+        
+        Op op = new Op();
+        op.cmd = OP_SHOW;
+        op.fragment = fragment;
+        addOp(op);
+        
+        return this;
+    }
+    
+    public FragmentTransaction setCustomAnimations(int enter, int exit) {
+        mEnterAnim = enter;
+        mExitAnim = exit;
+        return this;
+    }
+    
+    public FragmentTransaction setTransition(int transition) {
+        mTransition = transition;
+        return this;
+    }
+    
+    public FragmentTransaction setTransitionStyle(int styleRes) {
+        mTransitionStyle = styleRes;
+        return this;
+    }
+    
+    public FragmentTransaction addToBackStack(String name) {
+        mAddToBackStack = true;
+        mName = name;
+        return this;
+    }
+
+    public int commit() {
+        if (mCommitted) throw new IllegalStateException("commit already called");
+        if (FragmentManager.DEBUG) Log.v(TAG, "Commit: " + this);
+        mCommitted = true;
+        if (mAddToBackStack) {
+            mIndex = mManager.allocBackStackIndex(this);
+        } else {
+            mIndex = -1;
+        }
+        mManager.enqueueAction(this);
+        return mIndex;
+    }
+    
+    public void run() {
+        if (FragmentManager.DEBUG) Log.v(TAG, "Run: " + this);
+        
+        if (mAddToBackStack) {
+            if (mIndex < 0) {
+                throw new IllegalStateException("addToBackStack() called after commit()");
+            }
+        }
+
+        Op op = mHead;
+        while (op != null) {
+            switch (op.cmd) {
+                case OP_ADD: {
+                    Fragment f = op.fragment;
+                    if (mAddToBackStack) {
+                        f.mBackStackNesting++;
+                    }
+                    f.mNextAnim = op.enterAnim;
+                    mManager.addFragment(f, false);
+                } break;
+                case OP_REPLACE: {
+                    Fragment f = op.fragment;
+                    if (mManager.mAdded != null) {
+                        for (int i=0; i<mManager.mAdded.size(); i++) {
+                            Fragment old = mManager.mAdded.get(i);
+                            if (FragmentManager.DEBUG) Log.v(TAG,
+                                    "OP_REPLACE: adding=" + f + " old=" + old);
+                            if (old.mContainerId == f.mContainerId) {
+                                if (op.removed == null) {
+                                    op.removed = new ArrayList<Fragment>();
+                                }
+                                op.removed.add(old);
+                                if (mAddToBackStack) {
+                                    old.mBackStackNesting++;
+                                }
+                                old.mNextAnim = op.exitAnim;
+                                mManager.removeFragment(old, mTransition, mTransitionStyle);
+                            }
+                        }
+                    }
+                    if (mAddToBackStack) {
+                        f.mBackStackNesting++;
+                    }
+                    f.mNextAnim = op.enterAnim;
+                    mManager.addFragment(f, false);
+                } break;
+                case OP_REMOVE: {
+                    Fragment f = op.fragment;
+                    if (mAddToBackStack) {
+                        f.mBackStackNesting++;
+                    }
+                    f.mNextAnim = op.exitAnim;
+                    mManager.removeFragment(f, mTransition, mTransitionStyle);
+                } break;
+                case OP_HIDE: {
+                    Fragment f = op.fragment;
+                    if (mAddToBackStack) {
+                        f.mBackStackNesting++;
+                    }
+                    f.mNextAnim = op.exitAnim;
+                    mManager.hideFragment(f, mTransition, mTransitionStyle);
+                } break;
+                case OP_SHOW: {
+                    Fragment f = op.fragment;
+                    if (mAddToBackStack) {
+                        f.mBackStackNesting++;
+                    }
+                    f.mNextAnim = op.enterAnim;
+                    mManager.showFragment(f, mTransition, mTransitionStyle);
+                } break;
+                default: {
+                    throw new IllegalArgumentException("Unknown cmd: " + op.cmd);
+                }
+            }
+            
+            op = op.next;
+        }
+        
+        mManager.moveToState(mManager.mCurState, mTransition,
+                mTransitionStyle, true);
+        if (mManager.mNeedMenuInvalidate && mManager.mActivity != null) {
+            mManager.mActivity.invalidateOptionsMenu();
+            mManager.mNeedMenuInvalidate = false;
+        }
+        
+        if (mAddToBackStack) {
+            mManager.addBackStackState(this);
+        }
+    }
+    
+    public void popFromBackStack() {
+        if (FragmentManager.DEBUG) Log.v(TAG, "popFromBackStack: " + this);
+        
+        Op op = mTail;
+        while (op != null) {
+            switch (op.cmd) {
+                case OP_ADD: {
+                    Fragment f = op.fragment;
+                    if (mAddToBackStack) {
+                        f.mBackStackNesting--;
+                    }
+                    f.mImmediateActivity = null;
+                    mManager.removeFragment(f,
+                            FragmentManager.reverseTransit(mTransition),
+                            mTransitionStyle);
+                } break;
+                case OP_REPLACE: {
+                    Fragment f = op.fragment;
+                    if (mAddToBackStack) {
+                        f.mBackStackNesting--;
+                    }
+                    f.mImmediateActivity = null;
+                    mManager.removeFragment(f,
+                            FragmentManager.reverseTransit(mTransition),
+                            mTransitionStyle);
+                    if (op.removed != null) {
+                        for (int i=0; i<op.removed.size(); i++) {
+                            Fragment old = op.removed.get(i);
+                            if (mAddToBackStack) {
+                                old.mBackStackNesting--;
+                            }
+                            f.mImmediateActivity = mManager.mActivity;
+                            mManager.addFragment(old, false);
+                        }
+                    }
+                } break;
+                case OP_REMOVE: {
+                    Fragment f = op.fragment;
+                    if (mAddToBackStack) {
+                        f.mBackStackNesting--;
+                    }
+                    f.mImmediateActivity = mManager.mActivity;
+                    mManager.addFragment(f, false);
+                } break;
+                case OP_HIDE: {
+                    Fragment f = op.fragment;
+                    if (mAddToBackStack) {
+                        f.mBackStackNesting--;
+                    }
+                    mManager.showFragment(f,
+                            FragmentManager.reverseTransit(mTransition), mTransitionStyle);
+                } break;
+                case OP_SHOW: {
+                    Fragment f = op.fragment;
+                    if (mAddToBackStack) {
+                        f.mBackStackNesting--;
+                    }
+                    mManager.hideFragment(f,
+                            FragmentManager.reverseTransit(mTransition), mTransitionStyle);
+                } break;
+                default: {
+                    throw new IllegalArgumentException("Unknown cmd: " + op.cmd);
+                }
+            }
+            
+            op = op.prev;
+        }
+        
+        mManager.moveToState(mManager.mCurState,
+                FragmentManager.reverseTransit(mTransition), mTransitionStyle, true);
+        if (mManager.mNeedMenuInvalidate && mManager.mActivity != null) {
+            mManager.mActivity.invalidateOptionsMenu();
+            mManager.mNeedMenuInvalidate = false;
+        }
+
+        if (mIndex >= 0) {
+            mManager.freeBackStackIndex(mIndex);
+            mIndex = -1;
+        }
+    }
+    
+    public String getName() {
+        return mName;
+    }
+    
+    public int getTransition() {
+        return mTransition;
+    }
+    
+    public int getTransitionStyle() {
+        return mTransitionStyle;
+    }
+}
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 3bfc7e7..2786372 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -56,11 +56,14 @@
 import android.content.res.AssetManager;
 import android.content.res.Resources;
 import android.content.res.XmlResourceParser;
+import android.database.DatabaseErrorHandler;
 import android.database.sqlite.SQLiteDatabase;
 import android.database.sqlite.SQLiteDatabase.CursorFactory;
 import android.graphics.Bitmap;
 import android.graphics.drawable.Drawable;
 import android.hardware.SensorManager;
+import android.location.CountryDetector;
+import android.location.ICountryDetector;
 import android.location.ILocationManager;
 import android.location.LocationManager;
 import android.media.AudioManager;
@@ -89,7 +92,7 @@
 import android.os.FileUtils.FileStatus;
 import android.os.storage.StorageManager;
 import android.telephony.TelephonyManager;
-import android.text.ClipboardManager;
+import android.content.ClipboardManager;
 import android.util.AndroidRuntimeException;
 import android.util.Log;
 import android.view.ContextThemeWrapper;
@@ -167,6 +170,7 @@
     private static ThrottleManager sThrottleManager;
     private static WifiManager sWifiManager;
     private static LocationManager sLocationManager;
+    private static CountryDetector sCountryDetector;
     private static final HashMap<File, SharedPreferencesImpl> sSharedPrefs =
             new HashMap<File, SharedPreferencesImpl>();
 
@@ -542,6 +546,15 @@
     }
 
     @Override
+    public SQLiteDatabase openOrCreateDatabase(String name, int mode, CursorFactory factory,
+            DatabaseErrorHandler errorHandler) {
+        File f = validateFilePath(name, true);
+        SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase(f.getPath(), factory, errorHandler);
+        setFilePermissionsFromMode(f.getPath(), mode, 0);
+        return db;
+    }
+
+    @Override
     public boolean deleteDatabase(String name) {
         try {
             File f = validateFilePath(name, false);
@@ -619,7 +632,8 @@
                     + " Is this really what you want?");
         }
         mMainThread.getInstrumentation().execStartActivity(
-            getOuterContext(), mMainThread.getApplicationThread(), null, null, intent, -1);
+            getOuterContext(), mMainThread.getApplicationThread(), null,
+            (Activity)null, intent, -1);
     }
 
     @Override
@@ -943,6 +957,8 @@
             return AccessibilityManager.getInstance(this);
         } else if (LOCATION_SERVICE.equals(name)) {
             return getLocationManager();
+        } else if (COUNTRY_DETECTOR.equals(name)) {
+            return getCountryDetector();
         } else if (SEARCH_SERVICE.equals(name)) {
             return getSearchManager();
         } else if (SENSOR_SERVICE.equals(name)) {
@@ -1109,6 +1125,17 @@
         return sLocationManager;
     }
 
+    private CountryDetector getCountryDetector() {
+        synchronized (sSync) {
+            if (sCountryDetector == null) {
+                IBinder b = ServiceManager.getService(COUNTRY_DETECTOR);
+                ICountryDetector service = ICountryDetector.Stub.asInterface(b);
+                sCountryDetector = new CountryDetector(service);
+            }
+        }
+        return sCountryDetector;
+    }
+
     private SearchManager getSearchManager() {
         synchronized (mSync) {
             if (mSearchManager == null) {
@@ -2757,6 +2784,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) {
@@ -2799,6 +2833,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/Dialog.java b/core/java/android/app/Dialog.java
index da8c9e5..a9420b4e 100644
--- a/core/java/android/app/Dialog.java
+++ b/core/java/android/app/Dialog.java
@@ -18,16 +18,18 @@
 
 import com.android.internal.policy.PolicyManager;
 
-import android.content.Context;
-import android.content.DialogInterface;
 import android.content.ComponentName;
+import android.content.Context;
 import android.content.ContextWrapper;
+import android.content.DialogInterface;
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Message;
 import android.view.ContextMenu;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.view.ActionMode;
 import android.view.ContextThemeWrapper;
 import android.view.Gravity;
 import android.view.KeyEvent;
@@ -36,13 +38,12 @@
 import android.view.MenuItem;
 import android.view.MotionEvent;
 import android.view.View;
+import android.view.View.OnCreateContextMenuListener;
 import android.view.ViewConfiguration;
 import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
 import android.view.Window;
 import android.view.WindowManager;
-import android.view.ContextMenu.ContextMenuInfo;
-import android.view.View.OnCreateContextMenuListener;
-import android.view.ViewGroup.LayoutParams;
 import android.view.accessibility.AccessibilityEvent;
 
 import java.lang.ref.WeakReference;
@@ -832,6 +833,11 @@
         }
     }
 
+    public ActionMode onStartActionMode(ActionMode.Callback callback) {
+        // TODO Support context modes in dialogs
+        return null;
+    }
+
     /**
      * @return The activity associated with this dialog, or null if there is no assocaited activity.
      */
diff --git a/core/java/android/app/DialogFragment.java b/core/java/android/app/DialogFragment.java
new file mode 100644
index 0000000..391f672
--- /dev/null
+++ b/core/java/android/app/DialogFragment.java
@@ -0,0 +1,277 @@
+/*
+ * 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.DialogInterface;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.view.WindowManager;
+
+/**
+ * A fragment that displays a dialog window, floating on top of its
+ * activity's window.  This fragment contains a Dialog object, which it
+ * displays as appropriate based on the fragment's state.  Control of
+ * the dialog (deciding when to show, hide, dismiss it) should be done through
+ * the API here, not with direct calls on the dialog.
+ *
+ * <p>Implementations should override this class and implement
+ * {@link #onCreateView(LayoutInflater, ViewGroup, Bundle)} to supply the
+ * content of the dialog.  Alternatively, they can override
+ * {@link #onCreateDialog(Bundle)} to create an entirely custom dialog, such
+ * as an AlertDialog, with its own content.
+ */
+public class DialogFragment extends Fragment
+        implements DialogInterface.OnCancelListener, DialogInterface.OnDismissListener {
+
+    /**
+     * Style for {@link #DialogFragment(int, int)} constructor: a basic,
+     * normal dialog.
+     */
+    public static final int STYLE_NORMAL = 0;
+
+    /**
+     * Style for {@link #DialogFragment(int, int)} constructor: don't include
+     * a title area.
+     */
+    public static final int STYLE_NO_TITLE = 1;
+
+    /**
+     * Style for {@link #DialogFragment(int, int)} constructor: don't draw
+     * any frame at all; the view hierarchy returned by {@link #onCreateView}
+     * is entirely responsible for drawing the dialog.
+     */
+    public static final int STYLE_NO_FRAME = 2;
+
+    /**
+     * Style for {@link #DialogFragment(int, int)} constructor: like
+     * {@link #STYLE_NO_FRAME}, but also disables all input to the dialog.
+     * The user can not touch it, and its window will not receive input focus.
+     */
+    public static final int STYLE_NO_INPUT = 3;
+
+    private static final String SAVED_DIALOG_STATE_TAG = "android:savedDialogState";
+    private static final String SAVED_STYLE = "android:style";
+    private static final String SAVED_THEME = "android:theme";
+    private static final String SAVED_CANCELABLE = "android:cancelable";
+    private static final String SAVED_BACK_STACK_ID = "android:backStackId";
+
+    int mStyle = STYLE_NORMAL;
+    int mTheme = 0;
+    boolean mCancelable = true;
+    int mBackStackId = -1;
+
+    Dialog mDialog;
+    boolean mDestroyed;
+
+    public DialogFragment() {
+    }
+
+    /**
+     * Constructor to customize the basic appearance and behavior of the
+     * fragment's dialog.  This can be used for some common dialog behaviors,
+     * taking care of selecting flags, theme, and other options for you.  The
+     * same effect can be achieve by manually setting Dialog and Window
+     * attributes yourself.
+     *
+     * @param style Selects a standard style: may be {@link #STYLE_NORMAL},
+     * {@link #STYLE_NO_TITLE}, {@link #STYLE_NO_FRAME}, or
+     * {@link #STYLE_NO_INPUT}.
+     * @param theme Optional custom theme.  If 0, an appropriate theme (based
+     * on the style) will be selected for you.
+     */
+    public DialogFragment(int style, int theme) {
+        mStyle = style;
+        if (mStyle == STYLE_NO_FRAME || mStyle == STYLE_NO_INPUT) {
+            mTheme = android.R.style.Theme_Dialog_NoFrame;
+        }
+        if (theme != 0) {
+            mTheme = theme;
+        }
+    }
+
+    /**
+     * Display the dialog, adding the fragment to the given activity.  This
+     * is a convenience for explicitly creating a transaction, adding the
+     * fragment to it with the given tag, and committing it.  This does
+     * <em>not</em> add the transaction to the back stack.  When the fragment
+     * is dismissed, a new transaction will be executed to remove it from
+     * the activity.
+     * @param activity The activity this fragment will be added to.
+     * @param tag The tag for this fragment, as per
+     * {@link FragmentTransaction#add(Fragment, String) FragmentTransaction.add}.
+     */
+    public void show(Activity activity, String tag) {
+        FragmentTransaction ft = activity.openFragmentTransaction();
+        ft.add(this, tag);
+        ft.commit();
+    }
+
+    /**
+     * Display the dialog, adding the fragment to the given activity using
+     * an existing transaction and then committing the transaction.
+     * @param activity The activity this fragment will be added to.
+     * @param transaction An existing transaction in which to add the fragment.
+     * @param tag The tag for this fragment, as per
+     * {@link FragmentTransaction#add(Fragment, String) FragmentTransaction.add}.
+     * @return Returns the identifier of the committed transaction, as per
+     * {@link FragmentTransaction#commit() FragmentTransaction.commit()}.
+     */
+    public int show(Activity activity, FragmentTransaction transaction, String tag) {
+        transaction.add(this, tag);
+        mBackStackId = transaction.commit();
+        return mBackStackId;
+    }
+
+    /**
+     * Dismiss the fragment and its dialog.  If the fragment was added to the
+     * back stack, all back stack state up to and including this entry will
+     * be popped.  Otherwise, a new transaction will be committed to remove
+     * the fragment.
+     */
+    public void dismiss() {
+        if (mDialog != null) {
+            mDialog.dismiss();
+        }
+        if (mBackStackId >= 0) {
+            getActivity().popBackStack(mBackStackId, Activity.POP_BACK_STACK_INCLUSIVE);
+            mBackStackId = -1;
+        } else {
+            FragmentTransaction ft = getActivity().openFragmentTransaction();
+            ft.remove(this);
+            ft.commit();
+        }
+    }
+
+    public Dialog getDialog() {
+        return mDialog;
+    }
+
+    public int getTheme() {
+        return mTheme;
+    }
+
+    public void setCancelable(boolean cancelable) {
+        mCancelable = cancelable;
+        if (mDialog != null) mDialog.setCancelable(cancelable);
+    }
+
+    public boolean getCancelable() {
+        return mCancelable;
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        if (savedInstanceState != null) {
+            mStyle = savedInstanceState.getInt(SAVED_STYLE, mStyle);
+            mTheme = savedInstanceState.getInt(SAVED_THEME, mTheme);
+            mCancelable = savedInstanceState.getBoolean(SAVED_CANCELABLE, mCancelable);
+            mBackStackId = savedInstanceState.getInt(SAVED_BACK_STACK_ID, mBackStackId);
+        }
+    }
+
+    public Dialog onCreateDialog(Bundle savedInstanceState) {
+        return new Dialog(getActivity(), getTheme());
+    }
+
+    public void onCancel(DialogInterface dialog) {
+        if (mBackStackId >= 0) {
+            // If this fragment is part of the back stack, then cancelling
+            // the dialog means popping off the back stack.
+            getActivity().popBackStack(mBackStackId, Activity.POP_BACK_STACK_INCLUSIVE);
+            mBackStackId = -1;
+        }
+    }
+
+    public void onDismiss(DialogInterface dialog) {
+    }
+
+    @Override
+    public void onActivityCreated(Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+        mDialog = onCreateDialog(savedInstanceState);
+        mDestroyed = false;
+        switch (mStyle) {
+            case STYLE_NO_INPUT:
+                mDialog.getWindow().addFlags(
+                        WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
+                        WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE);
+                // fall through...
+            case STYLE_NO_FRAME:
+            case STYLE_NO_TITLE:
+                mDialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
+        }
+        View view = getView();
+        if (view != null) {
+            if (view.getParent() != null) {
+                throw new IllegalStateException("DialogFragment can not be attached to a container view");
+            }
+            mDialog.setContentView(view);
+        }
+        mDialog.setOwnerActivity(getActivity());
+        mDialog.setCancelable(mCancelable);
+        mDialog.setOnCancelListener(this);
+        mDialog.setOnDismissListener(this);
+        if (savedInstanceState != null) {
+            Bundle dialogState = savedInstanceState.getBundle(SAVED_DIALOG_STATE_TAG);
+            if (dialogState != null) {
+                mDialog.onRestoreInstanceState(dialogState);
+            }
+        }
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+        mDialog.show();
+    }
+
+    @Override
+    public void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        if (mDialog != null) {
+            Bundle dialogState = mDialog.onSaveInstanceState();
+            if (dialogState != null) {
+                outState.putBundle(SAVED_DIALOG_STATE_TAG, dialogState);
+            }
+        }
+        outState.putInt(SAVED_STYLE, mStyle);
+        outState.putInt(SAVED_THEME, mTheme);
+        outState.putBoolean(SAVED_CANCELABLE, mCancelable);
+        outState.putInt(SAVED_BACK_STACK_ID, mBackStackId);
+    }
+
+    @Override
+    public void onStop() {
+        super.onStop();
+        mDialog.hide();
+    }
+
+    /**
+     * Detach from list view.
+     */
+    @Override
+    public void onDestroyView() {
+        super.onDestroyView();
+        mDestroyed = true;
+        mDialog.dismiss();
+        mDialog = null;
+    }
+}
diff --git a/core/java/android/app/Fragment.java b/core/java/android/app/Fragment.java
new file mode 100644
index 0000000..26cd9de
--- /dev/null
+++ b/core/java/android/app/Fragment.java
@@ -0,0 +1,789 @@
+/*
+ * 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.Context;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.AttributeSet;
+import android.util.SparseArray;
+import android.view.ContextMenu;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.view.View.OnCreateContextMenuListener;
+import android.view.animation.Animation;
+import android.widget.AdapterView;
+
+import java.lang.reflect.InvocationTargetException;
+import java.util.HashMap;
+
+final class FragmentState implements Parcelable {
+    static final String VIEW_STATE_TAG = "android:view_state";
+    
+    final String mClassName;
+    final int mIndex;
+    final boolean mFromLayout;
+    final int mFragmentId;
+    final int mContainerId;
+    final String mTag;
+    final boolean mRetainInstance;
+    
+    Bundle mSavedFragmentState;
+    
+    Fragment mInstance;
+    
+    public FragmentState(Fragment frag) {
+        mClassName = frag.getClass().getName();
+        mIndex = frag.mIndex;
+        mFromLayout = frag.mFromLayout;
+        mFragmentId = frag.mFragmentId;
+        mContainerId = frag.mContainerId;
+        mTag = frag.mTag;
+        mRetainInstance = frag.mRetainInstance;
+    }
+    
+    public FragmentState(Parcel in) {
+        mClassName = in.readString();
+        mIndex = in.readInt();
+        mFromLayout = in.readInt() != 0;
+        mFragmentId = in.readInt();
+        mContainerId = in.readInt();
+        mTag = in.readString();
+        mRetainInstance = in.readInt() != 0;
+        mSavedFragmentState = in.readBundle();
+    }
+    
+    public Fragment instantiate(Activity activity) {
+        if (mInstance != null) {
+            return mInstance;
+        }
+        
+        try {
+            mInstance = Fragment.instantiate(activity, mClassName);
+        } catch (Exception e) {
+            throw new RuntimeException("Unable to restore fragment " + mClassName, e);
+        }
+        
+        if (mSavedFragmentState != null) {
+            mSavedFragmentState.setClassLoader(activity.getClassLoader());
+            mInstance.mSavedFragmentState = mSavedFragmentState;
+            mInstance.mSavedViewState
+                    = mSavedFragmentState.getSparseParcelableArray(VIEW_STATE_TAG);
+        }
+        mInstance.setIndex(mIndex);
+        mInstance.mFromLayout = mFromLayout;
+        mInstance.mFragmentId = mFragmentId;
+        mInstance.mContainerId = mContainerId;
+        mInstance.mTag = mTag;
+        mInstance.mRetainInstance = mRetainInstance;
+        
+        return mInstance;
+    }
+    
+    public int describeContents() {
+        return 0;
+    }
+
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(mClassName);
+        dest.writeInt(mIndex);
+        dest.writeInt(mFromLayout ? 1 : 0);
+        dest.writeInt(mFragmentId);
+        dest.writeInt(mContainerId);
+        dest.writeString(mTag);
+        dest.writeInt(mRetainInstance ? 1 : 0);
+        dest.writeBundle(mSavedFragmentState);
+    }
+    
+    public static final Parcelable.Creator<FragmentState> CREATOR
+            = new Parcelable.Creator<FragmentState>() {
+        public FragmentState createFromParcel(Parcel in) {
+            return new FragmentState(in);
+        }
+        
+        public FragmentState[] newArray(int size) {
+            return new FragmentState[size];
+        }
+    };
+}
+
+/**
+ * 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, OnCreateContextMenuListener {
+    private static final HashMap<String, Class<?>> sClassMap =
+            new HashMap<String, Class<?>>();
+    
+    static final int INITIALIZING = 0;     // Not yet created.
+    static final int CREATED = 1;          // Created.
+    static final int ACTIVITY_CREATED = 2; // The activity has finished its creation.
+    static final int STARTED = 3;          // Created and started, not resumed.
+    static final int RESUMED = 4;          // Created started and resumed.
+    
+    int mState = INITIALIZING;
+    
+    // When instantiated from saved state, this is the saved state.
+    Bundle mSavedFragmentState;
+    SparseArray<Parcelable> mSavedViewState;
+    
+    // Index into active fragment array.
+    int mIndex = -1;
+    
+    // Internal unique name for this fragment;
+    String mWho;
+    
+    // True if the fragment is in the list of added fragments.
+    boolean mAdded;
+    
+    // True if the fragment is in the resumed state.
+    boolean mResumed;
+    
+    // Set to true if this fragment was instantiated from a layout file.
+    boolean mFromLayout;
+    
+    // Number of active back stack entries this fragment is in.
+    int mBackStackNesting;
+    
+    // Set as soon as a fragment is added to a transaction (or removed),
+    // to be able to do validation.
+    Activity mImmediateActivity;
+    
+    // Activity this fragment is attached to.
+    Activity mActivity;
+    
+    // The optional identifier for this fragment -- either the container ID if it
+    // was dynamically added to the view hierarchy, or the ID supplied in
+    // layout.
+    int mFragmentId;
+    
+    // When a fragment is being dynamically added to the view hierarchy, this
+    // is the identifier of the parent container it is being added to.
+    int mContainerId;
+    
+    // The optional named tag for this fragment -- usually used to find
+    // fragments that are not part of the layout.
+    String mTag;
+    
+    // Set to true when the app has requested that this fragment be hidden
+    // from the user.
+    boolean mHidden;
+    
+    // If set this fragment would like its instance retained across
+    // configuration changes.
+    boolean mRetainInstance;
+    
+    // If set this fragment is being retained across the current config change.
+    boolean mRetaining;
+    
+    // If set this fragment has menu items to contribute.
+    boolean mHasMenu;
+    
+    // Used to verify that subclasses call through to super class.
+    boolean mCalled;
+    
+    // If app has requested a specific animation, this is the one to use.
+    int mNextAnim;
+    
+    // The parent container of the fragment after dynamically added to UI.
+    ViewGroup mContainer;
+    
+    // The View generated for this fragment.
+    View mView;
+    
+    LoaderManagerImpl mLoaderManager;
+    boolean mStarted;
+    
+    /**
+     * Default constructor.  <strong>Every</string> fragment must have an
+     * empty constructor, so it can be instantiated when restoring its
+     * activity's state.  It is strongly recommended that subclasses do not
+     * have other constructors with parameters, since these constructors
+     * will not be called when the fragment is re-instantiated; instead,
+     * retrieve such parameters from the activity in {@link #onAttach(Activity)}.
+     */
+    public Fragment() {
+    }
+
+    /**
+     * Create a new instance of a Fragment with the given class name.  This is
+     * the same as calling its empty constructor.
+     *
+     * @param context The calling context being used to instantiate the fragment.
+     * This is currently just used to get its ClassLoader.
+     * @param fname The class name of the fragment to instantiate.
+     * @return Returns a new fragment instance.
+     * @throws NoSuchMethodException The fragment does not have an empty constructor.
+     * @throws ClassNotFoundException The fragment class does not exist.
+     * @throws IllegalArgumentException Bad arguments supplied to fragment class
+     * constructor (should not happen).
+     * @throws InstantiationException Caller does not have permission to instantiate
+     * the fragment (for example its constructor is not public).
+     * @throws IllegalAccessException Caller does not have permission to access
+     * the given fragment class.
+     * @throws InvocationTargetException Failure running the fragment's constructor.
+     */
+    public static Fragment instantiate(Context context, String fname)
+            throws NoSuchMethodException, ClassNotFoundException,
+            IllegalArgumentException, InstantiationException,
+            IllegalAccessException, InvocationTargetException {
+        Class<?> clazz = sClassMap.get(fname);
+
+        if (clazz == null) {
+            // Class not found in the cache, see if it's real, and try to add it
+            clazz = context.getClassLoader().loadClass(fname);
+            sClassMap.put(fname, clazz);
+        }
+        return (Fragment)clazz.newInstance();
+    }
+    
+    void restoreViewState() {
+        if (mSavedViewState != null) {
+            mView.restoreHierarchyState(mSavedViewState);
+            mSavedViewState = null;
+        }
+    }
+    
+    void setIndex(int index) {
+        mIndex = index;
+        mWho = "android:fragment:" + mIndex;
+   }
+    
+    void clearIndex() {
+        mIndex = -1;
+        mWho = null;
+    }
+    
+    /**
+     * Subclasses can not override equals().
+     */
+    @Override final public boolean equals(Object o) {
+        return super.equals(o);
+    }
+
+    /**
+     * Subclasses can not override hashCode().
+     */
+    @Override final public int hashCode() {
+        return super.hashCode();
+    }
+    
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder(128);
+        sb.append("Fragment{");
+        sb.append(Integer.toHexString(System.identityHashCode(this)));
+        if (mIndex >= 0) {
+            sb.append(" #");
+            sb.append(mIndex);
+        }
+        if (mFragmentId != 0) {
+            sb.append(" id=0x");
+            sb.append(Integer.toHexString(mFragmentId));
+        }
+        if (mTag != null) {
+            sb.append(" ");
+            sb.append(mTag);
+        }
+        sb.append('}');
+        return sb.toString();
+    }
+    
+    /**
+     * Return the identifier this fragment is known by.  This is either
+     * the android:id value supplied in a layout or the container view ID
+     * supplied when adding the fragment.
+     */
+    final public int getId() {
+        return mFragmentId;
+    }
+    
+    /**
+     * Get the tag name of the fragment, if specified.
+     */
+    final public String getTag() {
+        return mTag;
+    }
+    
+    /**
+     * Return the Activity this fragment is currently associated with.
+     */
+    final public Activity getActivity() {
+        return mActivity;
+    }
+    
+    /**
+     * Return true if the fragment is currently added to its activity.
+     */
+    final public boolean isAdded() {
+        return mActivity != null && mActivity.mFragments.mAdded.contains(this);
+    }
+    
+    /**
+     * Return true if the fragment is in the resumed state.  This is true
+     * for the duration of {@link #onResume()} and {@link #onPause()} as well.
+     */
+    final public boolean isResumed() {
+        return mResumed;
+    }
+    
+    /**
+     * Return true if the fragment is currently visible to the user.  This means
+     * it: (1) has been added, (2) has its view attached to the window, and 
+     * (3) is not hidden.
+     */
+    final public boolean isVisible() {
+        return isAdded() && !isHidden() && mView != null
+                && mView.getWindowToken() != null && mView.getVisibility() == View.VISIBLE;
+    }
+    
+    /**
+     * Return true if the fragment has been hidden.  By default fragments
+     * are shown.  You can find out about changes to this state with
+     * {@link #onHiddenChanged}.  Note that the hidden state is orthogonal
+     * to other states -- that is, to be visible to the user, a fragment
+     * must be both started and not hidden.
+     */
+    final public boolean isHidden() {
+        return mHidden;
+    }
+    
+    /**
+     * Called when the hidden state (as returned by {@link #isHidden()} of
+     * the fragment has changed.  Fragments start out not hidden; this will
+     * be called whenever the fragment changes state from that.
+     * @param hidden True if the fragment is now hidden, false if it is not
+     * visible.
+     */
+    public void onHiddenChanged(boolean hidden) {
+    }
+    
+    /**
+     * Control whether a fragment instance is retained across Activity
+     * re-creation (such as from a configuration change).  This can only
+     * be used with fragments not in the back stack.  If set, the fragment
+     * lifecycle will be slightly different when an activity is recreated:
+     * <ul>
+     * <li> {@link #onDestroy()} will not be called (but {@link #onDetach()} still
+     * will be, because the fragment is being detached from its current activity).
+     * <li> {@link #onCreate(Bundle)} will not be called since the fragment
+     * is not being re-created.
+     * <li> {@link #onAttach(Activity)} and {@link #onActivityCreated(Bundle)} <b>will</b>
+     * still be called.
+     * </ul>
+     */
+    public void setRetainInstance(boolean retain) {
+        mRetainInstance = retain;
+    }
+    
+    final public boolean getRetainInstance() {
+        return mRetainInstance;
+    }
+    
+    /**
+     * Report that this fragment would like to participate in populating
+     * the options menu by receiving a call to {@link #onCreateOptionsMenu}
+     * and related methods.
+     * 
+     * @param hasMenu If true, the fragment has menu items to contribute.
+     */
+    public void setHasOptionsMenu(boolean hasMenu) {
+        if (mHasMenu != hasMenu) {
+            mHasMenu = hasMenu;
+            if (isAdded() && !isHidden()) {
+                mActivity.invalidateOptionsMenu();
+            }
+        }
+    }
+    
+    /**
+     * Return the LoaderManager for this fragment, creating it if needed.
+     */
+    public LoaderManager getLoaderManager() {
+        if (mLoaderManager != null) {
+            return mLoaderManager;
+        }
+        mLoaderManager = mActivity.getLoaderManager(mIndex, mStarted);
+        return mLoaderManager;
+    }
+    
+    /**
+     * Call {@link Activity#startActivity(Intent)} on the fragment's
+     * containing Activity.
+     */
+    public void startActivity(Intent intent) {
+        mActivity.startActivityFromFragment(this, intent, -1);
+    }
+    
+    /**
+     * Call {@link Activity#startActivityForResult(Intent, int)} on the fragment's
+     * containing Activity.
+     */
+    public void startActivityForResult(Intent intent, int requestCode) {
+        mActivity.startActivityFromFragment(this, intent, requestCode);
+    }
+    
+    /**
+     * Receive the result from a previous call to
+     * {@link #startActivityForResult(Intent, int)}.  This follows the
+     * related Activity API as described there in
+     * {@link Activity#onActivityResult(int, int, Intent)}.
+     * 
+     * @param requestCode The integer request code originally supplied to
+     *                    startActivityForResult(), allowing you to identify who this
+     *                    result came from.
+     * @param resultCode The integer result code returned by the child activity
+     *                   through its setResult().
+     * @param data An Intent, which can return result data to the caller
+     *               (various data can be attached to Intent "extras").
+     */
+    public void onActivityResult(int requestCode, int resultCode, Intent data) {
+    }
+    
+    /**
+     * Called when a fragment is being created as part of a view layout
+     * inflation, typically from setting the content view of an activity.  This
+     * will be called both the first time the fragment is created, as well
+     * later when it is being re-created from its saved state (which is also
+     * given here).
+     * 
+     * XXX This is kind-of yucky...  maybe we could just supply the
+     * AttributeSet to onCreate()?
+     * 
+     * @param activity The Activity that is inflating the fragment.
+     * @param attrs The attributes at the tag where the fragment is
+     * being created.
+     * @param savedInstanceState If the fragment is being re-created from
+     * a previous saved state, this is the state.
+     */
+    public void onInflate(Activity activity, AttributeSet attrs,
+            Bundle savedInstanceState) {
+        mCalled = true;
+    }
+    
+    /**
+     * Called when a fragment is first attached to its activity.
+     * {@link #onCreate(Bundle)} will be called after this.
+     */
+    public void onAttach(Activity activity) {
+        mCalled = true;
+    }
+    
+    public Animation onCreateAnimation(int transit, boolean enter, int nextAnim) {
+        return null;
+    }
+    
+    /**
+     * Called to do initial creation of a fragment.  This is called after
+     * {@link #onAttach(Activity)} and before
+     * {@link #onCreateView(LayoutInflater, ViewGroup, Bundle)}.
+     * 
+     * <p>Note that this can be called while the fragment's activity is
+     * still in the process of being created.  As such, you can not rely
+     * on things like the activity's content view hierarchy being initialized
+     * at this point.  If you want to do work once the activity itself is
+     * created, see {@link #onActivityCreated(Bundle)}.
+     * 
+     * @param savedInstanceState If the fragment is being re-created from
+     * a previous saved state, this is the state.
+     */
+    public void onCreate(Bundle savedInstanceState) {
+        mCalled = true;
+    }
+    
+    /**
+     * Called to have the fragment instantiate its user interface view.
+     * This is optional, and non-graphical fragments can return null (which
+     * is the default implementation).  This will be called between
+     * {@link #onCreate(Bundle)} and {@link #onActivityCreated(Bundle)}.
+     * 
+     * <p>If you return a View from here, you will later be called in
+     * {@link #onDestroyView} when the view is being released.
+     * 
+     * @param inflater The LayoutInflater object that can be used to inflate
+     * any views in the fragment,
+     * @param container If non-null, this is the parent view that the fragment's
+     * UI should be attached to.  The fragment should not add the view itself,
+     * but this can be used to generate the LayoutParams of the view.
+     * @param savedInstanceState If non-null, this fragment is being re-constructed
+     * from a previous saved state as given here.
+     * 
+     * @return Return the View for the fragment's UI, or null.
+     */
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+        return null;
+    }
+    
+    public View getView() {
+        return mView;
+    }
+    
+    /**
+     * Called when the fragment's activity has been created and this
+     * fragment's view hierarchy instantiated.  It can be used to do final
+     * initialization once these pieces are in place, such as retrieving
+     * views or restoring state.  It is also useful for fragments that use
+     * {@link #setRetainInstance(boolean)} to retain their instance,
+     * as this callback tells the fragment when it is fully associated with
+     * the new activity instance.  This is called after {@link #onCreateView}
+     * and before {@link #onStart()}.
+     * 
+     * @param savedInstanceState If the fragment is being re-created from
+     * a previous saved state, this is the state.
+     */
+    public void onActivityCreated(Bundle savedInstanceState) {
+        mCalled = true;
+    }
+    
+    /**
+     * Called when the Fragment is visible to the user.  This is generally
+     * tied to {@link Activity#onStart() Activity.onStart} of the containing
+     * Activity's lifecycle.
+     */
+    public void onStart() {
+        mCalled = true;
+        mStarted = true;
+        if (mLoaderManager != null) {
+            mLoaderManager.doStart();
+        }
+    }
+    
+    /**
+     * Called when the fragment is visible to the user and actively running.
+     * This is generally
+     * tied to {@link Activity#onResume() Activity.onResume} of the containing
+     * Activity's lifecycle.
+     */
+    public void onResume() {
+        mCalled = true;
+    }
+    
+    public void onSaveInstanceState(Bundle outState) {
+    }
+    
+    public void onConfigurationChanged(Configuration newConfig) {
+        mCalled = true;
+    }
+    
+    /**
+     * Called when the Fragment is no longer resumed.  This is generally
+     * tied to {@link Activity#onPause() Activity.onPause} of the containing
+     * Activity's lifecycle.
+     */
+    public void onPause() {
+        mCalled = true;
+    }
+    
+    /**
+     * Called when the Fragment is no longer started.  This is generally
+     * tied to {@link Activity#onStop() Activity.onStop} of the containing
+     * Activity's lifecycle.
+     */
+    public void onStop() {
+        mCalled = true;
+    }
+    
+    public void onLowMemory() {
+        mCalled = true;
+    }
+    
+    /**
+     * Called when the view previously created by {@link #onCreateView} has
+     * been detached from the fragment.  The next time the fragment needs
+     * to be displayed, a new view will be created.  This is called
+     * after {@link #onStop()} and before {@link #onDestroy()}; it is only
+     * called if {@link #onCreateView} returns a non-null View.
+     */
+    public void onDestroyView() {
+        mCalled = true;
+    }
+    
+    /**
+     * Called when the fragment is no longer in use.  This is called
+     * after {@link #onStop()} and before {@link #onDetach()}.
+     */
+    public void onDestroy() {
+        mCalled = true;
+        if (mLoaderManager != null) {
+            mLoaderManager.doDestroy();
+        }
+    }
+
+    /**
+     * Called when the fragment is no longer attached to its activity.  This
+     * is called after {@link #onDestroy()}.
+     */
+    public void onDetach() {
+        mCalled = true;
+    }
+    
+    /**
+     * Initialize the contents of the Activity's standard options menu.  You
+     * should place your menu items in to <var>menu</var>.  For this method
+     * to be called, you must have first called {@link #setHasOptionsMenu}.  See
+     * {@link Activity#onCreateOptionsMenu(Menu) Activity.onCreateOptionsMenu}
+     * for more information.
+     * 
+     * @param menu The options menu in which you place your items.
+     * 
+     * @see #setHasOptionsMenu
+     * @see #onPrepareOptionsMenu
+     * @see #onOptionsItemSelected
+     */
+    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+    }
+
+    /**
+     * Prepare the Screen's standard options menu to be displayed.  This is
+     * called right before the menu is shown, every time it is shown.  You can
+     * use this method to efficiently enable/disable items or otherwise
+     * dynamically modify the contents.  See
+     * {@link Activity#onPrepareOptionsMenu(Menu) Activity.onPrepareOptionsMenu}
+     * for more information.
+     * 
+     * @param menu The options menu as last shown or first initialized by
+     *             onCreateOptionsMenu().
+     * 
+     * @see #setHasOptionsMenu
+     * @see #onCreateOptionsMenu
+     */
+    public void onPrepareOptionsMenu(Menu menu) {
+    }
+
+    /**
+     * This hook is called whenever an item in your options menu is selected.
+     * The default implementation simply returns false to have the normal
+     * processing happen (calling the item's Runnable or sending a message to
+     * its Handler as appropriate).  You can use this method for any items
+     * for which you would like to do processing without those other
+     * facilities.
+     * 
+     * <p>Derived classes should call through to the base class for it to
+     * perform the default menu handling.
+     * 
+     * @param item The menu item that was selected.
+     * 
+     * @return boolean Return false to allow normal menu processing to
+     *         proceed, true to consume it here.
+     * 
+     * @see #onCreateOptionsMenu
+     */
+    public boolean onOptionsItemSelected(MenuItem item) {
+        return false;
+    }
+
+    /**
+     * This hook is called whenever the options menu is being closed (either by the user canceling
+     * the menu with the back/menu button, or when an item is selected).
+     *  
+     * @param menu The options menu as last shown or first initialized by
+     *             onCreateOptionsMenu().
+     */
+    public void onOptionsMenuClosed(Menu menu) {
+    }
+    
+    /**
+     * Called when a context menu for the {@code view} is about to be shown.
+     * Unlike {@link #onCreateOptionsMenu}, this will be called every
+     * time the context menu is about to be shown and should be populated for
+     * the view (or item inside the view for {@link AdapterView} subclasses,
+     * this can be found in the {@code menuInfo})).
+     * <p>
+     * Use {@link #onContextItemSelected(android.view.MenuItem)} to know when an
+     * item has been selected.
+     * <p>
+     * The default implementation calls up to
+     * {@link Activity#onCreateContextMenu Activity.onCreateContextMenu}, though
+     * you can not call this implementation if you don't want that behavior.
+     * <p>
+     * It is not safe to hold onto the context menu after this method returns.
+     * {@inheritDoc}
+     */
+    public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
+        getActivity().onCreateContextMenu(menu, v, menuInfo);
+    }
+
+    /**
+     * Registers a context menu to be shown for the given view (multiple views
+     * can show the context menu). This method will set the
+     * {@link OnCreateContextMenuListener} on the view to this fragment, so
+     * {@link #onCreateContextMenu(ContextMenu, View, ContextMenuInfo)} will be
+     * called when it is time to show the context menu.
+     * 
+     * @see #unregisterForContextMenu(View)
+     * @param view The view that should show a context menu.
+     */
+    public void registerForContextMenu(View view) {
+        view.setOnCreateContextMenuListener(this);
+    }
+    
+    /**
+     * Prevents a context menu to be shown for the given view. This method will
+     * remove the {@link OnCreateContextMenuListener} on the view.
+     * 
+     * @see #registerForContextMenu(View)
+     * @param view The view that should stop showing a context menu.
+     */
+    public void unregisterForContextMenu(View view) {
+        view.setOnCreateContextMenuListener(null);
+    }
+    
+    /**
+     * This hook is called whenever an item in a context menu is selected. The
+     * default implementation simply returns false to have the normal processing
+     * happen (calling the item's Runnable or sending a message to its Handler
+     * as appropriate). You can use this method for any items for which you
+     * would like to do processing without those other facilities.
+     * <p>
+     * Use {@link MenuItem#getMenuInfo()} to get extra information set by the
+     * View that added this menu item.
+     * <p>
+     * Derived classes should call through to the base class for it to perform
+     * the default menu handling.
+     * 
+     * @param item The context menu item that was selected.
+     * @return boolean Return false to allow normal context menu processing to
+     *         proceed, true to consume it here.
+     */
+    public boolean onContextItemSelected(MenuItem item) {
+        return false;
+    }
+    
+    void performStop() {
+        onStop();
+        if (mStarted) {
+            mStarted = false;
+            if (mLoaderManager != null) {
+                if (mActivity == null || !mActivity.mChangingConfigurations) {
+                    mLoaderManager.doStop();
+                } else {
+                    mLoaderManager.doRetain();
+                }
+            }
+        }
+    }
+}
diff --git a/core/java/android/app/FragmentManager.java b/core/java/android/app/FragmentManager.java
new file mode 100644
index 0000000..2054e2a
--- /dev/null
+++ b/core/java/android/app/FragmentManager.java
@@ -0,0 +1,1099 @@
+/*
+ * 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.res.TypedArray;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.Animation;
+import android.view.animation.AnimationUtils;
+
+import java.util.ArrayList;
+
+final class FragmentManagerState implements Parcelable {
+    FragmentState[] mActive;
+    int[] mAdded;
+    BackStackState[] mBackStack;
+    
+    public FragmentManagerState() {
+    }
+    
+    public FragmentManagerState(Parcel in) {
+        mActive = in.createTypedArray(FragmentState.CREATOR);
+        mAdded = in.createIntArray();
+        mBackStack = in.createTypedArray(BackStackState.CREATOR);
+    }
+    
+    public int describeContents() {
+        return 0;
+    }
+
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeTypedArray(mActive, flags);
+        dest.writeIntArray(mAdded);
+        dest.writeTypedArray(mBackStack, flags);
+    }
+    
+    public static final Parcelable.Creator<FragmentManagerState> CREATOR
+            = new Parcelable.Creator<FragmentManagerState>() {
+        public FragmentManagerState createFromParcel(Parcel in) {
+            return new FragmentManagerState(in);
+        }
+        
+        public FragmentManagerState[] newArray(int size) {
+            return new FragmentManagerState[size];
+        }
+    };
+}
+
+/**
+ * @hide
+ * Container for fragments associated with an activity.
+ */
+public class FragmentManager {
+    static final boolean DEBUG = true;
+    static final String TAG = "FragmentManager";
+    
+    ArrayList<Runnable> mPendingActions;
+    Runnable[] mTmpActions;
+    boolean mExecutingActions;
+    
+    ArrayList<Fragment> mActive;
+    ArrayList<Fragment> mAdded;
+    ArrayList<Integer> mAvailIndices;
+    ArrayList<BackStackEntry> mBackStack;
+    
+    // Must be accessed while locked.
+    ArrayList<BackStackEntry> mBackStackIndices;
+    ArrayList<Integer> mAvailBackStackIndices;
+
+    int mCurState = Fragment.INITIALIZING;
+    Activity mActivity;
+    
+    boolean mNeedMenuInvalidate;
+    
+    // Temporary vars for state save and restore.
+    Bundle mStateBundle = null;
+    SparseArray<Parcelable> mStateArray = null;
+    
+    Runnable mExecCommit = new Runnable() {
+        @Override
+        public void run() {
+            execPendingActions();
+        }
+    };
+    
+    Animation loadAnimation(Fragment fragment, int transit, boolean enter,
+            int transitionStyle) {
+        Animation animObj = fragment.onCreateAnimation(transitionStyle, enter,
+                fragment.mNextAnim);
+        if (animObj != null) {
+            return animObj;
+        }
+        
+        if (fragment.mNextAnim != 0) {
+            Animation anim = AnimationUtils.loadAnimation(mActivity, fragment.mNextAnim);
+            if (anim != null) {
+                return anim;
+            }
+        }
+        
+        if (transit == 0) {
+            return null;
+        }
+        
+        int styleIndex = transitToStyleIndex(transit, enter);
+        if (styleIndex < 0) {
+            return null;
+        }
+        
+        if (transitionStyle == 0 && mActivity.getWindow() != null) {
+            transitionStyle = mActivity.getWindow().getAttributes().windowAnimations;
+        }
+        if (transitionStyle == 0) {
+            return null;
+        }
+        
+        TypedArray attrs = mActivity.obtainStyledAttributes(transitionStyle,
+                com.android.internal.R.styleable.WindowAnimation);
+        int anim = attrs.getResourceId(styleIndex, 0);
+        attrs.recycle();
+        
+        if (anim == 0) {
+            return null;
+        }
+        
+        return AnimationUtils.loadAnimation(mActivity, anim);
+    }
+    
+    void moveToState(Fragment f, int newState, int transit, int transitionStyle) {
+        // Fragments that are not currently added will sit in the onCreate() state.
+        if (!f.mAdded && newState > Fragment.CREATED) {
+            newState = Fragment.CREATED;
+        }
+        
+        if (f.mState < newState) {
+            switch (f.mState) {
+                case Fragment.INITIALIZING:
+                    if (DEBUG) Log.v(TAG, "moveto CREATED: " + f);
+                    f.mActivity = mActivity;
+                    f.mCalled = false;
+                    f.onAttach(mActivity);
+                    if (!f.mCalled) {
+                        throw new SuperNotCalledException("Fragment " + f
+                                + " did not call through to super.onAttach()");
+                    }
+                    mActivity.onAttachFragment(f);
+                    
+                    if (!f.mRetaining) {
+                        f.mCalled = false;
+                        f.onCreate(f.mSavedFragmentState);
+                        if (!f.mCalled) {
+                            throw new SuperNotCalledException("Fragment " + f
+                                    + " did not call through to super.onCreate()");
+                        }
+                    }
+                    f.mRetaining = false;
+                    if (f.mFromLayout) {
+                        // For fragments that are part of the content view
+                        // layout, we need to instantiate the view immediately
+                        // and the inflater will take care of adding it.
+                        f.mView = f.onCreateView(mActivity.getLayoutInflater(),
+                                null, f.mSavedFragmentState);
+                        if (f.mView != null) {
+                            f.mView.setSaveFromParentEnabled(false);
+                            f.restoreViewState();
+                            if (f.mHidden) f.mView.setVisibility(View.GONE); 
+                        }
+                    }
+                case Fragment.CREATED:
+                    if (newState > Fragment.CREATED) {
+                        if (DEBUG) Log.v(TAG, "moveto CONTENT: " + f);
+                        if (!f.mFromLayout) {
+                            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, f.mSavedFragmentState);
+                            if (f.mView != null) {
+                                f.mView.setSaveFromParentEnabled(false);
+                                if (container != null) {
+                                    Animation anim = loadAnimation(f, transit, true,
+                                            transitionStyle);
+                                    if (anim != null) {
+                                        f.mView.setAnimation(anim);
+                                    }
+                                    container.addView(f.mView);
+                                    f.restoreViewState();
+                                }
+                                if (f.mHidden) f.mView.setVisibility(View.GONE); 
+                            }
+                        }
+                        
+                        f.mCalled = false;
+                        f.onActivityCreated(f.mSavedFragmentState);
+                        if (!f.mCalled) {
+                            throw new SuperNotCalledException("Fragment " + f
+                                    + " did not call through to super.onReady()");
+                        }
+                        f.mSavedFragmentState = null;
+                    }
+                case Fragment.ACTIVITY_CREATED:
+                    if (newState > Fragment.ACTIVITY_CREATED) {
+                        if (DEBUG) Log.v(TAG, "moveto STARTED: " + f);
+                        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) {
+                        if (DEBUG) Log.v(TAG, "moveto RESUMED: " + f);
+                        f.mCalled = false;
+                        f.mResumed = true;
+                        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) {
+                        if (DEBUG) Log.v(TAG, "movefrom RESUMED: " + f);
+                        f.mCalled = false;
+                        f.onPause();
+                        if (!f.mCalled) {
+                            throw new SuperNotCalledException("Fragment " + f
+                                    + " did not call through to super.onPause()");
+                        }
+                        f.mResumed = false;
+                    }
+                case Fragment.STARTED:
+                    if (newState < Fragment.STARTED) {
+                        if (DEBUG) Log.v(TAG, "movefrom STARTED: " + f);
+                        f.mCalled = false;
+                        f.performStop();
+                        if (!f.mCalled) {
+                            throw new SuperNotCalledException("Fragment " + f
+                                    + " did not call through to super.onStop()");
+                        }
+                    }
+                case Fragment.ACTIVITY_CREATED:
+                    if (newState < Fragment.ACTIVITY_CREATED) {
+                        if (DEBUG) Log.v(TAG, "movefrom CONTENT: " + f);
+                        if (f.mView != null) {
+                            f.mCalled = false;
+                            f.onDestroyView();
+                            if (!f.mCalled) {
+                                throw new SuperNotCalledException("Fragment " + f
+                                        + " did not call through to super.onDestroyedView()");
+                            }
+                            // Need to save the current view state if not
+                            // done already.
+                            if (!mActivity.isFinishing() && f.mSavedFragmentState == null) {
+                                saveFragmentViewState(f);
+                            }
+                            if (f.mContainer != null) {
+                                if (mCurState > Fragment.INITIALIZING) {
+                                    Animation anim = loadAnimation(f, transit, false,
+                                            transitionStyle);
+                                    if (anim != null) {
+                                        f.mView.setAnimation(anim);
+                                    }
+                                }
+                                f.mContainer.removeView(f.mView);
+                            }
+                        }
+                        f.mContainer = null;
+                        f.mView = null;
+                    }
+                case Fragment.CREATED:
+                    if (newState < Fragment.CREATED) {
+                        if (DEBUG) Log.v(TAG, "movefrom CREATED: " + f);
+                        if (!f.mRetaining) {
+                            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, boolean always) {
+        moveToState(newState, 0, 0, always);
+    }
+    
+    void moveToState(int newState, int transit, int transitStyle, boolean always) {
+        if (mActivity == null && newState != Fragment.INITIALIZING) {
+            throw new IllegalStateException("No activity");
+        }
+        
+        if (!always && mCurState == newState) {
+            return;
+        }
+        
+        mCurState = newState;
+        if (mActive != null) {
+            for (int i=0; i<mActive.size(); i++) {
+                Fragment f = mActive.get(i);
+                if (f != null) {
+                    moveToState(f, newState, transit, transitStyle);
+                }
+            }
+        }
+    }
+    
+    void makeActive(Fragment f) {
+        if (f.mIndex >= 0) {
+            return;
+        }
+        
+        if (mAvailIndices == null || mAvailIndices.size() <= 0) {
+            if (mActive == null) {
+                mActive = new ArrayList<Fragment>();
+            }
+            f.setIndex(mActive.size());
+            mActive.add(f);
+            
+        } else {
+            f.setIndex(mAvailIndices.remove(mAvailIndices.size()-1));
+            mActive.set(f.mIndex, f);
+        }
+    }
+    
+    void makeInactive(Fragment f) {
+        if (f.mIndex < 0) {
+            return;
+        }
+        
+        mActive.set(f.mIndex, null);
+        if (mAvailIndices == null) {
+            mAvailIndices = new ArrayList<Integer>();
+        }
+        mAvailIndices.add(f.mIndex);
+        mActivity.invalidateFragmentIndex(f.mIndex);
+        f.clearIndex();
+    }
+    
+    public void addFragment(Fragment fragment, boolean moveToStateNow) {
+        if (DEBUG) Log.v(TAG, "add: " + fragment);
+        if (mAdded == null) {
+            mAdded = new ArrayList<Fragment>();
+        }
+        mAdded.add(fragment);
+        makeActive(fragment);
+        fragment.mAdded = true;
+        if (fragment.mHasMenu) {
+            mNeedMenuInvalidate = true;
+        }
+        if (moveToStateNow) {
+            moveToState(fragment, mCurState, 0, 0);
+        }
+    }
+    
+    public void removeFragment(Fragment fragment, int transition, int transitionStyle) {
+        if (DEBUG) Log.v(TAG, "remove: " + fragment);
+        mAdded.remove(fragment);
+        final boolean inactive = fragment.mBackStackNesting <= 0;
+        if (inactive) {
+            makeInactive(fragment);
+        }
+        if (fragment.mHasMenu) {
+            mNeedMenuInvalidate = true;
+        }
+        fragment.mAdded = false;
+        moveToState(fragment, inactive ? Fragment.INITIALIZING : Fragment.CREATED,
+                transition, transitionStyle);
+    }
+    
+    public void hideFragment(Fragment fragment, int transition, int transitionStyle) {
+        if (DEBUG) Log.v(TAG, "hide: " + fragment);
+        if (!fragment.mHidden) {
+            fragment.mHidden = true;
+            if (fragment.mView != null) {
+                Animation anim = loadAnimation(fragment, transition, false,
+                        transitionStyle);
+                if (anim != null) {
+                    fragment.mView.setAnimation(anim);
+                }
+                fragment.mView.setVisibility(View.GONE);
+            }
+            if (fragment.mAdded && fragment.mHasMenu) {
+                mNeedMenuInvalidate = true;
+            }
+            fragment.onHiddenChanged(true);
+        }
+    }
+    
+    public void showFragment(Fragment fragment, int transition, int transitionStyle) {
+        if (DEBUG) Log.v(TAG, "show: " + fragment);
+        if (fragment.mHidden) {
+            fragment.mHidden = false;
+            if (fragment.mView != null) {
+                Animation anim = loadAnimation(fragment, transition, true,
+                        transitionStyle);
+                if (anim != null) {
+                    fragment.mView.setAnimation(anim);
+                }
+                fragment.mView.setVisibility(View.VISIBLE);
+            }
+            if (fragment.mAdded && fragment.mHasMenu) {
+                mNeedMenuInvalidate = true;
+            }
+            fragment.onHiddenChanged(false);
+        }
+    }
+    
+    public Fragment findFragmentById(int id) {
+        if (mActive != null) {
+            // First look through added fragments.
+            for (int i=mAdded.size()-1; i>=0; i--) {
+                Fragment f = mAdded.get(i);
+                if (f != null && f.mFragmentId == id) {
+                    return f;
+                }
+            }
+            // Now for any known fragment.
+            for (int i=mActive.size()-1; i>=0; i--) {
+                Fragment f = mActive.get(i);
+                if (f != null && f.mFragmentId == id) {
+                    return f;
+                }
+            }
+        }
+        return null;
+    }
+    
+    public Fragment findFragmentByTag(String tag) {
+        if (mActive != null && tag != null) {
+            // First look through added fragments.
+            for (int i=mAdded.size()-1; i>=0; i--) {
+                Fragment f = mAdded.get(i);
+                if (f != null && tag.equals(f.mTag)) {
+                    return f;
+                }
+            }
+            // Now for any known fragment.
+            for (int i=mActive.size()-1; i>=0; i--) {
+                Fragment f = mActive.get(i);
+                if (f != null && tag.equals(f.mTag)) {
+                    return f;
+                }
+            }
+        }
+        return null;
+    }
+    
+    public Fragment findFragmentByWho(String who) {
+        if (mActive != null && who != null) {
+            for (int i=mActive.size()-1; i>=0; i--) {
+                Fragment f = mActive.get(i);
+                if (f != null && who.equals(f.mWho)) {
+                    return f;
+                }
+            }
+        }
+        return null;
+    }
+    
+    public void enqueueAction(Runnable action) {
+        synchronized (this) {
+            if (mPendingActions == null) {
+                mPendingActions = new ArrayList<Runnable>();
+            }
+            mPendingActions.add(action);
+            if (mPendingActions.size() == 1) {
+                mActivity.mHandler.removeCallbacks(mExecCommit);
+                mActivity.mHandler.post(mExecCommit);
+            }
+        }
+    }
+    
+    public int allocBackStackIndex(BackStackEntry bse) {
+        synchronized (this) {
+            if (mAvailBackStackIndices == null || mAvailBackStackIndices.size() <= 0) {
+                if (mBackStackIndices == null) {
+                    mBackStackIndices = new ArrayList<BackStackEntry>();
+                }
+                int index = mBackStackIndices.size();
+                if (DEBUG) Log.v(TAG, "Setting back stack index " + index + " to " + bse);
+                mBackStackIndices.add(bse);
+                return index;
+
+            } else {
+                int index = mAvailBackStackIndices.remove(mAvailBackStackIndices.size()-1);
+                if (DEBUG) Log.v(TAG, "Adding back stack index " + index + " with " + bse);
+                mBackStackIndices.set(index, bse);
+                return index;
+            }
+        }
+    }
+
+    public void setBackStackIndex(int index, BackStackEntry bse) {
+        synchronized (this) {
+            if (mBackStackIndices == null) {
+                mBackStackIndices = new ArrayList<BackStackEntry>();
+            }
+            int N = mBackStackIndices.size();
+            if (index < N) {
+                if (DEBUG) Log.v(TAG, "Setting back stack index " + index + " to " + bse);
+                mBackStackIndices.set(index, bse);
+            } else {
+                while (N < index) {
+                    mBackStackIndices.add(null);
+                    if (mAvailBackStackIndices == null) {
+                        mAvailBackStackIndices = new ArrayList<Integer>();
+                    }
+                    if (DEBUG) Log.v(TAG, "Adding available back stack index " + N);
+                    mAvailBackStackIndices.add(N);
+                    N++;
+                }
+                if (DEBUG) Log.v(TAG, "Adding back stack index " + index + " with " + bse);
+                mBackStackIndices.add(bse);
+            }
+        }
+    }
+
+    public void freeBackStackIndex(int index) {
+        synchronized (this) {
+            mBackStackIndices.set(index, null);
+            if (mAvailBackStackIndices == null) {
+                mAvailBackStackIndices = new ArrayList<Integer>();
+            }
+            if (DEBUG) Log.v(TAG, "Freeing back stack index " + index);
+            mAvailBackStackIndices.add(index);
+        }
+    }
+
+    /**
+     * Only call from main thread!
+     */
+    public void execPendingActions() {
+        if (mExecutingActions) {
+            throw new IllegalStateException("Recursive entry to execPendingActions");
+        }
+        
+        while (true) {
+            int numActions;
+            
+            synchronized (this) {
+                if (mPendingActions == null || mPendingActions.size() == 0) {
+                    return;
+                }
+                
+                numActions = mPendingActions.size();
+                if (mTmpActions == null || mTmpActions.length < numActions) {
+                    mTmpActions = new Runnable[numActions];
+                }
+                mPendingActions.toArray(mTmpActions);
+                mPendingActions.clear();
+                mActivity.mHandler.removeCallbacks(mExecCommit);
+            }
+            
+            mExecutingActions = true;
+            for (int i=0; i<numActions; i++) {
+                mTmpActions[i].run();
+            }
+            mExecutingActions = false;
+        }
+    }
+    
+    public void addBackStackState(BackStackEntry state) {
+        if (mBackStack == null) {
+            mBackStack = new ArrayList<BackStackEntry>();
+        }
+        mBackStack.add(state);
+    }
+    
+    public boolean popBackStackState(Handler handler, String name, int flags) {
+        return popBackStackState(handler, name, -1, flags);
+    }
+
+    public boolean popBackStackState(Handler handler, int id, int flags) {
+        if (id < 0) {
+            return false;
+        }
+        return popBackStackState(handler, null, id, flags);
+    }
+
+    boolean popBackStackState(Handler handler, String name, int id, int flags) {
+        if (mBackStack == null) {
+            return false;
+        }
+        if (name == null && id < 0 && (flags&Activity.POP_BACK_STACK_INCLUSIVE) == 0) {
+            int last = mBackStack.size()-1;
+            if (last < 0) {
+                return false;
+            }
+            final BackStackEntry bss = mBackStack.remove(last);
+            enqueueAction(new Runnable() {
+                public void run() {
+                    if (DEBUG) Log.v(TAG, "Popping back stack state: " + bss);
+                    bss.popFromBackStack();
+                    moveToState(mCurState, reverseTransit(bss.getTransition()),
+                            bss.getTransitionStyle(), true);
+                }
+            });
+        } else {
+            int index = -1;
+            if (name != null || id >= 0) {
+                // If a name or ID is specified, look for that place in
+                // the stack.
+                index = mBackStack.size()-1;
+                while (index >= 0) {
+                    BackStackEntry bss = mBackStack.get(index);
+                    if (name != null && name.equals(bss.getName())) {
+                        break;
+                    }
+                    if (id >= 0 && id == bss.mIndex) {
+                        break;
+                    }
+                    index--;
+                }
+                if (index < 0) {
+                    return false;
+                }
+                if ((flags&Activity.POP_BACK_STACK_INCLUSIVE) != 0) {
+                    index--;
+                    // Consume all following entries that match.
+                    while (index >= 0) {
+                        BackStackEntry bss = mBackStack.get(index);
+                        if ((name != null && name.equals(bss.getName()))
+                                || (id >= 0 && id == bss.mIndex)) {
+                            index--;
+                            continue;
+                        }
+                        break;
+                    }
+                }
+            }
+            if (index == mBackStack.size()-1) {
+                return false;
+            }
+            final ArrayList<BackStackEntry> states
+                    = new ArrayList<BackStackEntry>();
+            for (int i=mBackStack.size()-1; i>index; i--) {
+                states.add(mBackStack.remove(i));
+            }
+            enqueueAction(new Runnable() {
+                public void run() {
+                    for (int i=0; i<states.size(); i++) {
+                        if (DEBUG) Log.v(TAG, "Popping back stack state: " + states.get(i));
+                        states.get(i).popFromBackStack();
+                    }
+                    moveToState(mCurState, true);
+                }
+            });
+        }
+        return true;
+    }
+    
+    ArrayList<Fragment> retainNonConfig() {
+        ArrayList<Fragment> fragments = null;
+        if (mActive != null) {
+            for (int i=0; i<mActive.size(); i++) {
+                Fragment f = mActive.get(i);
+                if (f != null && f.mRetainInstance) {
+                    if (fragments == null) {
+                        fragments = new ArrayList<Fragment>();
+                    }
+                    fragments.add(f);
+                    f.mRetaining = true;
+                }
+            }
+        }
+        return fragments;
+    }
+    
+    void saveFragmentViewState(Fragment f) {
+        if (f.mView == null) {
+            return;
+        }
+        if (mStateArray == null) {
+            mStateArray = new SparseArray<Parcelable>();
+        }
+        f.mView.saveHierarchyState(mStateArray);
+        if (mStateArray.size() > 0) {
+            f.mSavedViewState = mStateArray;
+            mStateArray = null;
+        }
+    }
+    
+    Parcelable saveAllState() {
+        if (mActive == null || mActive.size() <= 0) {
+            return null;
+        }
+        
+        // First collect all active fragments.
+        int N = mActive.size();
+        FragmentState[] active = new FragmentState[N];
+        boolean haveFragments = false;
+        for (int i=0; i<N; i++) {
+            Fragment f = mActive.get(i);
+            if (f != null) {
+                haveFragments = true;
+                
+                FragmentState fs = new FragmentState(f);
+                active[i] = fs;
+                
+                if (mStateBundle == null) {
+                    mStateBundle = new Bundle();
+                }
+                f.onSaveInstanceState(mStateBundle);
+                if (!mStateBundle.isEmpty()) {
+                    fs.mSavedFragmentState = mStateBundle;
+                    mStateBundle = null;
+                }
+                
+                if (f.mView != null) {
+                    saveFragmentViewState(f);
+                    if (f.mSavedViewState != null) {
+                        if (fs.mSavedFragmentState == null) {
+                            fs.mSavedFragmentState = new Bundle();
+                        }
+                        fs.mSavedFragmentState.putSparseParcelableArray(
+                                FragmentState.VIEW_STATE_TAG, f.mSavedViewState);
+                    }
+                }
+                
+            }
+        }
+        
+        if (!haveFragments) {
+            return null;
+        }
+        
+        int[] added = null;
+        BackStackState[] backStack = null;
+        
+        // Build list of currently added fragments.
+        N = mAdded.size();
+        if (N > 0) {
+            added = new int[N];
+            for (int i=0; i<N; i++) {
+                added[i] = mAdded.get(i).mIndex;
+            }
+        }
+        
+        // Now save back stack.
+        if (mBackStack != null) {
+            N = mBackStack.size();
+            if (N > 0) {
+                backStack = new BackStackState[N];
+                for (int i=0; i<N; i++) {
+                    backStack[i] = new BackStackState(this, mBackStack.get(i));
+                }
+            }
+        }
+        
+        FragmentManagerState fms = new FragmentManagerState();
+        fms.mActive = active;
+        fms.mAdded = added;
+        fms.mBackStack = backStack;
+        return fms;
+    }
+    
+    void restoreAllState(Parcelable state, ArrayList<Fragment> nonConfig) {
+        // If there is no saved state at all, then there can not be
+        // any nonConfig fragments either, so that is that.
+        if (state == null) return;
+        FragmentManagerState fms = (FragmentManagerState)state;
+        if (fms.mActive == null) return;
+        
+        // First re-attach any non-config instances we are retaining back
+        // to their saved state, so we don't try to instantiate them again.
+        if (nonConfig != null) {
+            for (int i=0; i<nonConfig.size(); i++) {
+                Fragment f = nonConfig.get(i);
+                FragmentState fs = fms.mActive[f.mIndex];
+                fs.mInstance = f;
+                f.mSavedViewState = null;
+                f.mBackStackNesting = 0;
+                f.mAdded = false;
+                if (fs.mSavedFragmentState != null) {
+                    f.mSavedViewState = fs.mSavedFragmentState.getSparseParcelableArray(
+                            FragmentState.VIEW_STATE_TAG);
+                }
+            }
+        }
+        
+        // Build the full list of active fragments, instantiating them from
+        // their saved state.
+        mActive = new ArrayList<Fragment>(fms.mActive.length);
+        if (mAvailIndices != null) {
+            mAvailIndices.clear();
+        }
+        for (int i=0; i<fms.mActive.length; i++) {
+            FragmentState fs = fms.mActive[i];
+            if (fs != null) {
+                mActive.add(fs.instantiate(mActivity));
+            } else {
+                mActive.add(null);
+                if (mAvailIndices == null) {
+                    mAvailIndices = new ArrayList<Integer>();
+                }
+                mAvailIndices.add(i);
+            }
+        }
+        
+        // Build the list of currently added fragments.
+        if (fms.mAdded != null) {
+            mAdded = new ArrayList<Fragment>(fms.mAdded.length);
+            for (int i=0; i<fms.mAdded.length; i++) {
+                Fragment f = mActive.get(fms.mAdded[i]);
+                if (f == null) {
+                    throw new IllegalStateException(
+                            "No instantiated fragment for index #" + fms.mAdded[i]);
+                }
+                f.mAdded = true;
+                f.mImmediateActivity = mActivity;
+                mAdded.add(f);
+            }
+        } else {
+            mAdded = null;
+        }
+        
+        // Build the back stack.
+        if (fms.mBackStack != null) {
+            mBackStack = new ArrayList<BackStackEntry>(fms.mBackStack.length);
+            for (int i=0; i<fms.mBackStack.length; i++) {
+                BackStackEntry bse = fms.mBackStack[i].instantiate(this);
+                mBackStack.add(bse);
+                if (bse.mIndex >= 0) {
+                    setBackStackIndex(bse.mIndex, bse);
+                }
+            }
+        } else {
+            mBackStack = null;
+        }
+    }
+    
+    public void attachActivity(Activity activity) {
+        if (mActivity != null) throw new IllegalStateException();
+        mActivity = activity;
+    }
+    
+    public void dispatchCreate() {
+        moveToState(Fragment.CREATED, false);
+    }
+    
+    public void dispatchActivityCreated() {
+        moveToState(Fragment.ACTIVITY_CREATED, false);
+    }
+    
+    public void dispatchStart() {
+        moveToState(Fragment.STARTED, false);
+    }
+    
+    public void dispatchResume() {
+        moveToState(Fragment.RESUMED, false);
+    }
+    
+    public void dispatchPause() {
+        moveToState(Fragment.STARTED, false);
+    }
+    
+    public void dispatchStop() {
+        moveToState(Fragment.ACTIVITY_CREATED, false);
+    }
+    
+    public void dispatchDestroy() {
+        moveToState(Fragment.INITIALIZING, false);
+        mActivity = null;
+    }
+    
+    public boolean dispatchCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+        boolean show = false;
+        if (mActive != null) {
+            for (int i=0; i<mAdded.size(); i++) {
+                Fragment f = mAdded.get(i);
+                if (f != null && !f.mHidden && f.mHasMenu) {
+                    show = true;
+                    f.onCreateOptionsMenu(menu, inflater);
+                }
+            }
+        }
+        return show;
+    }
+    
+    public boolean dispatchPrepareOptionsMenu(Menu menu) {
+        boolean show = false;
+        if (mActive != null) {
+            for (int i=0; i<mAdded.size(); i++) {
+                Fragment f = mAdded.get(i);
+                if (f != null && !f.mHidden && f.mHasMenu) {
+                    show = true;
+                    f.onPrepareOptionsMenu(menu);
+                }
+            }
+        }
+        return show;
+    }
+    
+    public boolean dispatchOptionsItemSelected(MenuItem item) {
+        if (mActive != null) {
+            for (int i=0; i<mAdded.size(); i++) {
+                Fragment f = mAdded.get(i);
+                if (f != null && !f.mHidden && f.mHasMenu) {
+                    if (f.onOptionsItemSelected(item)) {
+                        return true;
+                    }
+                }
+            }
+        }
+        return false;
+    }
+    
+    public boolean dispatchContextItemSelected(MenuItem item) {
+        if (mActive != null) {
+            for (int i=0; i<mAdded.size(); i++) {
+                Fragment f = mAdded.get(i);
+                if (f != null && !f.mHidden) {
+                    if (f.onContextItemSelected(item)) {
+                        return true;
+                    }
+                }
+            }
+        }
+        return false;
+    }
+    
+    public void dispatchOptionsMenuClosed(Menu menu) {
+        if (mActive != null) {
+            for (int i=0; i<mAdded.size(); i++) {
+                Fragment f = mAdded.get(i);
+                if (f != null && !f.mHidden && f.mHasMenu) {
+                    f.onOptionsMenuClosed(menu);
+                }
+            }
+        }
+    }
+    
+    public static int reverseTransit(int transit) {
+        int rev = 0;
+        switch (transit) {
+            case FragmentTransaction.TRANSIT_ENTER:
+                rev = FragmentTransaction.TRANSIT_EXIT;
+                break;
+            case FragmentTransaction.TRANSIT_EXIT:
+                rev = FragmentTransaction.TRANSIT_ENTER;
+                break;
+            case FragmentTransaction.TRANSIT_SHOW:
+                rev = FragmentTransaction.TRANSIT_HIDE;
+                break;
+            case FragmentTransaction.TRANSIT_HIDE:
+                rev = FragmentTransaction.TRANSIT_SHOW;
+                break;
+            case FragmentTransaction.TRANSIT_ACTIVITY_OPEN:
+                rev = FragmentTransaction.TRANSIT_ACTIVITY_CLOSE;
+                break;
+            case FragmentTransaction.TRANSIT_ACTIVITY_CLOSE:
+                rev = FragmentTransaction.TRANSIT_ACTIVITY_OPEN;
+                break;
+            case FragmentTransaction.TRANSIT_TASK_OPEN:
+                rev = FragmentTransaction.TRANSIT_TASK_CLOSE;
+                break;
+            case FragmentTransaction.TRANSIT_TASK_CLOSE:
+                rev = FragmentTransaction.TRANSIT_TASK_OPEN;
+                break;
+            case FragmentTransaction.TRANSIT_TASK_TO_FRONT:
+                rev = FragmentTransaction.TRANSIT_TASK_TO_BACK;
+                break;
+            case FragmentTransaction.TRANSIT_TASK_TO_BACK:
+                rev = FragmentTransaction.TRANSIT_TASK_TO_FRONT;
+                break;
+            case FragmentTransaction.TRANSIT_WALLPAPER_OPEN:
+                rev = FragmentTransaction.TRANSIT_WALLPAPER_CLOSE;
+                break;
+            case FragmentTransaction.TRANSIT_WALLPAPER_CLOSE:
+                rev = FragmentTransaction.TRANSIT_WALLPAPER_OPEN;
+                break;
+            case FragmentTransaction.TRANSIT_WALLPAPER_INTRA_OPEN:
+                rev = FragmentTransaction.TRANSIT_WALLPAPER_INTRA_CLOSE;
+                break;
+            case FragmentTransaction.TRANSIT_WALLPAPER_INTRA_CLOSE:
+                rev = FragmentTransaction.TRANSIT_WALLPAPER_INTRA_OPEN;
+                break;
+        }
+        return rev;
+        
+    }
+    
+    public static int transitToStyleIndex(int transit, boolean enter) {
+        int animAttr = -1;
+        switch (transit) {
+            case FragmentTransaction.TRANSIT_ENTER:
+                animAttr = com.android.internal.R.styleable.WindowAnimation_windowEnterAnimation;
+                break;
+            case FragmentTransaction.TRANSIT_EXIT:
+                animAttr = com.android.internal.R.styleable.WindowAnimation_windowExitAnimation;
+                break;
+            case FragmentTransaction.TRANSIT_SHOW:
+                animAttr = com.android.internal.R.styleable.WindowAnimation_windowShowAnimation;
+                break;
+            case FragmentTransaction.TRANSIT_HIDE:
+                animAttr = com.android.internal.R.styleable.WindowAnimation_windowHideAnimation;
+                break;
+            case FragmentTransaction.TRANSIT_ACTIVITY_OPEN:
+                animAttr = enter
+                        ? com.android.internal.R.styleable.WindowAnimation_activityOpenEnterAnimation
+                        : com.android.internal.R.styleable.WindowAnimation_activityOpenExitAnimation;
+                break;
+            case FragmentTransaction.TRANSIT_ACTIVITY_CLOSE:
+                animAttr = enter
+                        ? com.android.internal.R.styleable.WindowAnimation_activityCloseEnterAnimation
+                        : com.android.internal.R.styleable.WindowAnimation_activityCloseExitAnimation;
+                break;
+            case FragmentTransaction.TRANSIT_TASK_OPEN:
+                animAttr = enter
+                        ? com.android.internal.R.styleable.WindowAnimation_taskOpenEnterAnimation
+                        : com.android.internal.R.styleable.WindowAnimation_taskOpenExitAnimation;
+                break;
+            case FragmentTransaction.TRANSIT_TASK_CLOSE:
+                animAttr = enter
+                        ? com.android.internal.R.styleable.WindowAnimation_taskCloseEnterAnimation
+                        : com.android.internal.R.styleable.WindowAnimation_taskCloseExitAnimation;
+                break;
+            case FragmentTransaction.TRANSIT_TASK_TO_FRONT:
+                animAttr = enter
+                        ? com.android.internal.R.styleable.WindowAnimation_taskToFrontEnterAnimation
+                        : com.android.internal.R.styleable.WindowAnimation_taskToFrontExitAnimation;
+                break;
+            case FragmentTransaction.TRANSIT_TASK_TO_BACK:
+                animAttr = enter
+                        ? com.android.internal.R.styleable.WindowAnimation_taskToBackEnterAnimation
+                        : com.android.internal.R.styleable.WindowAnimation_taskToBackExitAnimation;
+                break;
+            case FragmentTransaction.TRANSIT_WALLPAPER_OPEN:
+                animAttr = enter
+                        ? com.android.internal.R.styleable.WindowAnimation_wallpaperOpenEnterAnimation
+                        : com.android.internal.R.styleable.WindowAnimation_wallpaperOpenExitAnimation;
+                break;
+            case FragmentTransaction.TRANSIT_WALLPAPER_CLOSE:
+                animAttr = enter
+                        ? com.android.internal.R.styleable.WindowAnimation_wallpaperCloseEnterAnimation
+                        : com.android.internal.R.styleable.WindowAnimation_wallpaperCloseExitAnimation;
+                break;
+            case FragmentTransaction.TRANSIT_WALLPAPER_INTRA_OPEN:
+                animAttr = enter
+                        ? com.android.internal.R.styleable.WindowAnimation_wallpaperIntraOpenEnterAnimation
+                        : com.android.internal.R.styleable.WindowAnimation_wallpaperIntraOpenExitAnimation;
+                break;
+            case FragmentTransaction.TRANSIT_WALLPAPER_INTRA_CLOSE:
+                animAttr = enter
+                        ? com.android.internal.R.styleable.WindowAnimation_wallpaperIntraCloseEnterAnimation
+                        : com.android.internal.R.styleable.WindowAnimation_wallpaperIntraCloseExitAnimation;
+                break;
+        }
+        return animAttr;
+    }
+}
diff --git a/core/java/android/app/FragmentTransaction.java b/core/java/android/app/FragmentTransaction.java
new file mode 100644
index 0000000..65cf85c
--- /dev/null
+++ b/core/java/android/app/FragmentTransaction.java
@@ -0,0 +1,161 @@
+package android.app;
+
+/**
+ * API for performing a set of Fragment operations.
+ */
+public interface FragmentTransaction {
+    /**
+     * Calls {@link #add(int, Fragment, String)} with a 0 containerViewId.
+     */
+    public FragmentTransaction add(Fragment fragment, String tag);
+    
+    /**
+     * Calls {@link #add(int, Fragment, String)} with a null tag.
+     */
+    public FragmentTransaction add(int containerViewId, Fragment fragment);
+    
+    /**
+     * Add a fragment to the activity state.  This fragment may optionally
+     * also have its view (if {@link Fragment#onCreateView Fragment.onCreateView}
+     * returns non-null) into a container view of the activity.
+     * 
+     * @param containerViewId Optional identifier of the container this fragment is
+     * to be placed in.  If 0, it will not be placed in a container.
+     * @param fragment The fragment to be added.  This fragment must not already
+     * be added to the activity.
+     * @param tag Optional tag name for the fragment, to later retrieve the
+     * fragment with {@link Activity#findFragmentByTag(String)
+     * Activity.findFragmentByTag(String)}.
+     * 
+     * @return Returns the same FragmentTransaction instance.
+     */
+    public FragmentTransaction add(int containerViewId, Fragment fragment, String tag);
+    
+    /**
+     * Calls {@link #replace(int, Fragment, String)} with a null tag.
+     */
+    public FragmentTransaction replace(int containerViewId, Fragment fragment);
+    
+    /**
+     * Replace an existing fragment that was added to a container.  This is
+     * essentially the same as calling {@link #remove(Fragment)} for all
+     * currently added fragments that were added with the same containerViewId
+     * and then {@link #add(int, Fragment, String)} with the same arguments
+     * given here.
+     * 
+     * @param containerViewId Identifier of the container whose fragment(s) are
+     * to be replaced.
+     * @param fragment The new fragment to place in the container.
+     * @param tag Optional tag name for the fragment, to later retrieve the
+     * fragment with {@link Activity#findFragmentByTag(String)
+     * Activity.findFragmentByTag(String)}.
+     * 
+     * @return Returns the same FragmentTransaction instance.
+     */
+    public FragmentTransaction replace(int containerViewId, Fragment fragment, String tag);
+    
+    /**
+     * Remove an existing fragment.  If it was added to a container, its view
+     * is also removed from that container.
+     * 
+     * @param fragment The fragment to be removed.
+     * 
+     * @return Returns the same FragmentTransaction instance.
+     */
+    public FragmentTransaction remove(Fragment fragment);
+    
+    /**
+     * Hides an existing fragment.  This is only relevant for fragments whose
+     * views have been added to a container, as this will cause the view to
+     * be hidden.
+     * 
+     * @param fragment The fragment to be hidden.
+     * 
+     * @return Returns the same FragmentTransaction instance.
+     */
+    public FragmentTransaction hide(Fragment fragment);
+    
+    /**
+     * Hides a previously hidden fragment.  This is only relevant for fragments whose
+     * views have been added to a container, as this will cause the view to
+     * be shown.
+     * 
+     * @param fragment The fragment to be shown.
+     * 
+     * @return Returns the same FragmentTransaction instance.
+     */
+    public FragmentTransaction show(Fragment fragment);
+    
+    /**
+     * Bit mask that is set for all enter transitions.
+     */
+    public final int TRANSIT_ENTER_MASK = 0x1000;
+    
+    /**
+     * Bit mask that is set for all exit transitions.
+     */
+    public final int TRANSIT_EXIT_MASK = 0x2000;
+    
+    /** Not set up for a transition. */
+    public final int TRANSIT_UNSET = -1;
+    /** No animation for transition. */
+    public final int TRANSIT_NONE = 0;
+    /** Window has been added to the screen. */
+    public final int TRANSIT_ENTER = 1 | TRANSIT_ENTER_MASK;
+    /** Window has been removed from the screen. */
+    public final int TRANSIT_EXIT = 2 | TRANSIT_EXIT_MASK;
+    /** Window has been made visible. */
+    public final int TRANSIT_SHOW = 3 | TRANSIT_ENTER_MASK;
+    /** Window has been made invisible. */
+    public final int TRANSIT_HIDE = 4 | TRANSIT_EXIT_MASK;
+    /** The "application starting" preview window is no longer needed, and will
+     * animate away to show the real window. */
+    public final int TRANSIT_PREVIEW_DONE = 5;
+    /** A window in a new activity is being opened on top of an existing one
+     * in the same task. */
+    public final int TRANSIT_ACTIVITY_OPEN = 6 | TRANSIT_ENTER_MASK;
+    /** The window in the top-most activity is being closed to reveal the
+     * previous activity in the same task. */
+    public final int TRANSIT_ACTIVITY_CLOSE = 7 | TRANSIT_EXIT_MASK;
+    /** A window in a new task is being opened on top of an existing one
+     * in another activity's task. */
+    public final int TRANSIT_TASK_OPEN = 8 | TRANSIT_ENTER_MASK;
+    /** A window in the top-most activity is being closed to reveal the
+     * previous activity in a different task. */
+    public final int TRANSIT_TASK_CLOSE = 9 | TRANSIT_EXIT_MASK;
+    /** A window in an existing task is being displayed on top of an existing one
+     * in another activity's task. */
+    public final int TRANSIT_TASK_TO_FRONT = 10 | TRANSIT_ENTER_MASK;
+    /** A window in an existing task is being put below all other tasks. */
+    public final int TRANSIT_TASK_TO_BACK = 11 | TRANSIT_EXIT_MASK;
+    /** A window in a new activity that doesn't have a wallpaper is being
+     * opened on top of one that does, effectively closing the wallpaper. */
+    public final int TRANSIT_WALLPAPER_CLOSE = 12 | TRANSIT_EXIT_MASK;
+    /** A window in a new activity that does have a wallpaper is being
+     * opened on one that didn't, effectively opening the wallpaper. */
+    public final int TRANSIT_WALLPAPER_OPEN = 13 | TRANSIT_ENTER_MASK;
+    /** A window in a new activity is being opened on top of an existing one,
+     * and both are on top of the wallpaper. */
+    public final int TRANSIT_WALLPAPER_INTRA_OPEN = 14 | TRANSIT_ENTER_MASK;
+    /** The window in the top-most activity is being closed to reveal the
+     * previous activity, and both are on top of he wallpaper. */
+    public final int TRANSIT_WALLPAPER_INTRA_CLOSE = 15 | TRANSIT_EXIT_MASK;
+    
+    public FragmentTransaction setCustomAnimations(int enter, int exit);
+    
+    public FragmentTransaction setTransition(int transit);
+    public FragmentTransaction setTransitionStyle(int styleRes);
+    
+    public FragmentTransaction addToBackStack(String name);
+
+    /**
+     * Schedules a commit of this transaction.  Note that the commit does
+     * not happen immediately; it will be scheduled as work on the main thread
+     * to be done the next time that thread is ready.
+     *
+     * @return Returns the identifier of this transaction's back stack entry,
+     * if {@link #addToBackStack(String)} had been called.  Otherwise, returns
+     * a negative number.
+     */
+    public int commit();
+}
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index 81b28b9..0d35ba4 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -317,7 +317,11 @@
     
     public void crashApplication(int uid, int initialPid, String packageName,
             String message) throws RemoteException;
-    
+
+    // Cause the specified process to dump the specified heap.
+    public boolean dumpHeap(String process, boolean managed, String path,
+        ParcelFileDescriptor fd) throws RemoteException;
+
     /*
      * Private non-Binder interfaces
      */
@@ -534,4 +538,5 @@
     int SET_IMMERSIVE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+111;
     int IS_TOP_ACTIVITY_IMMERSIVE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+112;
     int CRASH_APPLICATION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+113;
+    int DUMP_HEAP_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+114;
 }
diff --git a/core/java/android/app/IApplicationThread.java b/core/java/android/app/IApplicationThread.java
index c8ef17f..039bcb9 100644
--- a/core/java/android/app/IApplicationThread.java
+++ b/core/java/android/app/IApplicationThread.java
@@ -97,6 +97,8 @@
     void scheduleActivityConfigurationChanged(IBinder token) throws RemoteException;
     void profilerControl(boolean start, String path, ParcelFileDescriptor fd)
             throws RemoteException;
+    void dumpHeap(boolean managed, String path, ParcelFileDescriptor fd)
+            throws RemoteException;
     void setSchedulingGroup(int group) throws RemoteException;
     void getMemoryInfo(Debug.MemoryInfo outInfo) throws RemoteException;
     static final int PACKAGE_REMOVED = 0;
@@ -140,4 +142,5 @@
     int SCHEDULE_SUICIDE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+32;
     int DISPATCH_PACKAGE_BROADCAST_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+33;
     int SCHEDULE_CRASH_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+34;
+    int DUMP_HEAP_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+35;
 }
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index b8c3aa3..4d5f36a 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -997,8 +997,10 @@
             IllegalAccessException {
         Activity activity = (Activity)clazz.newInstance();
         ActivityThread aThread = null;
-        activity.attach(context, aThread, this, token, application, intent, info, title,
-                parent, id, lastNonConfigurationInstance, new Configuration());
+        activity.attach(context, aThread, this, token, application, intent,
+                info, title, parent, id,
+                (Activity.NonConfigurationInstances)lastNonConfigurationInstance,
+                new Configuration());
         return activity;
     }
 
@@ -1058,21 +1060,23 @@
     }
     
     public void callActivityOnDestroy(Activity activity) {
-      if (mWaitingActivities != null) {
-          synchronized (mSync) {
-              final int N = mWaitingActivities.size();
-              for (int i=0; i<N; i++) {
-                  final ActivityWaiter aw = mWaitingActivities.get(i);
-                  final Intent intent = aw.intent;
-                  if (intent.filterEquals(activity.getIntent())) {
-                      aw.activity = activity;
-                      mMessageQueue.addIdleHandler(new ActivityGoing(aw));
-                  }
-              }
-          }
-      }
+      // TODO: the following block causes intermittent hangs when using startActivity
+      // temporarily comment out until root cause is fixed (bug 2630683)
+//      if (mWaitingActivities != null) {
+//          synchronized (mSync) {
+//              final int N = mWaitingActivities.size();
+//              for (int i=0; i<N; i++) {
+//                  final ActivityWaiter aw = mWaitingActivities.get(i);
+//                  final Intent intent = aw.intent;
+//                  if (intent.filterEquals(activity.getIntent())) {
+//                      aw.activity = activity;
+//                      mMessageQueue.addIdleHandler(new ActivityGoing(aw));
+//                  }
+//              }
+//          }
+//      }
       
-      activity.onDestroy();
+      activity.performDestroy();
       
       if (mActivityMonitors != null) {
           synchronized (mSync) {
@@ -1331,7 +1335,7 @@
      *                      is being started.
      * @param token Internal token identifying to the system who is starting 
      *              the activity; may be null.
-     * @param target Which activity is perform the start (and thus receiving 
+     * @param target Which activity is performing the start (and thus receiving 
      *               any result); may be null if this call is not being made
      *               from an activity.
      * @param intent The actual Intent to start.
@@ -1381,6 +1385,64 @@
         return null;
     }
 
+    /**
+     * Like {@link #execStartActivity(Context, IBinder, IBinder, Activity, Intent, int)},
+     * but for calls from a {#link Fragment}.
+     * 
+     * @param who The Context from which the activity is being started.
+     * @param contextThread The main thread of the Context from which the activity
+     *                      is being started.
+     * @param token Internal token identifying to the system who is starting 
+     *              the activity; may be null.
+     * @param target Which fragment is performing the start (and thus receiving 
+     *               any result).
+     * @param intent The actual Intent to start.
+     * @param requestCode Identifier for this request's result; less than zero 
+     *                    if the caller is not expecting a result.
+     * 
+     * @return To force the return of a particular result, return an 
+     *         ActivityResult object containing the desired data; otherwise
+     *         return null.  The default implementation always returns null.
+     *  
+     * @throws android.content.ActivityNotFoundException
+     * 
+     * @see Activity#startActivity(Intent)
+     * @see Activity#startActivityForResult(Intent, int)
+     * @see Activity#startActivityFromChild
+     * 
+     * {@hide}
+     */
+    public ActivityResult execStartActivity(
+        Context who, IBinder contextThread, IBinder token, Fragment target,
+        Intent intent, int requestCode) {
+        IApplicationThread whoThread = (IApplicationThread) contextThread;
+        if (mActivityMonitors != null) {
+            synchronized (mSync) {
+                final int N = mActivityMonitors.size();
+                for (int i=0; i<N; i++) {
+                    final ActivityMonitor am = mActivityMonitors.get(i);
+                    if (am.match(who, null, intent)) {
+                        am.mHits++;
+                        if (am.isBlocking()) {
+                            return requestCode >= 0 ? am.getResult() : null;
+                        }
+                        break;
+                    }
+                }
+            }
+        }
+        try {
+            int result = ActivityManagerNative.getDefault()
+                .startActivity(whoThread, intent,
+                        intent.resolveTypeIfNeeded(who.getContentResolver()),
+                        null, 0, token, target != null ? target.mWho : null,
+                        requestCode, false, false);
+            checkStartActivityResult(result, intent);
+        } catch (RemoteException e) {
+        }
+        return null;
+    }
+
     /*package*/ final void init(ActivityThread thread,
             Context instrContext, Context appContext, ComponentName component, 
             IInstrumentationWatcher watcher) {
diff --git a/core/java/android/app/ListActivity.java b/core/java/android/app/ListActivity.java
index 4bf5518..d49968f 100644
--- a/core/java/android/app/ListActivity.java
+++ b/core/java/android/app/ListActivity.java
@@ -309,7 +309,7 @@
         if (mList != null) {
             return;
         }
-        setContentView(com.android.internal.R.layout.list_content);
+        setContentView(com.android.internal.R.layout.list_content_simple);
 
     }
 
diff --git a/core/java/android/app/ListFragment.java b/core/java/android/app/ListFragment.java
new file mode 100644
index 0000000..6e2f4b6
--- /dev/null
+++ b/core/java/android/app/ListFragment.java
@@ -0,0 +1,418 @@
+/*
+ * 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.os.Handler;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.AnimationUtils;
+import android.widget.AdapterView;
+import android.widget.ListAdapter;
+import android.widget.ListView;
+import android.widget.TextView;
+
+/**
+ * A fragment that displays a list of items by binding to a data source such as
+ * an array or Cursor, and exposes event handlers when the user selects an item.
+ * <p>
+ * ListFragment hosts a {@link android.widget.ListView ListView} object that can
+ * be bound to different data sources, typically either an array or a Cursor
+ * holding query results. Binding, screen layout, and row layout are discussed
+ * in the following sections.
+ * <p>
+ * <strong>Screen Layout</strong>
+ * </p>
+ * <p>
+ * ListFragment has a default layout that consists of a single list view.
+ * However, if you desire, you can customize the fragment layout by returning
+ * your own view hierarchy from {@link #onCreateView}.
+ * To do this, your view hierarchy <em>must</em> contain a ListView object with the
+ * id "@android:id/list" (or {@link android.R.id#list} if it's in code)
+ * <p>
+ * Optionally, your view hierarchy can contain another view object of any type to
+ * display when the list view is empty. This "empty list" notifier must have an
+ * id "android:empty". Note that when an empty view is present, the list view
+ * will be hidden when there is no data to display.
+ * <p>
+ * The following code demonstrates an (ugly) custom list layout. It has a list
+ * with a green background, and an alternate red "no data" message.
+ * </p>
+ *
+ * <pre>
+ * &lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
+ * &lt;LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ *         android:orientation=&quot;vertical&quot;
+ *         android:layout_width=&quot;match_parent&quot;
+ *         android:layout_height=&quot;match_parent&quot;
+ *         android:paddingLeft=&quot;8dp&quot;
+ *         android:paddingRight=&quot;8dp&quot;&gt;
+ *
+ *     &lt;ListView android:id=&quot;@id/android:list&quot;
+ *               android:layout_width=&quot;match_parent&quot;
+ *               android:layout_height=&quot;match_parent&quot;
+ *               android:background=&quot;#00FF00&quot;
+ *               android:layout_weight=&quot;1&quot;
+ *               android:drawSelectorOnTop=&quot;false&quot;/&gt;
+ *
+ *     &lt;TextView android:id=&quot;@id/android:empty&quot;
+ *               android:layout_width=&quot;match_parent&quot;
+ *               android:layout_height=&quot;match_parent&quot;
+ *               android:background=&quot;#FF0000&quot;
+ *               android:text=&quot;No data&quot;/&gt;
+ * &lt;/LinearLayout&gt;
+ * </pre>
+ *
+ * <p>
+ * <strong>Row Layout</strong>
+ * </p>
+ * <p>
+ * You can specify the layout of individual rows in the list. You do this by
+ * specifying a layout resource in the ListAdapter object hosted by the fragment
+ * (the ListAdapter binds the ListView to the data; more on this later).
+ * <p>
+ * A ListAdapter constructor takes a parameter that specifies a layout resource
+ * for each row. It also has two additional parameters that let you specify
+ * which data field to associate with which object in the row layout resource.
+ * These two parameters are typically parallel arrays.
+ * </p>
+ * <p>
+ * Android provides some standard row layout resources. These are in the
+ * {@link android.R.layout} class, and have names such as simple_list_item_1,
+ * simple_list_item_2, and two_line_list_item. The following layout XML is the
+ * source for the resource two_line_list_item, which displays two data
+ * fields,one above the other, for each list row.
+ * </p>
+ *
+ * <pre>
+ * &lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
+ * &lt;LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ *     android:layout_width=&quot;match_parent&quot;
+ *     android:layout_height=&quot;wrap_content&quot;
+ *     android:orientation=&quot;vertical&quot;&gt;
+ *
+ *     &lt;TextView android:id=&quot;@+id/text1&quot;
+ *         android:textSize=&quot;16sp&quot;
+ *         android:textStyle=&quot;bold&quot;
+ *         android:layout_width=&quot;match_parent&quot;
+ *         android:layout_height=&quot;wrap_content&quot;/&gt;
+ *
+ *     &lt;TextView android:id=&quot;@+id/text2&quot;
+ *         android:textSize=&quot;16sp&quot;
+ *         android:layout_width=&quot;match_parent&quot;
+ *         android:layout_height=&quot;wrap_content&quot;/&gt;
+ * &lt;/LinearLayout&gt;
+ * </pre>
+ *
+ * <p>
+ * You must identify the data bound to each TextView object in this layout. The
+ * syntax for this is discussed in the next section.
+ * </p>
+ * <p>
+ * <strong>Binding to Data</strong>
+ * </p>
+ * <p>
+ * You bind the ListFragment's ListView object to data using a class that
+ * implements the {@link android.widget.ListAdapter ListAdapter} interface.
+ * Android provides two standard list adapters:
+ * {@link android.widget.SimpleAdapter SimpleAdapter} for static data (Maps),
+ * and {@link android.widget.SimpleCursorAdapter SimpleCursorAdapter} for Cursor
+ * query results.
+ * </p>
+ * <p>
+ * You <b>must</b> use
+ * {@link #setListAdapter(ListAdapter) ListFragment.setListAdapter()} to
+ * associate the list with an adapter.  Do not directly call
+ * {@link ListView#setAdapter(ListAdapter) ListView.setAdapter()} or else
+ * important initialization will be skipped.
+ * </p>
+ *
+ * @see #setListAdapter
+ * @see android.widget.ListView
+ */
+public class ListFragment extends Fragment {
+    final private Handler mHandler = new Handler();
+
+    final private Runnable mRequestFocus = new Runnable() {
+        public void run() {
+            mList.focusableViewAvailable(mList);
+        }
+    };
+    
+    final private AdapterView.OnItemClickListener mOnClickListener
+            = new AdapterView.OnItemClickListener() {
+        public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
+            onListItemClick((ListView)parent, v, position, id);
+        }
+    };
+
+    ListAdapter mAdapter;
+    ListView mList;
+    View mEmptyView;
+    TextView mStandardEmptyView;
+    View mProgressContainer;
+    View mListContainer;
+    boolean mSetEmptyText;
+    boolean mListShown;
+
+    public ListFragment() {
+    }
+
+    /**
+     * Provide default implementation to return a simple list view.  Subclasses
+     * can override to replace with their own layout.  If doing so, the
+     * returned view hierarchy <em>must</em> have a ListView whose id
+     * is {@link android.R.id#list android.R.id.list} and can optionally
+     * have a sibling view id {@link android.R.id#empty android.R.id.empty}
+     * that is to be shown when the list is empty.
+     * 
+     * <p>If you are overriding this method with your own custom content,
+     * consider including the standard layout {@link android.R.layout#list_content}
+     * in your layout file, so that you continue to retain all of the standard
+     * behavior of ListFragment.  In particular, this is currently the only
+     * way to have the built-in indeterminant progress state be shown.
+     */
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+        return inflater.inflate(com.android.internal.R.layout.list_content,
+                container, false);
+    }
+
+    /**
+     * Attach to list view once Fragment is ready to run.
+     */
+    @Override
+    public void onActivityCreated(Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+        ensureList();
+    }
+
+    /**
+     * Detach from list view.
+     */
+    @Override
+    public void onDestroyView() {
+        mHandler.removeCallbacks(mRequestFocus);
+        mList = null;
+        super.onDestroyView();
+    }
+
+    /**
+     * This method will be called when an item in the list is selected.
+     * Subclasses should override. Subclasses can call
+     * getListView().getItemAtPosition(position) if they need to access the
+     * data associated with the selected item.
+     *
+     * @param l The ListView where the click happened
+     * @param v The view that was clicked within the ListView
+     * @param position The position of the view in the list
+     * @param id The row id of the item that was clicked
+     */
+    public void onListItemClick(ListView l, View v, int position, long id) {
+    }
+
+    /**
+     * Provide the cursor for the list view.
+     */
+    public void setListAdapter(ListAdapter adapter) {
+        boolean hadAdapter = mAdapter != null;
+        mAdapter = adapter;
+        if (mList != null) {
+            mList.setAdapter(adapter);
+            if (!mListShown && !hadAdapter) {
+                // The list was hidden, and previously didn't have an
+                // adapter.  It is now time to show it.
+                setListShown(true, getView().getWindowToken() != null);
+            }
+        }
+    }
+
+    /**
+     * Set the currently selected list item to the specified
+     * position with the adapter's data
+     *
+     * @param position
+     */
+    public void setSelection(int position) {
+        ensureList();
+        mList.setSelection(position);
+    }
+
+    /**
+     * Get the position of the currently selected list item.
+     */
+    public int getSelectedItemPosition() {
+        ensureList();
+        return mList.getSelectedItemPosition();
+    }
+
+    /**
+     * Get the cursor row ID of the currently selected list item.
+     */
+    public long getSelectedItemId() {
+        ensureList();
+        return mList.getSelectedItemId();
+    }
+
+    /**
+     * Get the activity's list view widget.
+     */
+    public ListView getListView() {
+        ensureList();
+        return mList;
+    }
+
+    /**
+     * The default content for a ListFragment has a TextView that can
+     * be shown when the list is empty.  If you would like to have it
+     * shown, call this method to supply the text it should use.
+     */
+    public void setEmptyText(CharSequence text) {
+        ensureList();
+        if (mStandardEmptyView == null) {
+            throw new IllegalStateException("Can't be used with a custom content view");
+        }
+        mStandardEmptyView.setText(text);
+        if (!mSetEmptyText) {
+            mList.setEmptyView(mStandardEmptyView);
+            mSetEmptyText = true;
+        }
+    }
+    
+    /**
+     * Control whether the list is being displayed.  You can make it not
+     * displayed if you are waiting for the initial data to show in it.  During
+     * this time an indeterminant progress indicator will be shown instead.
+     * 
+     * <p>Applications do not normally need to use this themselves.  The default
+     * behavior of ListFragment is to start with the list not being shown, only
+     * showing it once an adapter is given with {@link #setListAdapter(ListAdapter)}.
+     * If the list at that point had not been shown, when it does get shown
+     * it will be do without the user ever seeing the hidden state.
+     * 
+     * @param shown If true, the list view is shown; if false, the progress
+     * indicator.  The initial value is true.
+     */
+    public void setListShown(boolean shown) {
+        setListShown(shown, true);
+    }
+    
+    /**
+     * Like {@link #setListShown(boolean)}, but no animation is used when
+     * transitioning from the previous state.
+     */
+    public void setListShownNoAnimation(boolean shown) {
+        setListShown(shown, false);
+    }
+    
+    /**
+     * Control whether the list is being displayed.  You can make it not
+     * displayed if you are waiting for the initial data to show in it.  During
+     * this time an indeterminant progress indicator will be shown instead.
+     * 
+     * @param shown If true, the list view is shown; if false, the progress
+     * indicator.  The initial value is true.
+     * @param animate If true, an animation will be used to transition to the
+     * new state.
+     */
+    private void setListShown(boolean shown, boolean animate) {
+        ensureList();
+        if (mProgressContainer == null) {
+            throw new IllegalStateException("Can't be used with a custom content view");
+        }
+        if (mListShown == shown) {
+            return;
+        }
+        mListShown = shown;
+        if (shown) {
+            if (animate) {
+                mProgressContainer.startAnimation(AnimationUtils.loadAnimation(
+                        getActivity(), android.R.anim.fade_out));
+                mListContainer.startAnimation(AnimationUtils.loadAnimation(
+                        getActivity(), android.R.anim.fade_in));
+            }
+            mProgressContainer.setVisibility(View.GONE);
+            mListContainer.setVisibility(View.VISIBLE);
+        } else {
+            if (animate) {
+                mProgressContainer.startAnimation(AnimationUtils.loadAnimation(
+                        getActivity(), android.R.anim.fade_in));
+                mListContainer.startAnimation(AnimationUtils.loadAnimation(
+                        getActivity(), android.R.anim.fade_out));
+            }
+            mProgressContainer.setVisibility(View.VISIBLE);
+            mListContainer.setVisibility(View.GONE);
+        }
+    }
+    
+    /**
+     * Get the ListAdapter associated with this activity's ListView.
+     */
+    public ListAdapter getListAdapter() {
+        return mAdapter;
+    }
+
+    private void ensureList() {
+        if (mList != null) {
+            return;
+        }
+        View root = getView();
+        if (root == null) {
+            throw new IllegalStateException("Content view not yet created");
+        }
+        if (root instanceof ListView) {
+            mList = (ListView)root;
+        } else {
+            mStandardEmptyView = (TextView)root.findViewById(
+                    com.android.internal.R.id.internalEmpty);
+            if (mStandardEmptyView == null) {
+                mEmptyView = root.findViewById(android.R.id.empty);
+            }
+            mProgressContainer = root.findViewById(com.android.internal.R.id.progressContainer);
+            mListContainer = root.findViewById(com.android.internal.R.id.listContainer);
+            View rawListView = root.findViewById(android.R.id.list);
+            if (!(rawListView instanceof ListView)) {
+                throw new RuntimeException(
+                        "Content has view with id attribute 'android.R.id.list' "
+                        + "that is not a ListView class");
+            }
+            mList = (ListView)rawListView;
+            if (mList == null) {
+                throw new RuntimeException(
+                        "Your content must have a ListView whose id attribute is " +
+                        "'android.R.id.list'");
+            }
+            if (mEmptyView != null) {
+                mList.setEmptyView(mEmptyView);
+            }
+        }
+        mListShown = true;
+        mList.setOnItemClickListener(mOnClickListener);
+        if (mAdapter != null) {
+            setListAdapter(mAdapter);
+        } else {
+            // We are starting without an adapter, so assume we won't
+            // have our data right away and start with the progress indicator.
+            if (mProgressContainer != null) {
+                setListShown(false, false);
+            }
+        }
+        mHandler.post(mRequestFocus);
+    }
+}
diff --git a/core/java/android/app/LoaderManager.java b/core/java/android/app/LoaderManager.java
new file mode 100644
index 0000000..7fd5dde
--- /dev/null
+++ b/core/java/android/app/LoaderManager.java
@@ -0,0 +1,338 @@
+/*
+ * 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.Loader;
+import android.os.Bundle;
+import android.util.SparseArray;
+
+/**
+ * Interface associated with an {@link Activity} or {@link Fragment} for managing
+ * one or more {@link android.content.Loader} instances associated with it.
+ */
+public interface LoaderManager {
+    /**
+     * Callback interface for a client to interact with the manager.
+     */
+    public interface LoaderCallbacks<D> {
+        /**
+         * Instantiate and return a new Loader for the given ID.
+         *
+         * @param id The ID whose loader is to be created.
+         * @param args Any arguments supplied by the caller.
+         * @return Return a new Loader instance that is ready to start loading.
+         */
+        public Loader<D> onCreateLoader(int id, Bundle args);
+
+        /**
+         * Called when a previously created loader has finished its load.
+         * @param loader The Loader that has finished.
+         * @param data The data generated by the Loader.
+         */
+        public void onLoadFinished(Loader<D> loader, D data);
+    }
+    
+    /**
+     * Ensures a loader is initialized and active.  If the loader doesn't
+     * already exist, one is created and (if the activity/fragment is currently
+     * started) starts the loader.  Otherwise the last created
+     * loader is re-used.
+     *
+     * <p>In either case, the given callback is associated with the loader, and
+     * will be called as the loader state changes.  If at the point of call
+     * the caller is in its started state, and the requested loader
+     * already exists and has generated its data, then
+     * callback. {@link LoaderCallbacks#onLoadFinished} will
+     * be called immediately (inside of this function), so you must be prepared
+     * for this to happen.
+     */
+    public <D> Loader<D> initLoader(int id, Bundle args,
+            LoaderManager.LoaderCallbacks<D> callback);
+
+    /**
+     * Creates a new loader in this manager, registers the callbacks to it,
+     * and (if the activity/fragment is currently started) starts loading it.
+     * If a loader with the same id has previously been
+     * started it will automatically be destroyed when the new loader completes
+     * its work. The callback will be delivered before the old loader
+     * is destroyed.
+     */
+    public <D> Loader<D> restartLoader(int id, Bundle args,
+            LoaderManager.LoaderCallbacks<D> callback);
+
+    /**
+     * Stops and removes the loader with the given ID.
+     */
+    public void stopLoader(int id);
+
+    /**
+     * Return the Loader with the given id or null if no matching Loader
+     * is found.
+     */
+    public <D> Loader<D> getLoader(int id);
+}
+
+class LoaderManagerImpl implements LoaderManager {
+    final SparseArray<LoaderInfo> mLoaders = new SparseArray<LoaderInfo>();
+    final SparseArray<LoaderInfo> mInactiveLoaders = new SparseArray<LoaderInfo>();
+    boolean mStarted;
+    boolean mRetaining;
+    boolean mRetainingStarted;
+
+    final class LoaderInfo implements Loader.OnLoadCompleteListener<Object> {
+        final int mId;
+        final Bundle mArgs;
+        LoaderManager.LoaderCallbacks<Object> mCallbacks;
+        Loader<Object> mLoader;
+        Object mData;
+        boolean mStarted;
+        boolean mRetaining;
+        boolean mRetainingStarted;
+        boolean mDestroyed;
+        boolean mListenerRegistered;
+        
+        public LoaderInfo(int id, Bundle args, LoaderManager.LoaderCallbacks<Object> callbacks) {
+            mId = id;
+            mArgs = args;
+            mCallbacks = callbacks;
+        }
+        
+        void start() {
+            if (mRetaining && mRetainingStarted) {
+                // Our owner is started, but we were being retained from a
+                // previous instance in the started state...  so there is really
+                // nothing to do here, since the loaders are still started.
+                mStarted = true;
+                return;
+            }
+
+            if (mStarted) {
+                // If loader already started, don't restart.
+                return;
+            }
+
+            if (mLoader == null && mCallbacks != null) {
+               mLoader = mCallbacks.onCreateLoader(mId, mArgs);
+            }
+            if (mLoader != null) {
+                if (!mListenerRegistered) {
+                    mLoader.registerListener(mId, this);
+                    mListenerRegistered = true;
+                }
+                mLoader.startLoading();
+                mStarted = true;
+            }
+        }
+        
+        void retain() {
+            mRetaining = true;
+            mRetainingStarted = mStarted;
+            mStarted = false;
+            mCallbacks = null;
+        }
+        
+        void finishRetain() {
+            if (mRetaining) {
+                mRetaining = false;
+                if (mStarted != mRetainingStarted) {
+                    if (!mStarted) {
+                        // This loader was retained in a started state, but
+                        // at the end of retaining everything our owner is
+                        // no longer started...  so make it stop.
+                        stop();
+                    }
+                }
+                if (mStarted && mData != null && mCallbacks != null) {
+                    // This loader was retained, and now at the point of
+                    // finishing the retain we find we remain started, have
+                    // our data, and the owner has a new callback...  so
+                    // let's deliver the data now.
+                    mCallbacks.onLoadFinished(mLoader, mData);
+                }
+            }
+        }
+        
+        void stop() {
+            mStarted = false;
+            if (mLoader != null && mListenerRegistered) {
+                // Let the loader know we're done with it
+                mListenerRegistered = false;
+                mLoader.unregisterListener(this);
+                mLoader.stopLoading();
+            }
+        }
+        
+        void destroy() {
+            mDestroyed = true;
+            mCallbacks = null;
+            if (mLoader != null) {
+                if (mListenerRegistered) {
+                    mListenerRegistered = false;
+                    mLoader.unregisterListener(this);
+                }
+                mLoader.destroy();
+            }
+        }
+        
+        @Override public void onLoadComplete(Loader<Object> loader, Object data) {
+            if (mDestroyed) {
+                return;
+            }
+            
+            // Notify of the new data so the app can switch out the old data before
+            // we try to destroy it.
+            mData = data;
+            if (mCallbacks != null) {
+                mCallbacks.onLoadFinished(loader, data);
+            }
+
+            // Look for an inactive loader and destroy it if found
+            LoaderInfo info = mInactiveLoaders.get(mId);
+            if (info != null) {
+                Loader<Object> oldLoader = info.mLoader;
+                if (oldLoader != null) {
+                    if (info.mListenerRegistered) {
+                        oldLoader.unregisterListener(info);
+                    }
+                    oldLoader.destroy();
+                }
+                mInactiveLoaders.remove(mId);
+            }
+        }
+    }
+    
+    LoaderManagerImpl(boolean started) {
+        mStarted = started;
+    }
+    
+    private LoaderInfo createLoader(int id, Bundle args,
+            LoaderManager.LoaderCallbacks<Object> callback) {
+        LoaderInfo info = new LoaderInfo(id, args,  (LoaderManager.LoaderCallbacks<Object>)callback);
+        mLoaders.put(id, info);
+        Loader<Object> loader = callback.onCreateLoader(id, args);
+        info.mLoader = (Loader<Object>)loader;
+        if (mStarted) {
+            // The activity will start all existing loaders in it's onStart(),
+            // so only start them here if we're past that point of the activitiy's
+            // life cycle
+            info.start();
+        }
+        return info;
+    }
+    
+    @SuppressWarnings("unchecked")
+    public <D> Loader<D> initLoader(int id, Bundle args, LoaderManager.LoaderCallbacks<D> callback) {
+        LoaderInfo info = mLoaders.get(id);
+        
+        if (info == null) {
+            // Loader doesn't already exist; create.
+            info = createLoader(id, args,  (LoaderManager.LoaderCallbacks<Object>)callback);
+        } else {
+            info.mCallbacks = (LoaderManager.LoaderCallbacks<Object>)callback;
+        }
+        
+        if (info.mData != null && mStarted) {
+            // If the loader has already generated its data, report it now.
+            info.mCallbacks.onLoadFinished(info.mLoader, info.mData);
+        }
+        
+        return (Loader<D>)info.mLoader;
+    }
+    
+    @SuppressWarnings("unchecked")
+    public <D> Loader<D> restartLoader(int id, Bundle args, LoaderManager.LoaderCallbacks<D> callback) {
+        LoaderInfo info = mLoaders.get(id);
+        if (info != null) {
+            if (mInactiveLoaders.get(id) != null) {
+                // We already have an inactive loader for this ID that we are
+                // waiting for!  Now we have three active loaders... let's just
+                // drop the one in the middle, since we are still waiting for
+                // its result but that result is already out of date.
+                info.destroy();
+            } else {
+                // Keep track of the previous instance of this loader so we can destroy
+                // it when the new one completes.
+                mInactiveLoaders.put(id, info);
+            }
+        }
+        
+        info = createLoader(id, args,  (LoaderManager.LoaderCallbacks<Object>)callback);
+        return (Loader<D>)info.mLoader;
+    }
+    
+    public void stopLoader(int id) {
+        int idx = mLoaders.indexOfKey(id);
+        if (idx >= 0) {
+            LoaderInfo info = mLoaders.valueAt(idx);
+            mLoaders.removeAt(idx);
+            info.destroy();
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    public <D> Loader<D> getLoader(int id) {
+        LoaderInfo loaderInfo = mLoaders.get(id);
+        if (loaderInfo != null) {
+            return (Loader<D>)mLoaders.get(id).mLoader;
+        }
+        return null;
+    }
+ 
+    void doStart() {
+        // Call out to sub classes so they can start their loaders
+        // Let the existing loaders know that we want to be notified when a load is complete
+        for (int i = mLoaders.size()-1; i >= 0; i--) {
+            mLoaders.valueAt(i).start();
+        }
+        mStarted = true;
+    }
+    
+    void doStop() {
+        for (int i = mLoaders.size()-1; i >= 0; i--) {
+            mLoaders.valueAt(i).stop();
+        }
+        mStarted = false;
+    }
+    
+    void doRetain() {
+        mRetaining = true;
+        mStarted = false;
+        for (int i = mLoaders.size()-1; i >= 0; i--) {
+            mLoaders.valueAt(i).retain();
+        }
+    }
+    
+    void finishRetain() {
+        mRetaining = false;
+        for (int i = mLoaders.size()-1; i >= 0; i--) {
+            mLoaders.valueAt(i).finishRetain();
+        }
+    }
+    
+    void doDestroy() {
+        if (!mRetaining) {
+            for (int i = mLoaders.size()-1; i >= 0; i--) {
+                mLoaders.valueAt(i).destroy();
+            }
+        }
+        
+        for (int i = mInactiveLoaders.size()-1; i >= 0; i--) {
+            mInactiveLoaders.valueAt(i).destroy();
+        }
+        mInactiveLoaders.clear();
+    }
+}
diff --git a/core/java/android/app/LoaderManagingFragment.java b/core/java/android/app/LoaderManagingFragment.java
new file mode 100644
index 0000000..5d417a0
--- /dev/null
+++ b/core/java/android/app/LoaderManagingFragment.java
@@ -0,0 +1,204 @@
+/*
+ * 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.Loader;
+import android.os.Bundle;
+
+import java.util.HashMap;
+
+/**
+ * A Fragment that has utility methods for managing {@link Loader}s.
+ *
+ * @param <D> The type of data returned by the Loader. If you're using multiple Loaders with
+ * different return types use Object and case the results.
+ */
+public abstract class LoaderManagingFragment<D> extends Fragment
+        implements Loader.OnLoadCompleteListener<D> {
+    private boolean mStarted = false;
+
+    static final class LoaderInfo<D> {
+        public Bundle args;
+        public Loader<D> loader;
+    }
+    private HashMap<Integer, LoaderInfo<D>> mLoaders;
+    private HashMap<Integer, LoaderInfo<D>> mInactiveLoaders;
+
+    /**
+     * Registers a loader with this activity, registers the callbacks on it, and starts it loading.
+     * If a loader with the same id has previously been started it will automatically be destroyed
+     * when the new loader completes it's work. The callback will be delivered before the old loader
+     * is destroyed.
+     */
+    public Loader<D> startLoading(int id, Bundle args) {
+        LoaderInfo<D> info = mLoaders.get(id);
+        if (info != null) {
+            // Keep track of the previous instance of this loader so we can destroy
+            // it when the new one completes.
+            mInactiveLoaders.put(id, info);
+        }
+        info = new LoaderInfo<D>();
+        info.args = args;
+        mLoaders.put(id, info);
+        Loader<D> loader = onCreateLoader(id, args);
+        info.loader = loader;
+        if (mStarted) {
+            // The activity will start all existing loaders in it's onStart(), so only start them
+            // here if we're past that point of the activitiy's life cycle
+            loader.registerListener(id, this);
+            loader.startLoading();
+        }
+        return loader;
+    }
+
+    protected abstract Loader<D> onCreateLoader(int id, Bundle args);
+    protected abstract void onInitializeLoaders();
+    protected abstract void onLoadFinished(Loader<D> loader, D data);
+
+    public final void onLoadComplete(Loader<D> loader, D data) {
+        // Notify of the new data so the app can switch out the old data before
+        // we try to destroy it.
+        onLoadFinished(loader, data);
+
+        // Look for an inactive loader and destroy it if found
+        int id = loader.getId();
+        LoaderInfo<D> info = mInactiveLoaders.get(id);
+        if (info != null) {
+            Loader<D> oldLoader = info.loader;
+            if (oldLoader != null) {
+                oldLoader.destroy();
+            }
+            mInactiveLoaders.remove(id);
+        }
+    }
+
+    @Override
+    public void onCreate(Bundle savedState) {
+        super.onCreate(savedState);
+
+        if (mLoaders == null) {
+            // Look for a passed along loader and create a new one if it's not there
+// TODO: uncomment once getLastNonConfigurationInstance method is available
+//            mLoaders = (HashMap<Integer, LoaderInfo>) getLastNonConfigurationInstance();
+            if (mLoaders == null) {
+                mLoaders = new HashMap<Integer, LoaderInfo<D>>();
+                onInitializeLoaders();
+            }
+        }
+        if (mInactiveLoaders == null) {
+            mInactiveLoaders = new HashMap<Integer, LoaderInfo<D>>();
+        }
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+
+        // Call out to sub classes so they can start their loaders
+        // Let the existing loaders know that we want to be notified when a load is complete
+        for (HashMap.Entry<Integer, LoaderInfo<D>> entry : mLoaders.entrySet()) {
+            LoaderInfo<D> info = entry.getValue();
+            Loader<D> loader = info.loader;
+            int id = entry.getKey();
+            if (loader == null) {
+               loader = onCreateLoader(id, info.args);
+               info.loader = loader;
+            }
+            loader.registerListener(id, this);
+            loader.startLoading();
+        }
+
+        mStarted = true;
+    }
+
+    @Override
+    public void onStop() {
+        super.onStop();
+
+        for (HashMap.Entry<Integer, LoaderInfo<D>> entry : mLoaders.entrySet()) {
+            LoaderInfo<D> info = entry.getValue();
+            Loader<D> loader = info.loader;
+            if (loader == null) {
+                continue;
+            }
+
+            // Let the loader know we're done with it
+            loader.unregisterListener(this);
+
+            // The loader isn't getting passed along to the next instance so ask it to stop loading
+            if (!getActivity().isChangingConfigurations()) {
+                loader.stopLoading();
+            }
+        }
+
+        mStarted = false;
+    }
+
+    /** TO DO: This needs to be turned into a retained fragment.
+    @Override
+    public Object onRetainNonConfigurationInstance() {
+        // Pass the loader along to the next guy
+        Object result = mLoaders;
+        mLoaders = null;
+        return result;
+    }
+    **/
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+
+        if (mLoaders != null) {
+            for (HashMap.Entry<Integer, LoaderInfo<D>> entry : mLoaders.entrySet()) {
+                LoaderInfo<D> info = entry.getValue();
+                Loader<D> loader = info.loader;
+                if (loader == null) {
+                    continue;
+                }
+                loader.destroy();
+            }
+        }
+    }
+
+    /**
+     * Stops and removes the loader with the given ID.
+     */
+    public void stopLoading(int id) {
+        if (mLoaders != null) {
+            LoaderInfo<D> info = mLoaders.remove(id);
+            if (info != null) {
+                Loader<D> loader = info.loader;
+                if (loader != null) {
+                    loader.unregisterListener(this);
+                    loader.destroy();
+                }
+            }
+        }
+    }
+
+    /**
+     * @return the Loader with the given id or null if no matching Loader
+     * is found.
+     */
+    public Loader<D> getLoader(int id) {
+        LoaderInfo<D> loaderInfo = mLoaders.get(id);
+        if (loaderInfo != null) {
+            return mLoaders.get(id).loader;
+        }
+        return null;
+    }
+}
diff --git a/core/java/android/app/LocalActivityManager.java b/core/java/android/app/LocalActivityManager.java
index a24fcae..524de6f 100644
--- a/core/java/android/app/LocalActivityManager.java
+++ b/core/java/android/app/LocalActivityManager.java
@@ -20,13 +20,11 @@
 import android.content.pm.ActivityInfo;
 import android.os.Binder;
 import android.os.Bundle;
-import android.util.Config;
 import android.util.Log;
 import android.view.Window;
 
 import java.util.ArrayList;
 import java.util.HashMap;
-import java.util.Iterator;
 import java.util.Map;
 
 /**
@@ -38,7 +36,7 @@
  */
 public class LocalActivityManager {
     private static final String TAG = "LocalActivityManager";
-    private static final boolean localLOGV = false || Config.LOGV;
+    private static final boolean localLOGV = false;
 
     // Internal token for an Activity being managed by LocalActivityManager.
     private static class LocalActivityRecord extends Binder {
@@ -112,11 +110,16 @@
         
         if (r.curState == INITIALIZING) {
             // Get the lastNonConfigurationInstance for the activity
-            HashMap<String,Object> lastNonConfigurationInstances =
-                mParent.getLastNonConfigurationChildInstances();
-            Object instance = null;
+            HashMap<String, Object> lastNonConfigurationInstances =
+                    mParent.getLastNonConfigurationChildInstances();
+            Object instanceObj = null;
             if (lastNonConfigurationInstances != null) {
-                instance = lastNonConfigurationInstances.get(r.id);
+                instanceObj = lastNonConfigurationInstances.get(r.id);
+            }
+            Activity.NonConfigurationInstances instance = null;
+            if (instanceObj != null) {
+                instance = new Activity.NonConfigurationInstances();
+                instance.activity = instanceObj;
             }
             
             // We need to have always created the activity.
@@ -346,7 +349,7 @@
     }
 
     private Window performDestroy(LocalActivityRecord r, boolean finish) {
-        Window win = null;
+        Window win;
         win = r.window;
         if (r.curState == RESUMED && !finish) {
             performPause(r, finish);
@@ -380,7 +383,8 @@
         if (r != null) {
             win = performDestroy(r, finish);
             if (finish) {
-                mActivities.remove(r);
+                mActivities.remove(id);
+                mActivityArray.remove(r);
             }
         }
         return win;
@@ -441,10 +445,8 @@
      */
     public void dispatchCreate(Bundle state) {
         if (state != null) {
-            final Iterator<String> i = state.keySet().iterator();
-            while (i.hasNext()) {
+            for (String id : state.keySet()) {
                 try {
-                    final String id = i.next();
                     final Bundle astate = state.getBundle(id);
                     LocalActivityRecord r = mActivities.get(id);
                     if (r != null) {
@@ -457,9 +459,7 @@
                     }
                 } catch (Exception e) {
                     // Recover from -all- app errors.
-                    Log.e(TAG,
-                          "Exception thrown when restoring LocalActivityManager state",
-                          e);
+                    Log.e(TAG, "Exception thrown when restoring LocalActivityManager state", e);
                 }
             }
         }
diff --git a/core/java/android/app/admin/DeviceAdminInfo.java b/core/java/android/app/admin/DeviceAdminInfo.java
index 0bcd65c..2237c82 100644
--- a/core/java/android/app/admin/DeviceAdminInfo.java
+++ b/core/java/android/app/admin/DeviceAdminInfo.java
@@ -103,6 +103,15 @@
      */
     public static final int USES_POLICY_WIPE_DATA = 4;
 
+    /**
+     * A type of policy that this device admin can use: able to specify the
+     * device Global Proxy, via {@link DevicePolicyManager#setGlobalProxy}.
+     *
+     * <p>To control this policy, the device admin must have a "set-global-proxy"
+     * tag in the "uses-policies" section of its meta-data.
+     */
+    public static final int USES_POLICY_SETS_GLOBAL_PROXY = 5;
+
     /** @hide */
     public static class PolicyInfo {
         public final int ident;
@@ -138,6 +147,9 @@
         sPoliciesDisplayOrder.add(new PolicyInfo(USES_POLICY_FORCE_LOCK, "force-lock",
                 com.android.internal.R.string.policylab_forceLock,
                 com.android.internal.R.string.policydesc_forceLock));
+        sPoliciesDisplayOrder.add(new PolicyInfo(USES_POLICY_SETS_GLOBAL_PROXY, "set-global-proxy",
+                com.android.internal.R.string.policylab_setGlobalProxy,
+                com.android.internal.R.string.policydesc_setGlobalProxy));
         
         for (int i=0; i<sPoliciesDisplayOrder.size(); i++) {
             PolicyInfo pi = sPoliciesDisplayOrder.get(i);
@@ -328,7 +340,7 @@
      * the given policy control.  The possible policy identifier inputs are:
      * {@link #USES_POLICY_LIMIT_PASSWORD}, {@link #USES_POLICY_WATCH_LOGIN},
      * {@link #USES_POLICY_RESET_PASSWORD}, {@link #USES_POLICY_FORCE_LOCK},
-     * {@link #USES_POLICY_WIPE_DATA}.
+     * {@link #USES_POLICY_WIPE_DATA}, {@link #USES_POLICY_SETS_GLOBAL_PROXY}.
      */
     public boolean usesPolicy(int policyIdent) {
         return (mUsesPolicies & (1<<policyIdent)) != 0;
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 296d70a4..2b7e427 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -32,6 +32,8 @@
 import android.util.Log;
 
 import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.Proxy;
 import java.util.List;
 
 /**
@@ -46,7 +48,7 @@
 
     private final Context mContext;
     private final IDevicePolicyManager mService;
-    
+
     private final Handler mHandler;
 
     private DevicePolicyManager(Context context, Handler handler) {
@@ -61,14 +63,14 @@
         DevicePolicyManager me = new DevicePolicyManager(context, handler);
         return me.mService != null ? me : null;
     }
-    
+
     /**
      * Activity action: ask the user to add a new device administrator to the system.
      * The desired policy is the ComponentName of the policy in the
      * {@link #EXTRA_DEVICE_ADMIN} extra field.  This will invoke a UI to
      * bring the user through adding the device administrator to the system (or
      * allowing them to reject it).
-     * 
+     *
      * <p>You can optionally include the {@link #EXTRA_ADD_EXPLANATION}
      * field to provide the user with additional explanation (in addition
      * to your component's description) about what is being added.
@@ -76,7 +78,7 @@
     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
     public static final String ACTION_ADD_DEVICE_ADMIN
             = "android.app.action.ADD_DEVICE_ADMIN";
-    
+
     /**
      * Activity action: send when any policy admin changes a policy.
      * This is generally used to find out when a new policy is in effect.
@@ -92,7 +94,7 @@
      * @see #ACTION_ADD_DEVICE_ADMIN
      */
     public static final String EXTRA_DEVICE_ADMIN = "android.app.extra.DEVICE_ADMIN";
-    
+
     /**
      * An optional CharSequence providing additional explanation for why the
      * admin is being added.
@@ -100,22 +102,21 @@
      * @see #ACTION_ADD_DEVICE_ADMIN
      */
     public static final String EXTRA_ADD_EXPLANATION = "android.app.extra.ADD_EXPLANATION";
-    
+
     /**
-     * Activity action: have the user enter a new password.  This activity
-     * should be launched after using {@link #setPasswordQuality(ComponentName, int)}
-     * or {@link #setPasswordMinimumLength(ComponentName, int)} to have the
-     * user enter a new password that meets the current requirements.  You can
-     * use {@link #isActivePasswordSufficient()} to determine whether you need
-     * to have the user select a new password in order to meet the current
-     * constraints.  Upon being resumed from this activity,
-     * you can check the new password characteristics to see if they are
-     * sufficient.
+     * Activity action: have the user enter a new password. This activity should
+     * be launched after using {@link #setPasswordQuality(ComponentName, int)},
+     * or {@link #setPasswordMinimumLength(ComponentName, int)} to have the user
+     * enter a new password that meets the current requirements. You can use
+     * {@link #isActivePasswordSufficient()} to determine whether you need to
+     * have the user select a new password in order to meet the current
+     * constraints. Upon being resumed from this activity, you can check the new
+     * password characteristics to see if they are sufficient.
      */
     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
     public static final String ACTION_SET_NEW_PASSWORD
             = "android.app.action.SET_NEW_PASSWORD";
-    
+
     /**
      * Return true if the given administrator component is currently
      * active (enabled) in the system.
@@ -130,7 +131,7 @@
         }
         return false;
     }
-    
+
     /**
      * Return a list of all currently active device administrator's component
      * names.  Note that if there are no administrators than null may be
@@ -146,7 +147,7 @@
         }
         return null;
     }
-    
+
     /**
      * @hide
      */
@@ -160,7 +161,7 @@
         }
         return false;
     }
-    
+
     /**
      * Remove a current administration component.  This can only be called
      * by the application that owns the administration component; if you
@@ -176,28 +177,28 @@
             }
         }
     }
-    
+
     /**
      * Constant for {@link #setPasswordQuality}: the policy has no requirements
      * for the password.  Note that quality constants are ordered so that higher
      * values are more restrictive.
      */
     public static final int PASSWORD_QUALITY_UNSPECIFIED = 0;
-    
+
     /**
      * Constant for {@link #setPasswordQuality}: the policy requires some kind
      * of password, but doesn't care what it is.  Note that quality constants
      * are ordered so that higher values are more restrictive.
      */
     public static final int PASSWORD_QUALITY_SOMETHING = 0x10000;
-    
+
     /**
      * Constant for {@link #setPasswordQuality}: the user must have entered a
      * password containing at least numeric characters.  Note that quality
      * constants are ordered so that higher values are more restrictive.
      */
     public static final int PASSWORD_QUALITY_NUMERIC = 0x20000;
-    
+
     /**
      * Constant for {@link #setPasswordQuality}: the user must have entered a
      * password containing at least alphabetic (or other symbol) characters.
@@ -205,7 +206,7 @@
      * restrictive.
      */
     public static final int PASSWORD_QUALITY_ALPHABETIC = 0x40000;
-    
+
     /**
      * Constant for {@link #setPasswordQuality}: the user must have entered a
      * password containing at least <em>both></em> numeric <em>and</em>
@@ -213,7 +214,19 @@
      * ordered so that higher values are more restrictive.
      */
     public static final int PASSWORD_QUALITY_ALPHANUMERIC = 0x50000;
-    
+
+    /**
+     * Constant for {@link #setPasswordQuality}: the user must have entered a
+     * password containing at least a letter, a numerical digit and a special
+     * symbol, by default. With this password quality, passwords can be
+     * restricted to contain various sets of characters, like at least an
+     * uppercase letter, etc. These are specified using various methods,
+     * like {@link #setPasswordMinimumLowerCase(ComponentName, int)}. Note
+     * that quality constants are ordered so that higher values are more
+     * restrictive.
+     */
+    public static final int PASSWORD_QUALITY_COMPLEX = 0x60000;
+
     /**
      * Called by an application that is administering the device to set the
      * password restrictions it is imposing.  After setting this, the user
@@ -222,21 +235,21 @@
      * will remain until the user has set a new one, so the change does not
      * take place immediately.  To prompt the user for a new password, use
      * {@link #ACTION_SET_NEW_PASSWORD} after setting this value.
-     * 
+     *
      * <p>Quality constants are ordered so that higher values are more restrictive;
      * thus the highest requested quality constant (between the policy set here,
      * the user's preference, and any other considerations) is the one that
      * is in effect.
-     * 
+     *
      * <p>The calling device admin must have requested
      * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call
      * this method; if it has not, a security exception will be thrown.
-     * 
+     *
      * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
      * @param quality The new desired quality.  One of
      * {@link #PASSWORD_QUALITY_UNSPECIFIED}, {@link #PASSWORD_QUALITY_SOMETHING},
      * {@link #PASSWORD_QUALITY_NUMERIC}, {@link #PASSWORD_QUALITY_ALPHABETIC},
-     * or {@link #PASSWORD_QUALITY_ALPHANUMERIC}.
+     * {@link #PASSWORD_QUALITY_ALPHANUMERIC} or {@link #PASSWORD_QUALITY_COMPLEX}.
      */
     public void setPasswordQuality(ComponentName admin, int quality) {
         if (mService != null) {
@@ -247,7 +260,7 @@
             }
         }
     }
-    
+
     /**
      * Retrieve the current minimum password quality for all admins
      * or a particular one.
@@ -264,7 +277,7 @@
         }
         return PASSWORD_QUALITY_UNSPECIFIED;
     }
-    
+
     /**
      * Called by an application that is administering the device to set the
      * minimum allowed password length.  After setting this, the user
@@ -274,14 +287,14 @@
      * take place immediately.  To prompt the user for a new password, use
      * {@link #ACTION_SET_NEW_PASSWORD} after setting this value.  This
      * constraint is only imposed if the administrator has also requested either
-     * {@link #PASSWORD_QUALITY_NUMERIC}, {@link #PASSWORD_QUALITY_ALPHABETIC},
-     * or {@link #PASSWORD_QUALITY_ALPHANUMERIC}
+     * {@link #PASSWORD_QUALITY_NUMERIC}, {@link #PASSWORD_QUALITY_ALPHABETIC}
+     * {@link #PASSWORD_QUALITY_ALPHANUMERIC}, or {@link #PASSWORD_QUALITY_COMPLEX}
      * with {@link #setPasswordQuality}.
-     * 
+     *
      * <p>The calling device admin must have requested
      * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call
      * this method; if it has not, a security exception will be thrown.
-     * 
+     *
      * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
      * @param length The new desired minimum password length.  A value of 0
      * means there is no restriction.
@@ -295,7 +308,7 @@
             }
         }
     }
-    
+
     /**
      * Retrieve the current minimum password length for all admins
      * or a particular one.
@@ -312,7 +325,379 @@
         }
         return 0;
     }
-    
+
+    /**
+     * Called by an application that is administering the device to set the
+     * minimum number of upper case letters required in the password. After
+     * setting this, the user will not be able to enter a new password that is
+     * not at least as restrictive as what has been set. Note that the current
+     * password will remain until the user has set a new one, so the change does
+     * not take place immediately. To prompt the user for a new password, use
+     * {@link #ACTION_SET_NEW_PASSWORD} after setting this value. This
+     * constraint is only imposed if the administrator has also requested
+     * {@link #PASSWORD_QUALITY_COMPLEX} with {@link #setPasswordQuality}. The
+     * default value is 0.
+     * <p>
+     * The calling device admin must have requested
+     * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call
+     * this method; if it has not, a security exception will be thrown.
+     *
+     * @param admin Which {@link DeviceAdminReceiver} this request is associated
+     *            with.
+     * @param length The new desired minimum number of upper case letters
+     *            required in the password. A value of 0 means there is no
+     *            restriction.
+     */
+    public void setPasswordMinimumUpperCase(ComponentName admin, int length) {
+        if (mService != null) {
+            try {
+                mService.setPasswordMinimumUpperCase(admin, length);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed talking with device policy service", e);
+            }
+        }
+    }
+
+    /**
+     * Retrieve the current number of upper case letters required in the
+     * password for all admins or a particular one. This is the same value as
+     * set by {#link {@link #setPasswordMinimumUpperCase(ComponentName, int)}
+     * and only applies when the password quality is
+     * {@link #PASSWORD_QUALITY_COMPLEX}.
+     *
+     * @param admin The name of the admin component to check, or null to
+     *            aggregate all admins.
+     * @return The minimum number of upper case letters required in the
+     *         password.
+     */
+    public int getPasswordMinimumUpperCase(ComponentName admin) {
+        if (mService != null) {
+            try {
+                return mService.getPasswordMinimumUpperCase(admin);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed talking with device policy service", e);
+            }
+        }
+        return 0;
+    }
+
+    /**
+     * Called by an application that is administering the device to set the
+     * minimum number of lower case letters required in the password. After
+     * setting this, the user will not be able to enter a new password that is
+     * not at least as restrictive as what has been set. Note that the current
+     * password will remain until the user has set a new one, so the change does
+     * not take place immediately. To prompt the user for a new password, use
+     * {@link #ACTION_SET_NEW_PASSWORD} after setting this value. This
+     * constraint is only imposed if the administrator has also requested
+     * {@link #PASSWORD_QUALITY_COMPLEX} with {@link #setPasswordQuality}. The
+     * default value is 0.
+     * <p>
+     * The calling device admin must have requested
+     * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call
+     * this method; if it has not, a security exception will be thrown.
+     *
+     * @param admin Which {@link DeviceAdminReceiver} this request is associated
+     *            with.
+     * @param length The new desired minimum number of lower case letters
+     *            required in the password. A value of 0 means there is no
+     *            restriction.
+     */
+    public void setPasswordMinimumLowerCase(ComponentName admin, int length) {
+        if (mService != null) {
+            try {
+                mService.setPasswordMinimumLowerCase(admin, length);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed talking with device policy service", e);
+            }
+        }
+    }
+
+    /**
+     * Retrieve the current number of lower case letters required in the
+     * password for all admins or a particular one. This is the same value as
+     * set by {#link {@link #setPasswordMinimumLowerCase(ComponentName, int)}
+     * and only applies when the password quality is
+     * {@link #PASSWORD_QUALITY_COMPLEX}.
+     *
+     * @param admin The name of the admin component to check, or null to
+     *            aggregate all admins.
+     * @return The minimum number of lower case letters required in the
+     *         password.
+     */
+    public int getPasswordMinimumLowerCase(ComponentName admin) {
+        if (mService != null) {
+            try {
+                return mService.getPasswordMinimumLowerCase(admin);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed talking with device policy service", e);
+            }
+        }
+        return 0;
+    }
+
+    /**
+     * Called by an application that is administering the device to set the
+     * minimum number of letters required in the password. After setting this,
+     * the user will not be able to enter a new password that is not at least as
+     * restrictive as what has been set. Note that the current password will
+     * remain until the user has set a new one, so the change does not take
+     * place immediately. To prompt the user for a new password, use
+     * {@link #ACTION_SET_NEW_PASSWORD} after setting this value. This
+     * constraint is only imposed if the administrator has also requested
+     * {@link #PASSWORD_QUALITY_COMPLEX} with {@link #setPasswordQuality}. The
+     * default value is 1.
+     * <p>
+     * The calling device admin must have requested
+     * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call
+     * this method; if it has not, a security exception will be thrown.
+     *
+     * @param admin Which {@link DeviceAdminReceiver} this request is associated
+     *            with.
+     * @param length The new desired minimum number of letters required in the
+     *            password. A value of 0 means there is no restriction.
+     */
+    public void setPasswordMinimumLetters(ComponentName admin, int length) {
+        if (mService != null) {
+            try {
+                mService.setPasswordMinimumLetters(admin, length);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed talking with device policy service", e);
+            }
+        }
+    }
+
+    /**
+     * Retrieve the current number of letters required in the password for all
+     * admins or a particular one. This is the same value as
+     * set by {#link {@link #setPasswordMinimumLetters(ComponentName, int)}
+     * and only applies when the password quality is
+     * {@link #PASSWORD_QUALITY_COMPLEX}.
+     *
+     * @param admin The name of the admin component to check, or null to
+     *            aggregate all admins.
+     * @return The minimum number of letters required in the password.
+     */
+    public int getPasswordMinimumLetters(ComponentName admin) {
+        if (mService != null) {
+            try {
+                return mService.getPasswordMinimumLetters(admin);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed talking with device policy service", e);
+            }
+        }
+        return 0;
+    }
+
+    /**
+     * Called by an application that is administering the device to set the
+     * minimum number of numerical digits required in the password. After
+     * setting this, the user will not be able to enter a new password that is
+     * not at least as restrictive as what has been set. Note that the current
+     * password will remain until the user has set a new one, so the change does
+     * not take place immediately. To prompt the user for a new password, use
+     * {@link #ACTION_SET_NEW_PASSWORD} after setting this value. This
+     * constraint is only imposed if the administrator has also requested
+     * {@link #PASSWORD_QUALITY_COMPLEX} with {@link #setPasswordQuality}. The
+     * default value is 1.
+     * <p>
+     * The calling device admin must have requested
+     * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call
+     * this method; if it has not, a security exception will be thrown.
+     *
+     * @param admin Which {@link DeviceAdminReceiver} this request is associated
+     *            with.
+     * @param length The new desired minimum number of numerical digits required
+     *            in the password. A value of 0 means there is no restriction.
+     */
+    public void setPasswordMinimumNumeric(ComponentName admin, int length) {
+        if (mService != null) {
+            try {
+                mService.setPasswordMinimumNumeric(admin, length);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed talking with device policy service", e);
+            }
+        }
+    }
+
+    /**
+     * Retrieve the current number of numerical digits required in the password
+     * for all admins or a particular one. This is the same value as
+     * set by {#link {@link #setPasswordMinimumNumeric(ComponentName, int)}
+     * and only applies when the password quality is
+     * {@link #PASSWORD_QUALITY_COMPLEX}.
+     *
+     * @param admin The name of the admin component to check, or null to
+     *            aggregate all admins.
+     * @return The minimum number of numerical digits required in the password.
+     */
+    public int getPasswordMinimumNumeric(ComponentName admin) {
+        if (mService != null) {
+            try {
+                return mService.getPasswordMinimumNumeric(admin);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed talking with device policy service", e);
+            }
+        }
+        return 0;
+    }
+
+    /**
+     * Called by an application that is administering the device to set the
+     * minimum number of symbols required in the password. After setting this,
+     * the user will not be able to enter a new password that is not at least as
+     * restrictive as what has been set. Note that the current password will
+     * remain until the user has set a new one, so the change does not take
+     * place immediately. To prompt the user for a new password, use
+     * {@link #ACTION_SET_NEW_PASSWORD} after setting this value. This
+     * constraint is only imposed if the administrator has also requested
+     * {@link #PASSWORD_QUALITY_COMPLEX} with {@link #setPasswordQuality}. The
+     * default value is 1.
+     * <p>
+     * The calling device admin must have requested
+     * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call
+     * this method; if it has not, a security exception will be thrown.
+     *
+     * @param admin Which {@link DeviceAdminReceiver} this request is associated
+     *            with.
+     * @param length The new desired minimum number of symbols required in the
+     *            password. A value of 0 means there is no restriction.
+     */
+    public void setPasswordMinimumSymbols(ComponentName admin, int length) {
+        if (mService != null) {
+            try {
+                mService.setPasswordMinimumSymbols(admin, length);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed talking with device policy service", e);
+            }
+        }
+    }
+
+    /**
+     * Retrieve the current number of symbols required in the password for all
+     * admins or a particular one. This is the same value as
+     * set by {#link {@link #setPasswordMinimumSymbols(ComponentName, int)}
+     * and only applies when the password quality is
+     * {@link #PASSWORD_QUALITY_COMPLEX}.
+     *
+     * @param admin The name of the admin component to check, or null to
+     *            aggregate all admins.
+     * @return The minimum number of symbols required in the password.
+     */
+    public int getPasswordMinimumSymbols(ComponentName admin) {
+        if (mService != null) {
+            try {
+                return mService.getPasswordMinimumSymbols(admin);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed talking with device policy service", e);
+            }
+        }
+        return 0;
+    }
+
+    /**
+     * Called by an application that is administering the device to set the
+     * minimum number of non-letter characters (numerical digits or symbols)
+     * required in the password. After setting this, the user will not be able
+     * to enter a new password that is not at least as restrictive as what has
+     * been set. Note that the current password will remain until the user has
+     * set a new one, so the change does not take place immediately. To prompt
+     * the user for a new password, use {@link #ACTION_SET_NEW_PASSWORD} after
+     * setting this value. This constraint is only imposed if the administrator
+     * has also requested {@link #PASSWORD_QUALITY_COMPLEX} with
+     * {@link #setPasswordQuality}. The default value is 0.
+     * <p>
+     * The calling device admin must have requested
+     * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call
+     * this method; if it has not, a security exception will be thrown.
+     *
+     * @param admin Which {@link DeviceAdminReceiver} this request is associated
+     *            with.
+     * @param length The new desired minimum number of letters required in the
+     *            password. A value of 0 means there is no restriction.
+     */
+    public void setPasswordMinimumNonLetter(ComponentName admin, int length) {
+        if (mService != null) {
+            try {
+                mService.setPasswordMinimumNonLetter(admin, length);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed talking with device policy service", e);
+            }
+        }
+    }
+
+    /**
+     * Retrieve the current number of non-letter characters required in the
+     * password for all admins or a particular one. This is the same value as
+     * set by {#link {@link #setPasswordMinimumNonLetter(ComponentName, int)}
+     * and only applies when the password quality is
+     * {@link #PASSWORD_QUALITY_COMPLEX}.
+     *
+     * @param admin The name of the admin component to check, or null to
+     *            aggregate all admins.
+     * @return The minimum number of letters required in the password.
+     */
+    public int getPasswordMinimumNonLetter(ComponentName admin) {
+        if (mService != null) {
+            try {
+                return mService.getPasswordMinimumNonLetter(admin);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed talking with device policy service", e);
+            }
+        }
+        return 0;
+    }
+
+  /**
+   * Called by an application that is administering the device to set the length
+   * of the password history. After setting this, the user will not be able to
+   * enter a new password that is the same as any password in the history. Note
+   * that the current password will remain until the user has set a new one, so
+   * the change does not take place immediately. To prompt the user for a new
+   * password, use {@link #ACTION_SET_NEW_PASSWORD} after setting this value.
+   * This constraint is only imposed if the administrator has also requested
+   * either {@link #PASSWORD_QUALITY_NUMERIC},
+   * {@link #PASSWORD_QUALITY_ALPHABETIC}, or
+   * {@link #PASSWORD_QUALITY_ALPHANUMERIC} with {@link #setPasswordQuality}.
+   *
+   * <p>
+   * The calling device admin must have requested
+   * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call this
+   * method; if it has not, a security exception will be thrown.
+   *
+   * @param admin Which {@link DeviceAdminReceiver} this request is associated
+   *        with.
+   * @param length The new desired length of password history. A value of 0
+   *        means there is no restriction.
+   */
+    public void setPasswordHistoryLength(ComponentName admin, int length) {
+        if (mService != null) {
+            try {
+                mService.setPasswordHistoryLength(admin, length);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed talking with device policy service", e);
+            }
+        }
+    }
+
+    /**
+     * Retrieve the current password history length for all admins
+     * or a particular one.
+     * @param admin The name of the admin component to check, or null to aggregate
+     * all admins.
+     * @return The length of the password history
+     */
+    public int getPasswordHistoryLength(ComponentName admin) {
+        if (mService != null) {
+            try {
+                return mService.getPasswordHistoryLength(admin);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed talking with device policy service", e);
+            }
+        }
+        return 0;
+    }
+
     /**
      * Return the maximum password length that the device supports for a
      * particular password quality.
@@ -323,16 +708,16 @@
         // Kind-of arbitrary.
         return 16;
     }
-    
+
     /**
      * Determine whether the current password the user has set is sufficient
      * to meet the policy requirements (quality, minimum length) that have been
      * requested.
-     * 
+     *
      * <p>The calling device admin must have requested
      * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call
      * this method; if it has not, a security exception will be thrown.
-     * 
+     *
      * @return Returns true if the password meets the current requirements,
      * else false.
      */
@@ -346,11 +731,11 @@
         }
         return false;
     }
-    
+
     /**
      * Retrieve the number of times the user has failed at entering a
      * password since that last successful password entry.
-     * 
+     *
      * <p>The calling device admin must have requested
      * {@link DeviceAdminInfo#USES_POLICY_WATCH_LOGIN} to be able to call
      * this method; if it has not, a security exception will be thrown.
@@ -373,14 +758,14 @@
      * watching for failed passwords and wiping the device, and requires
      * that you request both {@link DeviceAdminInfo#USES_POLICY_WATCH_LOGIN} and
      * {@link DeviceAdminInfo#USES_POLICY_WIPE_DATA}}.
-     * 
+     *
      * <p>To implement any other policy (e.g. wiping data for a particular
      * application only, erasing or revoking credentials, or reporting the
      * failure to a server), you should implement
      * {@link DeviceAdminReceiver#onPasswordFailed(Context, android.content.Intent)}
      * instead.  Do not use this API, because if the maximum count is reached,
      * the device will be wiped immediately, and your callback will not be invoked.
-     * 
+     *
      * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
      * @param num The number of failed password attempts at which point the
      * device will wipe its data.
@@ -394,7 +779,7 @@
             }
         }
     }
-    
+
     /**
      * Retrieve the current maximum number of login attempts that are allowed
      * before the device wipes itself, for all admins
@@ -412,13 +797,13 @@
         }
         return 0;
     }
-    
+
     /**
      * Flag for {@link #resetPassword}: don't allow other admins to change
      * the password again until the user has entered it.
      */
     public static final int RESET_PASSWORD_REQUIRE_ENTRY = 0x0001;
-    
+
     /**
      * Force a new device unlock password (the password needed to access the
      * entire device, not for individual accounts) on the user.  This takes
@@ -431,11 +816,11 @@
      * that the password may be a stronger quality (containing alphanumeric
      * characters when the requested quality is only numeric), in which case
      * the currently active quality will be increased to match.
-     * 
+     *
      * <p>The calling device admin must have requested
      * {@link DeviceAdminInfo#USES_POLICY_RESET_PASSWORD} to be able to call
      * this method; if it has not, a security exception will be thrown.
-     * 
+     *
      * @param password The new password for the user.
      * @param flags May be 0 or {@link #RESET_PASSWORD_REQUIRE_ENTRY}.
      * @return Returns true if the password was applied, or false if it is
@@ -451,16 +836,16 @@
         }
         return false;
     }
-    
+
     /**
      * Called by an application that is administering the device to set the
      * maximum time for user activity until the device will lock.  This limits
      * the length that the user can set.  It takes effect immediately.
-     * 
+     *
      * <p>The calling device admin must have requested
      * {@link DeviceAdminInfo#USES_POLICY_FORCE_LOCK} to be able to call
      * this method; if it has not, a security exception will be thrown.
-     * 
+     *
      * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
      * @param timeMs The new desired maximum time to lock in milliseconds.
      * A value of 0 means there is no restriction.
@@ -474,7 +859,7 @@
             }
         }
     }
-    
+
     /**
      * Retrieve the current maximum time to unlock for all admins
      * or a particular one.
@@ -491,11 +876,11 @@
         }
         return 0;
     }
-    
+
     /**
      * Make the device lock immediately, as if the lock screen timeout has
      * expired at the point of this call.
-     * 
+     *
      * <p>The calling device admin must have requested
      * {@link DeviceAdminInfo#USES_POLICY_FORCE_LOCK} to be able to call
      * this method; if it has not, a security exception will be thrown.
@@ -509,16 +894,16 @@
             }
         }
     }
-    
+
     /**
      * Ask the user date be wiped.  This will cause the device to reboot,
      * erasing all user data while next booting up.  External storage such
      * as SD cards will not be erased.
-     * 
+     *
      * <p>The calling device admin must have requested
      * {@link DeviceAdminInfo#USES_POLICY_WIPE_DATA} to be able to call
      * this method; if it has not, a security exception will be thrown.
-     * 
+     *
      * @param flags Bit mask of additional options: currently must be 0.
      */
     public void wipeData(int flags) {
@@ -530,7 +915,92 @@
             }
         }
     }
-    
+
+    /**
+     * Called by an application that is administering the device to set the
+     * global proxy and exclusion list.
+     * <p>
+     * The calling device admin must have requested
+     * {@link DeviceAdminInfo#USES_POLICY_SETS_GLOBAL_PROXY} to be able to call
+     * this method; if it has not, a security exception will be thrown.
+     * Only the first device admin can set the proxy. If a second admin attempts
+     * to set the proxy, the {@link ComponentName} of the admin originally setting the
+     * proxy will be returned. If successful in setting the proxy, null will
+     * be returned.
+     * The method can be called repeatedly by the device admin alrady setting the
+     * proxy to update the proxy and exclusion list.
+     *
+     * @param admin Which {@link DeviceAdminReceiver} this request is associated
+     *            with.
+     * @param proxySpec the global proxy desired. Must be an HTTP Proxy.
+     *            Pass Proxy.NO_PROXY to reset the proxy.
+     * @param exclusionList a list of domains to be excluded from the global proxy.
+     * @return returns null if the proxy was successfully set, or a {@link ComponentName}
+     *            of the device admin that sets thew proxy otherwise.
+     */
+    public ComponentName setGlobalProxy(ComponentName admin, Proxy proxySpec,
+            List<String> exclusionList ) {
+        if (proxySpec == null) {
+            throw new NullPointerException();
+        }
+        if (mService != null) {
+            try {
+                String hostSpec;
+                String exclSpec;
+                if (proxySpec.equals(Proxy.NO_PROXY)) {
+                    hostSpec = null;
+                    exclSpec = null;
+                } else {
+                    if (!proxySpec.type().equals(Proxy.Type.HTTP)) {
+                        throw new IllegalArgumentException();
+                    }
+                    InetSocketAddress sa = (InetSocketAddress)proxySpec.address();
+                    String hostName = sa.getHostName();
+                    int port = sa.getPort();
+                    StringBuilder hostBuilder = new StringBuilder();
+                    hostSpec = hostBuilder.append(hostName)
+                        .append(":").append(Integer.toString(port)).toString();
+                    if (exclusionList == null) {
+                        exclSpec = "";
+                    } else {
+                        StringBuilder listBuilder = new StringBuilder();
+                        boolean firstDomain = true;
+                        for (String exclDomain : exclusionList) {
+                            if (!firstDomain) {
+                                listBuilder = listBuilder.append(",");
+                            } else {
+                                firstDomain = false;
+                            }
+                            listBuilder = listBuilder.append(exclDomain.trim());
+                        }
+                        exclSpec = listBuilder.toString();
+                    }
+                    android.net.Proxy.validate(hostName, Integer.toString(port), exclSpec);
+                }
+                return mService.setGlobalProxy(admin, hostSpec, exclSpec);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed talking with device policy service", e);
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Returns the component name setting the global proxy.
+     * @return ComponentName object of the device admin that set the global proxy, or
+     *            null if no admin has set the proxy.
+     */
+    public ComponentName getGlobalProxyAdmin() {
+        if (mService != null) {
+            try {
+                return mService.getGlobalProxyAdmin();
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed talking with device policy service", e);
+            }
+        }
+        return null;
+    }
+
     /**
      * @hide
      */
@@ -543,7 +1013,7 @@
             }
         }
     }
-    
+
     /**
      * @hide
      */
@@ -556,10 +1026,10 @@
             Log.w(TAG, "Unable to retrieve device policy " + cn, e);
             return null;
         }
-        
+
         ResolveInfo ri = new ResolveInfo();
         ri.activityInfo = ai;
-        
+
         try {
             return new DeviceAdminInfo(mContext, ri);
         } catch (XmlPullParserException e) {
@@ -570,7 +1040,7 @@
             return null;
         }
     }
-    
+
     /**
      * @hide
      */
@@ -587,16 +1057,18 @@
     /**
      * @hide
      */
-    public void setActivePasswordState(int quality, int length) {
+    public void setActivePasswordState(int quality, int length, int letters, int uppercase,
+            int lowercase, int numbers, int symbols, int nonletter) {
         if (mService != null) {
             try {
-                mService.setActivePasswordState(quality, length);
+                mService.setActivePasswordState(quality, length, letters, uppercase, lowercase,
+                        numbers, symbols, nonletter);
             } catch (RemoteException e) {
                 Log.w(TAG, "Failed talking with device policy service", e);
             }
         }
     }
-    
+
     /**
      * @hide
      */
@@ -609,7 +1081,7 @@
             }
         }
     }
-    
+
     /**
      * @hide
      */
@@ -622,4 +1094,5 @@
             }
         }
     }
+
 }
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 6fc4dc5..3fcd6fc 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -27,10 +27,31 @@
 interface IDevicePolicyManager {
     void setPasswordQuality(in ComponentName who, int quality);
     int getPasswordQuality(in ComponentName who);
-    
+
     void setPasswordMinimumLength(in ComponentName who, int length);
     int getPasswordMinimumLength(in ComponentName who);
+
+    void setPasswordMinimumUpperCase(in ComponentName who, int length);
+    int getPasswordMinimumUpperCase(in ComponentName who);
+
+    void setPasswordMinimumLowerCase(in ComponentName who, int length);
+    int getPasswordMinimumLowerCase(in ComponentName who);
+
+    void setPasswordMinimumLetters(in ComponentName who, int length);
+    int getPasswordMinimumLetters(in ComponentName who);
+
+    void setPasswordMinimumNumeric(in ComponentName who, int length);
+    int getPasswordMinimumNumeric(in ComponentName who);
+
+    void setPasswordMinimumSymbols(in ComponentName who, int length);
+    int getPasswordMinimumSymbols(in ComponentName who);
+
+    void setPasswordMinimumNonLetter(in ComponentName who, int length);
+    int getPasswordMinimumNonLetter(in ComponentName who);
     
+    void setPasswordHistoryLength(in ComponentName who, int length);
+    int getPasswordHistoryLength(in ComponentName who);
+
     boolean isActivePasswordSufficient();
     int getCurrentFailedPasswordAttempts();
     
@@ -45,6 +66,9 @@
     void lockNow();
     
     void wipeData(int flags);
+
+    ComponentName setGlobalProxy(in ComponentName admin, String proxySpec, String exclusionList);
+    ComponentName getGlobalProxyAdmin();
     
     void setActiveAdmin(in ComponentName policyReceiver);
     boolean isAdminActive(in ComponentName policyReceiver);
@@ -53,7 +77,8 @@
     void getRemoveWarning(in ComponentName policyReceiver, in RemoteCallback result);
     void removeActiveAdmin(in ComponentName policyReceiver);
     
-    void setActivePasswordState(int quality, int length);
+    void setActivePasswordState(int quality, int length, int letters, int uppercase, int lowercase,
+        int numbers, int symbols, int nonletter);
     void reportFailedPasswordAttempt();
     void reportSuccessfulPasswordAttempt();
 }
diff --git a/core/java/android/appwidget/AppWidgetHost.java b/core/java/android/appwidget/AppWidgetHost.java
index b2fc13f..6011eec 100644
--- a/core/java/android/appwidget/AppWidgetHost.java
+++ b/core/java/android/appwidget/AppWidgetHost.java
@@ -39,6 +39,7 @@
 
     static final int HANDLE_UPDATE = 1;
     static final int HANDLE_PROVIDER_CHANGED = 2;
+    static final int HANDLE_VIEW_DATA_CHANGED = 3;
 
     final static Object sServiceLock = new Object();
     static IAppWidgetService sService;
@@ -60,6 +61,14 @@
             msg.obj = info;
             msg.sendToTarget();
         }
+
+        public void viewDataChanged(int appWidgetId, RemoteViews views, int viewId) {
+            Message msg = mHandler.obtainMessage(HANDLE_VIEW_DATA_CHANGED);
+            msg.arg1 = appWidgetId;
+            msg.arg2 = viewId;
+            msg.obj = views;
+            msg.sendToTarget();
+        }
     }
 
     class UpdateHandler extends Handler {
@@ -77,6 +86,10 @@
                     onProviderChanged(msg.arg1, (AppWidgetProviderInfo)msg.obj);
                     break;
                 }
+                case HANDLE_VIEW_DATA_CHANGED: {
+                    viewDataChanged(msg.arg1, (RemoteViews) msg.obj, msg.arg2);
+                    break;
+                }
             }
         }
     }
@@ -250,6 +263,16 @@
             v.updateAppWidget(views);
         }
     }
+
+    void viewDataChanged(int appWidgetId, RemoteViews views, int viewId) {
+        AppWidgetHostView v;
+        synchronized (mViews) {
+            v = mViews.get(appWidgetId);
+        }
+        if (v != null) {
+            v.viewDataChanged(views, viewId);
+        }
+    }
 }
 
 
diff --git a/core/java/android/appwidget/AppWidgetHostView.java b/core/java/android/appwidget/AppWidgetHostView.java
index b33b097..22f4266 100644
--- a/core/java/android/appwidget/AppWidgetHostView.java
+++ b/core/java/android/appwidget/AppWidgetHostView.java
@@ -23,15 +23,18 @@
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Paint;
-import android.os.SystemClock;
-import android.os.Parcelable;
 import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.SystemClock;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.util.SparseArray;
 import android.view.Gravity;
 import android.view.LayoutInflater;
 import android.view.View;
+import android.widget.Adapter;
+import android.widget.AdapterView;
+import android.widget.BaseAdapter;
 import android.widget.FrameLayout;
 import android.widget.RemoteViews;
 import android.widget.TextView;
@@ -258,6 +261,22 @@
     }
 
     /**
+     * Process data-changed notifications for the specified view in the specified
+     * set of {@link RemoteViews} views.
+     */
+    void viewDataChanged(RemoteViews remoteViews, int viewId) {
+        View v = findViewById(viewId);
+        if ((v != null) && (v instanceof AdapterView<?>)) {
+            AdapterView<?> adapterView = (AdapterView<?>) v;
+            Adapter adapter = adapterView.getAdapter();
+            if (adapter instanceof BaseAdapter) {
+                BaseAdapter baseAdapter = (BaseAdapter) adapter;
+                baseAdapter.notifyDataSetChanged();
+            }
+        }
+    }
+
+    /**
      * Build a {@link Context} cloned into another package name, usually for the
      * purposes of reading remote resources.
      */
@@ -275,6 +294,7 @@
         }
     }
 
+    @Override
     protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
         if (CROSSFADE) {
             int alpha;
diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java
index d2ab85e..5ee721f 100644
--- a/core/java/android/appwidget/AppWidgetManager.java
+++ b/core/java/android/appwidget/AppWidgetManager.java
@@ -288,11 +288,48 @@
     }
 
     /**
+     * Notifies the specified collection view in all the specified AppWidget instances
+     * to invalidate their currently data.
+     *
+     * @param appWidgetIds  The AppWidget instances for which to notify of view data changes.
+     * @param views         The RemoteViews which contains the view referenced at viewId.
+     * @param viewId        The collection view id.
+     */
+    public void notifyAppWidgetViewDataChanged(int[] appWidgetIds, RemoteViews views, int viewId) {
+        try {
+            sService.notifyAppWidgetViewDataChanged(appWidgetIds, views, viewId);
+        }
+        catch (RemoteException e) {
+            throw new RuntimeException("system server dead?", e);
+        }
+    }
+
+    /**
+     * Notifies the specified collection view in all the specified AppWidget instance
+     * to invalidate it's currently data.
+     *
+     * @param appWidgetId  The AppWidget instance for which to notify of view data changes.
+     * @param views         The RemoteViews which contains the view referenced at viewId.
+     * @param viewId        The collection view id.
+     */
+    public void notifyAppWidgetViewDataChanged(int appWidgetId, RemoteViews views, int viewId) {
+        notifyAppWidgetViewDataChanged(new int[] { appWidgetId }, views, viewId);
+    }
+
+    /**
      * Return a list of the AppWidget providers that are currently installed.
      */
     public List<AppWidgetProviderInfo> getInstalledProviders() {
         try {
-            return sService.getInstalledProviders();
+            List<AppWidgetProviderInfo> providers = sService.getInstalledProviders();
+            for (AppWidgetProviderInfo info : providers) {
+                // Converting complex to dp.
+                info.minWidth =
+                        TypedValue.complexToDimensionPixelSize(info.minWidth, mDisplayMetrics);
+                info.minHeight =
+                        TypedValue.complexToDimensionPixelSize(info.minHeight, mDisplayMetrics);
+            }
+            return providers;
         }
         catch (RemoteException e) {
             throw new RuntimeException("system server dead?", e);
diff --git a/core/java/android/appwidget/AppWidgetProviderInfo.java b/core/java/android/appwidget/AppWidgetProviderInfo.java
index cee2865..396e92d 100644
--- a/core/java/android/appwidget/AppWidgetProviderInfo.java
+++ b/core/java/android/appwidget/AppWidgetProviderInfo.java
@@ -110,6 +110,17 @@
      * @hide Pending API approval
      */
     public String oldName;
+    
+    /**
+     * A preview of what the AppWidget will look like after it's configured.
+     * If not supplied, the AppWidget's icon will be used.
+     *
+     * <p>This field corresponds to the <code>android:previewImage</code> attribute in
+     * the <code>&lt;receiver&gt;</code> element in the AndroidManifest.xml file.
+     * 
+     * @hide Pending API approval
+     */
+	public int previewImage;
 
     public AppWidgetProviderInfo() {
     }
@@ -130,6 +141,7 @@
         }
         this.label = in.readString();
         this.icon = in.readInt();
+        this.previewImage = in.readInt();
     }
 
 
@@ -152,6 +164,7 @@
         }
         out.writeString(this.label);
         out.writeInt(this.icon);
+        out.writeInt(this.previewImage);
     }
 
     public int describeContents() {
diff --git a/core/java/android/bluetooth/BluetoothClass.java b/core/java/android/bluetooth/BluetoothClass.java
index c7fea9e..0c9bab2 100644
--- a/core/java/android/bluetooth/BluetoothClass.java
+++ b/core/java/android/bluetooth/BluetoothClass.java
@@ -259,6 +259,8 @@
     public static final int PROFILE_A2DP = 1;
     /** @hide */
     public static final int PROFILE_OPP = 2;
+    /** @hide */
+    public static final int PROFILE_HID = 3;
 
     /**
      * Check class bits for possible bluetooth profile support.
@@ -324,6 +326,8 @@
                 default:
                     return false;
             }
+        } else if (profile == PROFILE_HID) {
+            return (getDeviceClass() & Device.Major.PERIPHERAL) == Device.Major.PERIPHERAL;
         } else {
             return false;
         }
diff --git a/core/java/android/bluetooth/BluetoothDeviceProfileState.java b/core/java/android/bluetooth/BluetoothDeviceProfileState.java
index 8e655e2..1fd7151 100644
--- a/core/java/android/bluetooth/BluetoothDeviceProfileState.java
+++ b/core/java/android/bluetooth/BluetoothDeviceProfileState.java
@@ -58,19 +58,24 @@
     private static final String TAG = "BluetoothDeviceProfileState";
     private static final boolean DBG = true; //STOPSHIP - Change to false
 
+    // TODO(): Restructure the state machine to make it scalable with regard to profiles.
     public static final int CONNECT_HFP_OUTGOING = 1;
     public static final int CONNECT_HFP_INCOMING = 2;
     public static final int CONNECT_A2DP_OUTGOING = 3;
     public static final int CONNECT_A2DP_INCOMING = 4;
+    public static final int CONNECT_HID_OUTGOING = 5;
+    public static final int CONNECT_HID_INCOMING = 6;
 
-    public static final int DISCONNECT_HFP_OUTGOING = 5;
-    private static final int DISCONNECT_HFP_INCOMING = 6;
-    public static final int DISCONNECT_A2DP_OUTGOING = 7;
-    public static final int DISCONNECT_A2DP_INCOMING = 8;
+    public static final int DISCONNECT_HFP_OUTGOING = 50;
+    private static final int DISCONNECT_HFP_INCOMING = 51;
+    public static final int DISCONNECT_A2DP_OUTGOING = 52;
+    public static final int DISCONNECT_A2DP_INCOMING = 53;
+    public static final int DISCONNECT_HID_OUTGOING = 54;
+    public static final int DISCONNECT_HID_INCOMING = 55;
 
-    public static final int UNPAIR = 9;
-    public static final int AUTO_CONNECT_PROFILES = 10;
-    public static final int TRANSITION_TO_STABLE = 11;
+    public static final int UNPAIR = 100;
+    public static final int AUTO_CONNECT_PROFILES = 101;
+    public static final int TRANSITION_TO_STABLE = 102;
 
     private static final int AUTO_CONNECT_DELAY = 6000; // 6 secs
 
@@ -79,6 +84,8 @@
     private IncomingHandsfree mIncomingHandsfree = new IncomingHandsfree();
     private IncomingA2dp mIncomingA2dp = new IncomingA2dp();
     private OutgoingA2dp mOutgoingA2dp = new OutgoingA2dp();
+    private OutgoingHid mOutgoingHid = new OutgoingHid();
+    private IncomingHid mIncomingHid = new IncomingHid();
 
     private Context mContext;
     private BluetoothService mService;
@@ -89,6 +96,7 @@
     private BluetoothDevice mDevice;
     private int mHeadsetState;
     private int mA2dpState;
+    private int mHidState;
 
     private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
         @Override
@@ -125,6 +133,19 @@
                     newState == BluetoothA2dp.STATE_DISCONNECTED) {
                     sendMessage(TRANSITION_TO_STABLE);
                 }
+            } else if (action.equals(BluetoothInputDevice.ACTION_INPUT_DEVICE_STATE_CHANGED)) {
+                int newState = intent.getIntExtra(BluetoothInputDevice.EXTRA_INPUT_DEVICE_STATE, 0);
+                int oldState =
+                    intent.getIntExtra(BluetoothInputDevice.EXTRA_PREVIOUS_INPUT_DEVICE_STATE, 0);
+                mHidState = newState;
+                if (oldState == BluetoothInputDevice.STATE_CONNECTED &&
+                    newState == BluetoothInputDevice.STATE_DISCONNECTED) {
+                    sendMessage(DISCONNECT_HID_INCOMING);
+                }
+                if (newState == BluetoothInputDevice.STATE_CONNECTED ||
+                    newState == BluetoothInputDevice.STATE_DISCONNECTED) {
+                    sendMessage(TRANSITION_TO_STABLE);
+                }
             } else if (action.equals(BluetoothDevice.ACTION_ACL_CONNECTED)) {
                 if (!getCurrentState().equals(mBondedDevice)) {
                     Log.e(TAG, "State is: " + getCurrentState());
@@ -165,6 +186,8 @@
         addState(mIncomingHandsfree);
         addState(mIncomingA2dp);
         addState(mOutgoingA2dp);
+        addState(mOutgoingHid);
+        addState(mIncomingHid);
         setInitialState(mBondedDevice);
 
         IntentFilter filter = new IntentFilter();
@@ -172,6 +195,7 @@
         filter.addAction(BluetoothA2dp.ACTION_SINK_STATE_CHANGED);
         filter.addAction(BluetoothHeadset.ACTION_STATE_CHANGED);
         filter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED);
+        filter.addAction(BluetoothInputDevice.ACTION_INPUT_DEVICE_STATE_CHANGED);
 
         mContext.registerReceiver(mBroadcastReceiver, filter);
 
@@ -224,6 +248,14 @@
                 case DISCONNECT_A2DP_INCOMING:
                     transitionTo(mIncomingA2dp);
                     break;
+                case CONNECT_HID_OUTGOING:
+                case DISCONNECT_HID_OUTGOING:
+                    transitionTo(mOutgoingHid);
+                    break;
+                case CONNECT_HID_INCOMING:
+                case DISCONNECT_HID_INCOMING:
+                    transitionTo(mIncomingHid);
+                    break;
                 case UNPAIR:
                     if (mHeadsetState != BluetoothHeadset.STATE_DISCONNECTED) {
                         sendMessage(DISCONNECT_HFP_OUTGOING);
@@ -233,6 +265,10 @@
                         sendMessage(DISCONNECT_A2DP_OUTGOING);
                         deferMessage(message);
                         break;
+                    } else if (mHidState != BluetoothInputDevice.STATE_DISCONNECTED) {
+                        sendMessage(DISCONNECT_HID_OUTGOING);
+                        deferMessage(message);
+                        break;
                     }
                     processCommand(UNPAIR);
                     break;
@@ -254,6 +290,10 @@
                               mA2dpService.getConnectedSinks().length == 0) {
                             mA2dpService.connectSink(mDevice);
                         }
+                        if (mService.getInputDevicePriority(mDevice) ==
+                              BluetoothInputDevice.PRIORITY_AUTO_CONNECT) {
+                            mService.connectInputDevice(mDevice);
+                        }
                     }
                     break;
                 case TRANSITION_TO_STABLE:
@@ -342,6 +382,23 @@
                        deferMessage(deferMsg);
                     }
                     break;
+                case CONNECT_HID_OUTGOING:
+                case DISCONNECT_HID_OUTGOING:
+                    deferMessage(message);
+                    break;
+                case CONNECT_HID_INCOMING:
+                    transitionTo(mIncomingHid);
+                    if (mStatus) {
+                        deferMsg.what = mCommand;
+                        deferMessage(deferMsg);
+                    }
+                    break;
+                case DISCONNECT_HID_INCOMING:
+                    if (mStatus) {
+                        deferMsg.what = mCommand;
+                        deferMessage(deferMsg);
+                    }
+                    break; // ignore
                 case UNPAIR:
                 case AUTO_CONNECT_PROFILES:
                     deferMessage(message);
@@ -409,6 +466,13 @@
                     // If this causes incoming HFP to fail, it is more of a headset problem
                     // since both connections are incoming ones.
                     break;
+                case CONNECT_HID_OUTGOING:
+                case DISCONNECT_HID_OUTGOING:
+                    deferMessage(message);
+                    break;
+                case CONNECT_HID_INCOMING:
+                case DISCONNECT_HID_INCOMING:
+                     break; // ignore
                 case UNPAIR:
                 case AUTO_CONNECT_PROFILES:
                     deferMessage(message);
@@ -496,6 +560,23 @@
                 case DISCONNECT_A2DP_INCOMING:
                     // Ignore, will be handled by Bluez
                     break;
+                case CONNECT_HID_OUTGOING:
+                case DISCONNECT_HID_OUTGOING:
+                    deferMessage(message);
+                    break;
+                case CONNECT_HID_INCOMING:
+                    transitionTo(mIncomingHid);
+                    if (mStatus) {
+                        deferMsg.what = mCommand;
+                        deferMessage(deferMsg);
+                    }
+                    break;
+                case DISCONNECT_HID_INCOMING:
+                    if (mStatus) {
+                        deferMsg.what = mCommand;
+                        deferMessage(deferMsg);
+                    }
+                    break; // ignore
                 case UNPAIR:
                 case AUTO_CONNECT_PROFILES:
                     deferMessage(message);
@@ -561,6 +642,13 @@
                 case DISCONNECT_A2DP_INCOMING:
                     // Ignore, will be handled by Bluez
                     break;
+                case CONNECT_HID_OUTGOING:
+                case DISCONNECT_HID_OUTGOING:
+                    deferMessage(message);
+                    break;
+                case CONNECT_HID_INCOMING:
+                case DISCONNECT_HID_INCOMING:
+                     break; // ignore
                 case UNPAIR:
                 case AUTO_CONNECT_PROFILES:
                     deferMessage(message);
@@ -576,6 +664,143 @@
     }
 
 
+    private class OutgoingHid extends HierarchicalState {
+        private boolean mStatus = false;
+        private int mCommand;
+
+        @Override
+        protected void enter() {
+            log("Entering OutgoingHid state with: " + getCurrentMessage().what);
+            mCommand = getCurrentMessage().what;
+            if (mCommand != CONNECT_HID_OUTGOING &&
+                mCommand != DISCONNECT_HID_OUTGOING) {
+                Log.e(TAG, "Error: OutgoingHid state with command:" + mCommand);
+            }
+            mStatus = processCommand(mCommand);
+            if (!mStatus) sendMessage(TRANSITION_TO_STABLE);
+        }
+
+        @Override
+        protected boolean processMessage(Message message) {
+            log("OutgoingHid State->Processing Message: " + message.what);
+            Message deferMsg = new Message();
+            switch(message.what) {
+                // defer all outgoing messages
+                case CONNECT_HFP_OUTGOING:
+                case CONNECT_A2DP_OUTGOING:
+                case CONNECT_HID_OUTGOING:
+                case DISCONNECT_HFP_OUTGOING:
+                case DISCONNECT_A2DP_OUTGOING:
+                case DISCONNECT_HID_OUTGOING:
+                    deferMessage(message);
+                    break;
+
+                case CONNECT_HFP_INCOMING:
+                    transitionTo(mIncomingHandsfree);
+                case CONNECT_A2DP_INCOMING:
+                    transitionTo(mIncomingA2dp);
+
+                    // Don't cancel HID outgoing as there is no guarantee it
+                    // will get canceled.
+                    // It might already be connected but we might not have got the
+                    // INPUT_DEVICE_STATE_CHANGE. Hence, no point disconnecting here.
+                    // The worst case, the connection will fail, retry.
+                    if (mStatus) {
+                        deferMsg.what = mCommand;
+                        deferMessage(deferMsg);
+                    }
+                    break;
+                case CONNECT_HID_INCOMING:
+                  // Bluez will take care of the conflicts
+                    transitionTo(mIncomingHid);
+                    break;
+
+                case DISCONNECT_HFP_INCOMING:
+                case DISCONNECT_A2DP_INCOMING:
+                    // At this point, we are already disconnected
+                    // with HFP. Sometimes HID connection can
+                    // fail due to the disconnection of HFP. So add a retry
+                    // for the HID.
+                    if (mStatus) {
+                        deferMsg.what = mCommand;
+                        deferMessage(deferMsg);
+                    }
+                    break;
+                case DISCONNECT_HID_INCOMING:
+                    // Ignore, will be handled by Bluez
+                    break;
+
+                case UNPAIR:
+                case AUTO_CONNECT_PROFILES:
+                    deferMessage(message);
+                    break;
+                case TRANSITION_TO_STABLE:
+                    transitionTo(mBondedDevice);
+                    break;
+                default:
+                    return NOT_HANDLED;
+            }
+            return HANDLED;
+        }
+    }
+
+  private class IncomingHid extends HierarchicalState {
+      private boolean mStatus = false;
+      private int mCommand;
+
+      @Override
+      protected void enter() {
+          log("Entering IncomingHid state with: " + getCurrentMessage().what);
+          mCommand = getCurrentMessage().what;
+          if (mCommand != CONNECT_HID_INCOMING &&
+              mCommand != DISCONNECT_HID_INCOMING) {
+              Log.e(TAG, "Error: IncomingHid state with command:" + mCommand);
+          }
+          mStatus = processCommand(mCommand);
+          if (!mStatus) sendMessage(TRANSITION_TO_STABLE);
+      }
+
+      @Override
+      protected boolean processMessage(Message message) {
+          log("IncomingHid State->Processing Message: " + message.what);
+          Message deferMsg = new Message();
+          switch(message.what) {
+              case CONNECT_HFP_OUTGOING:
+              case CONNECT_HFP_INCOMING:
+              case DISCONNECT_HFP_OUTGOING:
+              case CONNECT_A2DP_INCOMING:
+              case CONNECT_A2DP_OUTGOING:
+              case DISCONNECT_A2DP_OUTGOING:
+              case CONNECT_HID_OUTGOING:
+              case CONNECT_HID_INCOMING:
+              case DISCONNECT_HID_OUTGOING:
+                  deferMessage(message);
+                  break;
+              case DISCONNECT_HFP_INCOMING:
+                  // Shouldn't happen but if does, we can handle it.
+                  // Depends if the headset can handle it.
+                  // Incoming HID will be handled by Bluez, Disconnect HFP
+                  // the socket would have already been closed.
+                  // ignore
+                  break;
+              case DISCONNECT_HID_INCOMING:
+              case DISCONNECT_A2DP_INCOMING:
+                  // Ignore, will be handled by Bluez
+                  break;
+              case UNPAIR:
+              case AUTO_CONNECT_PROFILES:
+                  deferMessage(message);
+                  break;
+              case TRANSITION_TO_STABLE:
+                  transitionTo(mBondedDevice);
+                  break;
+              default:
+                  return NOT_HANDLED;
+          }
+          return HANDLED;
+      }
+  }
+
 
     synchronized void cancelCommand(int command) {
         if (command == CONNECT_HFP_OUTGOING ) {
@@ -619,6 +844,10 @@
             case CONNECT_A2DP_INCOMING:
                 // ignore, Bluez takes care
                 return true;
+            case CONNECT_HID_OUTGOING:
+                return mService.connectInputDeviceInternal(mDevice);
+            case CONNECT_HID_INCOMING:
+                return true;
             case DISCONNECT_HFP_OUTGOING:
                 if (!mHeadsetServiceConnected) {
                     deferHeadsetMessage(command);
@@ -645,6 +874,15 @@
                     return mA2dpService.disconnectSinkInternal(mDevice);
                 }
                 break;
+            case DISCONNECT_HID_INCOMING:
+                // ignore
+                return true;
+            case DISCONNECT_HID_OUTGOING:
+                if (mService.getInputDevicePriority(mDevice) ==
+                    BluetoothInputDevice.PRIORITY_AUTO_CONNECT) {
+                    mService.setInputDevicePriority(mDevice, BluetoothInputDevice.PRIORITY_ON);
+                }
+                return mService.disconnectInputDeviceInternal(mDevice);
             case UNPAIR:
                 return mService.removeBondInternal(mDevice.getAddress());
             default:
@@ -653,6 +891,7 @@
         return false;
     }
 
+
     /*package*/ BluetoothDevice getDevice() {
         return mDevice;
     }
diff --git a/core/java/android/bluetooth/BluetoothInputDevice.java b/core/java/android/bluetooth/BluetoothInputDevice.java
new file mode 100644
index 0000000..1793838
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothInputDevice.java
@@ -0,0 +1,241 @@
+/*
+ * 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.bluetooth;
+
+import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
+import android.content.Context;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Public API for controlling the Bluetooth HID (Input Device) Profile
+ *
+ * BluetoothInputDevice is a proxy object used to make calls to Bluetooth Service
+ * which handles the HID profile.
+ *
+ * Creating a BluetoothInputDevice object will initiate a binding with the
+ * Bluetooth service. Users of this object should call close() when they
+ * are finished, so that this proxy object can unbind from the service.
+ *
+ * Currently the Bluetooth service runs in the system server and this
+ * proxy object will be immediately bound to the service on construction.
+ *
+ *  @hide
+ */
+public final class BluetoothInputDevice {
+    private static final String TAG = "BluetoothInputDevice";
+    private static final boolean DBG = false;
+
+    /** int extra for ACTION_INPUT_DEVICE_STATE_CHANGED */
+    public static final String EXTRA_INPUT_DEVICE_STATE =
+        "android.bluetooth.inputdevice.extra.INPUT_DEVICE_STATE";
+    /** int extra for ACTION_INPUT_DEVICE_STATE_CHANGED */
+    public static final String EXTRA_PREVIOUS_INPUT_DEVICE_STATE =
+        "android.bluetooth.inputdevice.extra.PREVIOUS_INPUT_DEVICE_STATE";
+
+    /** Indicates the state of an input device has changed.
+     * This intent will always contain EXTRA_INPUT_DEVICE_STATE,
+     * EXTRA_PREVIOUS_INPUT_DEVICE_STATE and BluetoothDevice.EXTRA_DEVICE
+     * extras.
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_INPUT_DEVICE_STATE_CHANGED =
+        "android.bluetooth.inputdevice.action.INPUT_DEVICE_STATE_CHANGED";
+
+    public static final int STATE_DISCONNECTED = 0;
+    public static final int STATE_CONNECTING   = 1;
+    public static final int STATE_CONNECTED    = 2;
+    public static final int STATE_DISCONNECTING = 3;
+
+    /**
+     * Auto connection, incoming and outgoing connection are allowed at this
+     * priority level.
+     */
+    public static final int PRIORITY_AUTO_CONNECT = 1000;
+    /**
+     * Incoming and outgoing connection are allowed at this priority level
+     */
+    public static final int PRIORITY_ON = 100;
+    /**
+     * Connections to the device are not allowed at this priority level.
+     */
+    public static final int PRIORITY_OFF = 0;
+    /**
+     * Default priority level when the device is unpaired.
+     */
+    public static final int PRIORITY_UNDEFINED = -1;
+
+    private final IBluetooth mService;
+    private final Context mContext;
+
+    /**
+     * Create a BluetoothInputDevice proxy object for interacting with the local
+     * Bluetooth Service which handle the HID profile.
+     * @param c Context
+     */
+    public BluetoothInputDevice(Context c) {
+        mContext = c;
+
+        IBinder b = ServiceManager.getService(BluetoothAdapter.BLUETOOTH_SERVICE);
+        if (b != null) {
+            mService = IBluetooth.Stub.asInterface(b);
+        } else {
+            Log.w(TAG, "Bluetooth Service not available!");
+
+            // Instead of throwing an exception which prevents people from going
+            // into Wireless settings in the emulator. Let it crash later when it is actually used.
+            mService = null;
+        }
+    }
+
+    /** Initiate a connection to an Input device.
+     *
+     *  This function returns false on error and true if the connection
+     *  attempt is being made.
+     *
+     *  Listen for INPUT_DEVICE_STATE_CHANGED_ACTION to find out when the
+     *  connection is completed.
+     *  @param device Remote BT device.
+     *  @return false on immediate error, true otherwise
+     *  @hide
+     */
+    public boolean connectInputDevice(BluetoothDevice device) {
+        if (DBG) log("connectInputDevice(" + device + ")");
+        try {
+            return mService.connectInputDevice(device);
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+            return false;
+        }
+    }
+
+    /** Initiate disconnect from an Input Device.
+     *  This function return false on error and true if the disconnection
+     *  attempt is being made.
+     *
+     *  Listen for INPUT_DEVICE_STATE_CHANGED_ACTION to find out when
+     *  disconnect is completed.
+     *
+     *  @param device Remote BT device.
+     *  @return false on immediate error, true otherwise
+     *  @hide
+     */
+    public boolean disconnectInputDevice(BluetoothDevice device) {
+        if (DBG) log("disconnectInputDevice(" + device + ")");
+        try {
+            return mService.disconnectInputDevice(device);
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+            return false;
+        }
+    }
+
+    /** Check if a specified InputDevice is connected.
+     *
+     *  @param device Remote BT device.
+     *  @return True if connected , false otherwise and on error.
+     *  @hide
+     */
+    public boolean isInputDeviceConnected(BluetoothDevice device) {
+        if (DBG) log("isInputDeviceConnected(" + device + ")");
+        int state = getInputDeviceState(device);
+        if (state == STATE_CONNECTED) return true;
+        return false;
+    }
+
+    /** Check if any Input Device is connected.
+     *
+     * @return a unmodifiable set of connected Input Devices, or null on error.
+     * @hide
+     */
+    public Set<BluetoothDevice> getConnectedInputDevices() {
+        if (DBG) log("getConnectedInputDevices()");
+        try {
+            return Collections.unmodifiableSet(
+                    new HashSet<BluetoothDevice>(
+                        Arrays.asList(mService.getConnectedInputDevices())));
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+            return null;
+        }
+    }
+
+    /** Get the state of an Input Device.
+     *
+     *  @param device Remote BT device.
+     *  @return The current state of the Input Device
+     *  @hide
+     */
+    public int getInputDeviceState(BluetoothDevice device) {
+        if (DBG) log("getInputDeviceState(" + device + ")");
+        try {
+            return mService.getInputDeviceState(device);
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+            return STATE_DISCONNECTED;
+        }
+    }
+
+    /**
+     * Set priority of an input device.
+     *
+     * Priority is a non-negative integer. Priority can take the following
+     * values:
+     * {@link PRIORITY_ON}, {@link PRIORITY_OFF}, {@link PRIORITY_AUTO_CONNECT}
+     *
+     * @param device Paired device.
+     * @param priority Integer priority
+     * @return true if priority is set, false on error
+     */
+    public boolean setInputDevicePriority(BluetoothDevice device, int priority) {
+        if (DBG) log("setInputDevicePriority(" + device + ", " + priority + ")");
+        try {
+            return mService.setInputDevicePriority(device, priority);
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+            return false;
+        }
+    }
+
+    /**
+     * Get the priority associated with an Input Device.
+     *
+     * @param device Input Device
+     * @return non-negative priority, or negative error code on error.
+     */
+    public int getInputDevicePriority(BluetoothDevice device) {
+        if (DBG) log("getInputDevicePriority(" + device + ")");
+        try {
+            return mService.getInputDevicePriority(device);
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+            return PRIORITY_OFF;
+        }
+    }
+
+    private static void log(String msg) {
+        Log.d(TAG, msg);
+    }
+}
diff --git a/core/java/android/bluetooth/BluetoothProfileState.java b/core/java/android/bluetooth/BluetoothProfileState.java
index 946dcaa..ad70d0d 100644
--- a/core/java/android/bluetooth/BluetoothProfileState.java
+++ b/core/java/android/bluetooth/BluetoothProfileState.java
@@ -43,8 +43,9 @@
     private static final boolean DBG = true; // STOPSHIP - change to false.
     private static final String TAG = "BluetoothProfileState";
 
-    public static int HFP = 0;
-    public static int A2DP = 1;
+    public static final int HFP = 0;
+    public static final int A2DP = 1;
+    public static final int HID = 2;
 
     private static int TRANSITION_TO_STABLE = 100;
 
@@ -70,6 +71,12 @@
                     newState == BluetoothA2dp.STATE_DISCONNECTED)) {
                     sendMessage(TRANSITION_TO_STABLE);
                 }
+            } else if (action.equals(BluetoothInputDevice.ACTION_INPUT_DEVICE_STATE_CHANGED)) {
+                int newState = intent.getIntExtra(BluetoothInputDevice.EXTRA_INPUT_DEVICE_STATE, 0);
+                if (mProfile == HID && (newState == BluetoothInputDevice.STATE_CONNECTED ||
+                    newState == BluetoothInputDevice.STATE_DISCONNECTED)) {
+                    sendMessage(TRANSITION_TO_STABLE);
+                }
             }
         }
     };
@@ -84,6 +91,7 @@
         IntentFilter filter = new IntentFilter();
         filter.addAction(BluetoothA2dp.ACTION_SINK_STATE_CHANGED);
         filter.addAction(BluetoothHeadset.ACTION_STATE_CHANGED);
+        filter.addAction(BluetoothInputDevice.ACTION_INPUT_DEVICE_STATE_CHANGED);
         context.registerReceiver(mBroadcastReceiver, filter);
     }
 
diff --git a/core/java/android/bluetooth/BluetoothUuid.java b/core/java/android/bluetooth/BluetoothUuid.java
index 4164a3d..f1ee907 100644
--- a/core/java/android/bluetooth/BluetoothUuid.java
+++ b/core/java/android/bluetooth/BluetoothUuid.java
@@ -49,6 +49,8 @@
             ParcelUuid.fromString("0000110C-0000-1000-8000-00805F9B34FB");
     public static final ParcelUuid ObexObjectPush =
             ParcelUuid.fromString("00001105-0000-1000-8000-00805f9b34fb");
+    public static final ParcelUuid Hid =
+            ParcelUuid.fromString("00001124-0000-1000-8000-00805f9b34fb");
 
     public static final ParcelUuid[] RESERVED_UUIDS = {
         AudioSink, AudioSource, AdvAudioDist, HSP, Handsfree, AvrcpController, AvrcpTarget,
@@ -82,6 +84,10 @@
         return uuid.equals(AvrcpTarget);
     }
 
+    public static boolean isInputDevice(ParcelUuid uuid) {
+        return uuid.equals(Hid);
+    }
+
     /**
      * Returns true if ParcelUuid is present in uuidArray
      *
diff --git a/core/java/android/bluetooth/IBluetooth.aidl b/core/java/android/bluetooth/IBluetooth.aidl
index ea71034..75f093c 100644
--- a/core/java/android/bluetooth/IBluetooth.aidl
+++ b/core/java/android/bluetooth/IBluetooth.aidl
@@ -17,6 +17,7 @@
 package android.bluetooth;
 
 import android.bluetooth.IBluetoothCallback;
+import android.bluetooth.BluetoothDevice;
 import android.os.ParcelUuid;
 
 /**
@@ -72,4 +73,12 @@
     boolean connectHeadset(String address);
     boolean disconnectHeadset(String address);
     boolean notifyIncomingConnection(String address);
+
+    // HID profile APIs
+    boolean connectInputDevice(in BluetoothDevice device);
+    boolean disconnectInputDevice(in BluetoothDevice device);
+    BluetoothDevice[] getConnectedInputDevices();  // change to Set<> once AIDL supports
+    int getInputDeviceState(in BluetoothDevice device);
+    boolean setInputDevicePriority(in BluetoothDevice device, int priority);
+    int getInputDevicePriority(in BluetoothDevice device);
 }
diff --git a/core/java/android/bluetooth/ScoSocket.java b/core/java/android/bluetooth/ScoSocket.java
deleted file mode 100644
index b65a99a..0000000
--- a/core/java/android/bluetooth/ScoSocket.java
+++ /dev/null
@@ -1,206 +0,0 @@
-/*
- * 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.
- */
-
-package android.bluetooth;
-
-import android.os.Handler;
-import android.os.Message;
-import android.os.PowerManager;
-import android.os.PowerManager.WakeLock;
-import android.util.Log;
-
-/**
- * The Android Bluetooth API is not finalized, and *will* change. Use at your
- * own risk.
- *
- * Simple SCO Socket.
- * Currently in Android, there is no support for sending data over a SCO
- * socket - this is managed by the hardware link to the Bluetooth Chip. This
- * class is instead intended for management of the SCO socket lifetime, 
- * and is tailored for use with the headset / handsfree profiles.
- * @hide
- */
-public class ScoSocket {
-    private static final String TAG = "ScoSocket";
-    private static final boolean DBG = true;
-    private static final boolean VDBG = false;  // even more logging
-
-    public static final int STATE_READY = 1;    // Ready for use. No threads or sockets
-    public static final int STATE_ACCEPT = 2;   // accept() thread running
-    public static final int STATE_CONNECTING = 3;  // connect() thread running
-    public static final int STATE_CONNECTED = 4;   // connected, waiting for close()
-    public static final int STATE_CLOSED = 5;   // was connected, now closed.
-
-    private int mState;
-    private int mNativeData;
-    private Handler mHandler;
-    private int mAcceptedCode;
-    private int mConnectedCode;
-    private int mClosedCode;
-
-    private WakeLock mWakeLock;  // held while in STATE_CONNECTING
-
-    static {
-        classInitNative();
-    }
-    private native static void classInitNative();
-
-    public ScoSocket(PowerManager pm, Handler handler, int acceptedCode, int connectedCode,
-                     int closedCode) {
-        initNative();
-        mState = STATE_READY;
-        mHandler = handler;
-        mAcceptedCode = acceptedCode;
-        mConnectedCode = connectedCode;
-        mClosedCode = closedCode;
-        mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "ScoSocket");
-        mWakeLock.setReferenceCounted(false);
-        if (VDBG) log(this + " SCO OBJECT CTOR");
-    }
-    private native void initNative();
-
-    protected void finalize() throws Throwable {
-        try {
-            if (VDBG) log(this + " SCO OBJECT DTOR");
-            destroyNative();
-            releaseWakeLockNow();
-        } finally {
-            super.finalize();
-        }
-    }
-    private native void destroyNative();
-
-    /** Connect this SCO socket to the given BT address.
-     *  Does not block.
-     */
-    public synchronized boolean connect(String address, String name) {
-        if (DBG) log("connect() " + this);
-        if (mState != STATE_READY) {
-            if (DBG) log("connect(): Bad state");
-            return false;
-        }
-        acquireWakeLock();
-        if (connectNative(address, name)) {
-            mState = STATE_CONNECTING;
-            return true;
-        } else {
-            mState = STATE_CLOSED;
-            releaseWakeLockNow();
-            return false;
-        }
-    }
-    private native boolean connectNative(String address, String name);
-
-    /** Accept incoming SCO connections.
-     *  Does not block.
-     */
-    public synchronized boolean accept() {
-        if (VDBG) log("accept() " + this);
-        if (mState != STATE_READY) {
-            if (DBG) log("Bad state");
-            return false;
-        }
-        if (acceptNative()) {
-            mState = STATE_ACCEPT;
-            return true;
-        } else {
-            mState = STATE_CLOSED;
-            return false;
-        }
-    }
-    private native boolean acceptNative();
-
-    public synchronized void close() {
-        if (DBG) log(this + " SCO OBJECT close() mState = " + mState);
-        acquireWakeLock();
-        mState = STATE_CLOSED;
-        closeNative();
-        releaseWakeLock();
-    }
-    private native void closeNative();
-
-    public synchronized int getState() {
-        return mState;
-    }
-
-    private synchronized void onConnected(int result) {
-        if (VDBG) log(this + " onConnected() mState = " + mState + " " + this);
-        if (mState != STATE_CONNECTING) {
-            if (DBG) log("Strange state, closing " + mState + " " + this);
-            return;
-        }
-        if (result >= 0) {
-            mState = STATE_CONNECTED;
-        } else {
-            mState = STATE_CLOSED;
-        }
-        mHandler.obtainMessage(mConnectedCode, mState, -1, this).sendToTarget();
-        releaseWakeLockNow();
-    }
-
-    private synchronized void onAccepted(int result) {
-        if (VDBG) log("onAccepted() " + this);
-        if (mState != STATE_ACCEPT) {
-            if (DBG) log("Strange state " + this);
-            return;
-        }
-        if (result >= 0) {
-            mState = STATE_CONNECTED;
-        } else {
-            mState = STATE_CLOSED;
-        }
-        mHandler.obtainMessage(mAcceptedCode, mState, -1, this).sendToTarget();
-    }
-
-    private synchronized void onClosed() {
-        if (DBG) log("onClosed() " + this);
-        if (mState != STATE_CLOSED) {
-            mState = STATE_CLOSED;
-            mHandler.obtainMessage(mClosedCode, mState, -1, this).sendToTarget();
-            releaseWakeLock();
-        }
-    }
-
-    private void acquireWakeLock() {
-        if (!mWakeLock.isHeld()) {
-            mWakeLock.acquire();
-            if (VDBG) log("mWakeLock.acquire() " + this);
-        }
-    }
-
-    private void releaseWakeLock() {
-        if (mWakeLock.isHeld()) {
-            // Keep apps processor awake for a further 2 seconds.
-            // This is a hack to resolve issue http://b/1616263 - in which
-            // we are left in a 80 mA power state when remotely terminating a
-            // call while connected to BT headset "HTC BH S100 " with A2DP and
-            // HFP profiles.
-            if (VDBG) log("mWakeLock.release() in 2 sec" + this);
-            mWakeLock.acquire(2000);
-        }
-    }
-
-    private void releaseWakeLockNow() {
-        if (mWakeLock.isHeld()) {
-            if (VDBG) log("mWakeLock.release() now" + this);
-            mWakeLock.release();
-        }
-    }
-
-    private void log(String msg) {
-        Log.d(TAG, msg);
-    }
-}
diff --git a/core/java/android/content/AsyncTaskLoader.java b/core/java/android/content/AsyncTaskLoader.java
new file mode 100644
index 0000000..b19c072
--- /dev/null
+++ b/core/java/android/content/AsyncTaskLoader.java
@@ -0,0 +1,107 @@
+/*
+ * 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.content;
+
+import android.os.AsyncTask;
+
+/**
+ * Abstract Loader that provides an {@link AsyncTask} to do the work.
+ *
+ * @param <D> the data type to be loaded.
+ */
+public abstract class AsyncTaskLoader<D> extends Loader<D> {
+    final class LoadTask extends AsyncTask<Void, Void, D> {
+
+        private D result;
+
+        /* Runs on a worker thread */
+        @Override
+        protected D doInBackground(Void... params) {
+            result = AsyncTaskLoader.this.loadInBackground();
+            return result;
+        }
+
+        /* Runs on the UI thread */
+        @Override
+        protected void onPostExecute(D data) {
+            AsyncTaskLoader.this.dispatchOnLoadComplete(data);
+        }
+
+        @Override
+        protected void onCancelled() {
+            AsyncTaskLoader.this.onCancelled(result);
+        }
+    }
+
+    LoadTask mTask;
+
+    public AsyncTaskLoader(Context context) {
+        super(context);
+    }
+
+    /**
+     * Force an asynchronous load. Unlike {@link #startLoading()} this will ignore a previously
+     * loaded data set and load a new one.
+     */
+    @Override
+    public void forceLoad() {
+        cancelLoad();
+        mTask = new LoadTask();
+        mTask.execute((Void[]) null);
+    }
+
+    /**
+     * Attempt to cancel the current load task. See {@link AsyncTask#cancel(boolean)}
+     * for more info.
+     *
+     * @return <tt>false</tt> if the task could not be canceled,
+     *         typically because it has already completed normally, or
+     *         because {@link #startLoading()} hasn't been called, and
+     *         <tt>true</tt> otherwise
+     */
+    public boolean cancelLoad() {
+        if (mTask != null) {
+            boolean cancelled = mTask.cancel(false);
+            mTask = null;
+            return cancelled;
+        }
+        return false;
+    }
+
+    /**
+     * Called if the task was canceled before it was completed.  Gives the class a chance
+     * to properly dispose of the result.
+     */
+    public void onCancelled(D data) {
+    }
+
+    void dispatchOnLoadComplete(D data) {
+        mTask = null;
+        deliverResult(data);
+    }
+
+    /**
+     * Called on a worker thread to perform the actual load. Implementations should not deliver the
+     * results directly, but should return them from this method, which will eventually end up
+     * calling deliverResult on the UI thread. If implementations need to process
+     * the results on the UI thread they may override deliverResult and do so
+     * there.
+     *
+     * @return the result of the load
+     */
+    public abstract D loadInBackground();
+}
diff --git a/core/java/android/content/ClipboardManager.java b/core/java/android/content/ClipboardManager.java
new file mode 100644
index 0000000..5371fa5
--- /dev/null
+++ b/core/java/android/content/ClipboardManager.java
@@ -0,0 +1,195 @@
+/**
+ * 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.content;
+
+import android.content.Context;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.ServiceManager;
+import android.util.Log;
+
+import java.util.ArrayList;
+
+/**
+ * Interface to the clipboard service, for placing and retrieving text in
+ * the global clipboard.
+ *
+ * <p>
+ * You do not instantiate this class directly; instead, retrieve it through
+ * {@link android.content.Context#getSystemService}.
+ *
+ * @see android.content.Context#getSystemService
+ */
+public class ClipboardManager extends android.text.ClipboardManager {
+    private final static Object sStaticLock = new Object();
+    private static IClipboard sService;
+
+    private final Context mContext;
+
+    private final ArrayList<OnPrimaryClipChangedListener> mPrimaryClipChangedListeners
+             = new ArrayList<OnPrimaryClipChangedListener>();
+
+    private final IOnPrimaryClipChangedListener.Stub mPrimaryClipChangedServiceListener
+            = new IOnPrimaryClipChangedListener.Stub() {
+        public void dispatchPrimaryClipChanged() {
+            mHandler.sendEmptyMessage(MSG_REPORT_PRIMARY_CLIP_CHANGED);
+        }
+    };
+
+    static final int MSG_REPORT_PRIMARY_CLIP_CHANGED = 1;
+
+    private final Handler mHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_REPORT_PRIMARY_CLIP_CHANGED:
+                    reportPrimaryClipChanged();
+            }
+        }
+    };
+
+    public interface OnPrimaryClipChangedListener {
+        void onPrimaryClipChanged();
+    }
+
+    static private IClipboard getService() {
+        synchronized (sStaticLock) {
+            if (sService != null) {
+                return sService;
+            }
+            IBinder b = ServiceManager.getService("clipboard");
+            sService = IClipboard.Stub.asInterface(b);
+            return sService;
+        }
+    }
+
+    /** {@hide} */
+    public ClipboardManager(Context context, Handler handler) {
+        mContext = context;
+    }
+
+    /**
+     * Sets the current primary clip on the clipboard.  This is the clip that
+     * is involved in normal cut and paste operations.
+     *
+     * @param clip The clipped data item to set.
+     */
+    public void setPrimaryClip(ClippedData clip) {
+        try {
+            getService().setPrimaryClip(clip);
+        } catch (RemoteException e) {
+        }
+    }
+
+    /**
+     * Returns the current primary clip on the clipboard.
+     */
+    public ClippedData getPrimaryClip() {
+        try {
+            return getService().getPrimaryClip();
+        } catch (RemoteException e) {
+            return null;
+        }
+    }
+
+    /**
+     * Returns true if there is currently a primary clip on the clipboard.
+     */
+    public boolean hasPrimaryClip() {
+        try {
+            return getService().hasPrimaryClip();
+        } catch (RemoteException e) {
+            return false;
+        }
+    }
+
+    public void addPrimaryClipChangedListener(OnPrimaryClipChangedListener what) {
+        synchronized (mPrimaryClipChangedListeners) {
+            if (mPrimaryClipChangedListeners.size() == 0) {
+                try {
+                    getService().addPrimaryClipChangedListener(
+                            mPrimaryClipChangedServiceListener);
+                } catch (RemoteException e) {
+                }
+            }
+            mPrimaryClipChangedListeners.add(what);
+        }
+    }
+
+    public void removePrimaryClipChangedListener(OnPrimaryClipChangedListener what) {
+        synchronized (mPrimaryClipChangedListeners) {
+            mPrimaryClipChangedListeners.remove(what);
+            if (mPrimaryClipChangedListeners.size() == 0) {
+                try {
+                    getService().removePrimaryClipChangedListener(
+                            mPrimaryClipChangedServiceListener);
+                } catch (RemoteException e) {
+                }
+            }
+        }
+    }
+
+    /**
+     * @deprecated Use {@link #getPrimaryClip()} instead.  This retrieves
+     * the primary clip and tries to coerce it to a string.
+     */
+    public CharSequence getText() {
+        ClippedData clip = getPrimaryClip();
+        if (clip != null && clip.getItemCount() > 0) {
+            return clip.getItem(0).getText();
+        }
+        return null;
+    }
+
+    /**
+     * @deprecated Use {@link #setPrimaryClip(ClippedData)} instead.  This
+     * creates a ClippedItem holding the given text and sets it as the
+     * primary clip.  It has no label or icon.
+     */
+    public void setText(CharSequence text) {
+        setPrimaryClip(new ClippedData(null, null, new ClippedData.Item(text)));
+    }
+
+    /**
+     * Returns true if the clipboard has a primary clip containing text; false otherwise.
+     */
+    public boolean hasText() {
+        try {
+            return getService().hasClipboardText();
+        } catch (RemoteException e) {
+            return false;
+        }
+    }
+
+    void reportPrimaryClipChanged() {
+        Object[] listeners;
+
+        synchronized (mPrimaryClipChangedListeners) {
+            final int N = mPrimaryClipChangedListeners.size();
+            if (N <= 0) {
+                return;
+            }
+            listeners = mPrimaryClipChangedListeners.toArray();
+        }
+
+        for (int i=0; i<listeners.length; i++) {
+            ((OnPrimaryClipChangedListener)listeners[i]).onPrimaryClipChanged();
+        }
+    }
+}
diff --git a/core/java/android/content/ClippedData.aidl b/core/java/android/content/ClippedData.aidl
new file mode 100644
index 0000000..5246526
--- /dev/null
+++ b/core/java/android/content/ClippedData.aidl
@@ -0,0 +1,19 @@
+/**
+ * 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.content;
+
+parcelable ClippedData;
diff --git a/core/java/android/content/ClippedData.java b/core/java/android/content/ClippedData.java
new file mode 100644
index 0000000..ebb194f
--- /dev/null
+++ b/core/java/android/content/ClippedData.java
@@ -0,0 +1,189 @@
+/**
+ * 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.content;
+
+import android.graphics.Bitmap;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import java.util.ArrayList;
+
+/**
+ * Representation of a clipped data on the clipboard.
+ *
+ * <p>ClippedData is a complex type containing one or Item instances,
+ * each of which can hold one or more representations of an item of data.
+ * For display to the user, it also has a label and iconic representation.</p>
+ *
+ * <p>The types than an individial item can currently contain are:</p>
+ *
+ * <ul>
+ * <li> Text: a basic string of text.  This is actually a CharSequence,
+ * so it can be formatted text supported by corresponding Android built-in
+ * style spans.  (Custom application spans are not supported and will be
+ * stripped when transporting through the clipboard.)
+ * <li> Intent: an arbitrary Intent object.  A typical use is the shortcut
+ * to create when pasting a clipped item on to the home screen.
+ * <li> Uri: a URI reference.  Currently this should only be a content: URI.
+ * This representation allows an application to share complex or large clips,
+ * by providing a URI to a content provider holding the data.
+ * </ul>
+ */
+public class ClippedData implements Parcelable {
+    CharSequence mLabel;
+    Bitmap mIcon;
+
+    final ArrayList<Item> mItems = new ArrayList<Item>();
+
+    public static class Item {
+        CharSequence mText;
+        Intent mIntent;
+        Uri mUri;
+
+        public Item(CharSequence text) {
+            mText = text;
+        }
+
+        public Item(Intent intent) {
+            mIntent = intent;
+        }
+
+        public Item(Uri uri) {
+            mUri = uri;
+        }
+
+        public Item(CharSequence text, Intent intent, Uri uri) {
+            mText = text;
+            mIntent = intent;
+            mUri = uri;
+        }
+
+        public CharSequence getText() {
+            return mText;
+        }
+
+        public Intent getIntent() {
+            return mIntent;
+        }
+
+        public Uri getUri() {
+            return mUri;
+        }
+    }
+
+    /**
+     * Create a new clip.
+     *
+     * @param label Label to show to the user describing this clip.
+     * @param icon Bitmap providing the user with an iconing representation of
+     * the clip.
+     * @param item The contents of the first item in the clip.
+     */
+    public ClippedData(CharSequence label, Bitmap icon, Item item) {
+        if (item == null) {
+            throw new NullPointerException("item is null");
+        }
+        mLabel = label;
+        mIcon = icon;
+        mItems.add(item);
+    }
+
+    public void addItem(Item item) {
+        if (item == null) {
+            throw new NullPointerException("item is null");
+        }
+        mItems.add(item);
+    }
+
+    public CharSequence getLabel() {
+        return mLabel;
+    }
+
+    public Bitmap getIcon() {
+        return mIcon;
+    }
+
+    public int getItemCount() {
+        return mItems.size();
+    }
+
+    public Item getItem(int index) {
+        return mItems.get(index);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        TextUtils.writeToParcel(mLabel, dest, flags);
+        if (mIcon != null) {
+            dest.writeInt(1);
+            mIcon.writeToParcel(dest, flags);
+        } else {
+            dest.writeInt(0);
+        }
+        final int N = mItems.size();
+        dest.writeInt(N);
+        for (int i=0; i<N; i++) {
+            Item item = mItems.get(i);
+            TextUtils.writeToParcel(item.mText, dest, flags);
+            if (item.mIntent != null) {
+                dest.writeInt(1);
+                item.mIntent.writeToParcel(dest, flags);
+            } else {
+                dest.writeInt(0);
+            }
+            if (item.mUri != null) {
+                dest.writeInt(1);
+                item.mUri.writeToParcel(dest, flags);
+            } else {
+                dest.writeInt(0);
+            }
+        }
+    }
+
+    ClippedData(Parcel in) {
+        mLabel = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+        if (in.readInt() != 0) {
+            mIcon = Bitmap.CREATOR.createFromParcel(in);
+        }
+        final int N = in.readInt();
+        for (int i=0; i<N; i++) {
+            CharSequence text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+            Intent intent = in.readInt() != 0 ? Intent.CREATOR.createFromParcel(in) : null;
+            Uri uri = in.readInt() != 0 ? Uri.CREATOR.createFromParcel(in) : null;
+            mItems.add(new Item(text, intent, uri));
+        }
+    }
+
+    public static final Parcelable.Creator<ClippedData> CREATOR =
+        new Parcelable.Creator<ClippedData>() {
+
+            public ClippedData createFromParcel(Parcel source) {
+                return new ClippedData(source);
+            }
+
+            public ClippedData[] newArray(int size) {
+                return new ClippedData[size];
+            }
+        };
+}
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
index 9b9f796..a3252ed 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -32,6 +32,7 @@
 import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
 import android.os.Process;
+import android.util.Log;
 
 import java.io.File;
 import java.io.FileNotFoundException;
@@ -76,6 +77,8 @@
  * cross-process calls.</p>
  */
 public abstract class ContentProvider implements ComponentCallbacks {
+    private static final String TAG = "ContentProvider";
+
     /*
      * Note: if you add methods to ContentProvider, you must add similar methods to
      *       MockContentProvider.
@@ -831,4 +834,34 @@
     public Bundle call(String method, String request, Bundle args) {
         return null;
     }
+
+    /**
+     * Shuts down this instance of the ContentProvider. It is useful when writing tests that use
+     * the ContentProvider.
+     * <p>
+     * If a unittest starts the ContentProvider in its test(..() methods, it could run into sqlite
+     * errors "disk I/O error" or "corruption" in the following scenario:
+     * <ul>
+     *   <li>Say, there are 2 test methods in the unittest</li>
+     *   <li>test1() (or setUp()) causes ContentProvider object to be initialized and
+     *   assume it opens a database connection to "foo.db"</li>
+     *   <li>est1() completes and test2() starts</li>
+     *   <li>During the execution of test2() there will be 2 connections to "foo.db"</li>
+     *   <li>Different threads in the ContentProvider may have one of these two connection
+     *   handles. This is not a problem per se</li>
+     *   <li>But if the two threads with 2 database connections don't interact correctly,
+     *   there could be unexpected errors from sqlite</li>
+     *   <li>Some of those unexpected errros are "disk I/O error" or "corruption" error</li>
+     *   <li>Common practice in tearDown() is to delete test directory (and the database files)</li>
+     *   <li>If this is done while some threads are still holding unclosed database connections,
+     *   sqlite quite easily gets into corruption and disk I/O errors</li>
+     * </ul>
+     * <p>
+     * tearDown() in the unittests should call this method to have ContentProvider gracefully
+     * shutdown all database connections.
+     */
+    public void shutdown() {
+        Log.w(TAG, "implement ContentProvider shutdown() to make sure all database " +
+                "connections are gracefully shutdown");
+    }
 }
diff --git a/core/java/android/content/ContentQueryMap.java b/core/java/android/content/ContentQueryMap.java
index dbcb4a7..c955094 100644
--- a/core/java/android/content/ContentQueryMap.java
+++ b/core/java/android/content/ContentQueryMap.java
@@ -129,7 +129,9 @@
     /** Requeries the cursor and reads the contents into the cache */
     public void requery() {
         mDirty = false;
-        mCursor.requery();
+        if (!mCursor.requery()) {
+            throw new IllegalStateException("trying to requery an already closed cursor");
+        }
         readCursorIntoCache();
         setChanged();
         notifyObservers();
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index 69f7611..2ea0df96 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -44,9 +44,9 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Random;
-import java.util.ArrayList;
 
 
 /**
@@ -401,8 +401,7 @@
     /**
      * Open a raw file descriptor to access data under a "content:" URI.  This
      * interacts with the underlying {@link ContentProvider#openAssetFile}
-     * ContentProvider.openAssetFile()} method of the provider associated with the
-     * given URI, to retrieve any file stored there.
+     * method of the provider associated with the given URI, to retrieve any file stored there.
      *
      * <h5>Accepts the following URI schemes:</h5>
      * <ul>
@@ -1342,7 +1341,7 @@
     }
 
     private final class CursorWrapperInner extends CursorWrapper {
-        private IContentProvider mContentProvider;
+        private final IContentProvider mContentProvider;
         public static final String TAG="CursorWrapperInner";
         private boolean mCloseFlag = false;
 
@@ -1371,7 +1370,7 @@
     }
 
     private final class ParcelFileDescriptorInner extends ParcelFileDescriptor {
-        private IContentProvider mContentProvider;
+        private final IContentProvider mContentProvider;
         public static final String TAG="ParcelFileDescriptorInner";
         private boolean mReleaseProviderFlag = false;
 
diff --git a/core/java/android/content/ContentValues.java b/core/java/android/content/ContentValues.java
index 75787cd..e6dedc1 100644
--- a/core/java/android/content/ContentValues.java
+++ b/core/java/android/content/ContentValues.java
@@ -446,6 +446,15 @@
         return mValues.entrySet();
     }
 
+    /**
+     * Returns a set of all of the keys
+     *
+     * @return a set of all of the keys
+     */
+    public Set<String> keySet() {
+        return mValues.keySet();
+    }
+
     public static final Parcelable.Creator<ContentValues> CREATOR =
             new Parcelable.Creator<ContentValues>() {
         @SuppressWarnings({"deprecation", "unchecked"})
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index aecdcb3..24c0305 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -21,10 +21,12 @@
 import android.content.res.AssetManager;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
+import android.database.DatabaseErrorHandler;
 import android.database.sqlite.SQLiteDatabase;
 import android.database.sqlite.SQLiteDatabase.CursorFactory;
 import android.graphics.Bitmap;
 import android.graphics.drawable.Drawable;
+import android.media.MediaScannerConnection.OnScanCompletedListener;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Handler;
@@ -588,6 +590,32 @@
             int mode, CursorFactory factory);
 
     /**
+     * Open a new private SQLiteDatabase associated with this Context's
+     * application package.  Creates the database file if it doesn't exist.
+     *
+     * <p>Accepts input param: a concrete instance of {@link DatabaseErrorHandler} to be
+     * used to handle corruption when sqlite reports database corruption.</p>
+     *
+     * @param name The name (unique in the application package) of the database.
+     * @param mode Operating mode.  Use 0 or {@link #MODE_PRIVATE} for the
+     *     default operation, {@link #MODE_WORLD_READABLE}
+     *     and {@link #MODE_WORLD_WRITEABLE} to control permissions.
+     * @param factory An optional factory class that is called to instantiate a
+     *     cursor when query is called.
+     * @param errorHandler the {@link DatabaseErrorHandler} to be used when sqlite reports database
+     * corruption. if null, {@link android.database.DefaultDatabaseErrorHandler} is assumed.
+     * @return The contents of a newly created database with the given name.
+     * @throws android.database.sqlite.SQLiteException if the database file could not be opened.
+     *
+     * @see #MODE_PRIVATE
+     * @see #MODE_WORLD_READABLE
+     * @see #MODE_WORLD_WRITEABLE
+     * @see #deleteDatabase
+     */
+    public abstract SQLiteDatabase openOrCreateDatabase(String name,
+            int mode, CursorFactory factory, DatabaseErrorHandler errorHandler);
+
+    /**
      * Delete an existing private SQLiteDatabase associated with this Context's
      * application package.
      *
@@ -1356,7 +1384,16 @@
      * @see android.location.LocationManager
      */
     public static final String LOCATION_SERVICE = "location";
-    
+
+    /**
+     * Use with {@link #getSystemService} to retrieve a
+     * {@link android.location.CountryDetector} for detecting the country that
+     * the user is in.
+     *
+     * @hide
+     */
+    public static final String COUNTRY_DETECTOR = "country_detector";
+
     /**
      * Use with {@link #getSystemService} to retrieve a {@link
      * android.app.SearchManager} for handling searches.
diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java
index a447108..3f5d215 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -20,6 +20,7 @@
 import android.content.pm.PackageManager;
 import android.content.res.AssetManager;
 import android.content.res.Resources;
+import android.database.DatabaseErrorHandler;
 import android.database.sqlite.SQLiteDatabase;
 import android.database.sqlite.SQLiteDatabase.CursorFactory;
 import android.graphics.Bitmap;
@@ -204,6 +205,12 @@
     }
 
     @Override
+    public SQLiteDatabase openOrCreateDatabase(String name, int mode, CursorFactory factory,
+            DatabaseErrorHandler errorHandler) {
+        return mBase.openOrCreateDatabase(name, mode, factory, errorHandler);
+    }
+
+    @Override
     public boolean deleteDatabase(String name) {
         return mBase.deleteDatabase(name);
     }
diff --git a/core/java/android/content/CursorLoader.java b/core/java/android/content/CursorLoader.java
new file mode 100644
index 0000000..850ff7f
--- /dev/null
+++ b/core/java/android/content/CursorLoader.java
@@ -0,0 +1,165 @@
+/*
+ * 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.content;
+
+import android.database.Cursor;
+import android.net.Uri;
+
+/**
+ * A loader that queries the {@link ContentResolver} and returns a {@link Cursor}.
+ */
+public class CursorLoader extends AsyncTaskLoader<Cursor> {
+    Cursor mCursor;
+    ForceLoadContentObserver mObserver;
+    boolean mStopped;
+    Uri mUri;
+    String[] mProjection;
+    String mSelection;
+    String[] mSelectionArgs;
+    String mSortOrder;
+
+    /* Runs on a worker thread */
+    @Override
+    public Cursor loadInBackground() {
+        Cursor cursor = getContext().getContentResolver().query(mUri, mProjection, mSelection,
+                mSelectionArgs, mSortOrder);
+        // Ensure the cursor window is filled
+        if (cursor != null) {
+            cursor.getCount();
+            cursor.registerContentObserver(mObserver);
+        }
+        return cursor;
+    }
+
+    /* Runs on the UI thread */
+    @Override
+    public void deliverResult(Cursor cursor) {
+        if (mStopped) {
+            // An async query came in while the loader is stopped
+            if (cursor != null) {
+                cursor.close();
+            }
+            return;
+        }
+        Cursor oldCursor = mCursor;
+        mCursor = cursor;
+        super.deliverResult(cursor);
+
+        if (oldCursor != null && !oldCursor.isClosed()) {
+            oldCursor.close();
+        }
+    }
+
+    public CursorLoader(Context context, Uri uri, String[] projection, String selection,
+            String[] selectionArgs, String sortOrder) {
+        super(context);
+        mObserver = new ForceLoadContentObserver();
+        mUri = uri;
+        mProjection = projection;
+        mSelection = selection;
+        mSelectionArgs = selectionArgs;
+        mSortOrder = sortOrder;
+    }
+
+    /**
+     * Starts an asynchronous load of the contacts list data. When the result is ready the callbacks
+     * will be called on the UI thread. If a previous load has been completed and is still valid
+     * the result may be passed to the callbacks immediately.
+     *
+     * Must be called from the UI thread
+     */
+    @Override
+    public void startLoading() {
+        mStopped = false;
+
+        if (mCursor != null) {
+            deliverResult(mCursor);
+        } else {
+            forceLoad();
+        }
+    }
+
+    /**
+     * Must be called from the UI thread
+     */
+    @Override
+    public void stopLoading() {
+        if (mCursor != null && !mCursor.isClosed()) {
+            mCursor.close();
+            mCursor = null;
+        }
+
+        // Attempt to cancel the current load task if possible.
+        cancelLoad();
+
+        // Make sure that any outstanding loads clean themselves up properly
+        mStopped = true;
+    }
+
+    @Override
+    public void onCancelled(Cursor cursor) {
+        if (cursor != null && !cursor.isClosed()) {
+            cursor.close();
+        }
+    }
+
+    @Override
+    public void destroy() {
+        // Ensure the loader is stopped
+        stopLoading();
+    }
+
+    public Uri getUri() {
+        return mUri;
+    }
+
+    public void setUri(Uri uri) {
+        mUri = uri;
+    }
+
+    public String[] getProjection() {
+        return mProjection;
+    }
+
+    public void setProjection(String[] projection) {
+        mProjection = projection;
+    }
+
+    public String getSelection() {
+        return mSelection;
+    }
+
+    public void setSelection(String selection) {
+        mSelection = selection;
+    }
+
+    public String[] getSelectionArgs() {
+        return mSelectionArgs;
+    }
+
+    public void setSelectionArgs(String[] selectionArgs) {
+        mSelectionArgs = selectionArgs;
+    }
+
+    public String getSortOrder() {
+        return mSortOrder;
+    }
+
+    public void setSortOrder(String sortOrder) {
+        mSortOrder = sortOrder;
+    }
+}
diff --git a/core/java/android/content/IClipboard.aidl b/core/java/android/content/IClipboard.aidl
new file mode 100644
index 0000000..b4534a9
--- /dev/null
+++ b/core/java/android/content/IClipboard.aidl
@@ -0,0 +1,38 @@
+/**
+ * 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.
+ */
+
+package android.content;
+
+import android.content.ClippedData;
+import android.content.IOnPrimaryClipChangedListener;
+
+/**
+ * Programming interface to the clipboard, which allows copying and pasting
+ * between applications.
+ * {@hide}
+ */
+interface IClipboard {
+    void setPrimaryClip(in ClippedData clip);
+    ClippedData getPrimaryClip();
+    boolean hasPrimaryClip();
+    void addPrimaryClipChangedListener(in IOnPrimaryClipChangedListener listener);
+    void removePrimaryClipChangedListener(in IOnPrimaryClipChangedListener listener);
+
+    /**
+     * Returns true if the clipboard contains text; false otherwise.
+     */
+    boolean hasClipboardText();
+}
diff --git a/core/java/android/content/IOnPrimaryClipChangedListener.aidl b/core/java/android/content/IOnPrimaryClipChangedListener.aidl
new file mode 100644
index 0000000..fb42a45
--- /dev/null
+++ b/core/java/android/content/IOnPrimaryClipChangedListener.aidl
@@ -0,0 +1,21 @@
+/**
+ * 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.
+ */
+
+package android.content;
+
+oneway interface IOnPrimaryClipChangedListener {
+    void dispatchPrimaryClipChanged();
+}
diff --git a/core/java/android/content/Loader.java b/core/java/android/content/Loader.java
new file mode 100644
index 0000000..234096a
--- /dev/null
+++ b/core/java/android/content/Loader.java
@@ -0,0 +1,164 @@
+/*
+ * 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.content;
+
+import android.database.ContentObserver;
+import android.os.Handler;
+
+/**
+ * An abstract class that performs asynchronous loading of data. While Loaders are active
+ * they should monitor the source of their data and deliver new results when the contents
+ * change.
+ *
+ * @param <D> The result returned when the load is complete
+ */
+public abstract class Loader<D> {
+    int mId;
+    OnLoadCompleteListener<D> mListener;
+    Context mContext;
+
+    public final class ForceLoadContentObserver extends ContentObserver {
+        public ForceLoadContentObserver() {
+            super(new Handler());
+        }
+
+        @Override
+        public boolean deliverSelfNotifications() {
+            return true;
+        }
+
+        @Override
+        public void onChange(boolean selfChange) {
+            onContentChanged();
+        }
+    }
+
+    public interface OnLoadCompleteListener<D> {
+        /**
+         * Called on the thread that created the Loader when the load is complete.
+         *
+         * @param loader the loader that completed the load
+         * @param data the result of the load
+         */
+        public void onLoadComplete(Loader<D> loader, D data);
+    }
+
+    /**
+     * Stores away the application context associated with context. Since Loaders can be used
+     * across multiple activities it's dangerous to store the context directly.
+     *
+     * @param context used to retrieve the application context.
+     */
+    public Loader(Context context) {
+        mContext = context.getApplicationContext();
+    }
+
+    /**
+     * Sends the result of the load to the registered listener. Should only be called by subclasses.
+     *
+     * Must be called from the UI thread.
+     *
+     * @param data the result of the load
+     */
+    public void deliverResult(D data) {
+        if (mListener != null) {
+            mListener.onLoadComplete(this, data);
+        }
+    }
+
+    /**
+     * @return an application context retrieved from the Context passed to the constructor.
+     */
+    public Context getContext() {
+        return mContext;
+    }
+
+    /**
+     * @return the ID of this loader
+     */
+    public int getId() {
+        return mId;
+    }
+
+    /**
+     * Registers a class that will receive callbacks when a load is complete. The callbacks will
+     * be called on the UI thread so it's safe to pass the results to widgets.
+     *
+     * Must be called from the UI thread
+     */
+    public void registerListener(int id, OnLoadCompleteListener<D> listener) {
+        if (mListener != null) {
+            throw new IllegalStateException("There is already a listener registered");
+        }
+        mListener = listener;
+        mId = id;
+    }
+
+    /**
+     * Must be called from the UI thread
+     */
+    public void unregisterListener(OnLoadCompleteListener<D> listener) {
+        if (mListener == null) {
+            throw new IllegalStateException("No listener register");
+        }
+        if (mListener != listener) {
+            throw new IllegalArgumentException("Attempting to unregister the wrong listener");
+        }
+        mListener = null;
+    }
+
+    /**
+     * Starts an asynchronous load of the contacts list data. When the result is ready the callbacks
+     * will be called on the UI thread. If a previous load has been completed and is still valid
+     * the result may be passed to the callbacks immediately. The loader will monitor the source of
+     * the data set and may deliver future callbacks if the source changes. Calling
+     * {@link #stopLoading} will stop the delivery of callbacks.
+     *
+     * Must be called from the UI thread
+     */
+    public abstract void startLoading();
+
+    /**
+     * Force an asynchronous load. Unlike {@link #startLoading()} this will ignore a previously
+     * loaded data set and load a new one.
+     */
+    public abstract void forceLoad();
+
+    /**
+     * Stops delivery of updates until the next time {@link #startLoading()} is called
+     *
+     * Must be called from the UI thread
+     */
+    public abstract void stopLoading();
+
+    /**
+     * Destroys the loader and frees its resources, making it unusable.
+     *
+     * Must be called from the UI thread
+     */
+    public abstract void destroy();
+
+    /**
+     * Called when {@link ForceLoadContentObserver} detects a change.  Calls {@link #forceLoad()}
+     * by default.
+     *
+     * Must be called from the UI thread
+     */
+    public void onContentChanged() {
+        forceLoad();
+    }
+}
\ No newline at end of file
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/content/XmlDocumentProvider.java b/core/java/android/content/XmlDocumentProvider.java
new file mode 100644
index 0000000..153ad38
--- /dev/null
+++ b/core/java/android/content/XmlDocumentProvider.java
@@ -0,0 +1,436 @@
+/*
+ * 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.content;
+
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpStatus;
+import org.apache.http.client.methods.HttpGet;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlPullParserFactory;
+
+import android.content.ContentResolver.OpenResourceIdResult;
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.net.Uri;
+import android.net.http.AndroidHttpClient;
+import android.util.Log;
+import android.widget.CursorAdapter;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.BitSet;
+import java.util.Stack;
+import java.util.regex.Pattern;
+
+/**
+ * A read-only content provider which extracts data out of an XML document.
+ *
+ * <p>A XPath-like selection pattern is used to select some nodes in the XML document. Each such
+ * node will create a row in the {@link Cursor} result.</p>
+ *
+ * Each row is then populated with columns that are also defined as XPath-like projections. These
+ * projections fetch attributes values or text in the matching row node or its children.
+ *
+ * <p>To add this provider in your application, you should add its declaration to your application
+ * manifest:
+ * <pre class="prettyprint">
+ * &lt;provider android:name="android.content.XmlDocumentProvider" android:authorities="xmldocument" /&gt;
+ * </pre>
+ * </p>
+ *
+ * <h2>Node selection syntax</h2>
+ * The node selection syntax is made of the concatenation of an arbitrary number (at least one) of
+ * <code>/node_name</code> node selection patterns.
+ *
+ * <p>The <code>/root/child1/child2</code> pattern will for instance match all nodes named
+ * <code>child2</code> which are children of a node named <code>child1</code> which are themselves
+ * children of a root node named <code>root</code>.</p>
+ *
+ * Any <code>/</code> separator in the previous expression can be replaced by a <code>//</code>
+ * separator instead, which indicated a <i>descendant</i> instead of a child.
+ *
+ * <p>The <code>//node1//node2</code> pattern will for instance match all nodes named
+ * <code>node2</code> which are descendant of a node named <code>node1</code> located anywhere in
+ * the document hierarchy.</p>
+ *
+ * Node names can contain namespaces in the form <code>namespace:node</code>.
+ *
+ * <h2>Projection syntax</h2>
+ * For every selected node, the projection will then extract actual data from this node and its
+ * descendant.
+ *
+ * <p>Use a syntax similar to the selection syntax described above to select the text associated
+ * with a child of the selected node. The implicit root of this projection pattern is the selected
+ * node. <code>/</code> will hence refer to the text of the selected node, while
+ * <code>/child1</code> will fetch the text of its child named <code>child1</code> and
+ * <code>//child1</code> will match any <i>descendant</i> named <code>child1</code>. If several
+ * nodes match the projection pattern, their texts are appended as a result.</p>
+ *
+ * A projection can also fetch any node attribute by appending a <code>@attribute_name</code>
+ * pattern to the previously described syntax. <code>//child1@price</code> will for instance match
+ * the attribute <code>price</code> of any <code>child1</code> descendant.
+ *
+ * <p>If a projection does not match any node/attribute, its associated value will be an empty
+ * string.</p>
+ *
+ * <h2>Example</h2>
+ * Using the following XML document:
+ * <pre class="prettyprint">
+ * &lt;library&gt;
+ *   &lt;book id="EH94"&gt;
+ *     &lt;title&gt;The Old Man and the Sea&lt;/title&gt;
+ *     &lt;author&gt;Ernest Hemingway&lt;/author&gt;
+ *   &lt;/book&gt;
+ *   &lt;book id="XX10"&gt;
+ *     &lt;title&gt;The Arabian Nights: Tales of 1,001 Nights&lt;/title&gt;
+ *   &lt;/book&gt;
+ *   &lt;no-id&gt;
+ *     &lt;book&gt;
+ *       &lt;title&gt;Animal Farm&lt;/title&gt;
+ *       &lt;author&gt;George Orwell&lt;/author&gt;
+ *     &lt;/book&gt;
+ *   &lt;/no-id&gt;
+ * &lt;/library&gt;
+ * </pre>
+ * A selection pattern of <code>/library//book</code> will match the three book entries (while
+ * <code>/library/book</code> will only match the first two ones).
+ *
+ * <p>Defining the projections as <code>/title</code>, <code>/author</code> and <code>@id</code>
+ * will retrieve the associated data. Note that the author of the second book as well as the id of
+ * the third are empty strings.
+ */
+public class XmlDocumentProvider extends ContentProvider {
+    /*
+     * Ideas for improvement:
+     * - Expand XPath-like syntax to allow for [nb] child number selector
+     * - Address the starting . bug in AbstractCursor which prevents a true XPath syntax.
+     * - Provide an alternative to concatenation when several node match (list-like).
+     * - Support namespaces in attribute names.
+     * - Incremental Cursor creation, pagination
+     */
+    private static final String LOG_TAG = "XmlDocumentProvider";
+    private AndroidHttpClient mHttpClient;
+
+    @Override
+    public boolean onCreate() {
+        return true;
+    }
+
+    /**
+     * Query data from the XML document referenced in the URI.
+     *
+     * <p>The XML document can be a local resource or a file that will be downloaded from the
+     * Internet. In the latter case, your application needs to request the INTERNET permission in
+     * its manifest.</p>
+     *
+     * The URI will be of the form <code>content://xmldocument/?resource=R.xml.myFile</code> for a
+     * local resource. <code>xmldocument</code> should match the authority declared for this
+     * provider in your manifest. Internet documents are referenced using
+     * <code>content://xmldocument/?url=</code> followed by an encoded version of the URL of your
+     * document (see {@link Uri#encode(String)}).
+     *
+     * <p>The number of columns of the resulting Cursor is equal to the size of the projection
+     * array plus one, named <code>_id</code> which will contain a unique row id (allowing the
+     * Cursor to be used with a {@link CursorAdapter}). The other columns' names are the projection
+     * patterns.</p>
+     *
+     * @param uri The URI of your local resource or Internet document.
+     * @param projection A set of patterns that will be used to extract data from each selected
+     * node. See class documentation for pattern syntax.
+     * @param selection A selection pattern which will select the nodes that will create the
+     * Cursor's rows. See class documentation for pattern syntax.
+     * @param selectionArgs This parameter is ignored.
+     * @param sortOrder The row order in the resulting cursor is determined from the node order in
+     * the XML document. This parameter is ignored.
+     * @return A Cursor or null in case of error.
+     */
+    @Override
+    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+            String sortOrder) {
+
+        XmlPullParser parser = null;
+        mHttpClient = null;
+
+        final String url = uri.getQueryParameter("url");
+        if (url != null) {
+            parser = getUriXmlPullParser(url);
+        } else {
+            final String resource = uri.getQueryParameter("resource");
+            if (resource != null) {
+                Uri resourceUri = Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://" +
+                        getContext().getPackageName() + "/" + resource);
+                parser = getResourceXmlPullParser(resourceUri);
+            }
+        }
+
+        if (parser != null) {
+            XMLCursor xmlCursor = new XMLCursor(selection, projection);
+            try {
+                xmlCursor.parseWith(parser);
+                return xmlCursor;
+            } catch (IOException e) {
+                Log.w(LOG_TAG, "I/O error while parsing XML " + uri, e);
+            } catch (XmlPullParserException e) {
+                Log.w(LOG_TAG, "Error while parsing XML " + uri, e);
+            } finally {
+                if (mHttpClient != null) {
+                    mHttpClient.close();
+                }
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Creates an XmlPullParser for the provided URL. Can be overloaded to provide your own parser.
+     * @param url The URL of the XML document that is to be parsed.
+     * @return An XmlPullParser on this document.
+     */
+    protected XmlPullParser getUriXmlPullParser(String url) {
+        XmlPullParser parser = null;
+        try {
+            XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
+            factory.setNamespaceAware(true);
+            parser = factory.newPullParser();
+        } catch (XmlPullParserException e) {
+            Log.e(LOG_TAG, "Unable to create XmlPullParser", e);
+            return null;
+        }
+
+        InputStream inputStream = null;
+        try {
+            final HttpGet get = new HttpGet(url);
+            mHttpClient = AndroidHttpClient.newInstance("Android");
+            HttpResponse response = mHttpClient.execute(get);
+            if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
+                final HttpEntity entity = response.getEntity();
+                if (entity != null) {
+                    inputStream = entity.getContent();
+                }
+            }
+        } catch (IOException e) {
+            Log.w(LOG_TAG, "Error while retrieving XML file " + url, e);
+            return null;
+        }
+
+        try {
+            parser.setInput(inputStream, null);
+        } catch (XmlPullParserException e) {
+            Log.w(LOG_TAG, "Error while reading XML file from " + url, e);
+            return null;
+        }
+
+        return parser;
+    }
+
+    /**
+     * Creates an XmlPullParser for the provided local resource. Can be overloaded to provide your
+     * own parser.
+     * @param resourceUri A fully qualified resource name referencing a local XML resource.
+     * @return An XmlPullParser on this resource.
+     */
+    protected XmlPullParser getResourceXmlPullParser(Uri resourceUri) {
+        OpenResourceIdResult resourceId;
+        try {
+            resourceId = getContext().getContentResolver().getResourceId(resourceUri);
+            return resourceId.r.getXml(resourceId.id);
+        } catch (FileNotFoundException e) {
+            Log.w(LOG_TAG, "XML resource not found: " + resourceUri.toString(), e);
+            return null;
+        }
+    }
+
+    /**
+     * Returns "vnd.android.cursor.dir/xmldoc".
+     */
+    @Override
+    public String getType(Uri uri) {
+        return "vnd.android.cursor.dir/xmldoc";
+    }
+
+    /**
+     * This ContentProvider is read-only. This method throws an UnsupportedOperationException.
+     **/
+    @Override
+    public Uri insert(Uri uri, ContentValues values) {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * This ContentProvider is read-only. This method throws an UnsupportedOperationException.
+     **/
+    @Override
+    public int delete(Uri uri, String selection, String[] selectionArgs) {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * This ContentProvider is read-only. This method throws an UnsupportedOperationException.
+     **/
+    @Override
+    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+        throw new UnsupportedOperationException();
+    }
+
+    private static class XMLCursor extends MatrixCursor {
+        private final Pattern mSelectionPattern;
+        private Pattern[] mProjectionPatterns;
+        private String[] mAttributeNames;
+        private String[] mCurrentValues;
+        private BitSet[] mActiveTextDepthMask;
+        private final int mNumberOfProjections;
+
+        public XMLCursor(String selection, String[] projections) {
+            super(projections);
+            // The first column in projections is used for the _ID
+            mNumberOfProjections = projections.length - 1;
+            mSelectionPattern = createPattern(selection);
+            createProjectionPattern(projections);
+        }
+
+        private Pattern createPattern(String input) {
+            String pattern = input.replaceAll("//", "/(.*/|)").replaceAll("^/", "^/") + "$";
+            return Pattern.compile(pattern);
+        }
+
+        private void createProjectionPattern(String[] projections) {
+            mProjectionPatterns = new Pattern[mNumberOfProjections];
+            mAttributeNames = new String[mNumberOfProjections];
+            mActiveTextDepthMask = new BitSet[mNumberOfProjections];
+            // Add a column to store _ID
+            mCurrentValues = new String[mNumberOfProjections + 1];
+
+            for (int i=0; i<mNumberOfProjections; i++) {
+                mActiveTextDepthMask[i] = new BitSet();
+                String projection = projections[i + 1]; // +1 to skip the _ID column
+                int atIndex = projection.lastIndexOf('@', projection.length());
+                if (atIndex >= 0) {
+                    mAttributeNames[i] = projection.substring(atIndex+1);
+                    projection = projection.substring(0, atIndex);
+                } else {
+                    mAttributeNames[i] = null;
+                }
+
+                // Conforms to XPath standard: reference to local context starts with a .
+                if (projection.charAt(0) == '.') {
+                    projection = projection.substring(1);
+                }
+                mProjectionPatterns[i] = createPattern(projection);
+            }
+        }
+
+        public void parseWith(XmlPullParser parser) throws IOException, XmlPullParserException {
+            StringBuilder path = new StringBuilder();
+            Stack<Integer> pathLengthStack = new Stack<Integer>();
+
+            // There are two parsing mode: in root mode, rootPath is updated and nodes matching
+            // selectionPattern are searched for and currentNodeDepth is negative.
+            // When a node matching selectionPattern is found, currentNodeDepth is set to 0 and
+            // updated as children are parsed and projectionPatterns are searched in nodePath.
+            int currentNodeDepth = -1;
+
+            // Index where local selected node path starts from in path
+            int currentNodePathStartIndex = 0;
+
+            int eventType = parser.getEventType();
+            while (eventType != XmlPullParser.END_DOCUMENT) {
+
+                if (eventType == XmlPullParser.START_TAG) {
+                    // Update path
+                    pathLengthStack.push(path.length());
+                    path.append('/');
+                    String prefix = null;
+                    try {
+                        // getPrefix is not supported by local Xml resource parser
+                        prefix = parser.getPrefix();
+                    } catch (RuntimeException e) {
+                        prefix = null;
+                    }
+                    if (prefix != null) {
+                        path.append(prefix);
+                        path.append(':');
+                    }
+                    path.append(parser.getName());
+
+                    if (currentNodeDepth >= 0) {
+                        currentNodeDepth++;
+                    } else {
+                        // A node matching selection is found: initialize child parsing mode
+                        if (mSelectionPattern.matcher(path.toString()).matches()) {
+                            currentNodeDepth = 0;
+                            currentNodePathStartIndex = path.length();
+                            mCurrentValues[0] = Integer.toString(getCount()); // _ID
+                            for (int i = 0; i < mNumberOfProjections; i++) {
+                                // Reset values to default (empty string)
+                                mCurrentValues[i + 1] = "";
+                                mActiveTextDepthMask[i].clear();
+                            }
+                        }
+                    }
+
+                    // This test has to be separated from the previous one as currentNodeDepth can
+                    // be modified above (when a node matching selection is found).
+                    if (currentNodeDepth >= 0) {
+                        final String localNodePath = path.substring(currentNodePathStartIndex);
+                        for (int i = 0; i < mNumberOfProjections; i++) {
+                            if (mProjectionPatterns[i].matcher(localNodePath).matches()) {
+                                String attribute = mAttributeNames[i];
+                                if (attribute != null) {
+                                    mCurrentValues[i + 1] =
+                                        parser.getAttributeValue(null, attribute);
+                                } else {
+                                    mActiveTextDepthMask[i].set(currentNodeDepth, true);
+                                }
+                            }
+                        }
+                    }
+
+                } else if (eventType == XmlPullParser.END_TAG) {
+                    // Pop last node from path
+                    final int length = pathLengthStack.pop();
+                    path.setLength(length);
+
+                    if (currentNodeDepth >= 0) {
+                        if (currentNodeDepth == 0) {
+                            // Leaving a selection matching node: add a new row with results
+                            addRow(mCurrentValues);
+                        } else {
+                            for (int i = 0; i < mNumberOfProjections; i++) {
+                                mActiveTextDepthMask[i].set(currentNodeDepth, false);
+                            }
+                        }
+                        currentNodeDepth--;
+                    }
+
+                } else if ((eventType == XmlPullParser.TEXT) && (!parser.isWhitespace())) {
+                    for (int i = 0; i < mNumberOfProjections; i++) {
+                        if ((currentNodeDepth >= 0) &&
+                            (mActiveTextDepthMask[i].get(currentNodeDepth))) {
+                            mCurrentValues[i + 1] += parser.getText();
+                        }
+                    }
+                }
+
+                eventType = parser.next();
+            }
+        }
+    }
+}
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index 364c91e..e21cb97 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -166,6 +166,11 @@
      */
     public static final int FLAG_IMMERSIVE = 0x0200;
     /**
+     * Value for {@link #flags}: true when the application's rendering should
+     * be hardware accelerated.
+     */
+    public static final int FLAG_HARDWARE_ACCELERATED = 0x0400;
+    /**
      * Options that have been set in the activity declaration in the
      * manifest.
      * These include:
@@ -175,7 +180,7 @@
      * {@link #FLAG_STATE_NOT_NEEDED}, {@link #FLAG_EXCLUDE_FROM_RECENTS},
      * {@link #FLAG_ALLOW_TASK_REPARENTING}, {@link #FLAG_NO_HISTORY},
      * {@link #FLAG_FINISH_ON_CLOSE_SYSTEM_DIALOGS},
-     * {@link #FLAG_IMMERSIVE}
+     * {@link #FLAG_IMMERSIVE}, {@link #FLAG_HARDWARE_ACCELERATED}
      */
     public int flags;
 
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 47e668d..155c86c 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -16,9 +16,6 @@
 
 package android.content.pm;
 
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
 import android.content.ComponentName;
 import android.content.Intent;
 import android.content.IntentFilter;
@@ -30,14 +27,14 @@
 import android.os.Build;
 import android.os.Bundle;
 import android.os.PatternMatcher;
-import android.provider.Settings;
 import android.util.AttributeSet;
 import android.util.Config;
 import android.util.DisplayMetrics;
 import android.util.Log;
 import android.util.TypedValue;
-
 import com.android.internal.util.XmlUtils;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.File;
 import java.io.IOException;
@@ -48,7 +45,6 @@
 import java.util.ArrayList;
 import java.util.Enumeration;
 import java.util.Iterator;
-import java.util.List;
 import java.util.jar.JarEntry;
 import java.util.jar.JarFile;
 
@@ -1535,6 +1531,10 @@
             ai.flags |= ApplicationInfo.FLAG_VM_SAFE_MODE;
         }
 
+        boolean hardwareAccelerated = sa.getBoolean(
+                com.android.internal.R.styleable.AndroidManifestApplication_hardwareAccelerated,
+                false);
+
         if (sa.getBoolean(
                 com.android.internal.R.styleable.AndroidManifestApplication_hasCode,
                 true)) {
@@ -1632,7 +1632,8 @@
 
             String tagName = parser.getName();
             if (tagName.equals("activity")) {
-                Activity a = parseActivity(owner, res, parser, attrs, flags, outError, false);
+                Activity a = parseActivity(owner, res, parser, attrs, flags, outError, false,
+                        hardwareAccelerated);
                 if (a == null) {
                     mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
                     return false;
@@ -1641,7 +1642,7 @@
                 owner.activities.add(a);
 
             } else if (tagName.equals("receiver")) {
-                Activity a = parseActivity(owner, res, parser, attrs, flags, outError, true);
+                Activity a = parseActivity(owner, res, parser, attrs, flags, outError, true, false);
                 if (a == null) {
                     mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
                     return false;
@@ -1776,7 +1777,8 @@
 
     private Activity parseActivity(Package owner, Resources res,
             XmlPullParser parser, AttributeSet attrs, int flags, String[] outError,
-            boolean receiver) throws XmlPullParserException, IOException {
+            boolean receiver, boolean hardwareAccelerated)
+            throws XmlPullParserException, IOException {
         TypedArray sa = res.obtainAttributes(attrs,
                 com.android.internal.R.styleable.AndroidManifestActivity);
 
@@ -1886,8 +1888,14 @@
                 false)) {
             a.info.flags |= ActivityInfo.FLAG_IMMERSIVE;
         }
-
+        
         if (!receiver) {
+            if (sa.getBoolean(
+                    com.android.internal.R.styleable.AndroidManifestActivity_hardwareAccelerated,
+                    hardwareAccelerated)) {
+                a.info.flags |= ActivityInfo.FLAG_HARDWARE_ACCELERATED;
+            }
+
             a.info.launchMode = sa.getInt(
                     com.android.internal.R.styleable.AndroidManifestActivity_launchMode,
                     ActivityInfo.LAUNCH_MULTIPLE);
diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java
index 02956ba..2f110f0 100644
--- a/core/java/android/content/res/Configuration.java
+++ b/core/java/android/content/res/Configuration.java
@@ -266,11 +266,18 @@
         sb.append("/");
         sb.append(navigationHidden);
         sb.append(" orien=");
-        sb.append(orientation);
-        sb.append(" layout=");
-        sb.append(screenLayout);
-        sb.append(" uiMode=");
-        sb.append(uiMode);
+        switch(orientation) {
+            case ORIENTATION_LANDSCAPE:
+                sb.append("L"); break;
+            case ORIENTATION_PORTRAIT:
+                sb.append("P"); break;
+            default:
+                sb.append(orientation);
+        }
+        sb.append(" layout=0x");
+        sb.append(java.lang.Integer.toHexString(screenLayout));
+        sb.append(" uiMode=0x");
+        sb.append(java.lang.Integer.toHexString(uiMode));
         if (seq != 0) {
             sb.append(" seq=");
             sb.append(seq);
diff --git a/core/java/android/content/res/PluralRules.java b/core/java/android/content/res/PluralRules.java
deleted file mode 100644
index 2dce3c1..0000000
--- a/core/java/android/content/res/PluralRules.java
+++ /dev/null
@@ -1,111 +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.content.res;
-
-import java.util.Locale;
-
-/*
- * Yuck-o.  This is not the right way to implement this.  When the ICU PluralRules
- * object has been integrated to android, we should switch to that.  For now, yuck-o.
- */
-
-abstract class PluralRules {
-
-    static final int QUANTITY_OTHER = 0x0000;
-    static final int QUANTITY_ZERO  = 0x0001;
-    static final int QUANTITY_ONE   = 0x0002;
-    static final int QUANTITY_TWO   = 0x0004;
-    static final int QUANTITY_FEW   = 0x0008;
-    static final int QUANTITY_MANY  = 0x0010;
-
-    static final int ID_OTHER = 0x01000004;
-
-    abstract int quantityForNumber(int n);
-
-    final int attrForNumber(int n) {
-        return PluralRules.attrForQuantity(quantityForNumber(n));
-    }
-
-    static final int attrForQuantity(int quantity) {
-        // see include/utils/ResourceTypes.h
-        switch (quantity) {
-            case QUANTITY_ZERO: return 0x01000005;
-            case QUANTITY_ONE:  return 0x01000006;
-            case QUANTITY_TWO:  return 0x01000007;
-            case QUANTITY_FEW:  return 0x01000008;
-            case QUANTITY_MANY: return 0x01000009;
-            default:            return ID_OTHER;
-        }
-    }
-
-    static final String stringForQuantity(int quantity) {
-        switch (quantity) {
-            case QUANTITY_ZERO:
-                return "zero";
-            case QUANTITY_ONE:
-                return "one";
-            case QUANTITY_TWO:
-                return "two";
-            case QUANTITY_FEW:
-                return "few";
-            case QUANTITY_MANY:
-                return "many";
-            default:
-                return "other";
-        }
-    }
-
-    static final PluralRules ruleForLocale(Locale locale) {
-        String lang = locale.getLanguage();
-        if ("cs".equals(lang)) {
-            if (cs == null) cs = new cs();
-            return cs;
-        }
-        else {
-            if (en == null) en = new en();
-            return en;
-        }
-    }
-
-    private static PluralRules cs;
-    private static class cs extends PluralRules {
-        int quantityForNumber(int n) {
-            if (n == 1) {
-                return QUANTITY_ONE;
-            }
-            else if (n >= 2 && n <= 4) {
-                return QUANTITY_FEW;
-            }
-            else {
-                return QUANTITY_OTHER;
-            }
-        }
-    }
-
-    private static PluralRules en;
-    private static class en extends PluralRules {
-        int quantityForNumber(int n) {
-            if (n == 1) {
-                return QUANTITY_ONE;
-            }
-            else {
-                return QUANTITY_OTHER;
-            }
-        }
-    }
-}
-
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index e671359..ba1b3a9 100755
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -16,7 +16,6 @@
 
 package android.content.res;
 
-
 import com.android.internal.util.XmlUtils;
 
 import org.xmlpull.v1.XmlPullParser;
@@ -42,6 +41,8 @@
 import java.lang.ref.WeakReference;
 import java.util.Locale;
 
+import libcore.icu.NativePluralRules;
+
 /**
  * Class for accessing an application's resources.  This sits on top of the
  * asset manager of the application (accessible through getAssets()) and
@@ -53,6 +54,8 @@
     private static final boolean DEBUG_CONFIG = false;
     private static final boolean TRACE_FOR_PRELOAD = false;
 
+    private static final int ID_OTHER = 0x01000004;
+
     // Use the current SDK version code.  If we are a development build,
     // also allow the previous SDK version + 1.
     private static final int sSdkVersion = Build.VERSION.SDK_INT
@@ -91,7 +94,7 @@
     /*package*/ final AssetManager mAssets;
     private final Configuration mConfiguration = new Configuration();
     /*package*/ final DisplayMetrics mMetrics = new DisplayMetrics();
-    PluralRules mPluralRule;
+    private NativePluralRules mPluralRule;
     
     private CompatibilityInfo mCompatibilityInfo;
     private Display mDefaultDisplay;
@@ -208,9 +211,17 @@
     }
 
     /**
+     * Return the character sequence associated with a particular resource ID for a particular
+     * numerical quantity.
+     *
+     * <p>See <a href="{@docRoot}guide/topics/resources/string-resource.html#Plurals">String
+     * Resources</a> for more on quantity strings.
+     *
      * @param id The desired resource identifier, as generated by the aapt
      *           tool. This integer encodes the package, type, and resource
      *           entry. The value 0 is an invalid identifier.
+     * @param quantity The number used to get the correct string for the current language's
+     *           plural rules.
      *
      * @throws NotFoundException Throws NotFoundException if the given ID does not exist.
      *
@@ -218,29 +229,52 @@
      *         possibly styled text information.
      */
     public CharSequence getQuantityText(int id, int quantity) throws NotFoundException {
-        PluralRules rule = getPluralRule();
-        CharSequence res = mAssets.getResourceBagText(id, rule.attrForNumber(quantity));
+        NativePluralRules rule = getPluralRule();
+        CharSequence res = mAssets.getResourceBagText(id,
+                attrForQuantityCode(rule.quantityForInt(quantity)));
         if (res != null) {
             return res;
         }
-        res = mAssets.getResourceBagText(id, PluralRules.ID_OTHER);
+        res = mAssets.getResourceBagText(id, ID_OTHER);
         if (res != null) {
             return res;
         }
         throw new NotFoundException("Plural resource ID #0x" + Integer.toHexString(id)
                 + " quantity=" + quantity
-                + " item=" + PluralRules.stringForQuantity(rule.quantityForNumber(quantity)));
+                + " item=" + stringForQuantityCode(rule.quantityForInt(quantity)));
     }
 
-    private PluralRules getPluralRule() {
+    private NativePluralRules getPluralRule() {
         synchronized (mSync) {
             if (mPluralRule == null) {
-                mPluralRule = PluralRules.ruleForLocale(mConfiguration.locale);
+                mPluralRule = NativePluralRules.forLocale(mConfiguration.locale);
             }
             return mPluralRule;
         }
     }
 
+    private static int attrForQuantityCode(int quantityCode) {
+        switch (quantityCode) {
+            case NativePluralRules.ZERO: return 0x01000005;
+            case NativePluralRules.ONE:  return 0x01000006;
+            case NativePluralRules.TWO:  return 0x01000007;
+            case NativePluralRules.FEW:  return 0x01000008;
+            case NativePluralRules.MANY: return 0x01000009;
+            default:                     return ID_OTHER;
+        }
+    }
+
+    private static String stringForQuantityCode(int quantityCode) {
+        switch (quantityCode) {
+            case NativePluralRules.ZERO: return "zero";
+            case NativePluralRules.ONE:  return "one";
+            case NativePluralRules.TWO:  return "two";
+            case NativePluralRules.FEW:  return "few";
+            case NativePluralRules.MANY: return "many";
+            default:                     return "other";
+        }
+    }
+
     /**
      * Return the string value associated with a particular resource ID.  It
      * will be stripped of any styled text information.
@@ -295,6 +329,9 @@
      * stripped of any styled text information.
      * {@more}
      *
+     * <p>See <a href="{@docRoot}guide/topics/resources/string-resource.html#Plurals">String
+     * Resources</a> for more on quantity strings.
+     *
      * @param id The desired resource identifier, as generated by the aapt
      *           tool. This integer encodes the package, type, and resource
      *           entry. The value 0 is an invalid identifier.
@@ -317,6 +354,9 @@
      * Return the string value associated with a particular resource ID for a particular
      * numerical quantity.
      *
+     * <p>See <a href="{@docRoot}guide/topics/resources/string-resource.html#Plurals">String
+     * Resources</a> for more on quantity strings.
+     *
      * @param id The desired resource identifier, as generated by the aapt
      *           tool. This integer encodes the package, type, and resource
      *           entry. The value 0 is an invalid identifier.
@@ -1315,7 +1355,7 @@
         }
         synchronized (mSync) {
             if (mPluralRule != null) {
-                mPluralRule = PluralRules.ruleForLocale(config.locale);
+                mPluralRule = NativePluralRules.forLocale(config.locale);
             }
         }
     }
diff --git a/core/java/android/database/AbstractCursor.java b/core/java/android/database/AbstractCursor.java
index a5e5e46..9b14998 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;
 
@@ -56,6 +51,10 @@
     abstract public double getDouble(int column);
     abstract public boolean isNull(int column);
 
+    public int getType(int column) {
+        throw new UnsupportedOperationException();
+    }
+
     // TODO implement getBlob in all cursor types
     public byte[] getBlob(int column) {
         throw new UnsupportedOperationException("getBlob is not supported");
@@ -88,7 +87,7 @@
         }
         mDataSetObservable.notifyInvalidated();
     }
-    
+
     public boolean requery() {
         if (mSelfObserver != null && mSelfObserverRegistered == false) {
             mContentResolver.registerContentObserver(mNotifyUri, true, mSelfObserver);
@@ -109,22 +108,6 @@
     }
 
     /**
-     * @hide
-     * @deprecated
-     */
-    public boolean commitUpdates(Map<? extends Long,? extends Map<String,Object>> values) {
-        return false;
-    }
-
-    /**
-     * @hide
-     * @deprecated
-     */
-    public boolean deleteRow() {
-        return false;
-    }
-
-    /**
      * This function is called every time the cursor is successfully scrolled
      * to a new position, giving the subclass a chance to update any state it
      * may have. If it returns false the move function will also do so and the
@@ -320,137 +303,6 @@
         return getColumnNames()[columnIndex];
     }
 
-    /**
-     * @hide
-     * @deprecated
-     */
-    public boolean updateBlob(int columnIndex, byte[] value) {
-        return update(columnIndex, value);
-    }
-
-    /**
-     * @hide
-     * @deprecated
-     */
-    public boolean updateString(int columnIndex, String value) {
-        return update(columnIndex, value);
-    }
-
-    /**
-     * @hide
-     * @deprecated
-     */
-    public boolean updateShort(int columnIndex, short value) {
-        return update(columnIndex, Short.valueOf(value));
-    }
-
-    /**
-     * @hide
-     * @deprecated
-     */
-    public boolean updateInt(int columnIndex, int value) {
-        return update(columnIndex, Integer.valueOf(value));
-    }
-
-    /**
-     * @hide
-     * @deprecated
-     */
-    public boolean updateLong(int columnIndex, long value) {
-        return update(columnIndex, Long.valueOf(value));
-    }
-
-    /**
-     * @hide
-     * @deprecated
-     */
-    public boolean updateFloat(int columnIndex, float value) {
-        return update(columnIndex, Float.valueOf(value));
-    }
-
-    /**
-     * @hide
-     * @deprecated
-     */
-    public boolean updateDouble(int columnIndex, double value) {
-        return update(columnIndex, Double.valueOf(value));
-    }
-
-    /**
-     * @hide
-     * @deprecated
-     */
-    public boolean updateToNull(int columnIndex) {
-        return update(columnIndex, null);
-    }
-
-    /**
-     * @hide
-     * @deprecated
-     */
-    public boolean update(int columnIndex, Object obj) {
-        if (!supportsUpdates()) {
-            return false;
-        }
-
-        // Long.valueOf() returns null sometimes!
-//        Long rowid = Long.valueOf(getLong(mRowIdColumnIndex));
-        Long rowid = new Long(getLong(mRowIdColumnIndex));
-        if (rowid == null) {
-            throw new IllegalStateException("null rowid. mRowIdColumnIndex = " + mRowIdColumnIndex);
-        }
-
-        synchronized(mUpdatedRows) {
-            Map<String, Object> row = mUpdatedRows.get(rowid);
-            if (row == null) {
-                row = new HashMap<String, Object>();
-                mUpdatedRows.put(rowid, row);
-            }
-            row.put(getColumnNames()[columnIndex], obj);
-        }
-
-        return true;
-    }
-
-    /**
-     * Returns <code>true</code> if there are pending updates that have not yet been committed.
-     * 
-     * @return <code>true</code> if there are pending updates that have not yet been committed.
-     * @hide
-     * @deprecated
-     */
-    public boolean hasUpdates() {
-        synchronized(mUpdatedRows) {
-            return mUpdatedRows.size() > 0;
-        }
-    }
-
-    /**
-     * @hide
-     * @deprecated
-     */
-    public void abortUpdates() {
-        synchronized(mUpdatedRows) {
-            mUpdatedRows.clear();
-        }
-    }
-
-    /**
-     * @hide
-     * @deprecated
-     */
-    public boolean commitUpdates() {
-        return commitUpdates(null);
-    }
-
-    /**
-     * @hide
-     * @deprecated
-     */
-    public boolean supportsUpdates() {
-        return mRowIdColumnIndex != -1;
-    }
-
     public void registerContentObserver(ContentObserver observer) {
         mContentObservable.registerObserver(observer);
     }
@@ -478,9 +330,9 @@
         return mDataSetObservable;
         
     }
+
     public void registerDataSetObserver(DataSetObserver observer) {
         mDataSetObservable.registerObserver(observer);
-        
     }
 
     public void unregisterDataSetObserver(DataSetObserver observer) {
@@ -535,36 +387,19 @@
     }
 
     /**
-     * This function returns true if the field has been updated and is
-     * used in conjunction with {@link #getUpdatedField} to allow subclasses to
-     * support reading uncommitted updates. NOTE: This function and
-     * {@link #getUpdatedField} should be called together inside of a
-     * block synchronized on mUpdatedRows.
-     *
-     * @param columnIndex the column index of the field to check
-     * @return true if the field has been updated, false otherwise
+     * @deprecated Always returns false since Cursors do not support updating rows
      */
+    @Deprecated
     protected boolean isFieldUpdated(int columnIndex) {
-        if (mRowIdColumnIndex != -1 && mUpdatedRows.size() > 0) {
-            Map<String, Object> updates = mUpdatedRows.get(mCurrentRowID);
-            if (updates != null && updates.containsKey(getColumnNames()[columnIndex])) {
-                return true;
-            }
-        }
         return false;
     }
 
     /**
-     * This function returns the uncommitted updated value for the field
-     * at columnIndex.  NOTE: This function and {@link #isFieldUpdated} should
-     * be called together inside of a block synchronized on mUpdatedRows.
-     *
-     * @param columnIndex the column index of the field to retrieve
-     * @return the updated value
+     * @deprecated Always returns null since Cursors do not support updating rows
      */
+    @Deprecated
     protected Object getUpdatedField(int columnIndex) {
-        Map<String, Object> updates = mUpdatedRows.get(mCurrentRowID);
-        return updates.get(getColumnNames()[columnIndex]);
+        return null;
     }
 
     /**
@@ -614,11 +449,9 @@
     }
 
     /**
-     * This HashMap contains a mapping from Long rowIDs to another Map
-     * that maps from String column names to new values. A NULL value means to
-     * remove an existing value, and all numeric values are in their class
-     * forms, i.e. Integer, Long, Float, etc.
+     * @deprecated This is never updated by this class and should not be used
      */
+    @Deprecated
     protected HashMap<Long, Map<String, Object>> mUpdatedRows;
 
     /**
@@ -628,6 +461,11 @@
     protected int mRowIdColumnIndex;
 
     protected int mPos;
+    /**
+     * If {@link #mRowIdColumnIndex} is not -1 this contains contains the value of
+     * the column at {@link #mRowIdColumnIndex} for the current row this cursor is
+     * pointing at.
+     */
     protected Long mCurrentRowID;
     protected ContentResolver mContentResolver;
     protected boolean mClosed = false;
diff --git a/core/java/android/database/AbstractWindowedCursor.java b/core/java/android/database/AbstractWindowedCursor.java
index 27a02e2..8addaa8 100644
--- a/core/java/android/database/AbstractWindowedCursor.java
+++ b/core/java/android/database/AbstractWindowedCursor.java
@@ -19,202 +19,105 @@
 /**
  * A base class for Cursors that store their data in {@link CursorWindow}s.
  */
-public abstract class AbstractWindowedCursor extends AbstractCursor
-{
+public abstract class AbstractWindowedCursor extends AbstractCursor {
     @Override
-    public byte[] getBlob(int columnIndex)
-    {
+    public byte[] getBlob(int columnIndex) {
         checkPosition();
-
-        synchronized(mUpdatedRows) {
-            if (isFieldUpdated(columnIndex)) {
-                return (byte[])getUpdatedField(columnIndex);
-            }
-        }
-
         return mWindow.getBlob(mPos, columnIndex);
     }
 
     @Override
-    public String getString(int columnIndex)
-    {
+    public String getString(int columnIndex) {
         checkPosition();
-
-        synchronized(mUpdatedRows) {
-            if (isFieldUpdated(columnIndex)) {
-                return (String)getUpdatedField(columnIndex);
-            }
-        }
-
         return mWindow.getString(mPos, columnIndex);
     }
-    
+
     @Override
-    public void copyStringToBuffer(int columnIndex, CharArrayBuffer buffer)
-    {
+    public void copyStringToBuffer(int columnIndex, CharArrayBuffer buffer) {
         checkPosition();
-        
-        synchronized(mUpdatedRows) {
-            if (isFieldUpdated(columnIndex)) {
-                super.copyStringToBuffer(columnIndex, buffer);
-            }
-        }
-        
         mWindow.copyStringToBuffer(mPos, columnIndex, buffer);
     }
 
     @Override
-    public short getShort(int columnIndex)
-    {
+    public short getShort(int columnIndex) {
         checkPosition();
-
-        synchronized(mUpdatedRows) {
-            if (isFieldUpdated(columnIndex)) {
-                Number value = (Number)getUpdatedField(columnIndex);
-                return value.shortValue();
-            }
-        }
-
         return mWindow.getShort(mPos, columnIndex);
     }
 
     @Override
-    public int getInt(int columnIndex)
-    {
+    public int getInt(int columnIndex) {
         checkPosition();
-
-        synchronized(mUpdatedRows) {
-            if (isFieldUpdated(columnIndex)) {
-                Number value = (Number)getUpdatedField(columnIndex);
-                return value.intValue();
-            }
-        }
-
         return mWindow.getInt(mPos, columnIndex);
     }
 
     @Override
-    public long getLong(int columnIndex)
-    {
+    public long getLong(int columnIndex) {
         checkPosition();
-
-        synchronized(mUpdatedRows) {
-            if (isFieldUpdated(columnIndex)) {
-                Number value = (Number)getUpdatedField(columnIndex);
-                return value.longValue();
-            }
-        }
-
         return mWindow.getLong(mPos, columnIndex);
     }
 
     @Override
-    public float getFloat(int columnIndex)
-    {
+    public float getFloat(int columnIndex) {
         checkPosition();
-
-        synchronized(mUpdatedRows) {
-            if (isFieldUpdated(columnIndex)) {
-                Number value = (Number)getUpdatedField(columnIndex);
-                return value.floatValue();
-            }
-        }
-
         return mWindow.getFloat(mPos, columnIndex);
     }
 
     @Override
-    public double getDouble(int columnIndex)
-    {
+    public double getDouble(int columnIndex) {
         checkPosition();
-
-        synchronized(mUpdatedRows) {
-            if (isFieldUpdated(columnIndex)) {
-                Number value = (Number)getUpdatedField(columnIndex);
-                return value.doubleValue();
-            }
-        }
-
         return mWindow.getDouble(mPos, columnIndex);
     }
 
     @Override
-    public boolean isNull(int columnIndex)
-    {
+    public boolean isNull(int columnIndex) {
         checkPosition();
-
-        synchronized(mUpdatedRows) {
-            if (isFieldUpdated(columnIndex)) {
-                return getUpdatedField(columnIndex) == null;
-            }
-        }
-
-        return mWindow.isNull(mPos, columnIndex);
+        return mWindow.getType(mPos, columnIndex) == Cursor.FIELD_TYPE_NULL;
     }
 
-    public boolean isBlob(int columnIndex)
-    {
-        checkPosition();
-
-        synchronized(mUpdatedRows) {
-            if (isFieldUpdated(columnIndex)) {
-                Object object = getUpdatedField(columnIndex);
-                return object == null || object instanceof byte[];
-            }
-        }
-
-        return mWindow.isBlob(mPos, columnIndex);
+    /**
+     * @deprecated Use {@link #getType}
+     */
+    @Deprecated
+    public boolean isBlob(int columnIndex) {
+        return getType(columnIndex) == Cursor.FIELD_TYPE_BLOB;
     }
 
-    public boolean isString(int columnIndex)
-    {
-        checkPosition();
-
-        synchronized(mUpdatedRows) {
-            if (isFieldUpdated(columnIndex)) {
-                Object object = getUpdatedField(columnIndex);
-                return object == null || object instanceof String;
-            }
-        }
-
-        return mWindow.isString(mPos, columnIndex);
+    /**
+     * @deprecated Use {@link #getType}
+     */
+    @Deprecated
+    public boolean isString(int columnIndex) {
+        return getType(columnIndex) == Cursor.FIELD_TYPE_STRING;
     }
 
-    public boolean isLong(int columnIndex)
-    {
-        checkPosition();
-
-        synchronized(mUpdatedRows) {
-            if (isFieldUpdated(columnIndex)) {
-                Object object = getUpdatedField(columnIndex);
-                return object != null && (object instanceof Integer || object instanceof Long);
-            }
-        }
-
-        return mWindow.isLong(mPos, columnIndex);
+    /**
+     * @deprecated Use {@link #getType}
+     */
+    @Deprecated
+    public boolean isLong(int columnIndex) {
+        return getType(columnIndex) == Cursor.FIELD_TYPE_INTEGER;
     }
 
-    public boolean isFloat(int columnIndex)
-    {
-        checkPosition();
-
-        synchronized(mUpdatedRows) {
-            if (isFieldUpdated(columnIndex)) {
-                Object object = getUpdatedField(columnIndex);
-                return object != null && (object instanceof Float || object instanceof Double);
-            }
-        }
-
-        return mWindow.isFloat(mPos, columnIndex);
+    /**
+     * @deprecated Use {@link #getType}
+     */
+    @Deprecated
+    public boolean isFloat(int columnIndex) {
+        return getType(columnIndex) == Cursor.FIELD_TYPE_FLOAT;
     }
 
     @Override
-    protected void checkPosition()
-    {
+    public int getType(int columnIndex) {
+        checkPosition();
+        return mWindow.getType(mPos, columnIndex);
+    }
+
+    @Override
+    protected void checkPosition() {
         super.checkPosition();
         
         if (mWindow == null) {
-            throw new StaleDataException("Access closed cursor");
+            throw new StaleDataException("Attempting to access a closed cursor");
         }
     }
 
diff --git a/core/java/android/database/BulkCursorNative.java b/core/java/android/database/BulkCursorNative.java
index baa94d8..fa62d69 100644
--- a/core/java/android/database/BulkCursorNative.java
+++ b/core/java/android/database/BulkCursorNative.java
@@ -17,13 +17,10 @@
 package android.database;
 
 import android.os.Binder;
-import android.os.RemoteException;
+import android.os.Bundle;
 import android.os.IBinder;
 import android.os.Parcel;
-import android.os.Bundle;
-
-import java.util.HashMap;
-import java.util.Map;
+import android.os.RemoteException;
 
 /**
  * Native implementation of the bulk cursor. This is only for use in implementing
@@ -120,26 +117,6 @@
                     return true;
                 }
 
-                case UPDATE_ROWS_TRANSACTION: {
-                    data.enforceInterface(IBulkCursor.descriptor);
-                    // TODO - what ClassLoader should be passed to readHashMap?
-                    // TODO - switch to Bundle
-                    HashMap<Long, Map<String, Object>> values = data.readHashMap(null);
-                    boolean result = updateRows(values);
-                    reply.writeNoException();
-                    reply.writeInt((result == true ? 1 : 0));
-                    return true;
-                }
-
-                case DELETE_ROW_TRANSACTION: {
-                    data.enforceInterface(IBulkCursor.descriptor);
-                    int position = data.readInt();
-                    boolean result = deleteRow(position);
-                    reply.writeNoException();
-                    reply.writeInt((result == true ? 1 : 0));
-                    return true;
-                }
-
                 case ON_MOVE_TRANSACTION: {
                     data.enforceInterface(IBulkCursor.descriptor);
                     int position = data.readInt();
@@ -343,48 +320,6 @@
         return count;
     }
 
-    public boolean updateRows(Map values) throws RemoteException
-    {
-        Parcel data = Parcel.obtain();
-        Parcel reply = Parcel.obtain();
-
-        data.writeInterfaceToken(IBulkCursor.descriptor);
-
-        data.writeMap(values);
-
-        mRemote.transact(UPDATE_ROWS_TRANSACTION, data, reply, 0);
-
-        DatabaseUtils.readExceptionFromParcel(reply);
-        
-        boolean result = (reply.readInt() == 1 ? true : false);
-
-        data.recycle();
-        reply.recycle();
-
-        return result;
-    }
-
-    public boolean deleteRow(int position) throws RemoteException
-    {
-        Parcel data = Parcel.obtain();
-        Parcel reply = Parcel.obtain();
-
-        data.writeInterfaceToken(IBulkCursor.descriptor);
-
-        data.writeInt(position);
-
-        mRemote.transact(DELETE_ROW_TRANSACTION, data, reply, 0);
-
-        DatabaseUtils.readExceptionFromParcel(reply);
-        
-        boolean result = (reply.readInt() == 1 ? true : false);
-
-        data.recycle();
-        reply.recycle();
-
-        return result;
-    }
-
     public boolean getWantsAllOnMoveCalls() throws RemoteException {
         Parcel data = Parcel.obtain();
         Parcel reply = Parcel.obtain();
diff --git a/core/java/android/database/BulkCursorToCursorAdaptor.java b/core/java/android/database/BulkCursorToCursorAdaptor.java
index 1469ea2..2cb2aec 100644
--- a/core/java/android/database/BulkCursorToCursorAdaptor.java
+++ b/core/java/android/database/BulkCursorToCursorAdaptor.java
@@ -16,12 +16,10 @@
 
 package android.database;
 
-import android.os.RemoteException;
 import android.os.Bundle;
+import android.os.RemoteException;
 import android.util.Log;
 
-import java.util.Map;
-
 /**
  * Adapts an {@link IBulkCursor} to a {@link Cursor} for use in the local
  * process.
@@ -174,38 +172,6 @@
         }
     }
 
-    /**
-     * @hide
-     * @deprecated
-     */
-    @Override
-    public boolean deleteRow() {
-        try {
-            boolean result = mBulkCursor.deleteRow(mPos);
-            if (result != false) {
-                // The window contains the old value, discard it
-                mWindow = null;
-    
-                // Fix up the position
-                mCount = mBulkCursor.count();
-                if (mPos < mCount) {
-                    int oldPos = mPos;
-                    mPos = -1;
-                    moveToPosition(oldPos);
-                } else {
-                    mPos = mCount;
-                }
-
-                // Send the change notification
-                onChange(true);
-            }
-            return result;
-        } catch (RemoteException ex) {
-            Log.e(TAG, "Unable to delete row because the remote process is dead");
-            return false;
-        }
-    }
-
     @Override
     public String[] getColumnNames() {
         if (mColumns == null) {
@@ -219,44 +185,6 @@
         return mColumns;
     }
 
-    /**
-     * @hide
-     * @deprecated
-     */
-    @Override
-    public boolean commitUpdates(Map<? extends Long,
-            ? extends Map<String,Object>> additionalValues) {
-        if (!supportsUpdates()) {
-            Log.e(TAG, "commitUpdates not supported on this cursor, did you include the _id column?");
-            return false;
-        }
-
-        synchronized(mUpdatedRows) {
-            if (additionalValues != null) {
-                mUpdatedRows.putAll(additionalValues);
-            }
-
-            if (mUpdatedRows.size() <= 0) {
-                return false;
-            }
-
-            try {
-                boolean result = mBulkCursor.updateRows(mUpdatedRows);
-    
-                if (result == true) {
-                    mUpdatedRows.clear();
-
-                    // Send the change notification
-                    onChange(true);
-                }
-                return result;
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Unable to commit updates because the remote process is dead");
-                return false;
-            }
-        }
-    }
-
     @Override
     public Bundle getExtras() {
         try {
diff --git a/core/java/android/database/Cursor.java b/core/java/android/database/Cursor.java
index 6539156..c03c586 100644
--- a/core/java/android/database/Cursor.java
+++ b/core/java/android/database/Cursor.java
@@ -30,6 +30,25 @@
  * threads should perform its own synchronization when using the Cursor.
  */
 public interface Cursor {
+    /*
+     * Values returned by {@link #getType(int)}.
+     * These should be consistent with the corresponding types defined in CursorWindow.h
+     */
+    /** Value returned by {@link #getType(int)} if the specified column is null */
+    static final int FIELD_TYPE_NULL = 0;
+
+    /** Value returned by {@link #getType(int)} if the specified  column type is integer */
+    static final int FIELD_TYPE_INTEGER = 1;
+
+    /** Value returned by {@link #getType(int)} if the specified column type is float */
+    static final int FIELD_TYPE_FLOAT = 2;
+
+    /** Value returned by {@link #getType(int)} if the specified column type is string */
+    static final int FIELD_TYPE_STRING = 3;
+
+    /** Value returned by {@link #getType(int)} if the specified column type is blob */
+    static final int FIELD_TYPE_BLOB = 4;
+
     /**
      * Returns the numbers of rows in the cursor.
      *
@@ -146,22 +165,6 @@
     boolean isAfterLast();
 
     /**
-     * Removes the row at the current cursor position from the underlying data
-     * store. After this method returns the cursor will be pointing to the row
-     * after the row that is deleted. This has the side effect of decrementing
-     * the result of count() by one.
-     * <p>
-     * The query must have the row ID column in its selection, otherwise this
-     * call will fail.
-     * 
-     * @hide
-     * @return whether the record was successfully deleted.
-     * @deprecated use {@link ContentResolver#delete(Uri, String, String[])}
-     */
-    @Deprecated
-    boolean deleteRow();
-
-    /**
      * Returns the zero-based index for the given column name, or -1 if the column doesn't exist.
      * If you expect the column to exist use {@link #getColumnIndexOrThrow(String)} instead, which
      * will make the error more clear.
@@ -295,6 +298,27 @@
     double getDouble(int columnIndex);
 
     /**
+     * Returns data type of the given column's value.
+     * The preferred type of the column is returned but the data may be converted to other types
+     * as documented in the get-type methods such as {@link #getInt(int)}, {@link #getFloat(int)}
+     * etc.
+     *<p>
+     * Returned column types are
+     * <ul>
+     *   <li>{@link #FIELD_TYPE_NULL}</li>
+     *   <li>{@link #FIELD_TYPE_INTEGER}</li>
+     *   <li>{@link #FIELD_TYPE_FLOAT}</li>
+     *   <li>{@link #FIELD_TYPE_STRING}</li>
+     *   <li>{@link #FIELD_TYPE_BLOB}</li>
+     *</ul>
+     *</p>
+     *
+     * @param columnIndex the zero-based index of the target column.
+     * @return column value type
+     */
+    int getType(int columnIndex);
+
+    /**
      * Returns <code>true</code> if the value in the indicated column is null.
      *
      * @param columnIndex the zero-based index of the target column.
@@ -303,188 +327,6 @@
     boolean isNull(int columnIndex);
 
     /**
-     * Returns <code>true</code> if the cursor supports updates.
-     *
-     * @return whether the cursor supports updates.
-     * @hide
-     * @deprecated use the {@link ContentResolver} update methods instead of the Cursor
-     * update methods
-     */
-    @Deprecated
-    boolean supportsUpdates();
-
-    /**
-     * Returns <code>true</code> if there are pending updates that have not yet been committed.
-     * 
-     * @return <code>true</code> if there are pending updates that have not yet been committed.
-     * @hide
-     * @deprecated use the {@link ContentResolver} update methods instead of the Cursor
-     * update methods
-     */
-    @Deprecated
-    boolean hasUpdates();
-
-    /**
-     * Updates the value for the given column in the row the cursor is
-     * currently pointing at. Updates are not committed to the backing store
-     * until {@link #commitUpdates()} is called.
-     *
-     * @param columnIndex the zero-based index of the target column.
-     * @param value the new value.
-     * @return whether the operation succeeded.
-     * @hide
-     * @deprecated use the {@link ContentResolver} update methods instead of the Cursor
-     * update methods
-     */
-    @Deprecated
-    boolean updateBlob(int columnIndex, byte[] value);
-
-    /**
-     * Updates the value for the given column in the row the cursor is
-     * currently pointing at. Updates are not committed to the backing store
-     * until {@link #commitUpdates()} is called.
-     *
-     * @param columnIndex the zero-based index of the target column.
-     * @param value the new value.
-     * @return whether the operation succeeded.
-     * @hide
-     * @deprecated use the {@link ContentResolver} update methods instead of the Cursor
-     * update methods
-     */
-    @Deprecated
-    boolean updateString(int columnIndex, String value);
-
-    /**
-     * Updates the value for the given column in the row the cursor is
-     * currently pointing at. Updates are not committed to the backing store
-     * until {@link #commitUpdates()} is called.
-     *
-     * @param columnIndex the zero-based index of the target column.
-     * @param value the new value.
-     * @return whether the operation succeeded.
-     * @hide
-     * @deprecated use the {@link ContentResolver} update methods instead of the Cursor
-     * update methods
-     */
-    @Deprecated
-    boolean updateShort(int columnIndex, short value);
-
-    /**
-     * Updates the value for the given column in the row the cursor is
-     * currently pointing at. Updates are not committed to the backing store
-     * until {@link #commitUpdates()} is called.
-     *
-     * @param columnIndex the zero-based index of the target column.
-     * @param value the new value.
-     * @return whether the operation succeeded.
-     * @hide
-     * @deprecated use the {@link ContentResolver} update methods instead of the Cursor
-     * update methods
-     */
-    @Deprecated
-    boolean updateInt(int columnIndex, int value);
-
-    /**
-     * Updates the value for the given column in the row the cursor is
-     * currently pointing at. Updates are not committed to the backing store
-     * until {@link #commitUpdates()} is called.
-     *
-     * @param columnIndex the zero-based index of the target column.
-     * @param value the new value.
-     * @return whether the operation succeeded.
-     * @hide
-     * @deprecated use the {@link ContentResolver} update methods instead of the Cursor
-     * update methods
-     */
-    @Deprecated
-    boolean updateLong(int columnIndex, long value);
-
-    /**
-     * Updates the value for the given column in the row the cursor is
-     * currently pointing at. Updates are not committed to the backing store
-     * until {@link #commitUpdates()} is called.
-     *
-     * @param columnIndex the zero-based index of the target column.
-     * @param value the new value.
-     * @return whether the operation succeeded.
-     * @hide
-     * @deprecated use the {@link ContentResolver} update methods instead of the Cursor
-     * update methods
-     */
-    @Deprecated
-    boolean updateFloat(int columnIndex, float value);
-
-    /**
-     * Updates the value for the given column in the row the cursor is
-     * currently pointing at. Updates are not committed to the backing store
-     * until {@link #commitUpdates()} is called.
-     *
-     * @param columnIndex the zero-based index of the target column.
-     * @param value the new value.
-     * @return whether the operation succeeded.
-     * @hide
-     * @deprecated use the {@link ContentResolver} update methods instead of the Cursor
-     * update methods
-     */
-    @Deprecated
-    boolean updateDouble(int columnIndex, double value);
-
-    /**
-     * Removes the value for the given column in the row the cursor is
-     * currently pointing at. Updates are not committed to the backing store
-     * until {@link #commitUpdates()} is called.
-     *
-     * @param columnIndex the zero-based index of the target column.
-     * @return whether the operation succeeded.
-     * @hide
-     * @deprecated use the {@link ContentResolver} update methods instead of the Cursor
-     * update methods
-     */
-    @Deprecated
-    boolean updateToNull(int columnIndex);
-
-    /**
-     * Atomically commits all updates to the backing store. After completion,
-     * this method leaves the data in an inconsistent state and you should call
-     * {@link #requery} before reading data from the cursor again.
-     *
-     * @return whether the operation succeeded.
-     * @hide
-     * @deprecated use the {@link ContentResolver} update methods instead of the Cursor
-     * update methods
-     */
-    @Deprecated
-    boolean commitUpdates();
-
-    /**
-     * Atomically commits all updates to the backing store, as well as the
-     * updates included in values. After completion,
-     * this method leaves the data in an inconsistent state and you should call
-     * {@link #requery} before reading data from the cursor again.
-     *
-     * @param values A map from row IDs to Maps associating column names with
-     *               updated values. A null value indicates the field should be
-                     removed.
-     * @return whether the operation succeeded.
-     * @hide
-     * @deprecated use the {@link ContentResolver} update methods instead of the Cursor
-     * update methods
-     */
-    @Deprecated
-    boolean commitUpdates(Map<? extends Long,
-            ? extends Map<String,Object>> values);
-
-    /**
-     * Reverts all updates made to the cursor since the last call to
-     * commitUpdates.
-     * @hide
-     * @deprecated use the {@link ContentResolver} update methods instead of the Cursor
-     * update methods
-     */
-    @Deprecated
-    void abortUpdates();
-
-    /**
      * Deactivates the Cursor, making all calls on it fail until {@link #requery} is called.
      * Inactive Cursors use fewer resources than active Cursors.
      * Calling {@link #requery} will make the cursor active again.
@@ -496,6 +338,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/CursorToBulkCursorAdaptor.java b/core/java/android/database/CursorToBulkCursorAdaptor.java
index 748eb99..8bc7de2 100644
--- a/core/java/android/database/CursorToBulkCursorAdaptor.java
+++ b/core/java/android/database/CursorToBulkCursorAdaptor.java
@@ -16,16 +16,12 @@
 
 package android.database;
 
-import android.database.sqlite.SQLiteMisuseException;
-import android.os.Binder;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Config;
 import android.util.Log;
 
-import java.util.Map;
-
 
 /**
  * Wraps a BulkCursor around an existing Cursor making it remotable.
@@ -38,7 +34,6 @@
     private final CrossProcessCursor mCursor;
     private CursorWindow mWindow;
     private final String mProviderName;
-    private final boolean mReadOnly;
     private ContentObserverProxy mObserver;
 
     private static final class ContentObserverProxy extends ContentObserver 
@@ -98,7 +93,6 @@
                     "Only CrossProcessCursor cursors are supported across process for now", e);
         }
         mProviderName = providerName;
-        mReadOnly = !allowWrite;
 
         createAndRegisterObserverProxy(observer);
     }
@@ -197,31 +191,6 @@
         }
     }
 
-    public boolean updateRows(Map<? extends Long, ? extends Map<String, Object>> values) {
-        if (mReadOnly) {
-            Log.w("ContentProvider", "Permission Denial: modifying "
-                    + mProviderName
-                    + " from pid=" + Binder.getCallingPid()
-                    + ", uid=" + Binder.getCallingUid());
-            return false;
-        }
-        return mCursor.commitUpdates(values);
-    }
-
-    public boolean deleteRow(int position) {
-        if (mReadOnly) {
-            Log.w("ContentProvider", "Permission Denial: modifying "
-                    + mProviderName
-                    + " from pid=" + Binder.getCallingPid()
-                    + ", uid=" + Binder.getCallingUid());
-            return false;
-        }
-        if (mCursor.moveToPosition(position) == false) {
-            return false;
-        }
-        return mCursor.deleteRow();
-    }
-
     public Bundle getExtras() {
         return mCursor.getExtras();
     }
diff --git a/core/java/android/database/CursorWindow.java b/core/java/android/database/CursorWindow.java
index c756825..599431f 100644
--- a/core/java/android/database/CursorWindow.java
+++ b/core/java/android/database/CursorWindow.java
@@ -217,18 +217,13 @@
      * @param row the row to read from, row - getStartPosition() being the actual row in the window
      * @param col the column to read from
      * @return {@code true} if given field is {@code NULL}
+     * @deprecated use {@link #getType(int, int)} instead
      */
+    @Deprecated
     public boolean isNull(int row, int col) {
-        acquireReference();
-        try {
-            return isNull_native(row - mStartPos, col);
-        } finally {
-            releaseReference();
-        }
+        return getType(row, col) == Cursor.FIELD_TYPE_NULL;
     }
     
-    private native boolean isNull_native(int row, int col);
-    
     /**
      * Returns a byte array for the given field.
      *
@@ -248,19 +243,43 @@
     private native byte[] getBlob_native(int row, int col);
 
     /**
+     * Returns data type of the given column's value.
+     *<p>
+     * Returned column types are
+     * <ul>
+     *   <li>{@link Cursor#FIELD_TYPE_NULL}</li>
+     *   <li>{@link Cursor#FIELD_TYPE_INTEGER}</li>
+     *   <li>{@link Cursor#FIELD_TYPE_FLOAT}</li>
+     *   <li>{@link Cursor#FIELD_TYPE_STRING}</li>
+     *   <li>{@link Cursor#FIELD_TYPE_BLOB}</li>
+     *</ul>
+     *</p>
+     *
+     * @param row the row to read from, row - getStartPosition() being the actual row in the window
+     * @param col the column to read from
+     * @return the value type
+     */
+    public int getType(int row, int col) {
+        acquireReference();
+        try {
+            return getType_native(row - mStartPos, col);
+        } finally {
+            releaseReference();
+        }
+    }
+
+    /**
      * Checks if a field contains either a blob or is null.
      *
      * @param row the row to read from, row - getStartPosition() being the actual row in the window
      * @param col the column to read from
      * @return {@code true} if given field is {@code NULL} or a blob
+     * @deprecated use {@link #getType(int, int)} instead
      */
+    @Deprecated
     public boolean isBlob(int row, int col) {
-        acquireReference();
-        try {
-            return isBlob_native(row - mStartPos, col);
-        } finally {
-            releaseReference();
-        }
+        int type = getType(row, col);
+        return type == Cursor.FIELD_TYPE_BLOB || type == Cursor.FIELD_TYPE_NULL;
     }
 
     /**
@@ -269,14 +288,11 @@
      * @param row the row to read from, row - getStartPosition() being the actual row in the window
      * @param col the column to read from
      * @return {@code true} if given field is a long
+     * @deprecated use {@link #getType(int, int)} instead
      */
+    @Deprecated
     public boolean isLong(int row, int col) {
-        acquireReference();
-        try {
-            return isInteger_native(row - mStartPos, col);
-        } finally {
-            releaseReference();
-        }
+        return getType(row, col) == Cursor.FIELD_TYPE_INTEGER;
     }
 
     /**
@@ -285,14 +301,11 @@
      * @param row the row to read from, row - getStartPosition() being the actual row in the window
      * @param col the column to read from
      * @return {@code true} if given field is a float
+     * @deprecated use {@link #getType(int, int)} instead
      */
+    @Deprecated
     public boolean isFloat(int row, int col) {
-        acquireReference();
-        try {
-            return isFloat_native(row - mStartPos, col);
-        } finally {
-            releaseReference();
-        }
+        return getType(row, col) == Cursor.FIELD_TYPE_FLOAT;
     }
 
     /**
@@ -301,20 +314,15 @@
      * @param row the row to read from, row - getStartPosition() being the actual row in the window
      * @param col the column to read from
      * @return {@code true} if given field is {@code NULL} or a String
+     * @deprecated use {@link #getType(int, int)} instead
      */
+    @Deprecated
     public boolean isString(int row, int col) {
-        acquireReference();
-        try {
-            return isString_native(row - mStartPos, col);
-        } finally {
-            releaseReference();
-        }
+        int type = getType(row, col);
+        return type == Cursor.FIELD_TYPE_STRING || type == Cursor.FIELD_TYPE_NULL;
     }
 
-    private native boolean isBlob_native(int row, int col);
-    private native boolean isString_native(int row, int col);
-    private native boolean isInteger_native(int row, int col);
-    private native boolean isFloat_native(int row, int col);
+    private native int getType_native(int row, int col);
 
     /**
      * Returns a String for the given field.
diff --git a/core/java/android/database/CursorWrapper.java b/core/java/android/database/CursorWrapper.java
index f0aa7d7..3c3bd43 100644
--- a/core/java/android/database/CursorWrapper.java
+++ b/core/java/android/database/CursorWrapper.java
@@ -17,28 +17,26 @@
 package android.database;
 
 import android.content.ContentResolver;
-import android.database.CharArrayBuffer;
 import android.net.Uri;
 import android.os.Bundle;
 
-import java.util.Map;
-
 /**
- * Wrapper class for Cursor that delegates all calls to the actual cursor object
+ * Wrapper class for Cursor that delegates all calls to the actual cursor object.  The primary
+ * use for this class is to extend a cursor while overriding only a subset of its methods.
  */
-
 public class CursorWrapper implements Cursor {
 
+    private final Cursor mCursor;
+
     public CursorWrapper(Cursor cursor) {
         mCursor = cursor;
     }
-    
+
     /**
-     * @hide
-     * @deprecated
+     * @return the wrapped cursor
      */
-    public void abortUpdates() {
-        mCursor.abortUpdates();
+    public Cursor getWrappedCursor() {
+        return mCursor;
     }
 
     public void close() {
@@ -49,23 +47,6 @@
         return mCursor.isClosed();
     }
 
-    /**
-     * @hide
-     * @deprecated
-     */
-    public boolean commitUpdates() {
-        return mCursor.commitUpdates();
-    }
-
-    /**
-     * @hide
-     * @deprecated
-     */
-    public boolean commitUpdates(
-            Map<? extends Long, ? extends Map<String, Object>> values) {
-        return mCursor.commitUpdates(values);
-    }
-
     public int getCount() {
         return mCursor.getCount();
     }
@@ -74,14 +55,6 @@
         mCursor.deactivate();
     }
 
-    /**
-     * @hide
-     * @deprecated
-     */
-    public boolean deleteRow() {
-        return mCursor.deleteRow();
-    }
-
     public boolean moveToFirst() {
         return mCursor.moveToFirst();
     }
@@ -147,14 +120,6 @@
         return mCursor.getWantsAllOnMoveCalls();
     }
 
-    /**
-     * @hide
-     * @deprecated
-     */
-    public boolean hasUpdates() {
-        return mCursor.hasUpdates();
-    }
-
     public boolean isAfterLast() {
         return mCursor.isAfterLast();
     }
@@ -171,6 +136,10 @@
         return mCursor.isLast();
     }
 
+    public int getType(int columnIndex) {
+        return mCursor.getType(columnIndex);
+    }
+
     public boolean isNull(int columnIndex) {
         return mCursor.isNull(columnIndex);
     }
@@ -219,14 +188,6 @@
         mCursor.setNotificationUri(cr, uri);        
     }
 
-    /**
-     * @hide
-     * @deprecated
-     */
-    public boolean supportsUpdates() {
-        return mCursor.supportsUpdates();
-    }
-
     public void unregisterContentObserver(ContentObserver observer) {
         mCursor.unregisterContentObserver(observer);        
     }
@@ -234,72 +195,5 @@
     public void unregisterDataSetObserver(DataSetObserver observer) {
         mCursor.unregisterDataSetObserver(observer);
     }
-
-    /**
-     * @hide
-     * @deprecated
-     */
-    public boolean updateDouble(int columnIndex, double value) {
-        return mCursor.updateDouble(columnIndex, value);
-    }
-
-    /**
-     * @hide
-     * @deprecated
-     */
-    public boolean updateFloat(int columnIndex, float value) {
-        return mCursor.updateFloat(columnIndex, value);
-    }
-
-    /**
-     * @hide
-     * @deprecated
-     */
-    public boolean updateInt(int columnIndex, int value) {
-        return mCursor.updateInt(columnIndex, value);
-    }
-
-    /**
-     * @hide
-     * @deprecated
-     */
-    public boolean updateLong(int columnIndex, long value) {
-        return mCursor.updateLong(columnIndex, value);
-    }
-
-    /**
-     * @hide
-     * @deprecated
-     */
-    public boolean updateShort(int columnIndex, short value) {
-        return mCursor.updateShort(columnIndex, value);
-    }
-
-    /**
-     * @hide
-     * @deprecated
-     */
-    public boolean updateString(int columnIndex, String value) {
-        return mCursor.updateString(columnIndex, value);
-    }
-
-    /**
-     * @hide
-     * @deprecated
-     */
-    public boolean updateBlob(int columnIndex, byte[] value) {
-        return mCursor.updateBlob(columnIndex, value);
-    }
-
-    /**
-     * @hide
-     * @deprecated
-     */
-    public boolean updateToNull(int columnIndex) {
-        return mCursor.updateToNull(columnIndex);
-    }
-    
-    private Cursor mCursor;
-    
 }
 
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/DatabaseUtils.java b/core/java/android/database/DatabaseUtils.java
index 66406ca..9ac45d8 100644
--- a/core/java/android/database/DatabaseUtils.java
+++ b/core/java/android/database/DatabaseUtils.java
@@ -52,6 +52,21 @@
 
     private static final String[] countProjection = new String[]{"count(*)"};
 
+    /** One of the values returned by {@link #getSqlStatementType(String)}. */
+    public static final int STATEMENT_SELECT = 1;
+    /** One of the values returned by {@link #getSqlStatementType(String)}. */
+    public static final int STATEMENT_UPDATE = 2;
+    /** One of the values returned by {@link #getSqlStatementType(String)}. */
+    public static final int STATEMENT_ATTACH = 3;
+    /** One of the values returned by {@link #getSqlStatementType(String)}. */
+    public static final int STATEMENT_BEGIN = 4;
+    /** One of the values returned by {@link #getSqlStatementType(String)}. */
+    public static final int STATEMENT_COMMIT = 5;
+    /** One of the values returned by {@link #getSqlStatementType(String)}. */
+    public static final int STATEMENT_ABORT = 6;
+    /** One of the values returned by {@link #getSqlStatementType(String)}. */
+    public static final int STATEMENT_OTHER = 7;
+
     /**
      * Special function for writing an exception result at the header of
      * a parcel, to be used when returning an exception from a transaction.
@@ -193,6 +208,37 @@
     }
 
     /**
+     * Returns data type of the given object's value.
+     *<p>
+     * Returned values are
+     * <ul>
+     *   <li>{@link Cursor#FIELD_TYPE_NULL}</li>
+     *   <li>{@link Cursor#FIELD_TYPE_INTEGER}</li>
+     *   <li>{@link Cursor#FIELD_TYPE_FLOAT}</li>
+     *   <li>{@link Cursor#FIELD_TYPE_STRING}</li>
+     *   <li>{@link Cursor#FIELD_TYPE_BLOB}</li>
+     *</ul>
+     *</p>
+     *
+     * @param obj the object whose value type is to be returned
+     * @return object value type
+     * @hide
+     */
+    public static int getTypeOfObject(Object obj) {
+        if (obj == null) {
+            return Cursor.FIELD_TYPE_NULL;
+        } else if (obj instanceof byte[]) {
+            return Cursor.FIELD_TYPE_BLOB;
+        } else if (obj instanceof Float || obj instanceof Double) {
+            return Cursor.FIELD_TYPE_FLOAT;
+        } else if (obj instanceof Long || obj instanceof Integer) {
+            return Cursor.FIELD_TYPE_INTEGER;
+        } else {
+            return Cursor.FIELD_TYPE_STRING;
+        }
+    }
+
+    /**
      * Appends an SQL string to the given StringBuilder, including the opening
      * and closing single quotes. Any single quotes internal to sqlString will
      * be escaped.
@@ -632,14 +678,8 @@
      * first column of the first row.
      */
     public static long longForQuery(SQLiteStatement prog, String[] selectionArgs) {
-        if (selectionArgs != null) {
-            int size = selectionArgs.length;
-            for (int i = 0; i < size; i++) {
-                bindObjectToProgram(prog, i + 1, selectionArgs[i]);
-            }
-        }
-        long value = prog.simpleQueryForLong();
-        return value;
+        prog.bindAllArgsAsStrings(selectionArgs);
+        return prog.simpleQueryForLong();
     }
 
     /**
@@ -660,14 +700,8 @@
      * first column of the first row.
      */
     public static String stringForQuery(SQLiteStatement prog, String[] selectionArgs) {
-        if (selectionArgs != null) {
-            int size = selectionArgs.length;
-            for (int i = 0; i < size; i++) {
-                bindObjectToProgram(prog, i + 1, selectionArgs[i]);
-            }
-        }
-        String value = prog.simpleQueryForString();
-        return value;
+        prog.bindAllArgsAsStrings(selectionArgs);
+        return prog.simpleQueryForString();
     }
 
     /**
@@ -1128,4 +1162,45 @@
         db.setVersion(dbVersion);
         db.close();
     }
+
+    /**
+     * Returns one of the following which represent the type of the given SQL statement.
+     * <ol>
+     *   <li>{@link #STATEMENT_SELECT}</li>
+     *   <li>{@link #STATEMENT_UPDATE}</li>
+     *   <li>{@link #STATEMENT_ATTACH}</li>
+     *   <li>{@link #STATEMENT_BEGIN}</li>
+     *   <li>{@link #STATEMENT_COMMIT}</li>
+     *   <li>{@link #STATEMENT_ABORT}</li>
+     *   <li>{@link #STATEMENT_OTHER}</li>
+     * </ol>
+     * @param sql the SQL statement whose type is returned by this method
+     * @return one of the values listed above
+     */
+    public static int getSqlStatementType(String sql) {
+        sql = sql.trim();
+        if (sql.length() < 3) {
+            return STATEMENT_OTHER;
+        }
+        String prefixSql = sql.substring(0, 3).toUpperCase();
+        if (prefixSql.equals("SEL")) {
+            return STATEMENT_SELECT;
+        } else if (prefixSql.equals("INS") ||
+                prefixSql.equals("UPD") ||
+                prefixSql.equals("REP") ||
+                prefixSql.equals("DEL")) {
+            return STATEMENT_UPDATE;
+        } else if (prefixSql.equals("ATT")) {
+            return STATEMENT_ATTACH;
+        } else if (prefixSql.equals("COM")) {
+            return STATEMENT_COMMIT;
+        } else if (prefixSql.equals("END")) {
+            return STATEMENT_COMMIT;
+        } else if (prefixSql.equals("ROL")) {
+            return STATEMENT_ABORT;
+        } else if (prefixSql.equals("BEG")) {
+            return STATEMENT_BEGIN;
+        }
+        return STATEMENT_OTHER;
+    }
 }
diff --git a/core/java/android/database/DefaultDatabaseErrorHandler.java b/core/java/android/database/DefaultDatabaseErrorHandler.java
new file mode 100644
index 0000000..3619e48
--- /dev/null
+++ b/core/java/android/database/DefaultDatabaseErrorHandler.java
@@ -0,0 +1,94 @@
+/*
+ * 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 java.util.ArrayList;
+
+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).
+            deleteDatabaseFile(dbObj.getPath());
+            return;
+        }
+
+        ArrayList<Pair<String, String>> attachedDbs = null;
+        try {
+            // Close the database, which will cause subsequent operations to fail.
+            // before that, get the attached database list first.
+            try {
+                attachedDbs = dbObj.getAttachedDbs();
+            } catch (SQLiteException e) {
+                /* ignore */
+            }
+            try {
+                dbObj.close();
+            } catch (SQLiteException e) {
+                /* ignore */
+            }
+        } finally {
+            // Delete all files of this corrupt database and/or attached databases
+            if (attachedDbs != null) {
+                for (Pair<String, String> p : attachedDbs) {
+                    deleteDatabaseFile(p.second);
+                }
+            } else {
+                // attachedDbs = null is possible when the database is so corrupt that even
+                // "PRAGMA database_list;" also fails. delete the main database file
+                deleteDatabaseFile(dbObj.getPath());
+            }
+        }
+    }
+
+    private void deleteDatabaseFile(String fileName) {
+        if (fileName.equalsIgnoreCase(":memory:") || fileName.trim().length() == 0) {
+            return;
+        }
+        Log.e(TAG, "deleting the database file: " + fileName);
+        try {
+            new File(fileName).delete();
+        } catch (Exception e) {
+            /* print warning and ignore exception */
+            Log.w(TAG, "delete failed: " + e.getMessage());
+        }
+    }
+}
diff --git a/core/java/android/database/IBulkCursor.java b/core/java/android/database/IBulkCursor.java
index 46790a3..244c88f 100644
--- a/core/java/android/database/IBulkCursor.java
+++ b/core/java/android/database/IBulkCursor.java
@@ -16,16 +16,14 @@
 
 package android.database;
 
-import android.os.RemoteException;
+import android.os.Bundle;
 import android.os.IBinder;
 import android.os.IInterface;
-import android.os.Bundle;
-
-import java.util.Map;
+import android.os.RemoteException;
 
 /**
  * This interface provides a low-level way to pass bulk cursor data across
- * both process and language boundries. Application code should use the Cursor
+ * both process and language boundaries. Application code should use the Cursor
  * interface directly.
  *
  * {@hide}
@@ -54,10 +52,6 @@
      */
     public String[] getColumnNames() throws RemoteException;
 
-    public boolean updateRows(Map<? extends Long, ? extends Map<String, Object>> values) throws RemoteException;
-
-    public boolean deleteRow(int position) throws RemoteException;
-
     public void deactivate() throws RemoteException;
 
     public void close() throws RemoteException;
@@ -76,8 +70,6 @@
     static final int GET_CURSOR_WINDOW_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION;
     static final int COUNT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 1;
     static final int GET_COLUMN_NAMES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 2;
-    static final int UPDATE_ROWS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 3;
-    static final int DELETE_ROW_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 4;
     static final int DEACTIVATE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 5;
     static final int REQUERY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 6;
     static final int ON_MOVE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 7;
diff --git a/core/java/android/database/MatrixCursor.java b/core/java/android/database/MatrixCursor.java
index d5c3a32..5c1b968 100644
--- a/core/java/android/database/MatrixCursor.java
+++ b/core/java/android/database/MatrixCursor.java
@@ -272,6 +272,11 @@
     }
 
     @Override
+    public int getType(int column) {
+        return DatabaseUtils.getTypeOfObject(get(column));
+    }
+
+    @Override
     public boolean isNull(int column) {
         return get(column) == null;
     }
diff --git a/core/java/android/database/MergeCursor.java b/core/java/android/database/MergeCursor.java
index 722d707..2c25db7 100644
--- a/core/java/android/database/MergeCursor.java
+++ b/core/java/android/database/MergeCursor.java
@@ -92,32 +92,6 @@
         return false;
     }
 
-    /**
-     * @hide
-     * @deprecated
-     */
-    @Override
-    public boolean deleteRow()
-    {
-        return mCursor.deleteRow();
-    }
-    
-    /**
-     * @hide
-     * @deprecated
-     */
-    @Override
-    public boolean commitUpdates() {
-        int length = mCursors.length;
-        for (int i = 0 ; i < length ; i++) {
-            if (mCursors[i] != null) {
-                mCursors[i].commitUpdates();
-            }
-        }
-        onChange(true);
-        return true;
-    }
-
     @Override
     public String getString(int column)
     {
@@ -155,6 +129,11 @@
     }
 
     @Override
+    public int getType(int column) {
+        return mCursor.getType(column);
+    }
+
+    @Override
     public boolean isNull(int column)
     {
         return mCursor.isNull(column);
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/DatabaseConnectionPool.java b/core/java/android/database/sqlite/DatabaseConnectionPool.java
new file mode 100644
index 0000000..54b0605
--- /dev/null
+++ b/core/java/android/database/sqlite/DatabaseConnectionPool.java
@@ -0,0 +1,364 @@
+/*
+ * Copyright (C) 20010 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.sqlite;
+
+import android.os.SystemClock;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Random;
+
+/**
+ * A connection pool to be used by readers.
+ * Note that each connection can be used by only one reader at a time.
+ */
+/* package */ class DatabaseConnectionPool {
+
+    private static final String TAG = "DatabaseConnectionPool";
+
+    /** The default connection pool size. It is set based on the amount of memory the device has.
+     * TODO: set this with 'a system call' which returns the amount of memory the device has
+     */
+    private static final int DEFAULT_CONNECTION_POOL_SIZE = 1;
+
+    /** the pool size set for this {@link SQLiteDatabase} */
+    private volatile int mMaxPoolSize = DEFAULT_CONNECTION_POOL_SIZE;
+
+    /** The connection pool objects are stored in this member.
+     * TODO: revisit this data struct as the number of pooled connections increase beyond
+     * single-digit values.
+     */
+    private final ArrayList<PoolObj> mPool = new ArrayList<PoolObj>(mMaxPoolSize);
+
+    /** the main database connection to which this connection pool is attached */
+    private final SQLiteDatabase mParentDbObj;
+
+    /** Random number generator used to pick a free connection out of the pool */
+    private Random rand; // lazily initialized
+
+    /* package */ DatabaseConnectionPool(SQLiteDatabase db) {
+        this.mParentDbObj = db;
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "Max Pool Size: " + mMaxPoolSize);
+        }
+    }
+
+    /**
+     * close all database connections in the pool - even if they are in use!
+     */
+    /* package */ void close() {
+        synchronized(mParentDbObj) {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "Closing the connection pool on " + mParentDbObj.getPath() + toString());
+            }
+            for (int i = mPool.size() - 1; i >= 0; i--) {
+                mPool.get(i).mDb.close();
+            }
+            mPool.clear();
+        }
+    }
+
+    /**
+     * get a free connection from the pool
+     *
+     * @param sql if not null, try to find a connection inthe pool which already has cached
+     * the compiled statement for this sql.
+     * @return the Database connection that the caller can use
+     */
+    /* package */ SQLiteDatabase get(String sql) {
+        SQLiteDatabase db = null;
+        PoolObj poolObj = null;
+        synchronized(mParentDbObj) {
+            int poolSize = mPool.size();
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                assert sql != null;
+                doAsserts();
+            }
+            if (getFreePoolSize() == 0) {
+                // no free ( = available) connections
+                if (mMaxPoolSize == poolSize) {
+                    // maxed out. can't open any more connections.
+                    // let the caller wait on one of the pooled connections
+                    // preferably a connection caching the pre-compiled statement of the given SQL
+                    if (mMaxPoolSize == 1) {
+                        poolObj = mPool.get(0);
+                    } else {
+                        for (int i = 0; i < mMaxPoolSize; i++) {
+                            if (mPool.get(i).mDb.isSqlInStatementCache(sql)) {
+                                poolObj = mPool.get(i);
+                                break;
+                            }
+                        }
+                        if (poolObj == null) {
+                            // there are no database connections with the given SQL pre-compiled.
+                            // ok to return any of the connections.
+                            if (rand == null) {
+                                rand = new Random(SystemClock.elapsedRealtime());
+                            }
+                            poolObj = mPool.get(rand.nextInt(mMaxPoolSize));
+                        }
+                    }
+                    db = poolObj.mDb;
+                } else {
+                    // create a new connection and add it to the pool, since we haven't reached
+                    // max pool size allowed
+                    db = mParentDbObj.createPoolConnection((short)(poolSize + 1));
+                    poolObj = new PoolObj(db);
+                    mPool.add(poolSize, poolObj);
+                }
+            } else {
+                // there are free connections available. pick one
+                // preferably a connection caching the pre-compiled statement of the given SQL
+                for (int i = 0; i < poolSize; i++) {
+                    if (mPool.get(i).isFree() && mPool.get(i).mDb.isSqlInStatementCache(sql)) {
+                        poolObj = mPool.get(i);
+                        break;
+                    }
+                }
+                if (poolObj == null) {
+                    // didn't find a free database connection with the given SQL already
+                    // pre-compiled. return a free connection (this means, the same SQL could be
+                    // pre-compiled on more than one database connection. potential wasted memory.)
+                    for (int i = 0; i < poolSize; i++) {
+                        if (mPool.get(i).isFree()) {
+                            poolObj = mPool.get(i);
+                            break;
+                        }
+                    }
+                }
+                db = poolObj.mDb;
+            }
+
+            assert poolObj != null;
+            assert poolObj.mDb == db;
+
+            poolObj.acquire();
+        }
+
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "END get-connection: " + toString() + poolObj.toString());
+        }
+        return db;
+        // TODO if a thread acquires a connection and dies without releasing the connection, then
+        // there could be a connection leak.
+    }
+
+    /**
+     * release the given database connection back to the pool.
+     * @param db the connection to be released
+     */
+    /* package */ void release(SQLiteDatabase db) {
+        PoolObj poolObj;
+        synchronized(mParentDbObj) {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                assert db.mConnectionNum > 0;
+                doAsserts();
+                assert mPool.get(db.mConnectionNum - 1).mDb == db;
+            }
+
+            poolObj = mPool.get(db.mConnectionNum - 1);
+
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "BEGIN release-conn: " + toString() + poolObj.toString());
+            }
+
+            if (poolObj.isFree()) {
+                throw new IllegalStateException("Releasing object already freed: " +
+                        db.mConnectionNum);
+            }
+
+            poolObj.release();
+        }
+
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "END release-conn: " + toString() + poolObj.toString());
+        }
+    }
+
+    /**
+     * Returns a list of all database connections in the pool (both free and busy connections).
+     * This method is used when "adb bugreport" is done.
+     */
+    /* package */ ArrayList<SQLiteDatabase> getConnectionList() {
+        ArrayList<SQLiteDatabase> list = new ArrayList<SQLiteDatabase>();
+        synchronized(mParentDbObj) {
+            for (int i = mPool.size() - 1; i >= 0; i--) {
+                list.add(mPool.get(i).mDb);
+            }
+        }
+        return list;
+    }
+
+    /**
+     * package level access for testing purposes only. otherwise, private should be sufficient.
+     */
+    /* package */ int getFreePoolSize() {
+        int count = 0;
+        for (int i = mPool.size() - 1; i >= 0; i--) {
+            if (mPool.get(i).isFree()) {
+                count++;
+            }
+        }
+        return count++;
+    }
+
+    /**
+     * only for testing purposes
+     */
+    /* package */ ArrayList<PoolObj> getPool() {
+        return mPool;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder buff = new StringBuilder();
+        buff.append("db: ");
+        buff.append(mParentDbObj.getPath());
+        buff.append(", totalsize = ");
+        buff.append(mPool.size());
+        buff.append(", #free = ");
+        buff.append(getFreePoolSize());
+        buff.append(", maxpoolsize = ");
+        buff.append(mMaxPoolSize);
+        for (PoolObj p : mPool) {
+            buff.append("\n");
+            buff.append(p.toString());
+        }
+        return buff.toString();
+    }
+
+    private void doAsserts() {
+        for (int i = 0; i < mPool.size(); i++) {
+            mPool.get(i).verify();
+            assert mPool.get(i).mDb.mConnectionNum == (i + 1);
+        }
+    }
+
+    /* package */ void setMaxPoolSize(int size) {
+        synchronized(mParentDbObj) {
+            mMaxPoolSize = size;
+        }
+    }
+
+    /* package */ int getMaxPoolSize() {
+        synchronized(mParentDbObj) {
+            return mMaxPoolSize;
+        }
+    }
+
+    /** only used for testing purposes. */
+    /* package */ boolean isDatabaseObjFree(SQLiteDatabase db) {
+        return mPool.get(db.mConnectionNum - 1).isFree();
+    }
+
+    /** only used for testing purposes. */
+    /* package */ int getSize() {
+        return mPool.size();
+    }
+
+    /**
+     * represents objects in the connection pool.
+     * package-level access for testing purposes only.
+     */
+    /* package */ static class PoolObj {
+
+        private final SQLiteDatabase mDb;
+        private boolean mFreeBusyFlag = FREE;
+        private static final boolean FREE = true;
+        private static final boolean BUSY = false;
+
+        /** the number of threads holding this connection */
+        // @GuardedBy("this")
+        private int mNumHolders = 0;
+
+        /** contains the threadIds of the threads holding this connection.
+         * used for debugging purposes only.
+         */
+        // @GuardedBy("this")
+        private HashSet<Long> mHolderIds = new HashSet<Long>();
+
+        public PoolObj(SQLiteDatabase db) {
+            mDb = db;
+        }
+
+        private synchronized void acquire() {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                assert isFree();
+                long id = Thread.currentThread().getId();
+                assert !mHolderIds.contains(id);
+                mHolderIds.add(id);
+            }
+
+            mNumHolders++;
+            mFreeBusyFlag = BUSY;
+        }
+
+        private synchronized void release() {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                long id = Thread.currentThread().getId();
+                assert mHolderIds.size() == mNumHolders;
+                assert mHolderIds.contains(id);
+                mHolderIds.remove(id);
+            }
+
+            mNumHolders--;
+            if (mNumHolders == 0) {
+                mFreeBusyFlag = FREE;
+            }
+        }
+
+        private synchronized boolean isFree() {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                verify();
+            }
+            return (mFreeBusyFlag == FREE);
+        }
+
+        private synchronized void verify() {
+            if (mFreeBusyFlag == FREE) {
+                assert mNumHolders == 0;
+            } else {
+                assert mNumHolders > 0;
+            }
+        }
+
+        /**
+         * only for testing purposes
+         */
+        /* package */ synchronized int getNumHolders() {
+            return mNumHolders;
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder buff = new StringBuilder();
+            buff.append(", conn # ");
+            buff.append(mDb.mConnectionNum);
+            buff.append(", mCountHolders = ");
+            synchronized(this) {
+                buff.append(mNumHolders);
+                buff.append(", freeBusyFlag = ");
+                buff.append(mFreeBusyFlag);
+                for (Long l : mHolderIds) {
+                    buff.append(", id = " + l);
+                }
+            }
+            return buff.toString();
+        }
+    }
+}
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/SQLiteAccessPermException.java b/core/java/android/database/sqlite/SQLiteAccessPermException.java
new file mode 100644
index 0000000..238da4b
--- /dev/null
+++ b/core/java/android/database/sqlite/SQLiteAccessPermException.java
@@ -0,0 +1,29 @@
+/*
+ * 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.sqlite;
+
+/**
+ * This exception class is used when sqlite can't access the database file
+ * due to lack of permissions on the file.
+ */
+public class SQLiteAccessPermException extends SQLiteException {
+    public SQLiteAccessPermException() {}
+
+    public SQLiteAccessPermException(String error) {
+        super(error);
+    }
+}
diff --git a/core/java/android/database/sqlite/SQLiteBindOrColumnIndexOutOfRangeException.java b/core/java/android/database/sqlite/SQLiteBindOrColumnIndexOutOfRangeException.java
new file mode 100644
index 0000000..41f2f9c
--- /dev/null
+++ b/core/java/android/database/sqlite/SQLiteBindOrColumnIndexOutOfRangeException.java
@@ -0,0 +1,28 @@
+/*
+ * 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.sqlite;
+
+/**
+ * Thrown if the the bind or column parameter index is out of range
+ */
+public class SQLiteBindOrColumnIndexOutOfRangeException extends SQLiteException {
+    public SQLiteBindOrColumnIndexOutOfRangeException() {}
+
+    public SQLiteBindOrColumnIndexOutOfRangeException(String error) {
+        super(error);
+    }
+}
diff --git a/core/java/android/database/sqlite/SQLiteBlobTooBigException.java b/core/java/android/database/sqlite/SQLiteBlobTooBigException.java
new file mode 100644
index 0000000..a82676b
--- /dev/null
+++ b/core/java/android/database/sqlite/SQLiteBlobTooBigException.java
@@ -0,0 +1,25 @@
+/*
+ * 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.sqlite;
+
+public class SQLiteBlobTooBigException extends SQLiteException {
+    public SQLiteBlobTooBigException() {}
+
+    public SQLiteBlobTooBigException(String error) {
+        super(error);
+    }
+}
diff --git a/core/java/android/database/sqlite/SQLiteCantOpenDatabaseException.java b/core/java/android/database/sqlite/SQLiteCantOpenDatabaseException.java
new file mode 100644
index 0000000..6f01796
--- /dev/null
+++ b/core/java/android/database/sqlite/SQLiteCantOpenDatabaseException.java
@@ -0,0 +1,25 @@
+/*
+ * 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.sqlite;
+
+public class SQLiteCantOpenDatabaseException extends SQLiteException {
+    public SQLiteCantOpenDatabaseException() {}
+
+    public SQLiteCantOpenDatabaseException(String error) {
+        super(error);
+    }
+}
diff --git a/core/java/android/database/sqlite/SQLiteCompiledSql.java b/core/java/android/database/sqlite/SQLiteCompiledSql.java
index 25aa9b3..aa0a57d 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);
         }
     }
 
@@ -102,13 +95,8 @@
             if (SQLiteDebug.DEBUG_ACTIVE_CURSOR_FINALIZATION) {
                 Log.v(TAG, "closed and deallocated DbObj (id#" + nStatement +")");
             }
-            try {
-                mDatabase.lock();
-                native_finalize();
-                nStatement = 0;
-            } finally {
-                mDatabase.unlock();
-            }
+            mDatabase.finalizeStatementLater(nStatement);
+            nStatement = 0;
         }
     }
 
@@ -134,6 +122,10 @@
         mInUse = false;
     }
 
+    /* package */ synchronized boolean isInUse() {
+        return mInUse;
+    }
+
     /**
      * Make sure that the native resource is cleaned up.
      */
@@ -142,9 +134,6 @@
         try {
             if (nStatement == 0) return;
             // finalizer should NEVER get called
-            if (SQLiteDebug.DEBUG_ACTIVE_CURSOR_FINALIZATION) {
-                Log.v(TAG, "** warning ** Finalized DbObj (id#" + nStatement + ")");
-            }
             int len = mSqlStmt.length();
             Log.w(TAG, "Releasing statement in a finalizer. Please ensure " +
                     "that you explicitly call close() on your cursor: " +
@@ -162,5 +151,4 @@
      * @param sql The SQL to compile.
      */
     private final native void native_compile(String sql);
-    private final native void native_finalize();
 }
diff --git a/core/java/android/database/sqlite/SQLiteCursor.java b/core/java/android/database/sqlite/SQLiteCursor.java
index c7e58fa..43dd5d7 100644
--- a/core/java/android/database/sqlite/SQLiteCursor.java
+++ b/core/java/android/database/sqlite/SQLiteCursor.java
@@ -16,20 +16,19 @@
 
 package android.database.sqlite;
 
+import android.app.ActivityThread;
 import android.database.AbstractWindowedCursor;
 import android.database.CursorWindow;
 import android.database.DataSetObserver;
-import android.database.SQLException;
-
+import android.database.RequeryOnUiThreadException;
 import android.os.Handler;
+import android.os.Looper;
 import android.os.Message;
 import android.os.Process;
-import android.text.TextUtils;
 import android.util.Config;
 import android.util.Log;
 
 import java.util.HashMap;
-import java.util.Iterator;
 import java.util.Map;
 import java.util.concurrent.locks.ReentrantLock;
 
@@ -45,19 +44,16 @@
     static final int NO_COUNT = -1;
 
     /** The name of the table to edit */
-    private String mEditTable;
+    private final String mEditTable;
 
     /** The names of the columns in the rows */
-    private String[] mColumns;
+    private final String[] mColumns;
 
     /** The query object for the cursor */
     private SQLiteQuery mQuery;
 
-    /** The database the cursor was created from */
-    private SQLiteDatabase mDatabase;
-
     /** The compiled query this cursor came from */
-    private SQLiteCursorDriver mDriver;
+    private final SQLiteCursorDriver mDriver;
 
     /** The number of rows in the cursor */
     private int mCount = NO_COUNT;
@@ -66,7 +62,7 @@
     private Map<String, Integer> mColumnNameMap;
 
     /** Used to find out where a cursor was allocated in case it never got released. */
-    private Throwable mStackTrace;
+    private final Throwable mStackTrace;
     
     /** 
      *  mMaxRead is the max items that each cursor window reads 
@@ -77,6 +73,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
@@ -136,7 +137,7 @@
                     break;
                 }
                 try {
-                    int count = mQuery.fillWindow(cw, mMaxRead, mCount);
+                    int count = getQuery().fillWindow(cw, mMaxRead, mCount);
                     // return -1 means not finished
                     if (count != 0) {
                         if (count == NO_COUNT){
@@ -201,24 +202,46 @@
      * has package scope.
      *
      * @param db a reference to a Database object that is already constructed
-     *     and opened
+     *     and opened. This param is not used any longer
      * @param editTable the name of the table used for this query
      * @param query the rest of the query terms
      *     cursor is finalized
+     * @deprecated use {@link #SQLiteCursor(SQLiteCursorDriver, String, SQLiteQuery)} instead
      */
+    @Deprecated
     public SQLiteCursor(SQLiteDatabase db, SQLiteCursorDriver driver,
             String editTable, SQLiteQuery query) {
+        this(driver, editTable, query);
+    }
+
+    /**
+     * Execute a query and provide access to its result set through a Cursor
+     * interface. For a query such as: {@code SELECT name, birth, phone FROM
+     * myTable WHERE ... LIMIT 1,20 ORDER BY...} the column names (name, birth,
+     * phone) would be in the projection argument and everything from
+     * {@code FROM} onward would be in the params argument. This constructor
+     * has package scope.
+     *
+     * @param editTable the name of the table used for this query
+     * @param query the {@link SQLiteQuery} object associated with this cursor object.
+     */
+    public SQLiteCursor(SQLiteCursorDriver driver, String editTable, SQLiteQuery query) {
         // The AbstractCursor constructor needs to do some setup.
         super();
+        if (query == null) {
+            throw new IllegalArgumentException("query object cannot be null");
+        }
+        if (query.mDatabase == null) {
+            throw new IllegalArgumentException("query.mDatabase cannot be null");
+        }
         mStackTrace = new DatabaseObjectNotClosedException().fillInStackTrace();
-        mDatabase = db;
         mDriver = driver;
         mEditTable = editTable;
         mColumnNameMap = null;
         mQuery = query;
 
         try {
-            db.lock();
+            query.mDatabase.lock();
 
             // Setup the list of columns
             int columnCount = mQuery.columnCountLocked();
@@ -239,7 +262,7 @@
                 }
             }
         } finally {
-            db.unlock();
+            query.mDatabase.unlock();
         }
     }
 
@@ -247,7 +270,9 @@
      * @return the SQLiteDatabase that this cursor is associated with.
      */
     public SQLiteDatabase getDatabase() {
-        return mDatabase;
+        synchronized (this) {
+            return mQuery.mDatabase;
+        }
     }
 
     @Override
@@ -283,7 +308,7 @@
                 }
         }
         mWindow.setStartPosition(startPos);
-        mCount = mQuery.fillWindow(mWindow, mInitialRead, 0);
+        mCount = getQuery().fillWindow(mWindow, mInitialRead, 0);
         // return -1 means not finished
         if (mCount == NO_COUNT){
             mCount = startPos + mInitialRead;
@@ -292,6 +317,10 @@
         } 
     }
 
+    private synchronized SQLiteQuery getQuery() {
+        return mQuery;
+    }
+
     @Override
     public int getColumnIndex(String columnName) {
         // Create mColumnNameMap on demand
@@ -321,166 +350,11 @@
         }
     }
 
-    /**
-     * @hide
-     * @deprecated
-     */
-    @Override
-    public boolean deleteRow() {
-        checkPosition();
-
-        // Only allow deletes if there is an ID column, and the ID has been read from it
-        if (mRowIdColumnIndex == -1 || mCurrentRowID == null) {
-            Log.e(TAG,
-                    "Could not delete row because either the row ID column is not available or it" +
-                    "has not been read.");
-            return false;
-        }
-
-        boolean success;
-
-        /*
-         * Ensure we don't change the state of the database when another
-         * thread is holding the database lock. requery() and moveTo() are also
-         * synchronized here to make sure they get the state of the database
-         * immediately following the DELETE.
-         */
-        mDatabase.lock();
-        try {
-            try {
-                mDatabase.delete(mEditTable, mColumns[mRowIdColumnIndex] + "=?",
-                        new String[] {mCurrentRowID.toString()});
-                success = true;
-            } catch (SQLException e) {
-                success = false;
-            }
-
-            int pos = mPos;
-            requery();
-
-            /*
-             * Ensure proper cursor state. Note that mCurrentRowID changes
-             * in this call.
-             */
-            moveToPosition(pos);
-        } finally {
-            mDatabase.unlock();
-        }
-
-        if (success) {
-            onChange(true);
-            return true;
-        } else {
-            return false;
-        }
-    }
-
     @Override
     public String[] getColumnNames() {
         return mColumns;
     }
 
-    /**
-     * @hide
-     * @deprecated
-     */
-    @Override
-    public boolean supportsUpdates() {
-        return super.supportsUpdates() && !TextUtils.isEmpty(mEditTable);
-    }
-
-    /**
-     * @hide
-     * @deprecated
-     */
-    @Override
-    public boolean commitUpdates(Map<? extends Long,
-            ? extends Map<String, Object>> additionalValues) {
-        if (!supportsUpdates()) {
-            Log.e(TAG, "commitUpdates not supported on this cursor, did you "
-                    + "include the _id column?");
-            return false;
-        }
-
-        /*
-         * Prevent other threads from changing the updated rows while they're
-         * being processed here.
-         */
-        synchronized (mUpdatedRows) {
-            if (additionalValues != null) {
-                mUpdatedRows.putAll(additionalValues);
-            }
-
-            if (mUpdatedRows.size() == 0) {
-                return true;
-            }
-
-            /*
-             * Prevent other threads from changing the database state while
-             * we process the updated rows, and prevents us from changing the
-             * database behind the back of another thread.
-             */
-            mDatabase.beginTransaction();
-            try {
-                StringBuilder sql = new StringBuilder(128);
-
-                // For each row that has been updated
-                for (Map.Entry<Long, Map<String, Object>> rowEntry :
-                        mUpdatedRows.entrySet()) {
-                    Map<String, Object> values = rowEntry.getValue();
-                    Long rowIdObj = rowEntry.getKey();
-
-                    if (rowIdObj == null || values == null) {
-                        throw new IllegalStateException("null rowId or values found! rowId = "
-                                + rowIdObj + ", values = " + values);
-                    }
-
-                    if (values.size() == 0) {
-                        continue;
-                    }
-
-                    long rowId = rowIdObj.longValue();
-
-                    Iterator<Map.Entry<String, Object>> valuesIter =
-                            values.entrySet().iterator();
-
-                    sql.setLength(0);
-                    sql.append("UPDATE " + mEditTable + " SET ");
-
-                    // For each column value that has been updated
-                    Object[] bindings = new Object[values.size()];
-                    int i = 0;
-                    while (valuesIter.hasNext()) {
-                        Map.Entry<String, Object> entry = valuesIter.next();
-                        sql.append(entry.getKey());
-                        sql.append("=?");
-                        bindings[i] = entry.getValue();
-                        if (valuesIter.hasNext()) {
-                            sql.append(", ");
-                        }
-                        i++;
-                    }
-
-                    sql.append(" WHERE " + mColumns[mRowIdColumnIndex]
-                            + '=' + rowId);
-                    sql.append(';');
-                    mDatabase.execSQL(sql.toString(), bindings);
-                    mDatabase.rowUpdated(mEditTable, rowId);
-                }
-                mDatabase.setTransactionSuccessful();
-            } finally {
-                mDatabase.endTransaction();
-            }
-
-            mUpdatedRows.clear();
-        }
-
-        // Let any change observers know about the update
-        onChange(true);
-
-        return true;
-    }
-
     private void deactivateCommon() {
         if (Config.LOGV) Log.v(TAG, "<<< Releasing cursor " + this);
         mCursorState = 0;
@@ -501,9 +375,32 @@
     @Override
     public void close() {
         super.close();
-        deactivateCommon();
-        mQuery.close();
-        mDriver.cursorClosed();
+        synchronized (this) {
+            deactivateCommon();
+            mQuery.close();
+            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 = getQuery().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();
+                Throwable t = null;
+                // BEGIN STOPSHIP remove the following line
+                t = new RequeryOnUiThreadException(packageName);
+                // END STOPSHIP
+                Log.w(TAG, "should not attempt requery on main (UI) thread: app = " +
+                        packageName == null ? "'unknown'" : packageName, t);
+            }
+        }
     }
 
     @Override
@@ -511,20 +408,30 @@
         if (isClosed()) {
             return false;
         }
+        warnIfUiThread();
         long timeStart = 0;
         if (Config.LOGV) {
             timeStart = System.currentTimeMillis();
         }
-        /*
-         * Synchronize on the database lock to ensure that mCount matches the
-         * results of mQuery.requery().
-         */
-        mDatabase.lock();
-        try {
+
+        synchronized (this) {
             if (mWindow != null) {
                 mWindow.clear();
             }
             mPos = -1;
+            SQLiteDatabase db = mQuery.mDatabase.getDatabaseHandle(mQuery.mSql);
+            if (!db.equals(mQuery.mDatabase)) {
+                // since we need to use a different database connection handle,
+                // re-compile the query
+                db.lock();
+                try {
+                    // close the old mQuery object and open a new one
+                    mQuery.close();
+                    mQuery = new SQLiteQuery(db, mQuery);
+                } finally {
+                    db.unlock();
+                }
+            }
             // This one will recreate the temp table, and get its count
             mDriver.cursorRequeried(this);
             mCount = NO_COUNT;
@@ -535,8 +442,6 @@
             } finally {
                 queryThreadUnlock();
             }
-        } finally {
-            mDatabase.unlock();
         }
 
         if (Config.LOGV) {
@@ -584,14 +489,14 @@
             if (mWindow != null) {
                 int len = mQuery.mSql.length();
                 Log.e(TAG, "Finalizing a Cursor that has not been deactivated or closed. " +
-                        "database = " + mDatabase.getPath() + ", table = " + mEditTable +
+                        "database = " + mQuery.mDatabase.getPath() + ", table = " + mEditTable +
                         ", query = " + mQuery.mSql.substring(0, (len > 100) ? 100 : len),
                         mStackTrace);
                 close();
                 SQLiteDebug.notifyActiveCursorFinalized();
             } else {
                 if (Config.LOGV) {
-                    Log.v(TAG, "Finalizing cursor on database = " + mDatabase.getPath() +
+                    Log.v(TAG, "Finalizing cursor on database = " + mQuery.mDatabase.getPath() +
                             ", table = " + mEditTable + ", query = " + mQuery.mSql);
                 }
             }
diff --git a/core/java/android/database/sqlite/SQLiteCursorDriver.java b/core/java/android/database/sqlite/SQLiteCursorDriver.java
index eda1b78..b3963f9 100644
--- a/core/java/android/database/sqlite/SQLiteCursorDriver.java
+++ b/core/java/android/database/sqlite/SQLiteCursorDriver.java
@@ -40,8 +40,6 @@
 
     /**
      * Called by a SQLiteCursor when it is requeryed.
-     * 
-     * @return The new count value.
      */
     void cursorRequeried(Cursor cursor);
 
diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index cdc9bbb..dc3b469 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -16,16 +16,16 @@
 
 package android.database.sqlite;
 
-import com.google.android.collect.Maps;
-
-import android.app.ActivityThread;
 import android.app.AppGlobals;
 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;
+import android.os.StatFs;
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.text.TextUtils;
@@ -38,15 +38,14 @@
 
 import java.io.File;
 import java.lang.ref.WeakReference;
-import java.text.SimpleDateFormat;
 import java.util.ArrayList;
+import java.util.Collections;
 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;
-import java.util.Set;
 import java.util.WeakHashMap;
 import java.util.concurrent.locks.ReentrantLock;
 import java.util.regex.Pattern;
@@ -67,7 +66,7 @@
  * is the Unicode Collation Algorithm and not tailored to the current locale.
  */
 public class SQLiteDatabase extends SQLiteClosable {
-    private static final String TAG = "Database";
+    private static final String TAG = "SQLiteDatabase";
     private static final int EVENT_DB_OPERATION = 52000;
     private static final int EVENT_DB_CORRUPT = 75004;
 
@@ -192,6 +191,11 @@
      */
     private SQLiteTransactionListener mTransactionListener;
 
+    /**
+     * this member is set if {@link #execSQL(String)} is used to begin and end transactions.
+     */
+    private boolean mTransactionUsingExecSql;
+
     /** Synchronize on this when accessing the database */
     private final ReentrantLock mLock = new ReentrantLock(true);
 
@@ -233,48 +237,78 @@
     // lock acquistions of the database.
     /* package */ static final String GET_LOCK_LOG_PREFIX = "GETLOCK:";
 
-    /** Used by native code, do not rename */
-    /* package */ int mNativeHandle = 0;
+    /** Used by native code, do not rename. make it volatile, so it is thread-safe. */
+    /* package */ volatile int mNativeHandle = 0;
 
-    /** Used to make temp table names unique */
-    /* package */ int mTempTableSequence = 0;
+    /**
+     * The size, in bytes, of a block on "/data". This corresponds to the Unix
+     * statfs.f_bsize field. note that this field is lazily initialized.
+     */
+    private static int sBlockSize = 0;
 
     /** The path for the database file */
-    private String mPath;
+    private final String mPath;
 
     /** The anonymized path for the database file for logging purposes */
     private String mPathForLogs = null;  // lazily populated
 
     /** The flags passed to open/create */
-    private int mFlags;
+    private final int mFlags;
 
     /** The optional factory to use when creating new Cursors */
-    private CursorFactory mFactory;
+    private final CursorFactory mFactory;
 
-    private WeakHashMap<SQLiteClosable, Object> mPrograms;
+    private final 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
+     *     key = SQL statement with "?" for bind args
      *     value = {@link SQLiteCompiledSql}
      * If an application opens the database and keeps it open during its entire life, then
-     * there will not be an overhead of compilation of sql statements by sqlite.
+     * there will not be an overhead of compilation of SQL statements by sqlite.
      *
      * why is this cache NOT static? because sqlite attaches compiledsql statements to the
      * struct created when {@link SQLiteDatabase#openDatabase(String, CursorFactory, int)} is
      * 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 */ final 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;
 
@@ -282,45 +316,59 @@
     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;
+    private final Throwable mStackTrace;
 
     // System property that enables logging of slow queries. Specify the threshold in ms.
     private static final String LOG_SLOW_QUERIES_PROPERTY = "db.log.slow_query_threshold";
     private final int mSlowQueryThreshold;
 
-    /**
-     * @param closable
+    /** stores the list of statement ids that need to be finalized by sqlite */
+    private final ArrayList<Integer> mClosedStatementIds = new ArrayList<Integer>();
+
+    /** {@link DatabaseErrorHandler} to be used when SQLite returns any of the following errors
+     *    Corruption
+     * */
+    private final DatabaseErrorHandler mErrorHandler;
+
+    /** The Database connection pool {@link DatabaseConnectionPool}.
+     * Visibility is package-private for testing purposes. otherwise, private visibility is enough.
      */
-    void addSQLiteClosable(SQLiteClosable closable) {
-        lock();
-        try {
-            mPrograms.put(closable, null);
-        } finally {
-            unlock();
-        }
+    /* package */ volatile DatabaseConnectionPool mConnectionPool = null;
+
+    /** Each database connection handle in the pool is assigned a number 1..N, where N is the
+     * size of the connection pool.
+     * The main connection handle to which the pool is attached is assigned a value of 0.
+     */
+    /* package */ final short mConnectionNum;
+
+    /** on pooled database connections, this member points to the parent ( = main)
+     * database connection handle.
+     * package visibility only for testing purposes
+     */
+    /* package */ SQLiteDatabase mParentConnObj = null;
+
+    private static final String MEMORY_DB_PATH = ":memory:";
+
+    /** stores reference to all databases opened in the current process. */
+    private static ArrayList<WeakReference<SQLiteDatabase>> mActiveDatabases =
+            new ArrayList<WeakReference<SQLiteDatabase>>();
+
+    synchronized void addSQLiteClosable(SQLiteClosable closable) {
+        // mPrograms is per instance of SQLiteDatabase and it doesn't actually touch the database
+        // itself. so, there is no need to lock().
+        mPrograms.put(closable, null);
     }
 
-    void removeSQLiteClosable(SQLiteClosable closable) {
-        lock();
-        try {
-            mPrograms.remove(closable);
-        } finally {
-            unlock();
-        }
+    synchronized void removeSQLiteClosable(SQLiteClosable closable) {
+        mPrograms.remove(closable);
     }
 
     @Override
     protected void onAllReferencesReleased() {
         if (isOpen()) {
-            if (SQLiteDebug.DEBUG_SQL_CACHE) {
-                mTimeClosed = getTime();
-            }
-            dbclose();
+            // close the database which will close all pending statements to be finalized also
+            close();
         }
     }
 
@@ -350,19 +398,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();
-            }
-        }
+        mErrorHandler.onCorruption(this);
     }
 
     /**
@@ -374,6 +411,7 @@
      * @see #unlock()
      */
     /* package */ void lock() {
+        verifyDbIsOpen();
         if (!mLockingEnabled) return;
         mLock.lock();
         if (SQLiteDebug.DEBUG_LOCK_TIME_TRACKING) {
@@ -394,6 +432,7 @@
      * @see #unlockForced()
      */
     private void lockForced() {
+        verifyDbIsOpen();
         mLock.lock();
         if (SQLiteDebug.DEBUG_LOCK_TIME_TRACKING) {
             if (mLock.getHoldCount() == 1) {
@@ -460,11 +499,14 @@
     }
 
     /**
-     * Begins a transaction. Transactions can be nested. When the outer transaction is ended all of
+     * Begins a transaction in EXCLUSIVE mode.
+     * <p>
+     * Transactions can be nested.
+     * When the outer transaction is ended all of
      * the work done in that transaction and all of the nested transactions will be committed or
      * rolled back. The changes will be rolled back if any transaction is ended without being
      * marked as clean (by calling setTransactionSuccessful). Otherwise they will be committed.
-     *
+     * </p>
      * <p>Here is the standard idiom for transactions:
      *
      * <pre>
@@ -478,15 +520,42 @@
      * </pre>
      */
     public void beginTransaction() {
-        beginTransactionWithListener(null /* transactionStatusCallback */);
+        beginTransaction(null /* transactionStatusCallback */, true);
     }
 
     /**
-     * Begins a transaction. Transactions can be nested. When the outer transaction is ended all of
+     * Begins a transaction in IMMEDIATE mode. Transactions can be nested. When
+     * the outer transaction is ended all of the work done in that transaction
+     * and all of the nested transactions will be committed or rolled back. The
+     * changes will be rolled back if any transaction is ended without being
+     * marked as clean (by calling setTransactionSuccessful). Otherwise they
+     * will be committed.
+     * <p>
+     * Here is the standard idiom for transactions:
+     *
+     * <pre>
+     *   db.beginTransactionNonExclusive();
+     *   try {
+     *     ...
+     *     db.setTransactionSuccessful();
+     *   } finally {
+     *     db.endTransaction();
+     *   }
+     * </pre>
+     */
+    public void beginTransactionNonExclusive() {
+        beginTransaction(null /* transactionStatusCallback */, false);
+    }
+
+    /**
+     * Begins a transaction in EXCLUSIVE mode.
+     * <p>
+     * Transactions can be nested.
+     * When the outer transaction is ended all of
      * the work done in that transaction and all of the nested transactions will be committed or
      * rolled back. The changes will be rolled back if any transaction is ended without being
      * marked as clean (by calling setTransactionSuccessful). Otherwise they will be committed.
-     *
+     * </p>
      * <p>Here is the standard idiom for transactions:
      *
      * <pre>
@@ -498,15 +567,48 @@
      *     db.endTransaction();
      *   }
      * </pre>
+     *
      * @param transactionListener listener that should be notified when the transaction begins,
      * commits, or is rolled back, either explicitly or by a call to
      * {@link #yieldIfContendedSafely}.
      */
     public void beginTransactionWithListener(SQLiteTransactionListener transactionListener) {
+        beginTransaction(transactionListener, true);
+    }
+
+    /**
+     * Begins a transaction in IMMEDIATE mode. Transactions can be nested. When
+     * the outer transaction is ended all of the work done in that transaction
+     * and all of the nested transactions will be committed or rolled back. The
+     * changes will be rolled back if any transaction is ended without being
+     * marked as clean (by calling setTransactionSuccessful). Otherwise they
+     * will be committed.
+     * <p>
+     * Here is the standard idiom for transactions:
+     *
+     * <pre>
+     *   db.beginTransactionWithListenerNonExclusive(listener);
+     *   try {
+     *     ...
+     *     db.setTransactionSuccessful();
+     *   } finally {
+     *     db.endTransaction();
+     *   }
+     * </pre>
+     *
+     * @param transactionListener listener that should be notified when the
+     *            transaction begins, commits, or is rolled back, either
+     *            explicitly or by a call to {@link #yieldIfContendedSafely}.
+     */
+    public void beginTransactionWithListenerNonExclusive(
+            SQLiteTransactionListener transactionListener) {
+        beginTransaction(transactionListener, false);
+    }
+
+    private void beginTransaction(SQLiteTransactionListener transactionListener,
+            boolean exclusive) {
+        verifyDbIsOpen();
         lockForced();
-        if (!isOpen()) {
-            throw new IllegalStateException("database not open");
-        }
         boolean ok = false;
         try {
             // If this thread already had the lock then get out
@@ -524,7 +626,11 @@
 
             // This thread didn't already have the lock, so begin a database
             // transaction now.
-            execSQL("BEGIN EXCLUSIVE;");
+            if (exclusive && mConnectionPool == null) {
+                execSQL("BEGIN EXCLUSIVE;");
+            } else {
+                execSQL("BEGIN IMMEDIATE;");
+            }
             mTransactionListener = transactionListener;
             mTransactionIsSuccessful = true;
             mInnerTransactionIsSuccessful = false;
@@ -551,12 +657,7 @@
      * are committed and rolled back.
      */
     public void endTransaction() {
-        if (!isOpen()) {
-            throw new IllegalStateException("database not open");
-        }
-        if (!mLock.isHeldByCurrentThread()) {
-            throw new IllegalStateException("no transaction pending");
-        }
+        verifyLockOwner();
         try {
             if (mInnerTransactionIsSuccessful) {
                 mInnerTransactionIsSuccessful = false;
@@ -581,6 +682,18 @@
             }
             if (mTransactionIsSuccessful) {
                 execSQL(COMMIT_SQL);
+                // if write-ahead logging is used, we have to take care of checkpoint.
+                // TODO: should applications be given the flexibility of choosing when to
+                // trigger checkpoint?
+                // for now, do checkpoint after every COMMIT because that is the fastest
+                // way to guarantee that readers will see latest data.
+                // but this is the slowest way to run sqlite with in write-ahead logging mode.
+                if (this.mConnectionPool != null) {
+                    execSQL("PRAGMA wal_checkpoint;");
+                    if (SQLiteDebug.DEBUG_SQL_STATEMENTS) {
+                        Log.i(TAG, "PRAGMA wal_Checkpoint done");
+                    }
+                }
             } else {
                 try {
                     execSQL("ROLLBACK;");
@@ -614,9 +727,7 @@
      * transaction is already marked as successful.
      */
     public void setTransactionSuccessful() {
-        if (!isOpen()) {
-            throw new IllegalStateException("database not open");
-        }
+        verifyDbIsOpen();
         if (!mLock.isHeldByCurrentThread()) {
             throw new IllegalStateException("no transaction pending");
         }
@@ -631,7 +742,50 @@
      * return true if there is a transaction pending
      */
     public boolean inTransaction() {
-        return mLock.getHoldCount() > 0;
+        return mLock.getHoldCount() > 0 || mTransactionUsingExecSql;
+    }
+
+    /* package */ synchronized void setTransactionUsingExecSqlFlag() {
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.i(TAG, "found execSQL('begin transaction')");
+        }
+        mTransactionUsingExecSql = true;
+    }
+
+    /* package */ synchronized void resetTransactionUsingExecSqlFlag() {
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            if (mTransactionUsingExecSql) {
+                Log.i(TAG, "found execSQL('commit or end or rollback')");
+            }
+        }
+        mTransactionUsingExecSql = false;
+    }
+
+    /**
+     * Returns true if the caller is considered part of the current transaction, if any.
+     * <p>
+     * Caller is part of the current transaction if either of the following is true
+     * <ol>
+     *   <li>If transaction is started by calling beginTransaction() methods AND if the caller is
+     *   in the same thread as the thread that started the transaction.
+     *   </li>
+     *   <li>If the transaction is started by calling {@link #execSQL(String)} like this:
+     *   execSQL("BEGIN transaction"). In this case, every thread in the process is considered
+     *   part of the current transaction.</li>
+     * </ol>
+     *
+     * @return true if the caller is considered part of the current transaction, if any.
+     */
+    /* package */ synchronized boolean amIInTransaction() {
+        // always do this test on the main database connection - NOT on pooled database connection
+        // since transactions always occur on the main database connections only.
+        SQLiteDatabase db = (isPooledConnection()) ? mParentConnObj : this;
+        boolean b = (!db.inTransaction()) ? false :
+                db.mTransactionUsingExecSql || db.mLock.isHeldByCurrentThread();
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.i(TAG, "amIinTransaction: " + b);
+        }
+        return b;
     }
 
     /**
@@ -735,54 +889,12 @@
         return true;
     }
 
-    /** Maps table names to info about what to which _sync_time column to set
-     * to NULL on an update. This is used to support syncing. */
-    private final Map<String, SyncUpdateInfo> mSyncUpdateInfo =
-            new HashMap<String, SyncUpdateInfo>();
-
-    public Map<String, String> getSyncedTables() {
-        synchronized(mSyncUpdateInfo) {
-            HashMap<String, String> tables = new HashMap<String, String>();
-            for (String table : mSyncUpdateInfo.keySet()) {
-                SyncUpdateInfo info = mSyncUpdateInfo.get(table);
-                if (info.deletedTable != null) {
-                    tables.put(table, info.deletedTable);
-                }
-            }
-            return tables;
-        }
-    }
-
     /**
-     * Internal class used to keep track what needs to be marked as changed
-     * when an update occurs. This is used for syncing, so the sync engine
-     * knows what data has been updated locally.
+     * @deprecated This method no longer serves any useful purpose and has been deprecated.
      */
-    static private class SyncUpdateInfo {
-        /**
-         * Creates the SyncUpdateInfo class.
-         *
-         * @param masterTable The table to set _sync_time to NULL in
-         * @param deletedTable The deleted table that corresponds to the
-         *          master table
-         * @param foreignKey The key that refers to the primary key in table
-         */
-        SyncUpdateInfo(String masterTable, String deletedTable,
-                String foreignKey) {
-            this.masterTable = masterTable;
-            this.deletedTable = deletedTable;
-            this.foreignKey = foreignKey;
-        }
-
-        /** The table containing the _sync_time column */
-        String masterTable;
-
-        /** The deleted table that corresponds to the master table */
-        String deletedTable;
-
-        /** The key in the local table the row in table. It may be _id, if table
-         * is the local table. */
-        String foreignKey;
+    @Deprecated
+    public Map<String, String> getSyncedTables() {
+        return new HashMap<String, String>(0);
     }
 
     /**
@@ -814,30 +926,80 @@
      * @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());
+    }
+
+    /**
+     * Open the database according to the flags {@link #OPEN_READWRITE}
+     * {@link #OPEN_READONLY} {@link #CREATE_IF_NECESSARY} and/or {@link #NO_LOCALIZED_COLLATORS}.
+     *
+     * <p>Sets the locale of the database to the  the system's current locale.
+     * Call {@link #setLocale} if you would like something else.</p>
+     *
+     * <p>Accepts input param: a concrete instance of {@link DatabaseErrorHandler} to be
+     * used to handle corruption when sqlite reports database corruption.</p>
+     *
+     * @param path to database file to open and/or create
+     * @param factory an optional factory class that is called to instantiate a
+     *            cursor when query is called, or null for default
+     * @param flags to control database access mode
+     * @param errorHandler the {@link DatabaseErrorHandler} obj to be used to handle corruption
+     * when sqlite reports database corruption
+     * @return the newly opened database
+     * @throws SQLiteException if the database cannot be opened
+     */
+    public static SQLiteDatabase openDatabase(String path, CursorFactory factory, int flags,
+            DatabaseErrorHandler errorHandler) {
+        SQLiteDatabase sqliteDatabase = openDatabase(path, factory, flags, errorHandler,
+                (short) 0 /* the main connection handle */);
+
+        // set sqlite pagesize to mBlockSize
+        if (sBlockSize == 0) {
+            // TODO: "/data" should be a static final String constant somewhere. it is hardcoded
+            // in several places right now.
+            sBlockSize = new StatFs("/data").getBlockSize();
+        }
+        sqliteDatabase.setPageSize(sBlockSize);
+        //STOPSHIP - uncomment the following line
+        //sqliteDatabase.setJournalMode(path, "TRUNCATE");
+        // STOPSHIP remove the following lines
+        if (!path.equalsIgnoreCase(MEMORY_DB_PATH)) {
+            sqliteDatabase.enableWriteAheadLogging();
+        }
+        // END STOPSHIP
+
+        // add this database to the list of databases opened in this process
+        synchronized(mActiveDatabases) {
+            mActiveDatabases.add(new WeakReference<SQLiteDatabase>(sqliteDatabase));
+        }
+        return sqliteDatabase;
+    }
+
+    private static SQLiteDatabase openDatabase(String path, CursorFactory factory, int flags,
+            DatabaseErrorHandler errorHandler, short connectionNum) {
+        SQLiteDatabase db = new SQLiteDatabase(path, factory, flags, errorHandler, connectionNum);
         try {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.i(TAG, "opening the db : " + path);
+            }
             // Open the database.
-            sqliteDatabase = new SQLiteDatabase(path, factory, flags);
+            db.dbopen(path, flags);
+            db.setLocale(Locale.getDefault());
             if (SQLiteDebug.DEBUG_SQL_STATEMENTS) {
-                sqliteDatabase.enableSqlTracing(path);
+                db.enableSqlTracing(path, connectionNum);
             }
             if (SQLiteDebug.DEBUG_SQL_TIME) {
-                sqliteDatabase.enableSqlProfiling(path);
+                db.enableSqlProfiling(path, connectionNum);
             }
+            return db;
         } 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();
-            }
-            sqliteDatabase = new SQLiteDatabase(path, factory, flags);
+            db.mErrorHandler.onCorruption(db);
+            return SQLiteDatabase.openDatabase(path, factory, flags, errorHandler);
+        } catch (SQLiteException e) {
+            Log.e(TAG, "Failed to open the database. closing it.", e);
+            db.close();
+            throw e;
         }
-        ActiveDatabases.getInstance().mActiveDatabases.add(
-                new WeakReference<SQLiteDatabase>(sqliteDatabase));
-        return sqliteDatabase;
     }
 
     /**
@@ -855,6 +1017,25 @@
     }
 
     /**
+     * Equivalent to openDatabase(path, factory, CREATE_IF_NECESSARY, errorHandler).
+     */
+    public static SQLiteDatabase openOrCreateDatabase(String path, CursorFactory factory,
+            DatabaseErrorHandler errorHandler) {
+        return openDatabase(path, factory, CREATE_IF_NECESSARY, errorHandler);
+    }
+
+    private void setJournalMode(final String dbPath, final String mode) {
+        // journal mode can be set only for non-memory databases
+        if (!dbPath.equalsIgnoreCase(MEMORY_DB_PATH)) {
+            String s = DatabaseUtils.stringForQuery(this, "PRAGMA journal_mode=" + mode, null);
+            if (!s.equalsIgnoreCase(mode)) {
+                Log.e(TAG, "setting journal_mode to " + mode + " failed for db: " + dbPath +
+                        " (on pragma set journal_mode, sqlite returned:" + s);
+            }
+        }
+    }
+
+    /**
      * Create a memory backed SQLite database.  Its contents will be destroyed
      * when the database is closed.
      *
@@ -867,7 +1048,7 @@
      */
     public static SQLiteDatabase create(CursorFactory factory) {
         // This is a magic string with special meaning for SQLite.
-        return openDatabase(":memory:", factory, CREATE_IF_NECESSARY);
+        return openDatabase(MEMORY_DB_PATH, factory, CREATE_IF_NECESSARY);
     }
 
     /**
@@ -877,18 +1058,31 @@
         if (!isOpen()) {
             return; // already closed
         }
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.i(TAG, "closing db: " + mPath + " (connection # " + mConnectionNum);
+        }
         lock();
         try {
             closeClosable();
+            // finalize ALL statements queued up so far
+            closePendingStatements();
+            releaseCustomFunctions();
             // close this database instance - regardless of its reference count value
-            onAllReferencesReleased();
+            dbclose();
+            if (mConnectionPool != null) {
+                if (Log.isLoggable(TAG, Log.DEBUG)) {
+                    assert mConnectionPool != null;
+                    Log.i(TAG, mConnectionPool.toString());
+                }
+                mConnectionPool.close();
+            }
         } finally {
             unlock();
         }
     }
 
     private void closeClosable() {
-        /* deallocate all compiled sql statement objects from mCompiledQueries cache.
+        /* deallocate all compiled SQL statement objects from mCompiledQueries cache.
          * this should be done before de-referencing all {@link SQLiteClosable} objects
          * from this database object because calling
          * {@link SQLiteClosable#onAllReferencesReleasedFromContainer()} could cause the database
@@ -913,24 +1107,60 @@
     private native void dbclose();
 
     /**
+     * A callback interface for a custom sqlite3 function.
+     * This can be used to create a function that can be called from
+     * sqlite3 database triggers.
+     * @hide
+     */
+    public interface CustomFunction {
+        public void callback(String[] args);
+    }
+
+    /**
+     * Registers a CustomFunction callback as a function that can be called from
+     * sqlite3 database triggers.
+     * @param name the name of the sqlite3 function
+     * @param numArgs the number of arguments for the function
+     * @param function callback to call when the function is executed
+     * @hide
+     */
+    public void addCustomFunction(String name, int numArgs, CustomFunction function) {
+        verifyDbIsOpen();
+        synchronized (mCustomFunctions) {
+            int ref = native_addCustomFunction(name, numArgs, function);
+            if (ref != 0) {
+                // save a reference to the function for cleanup later
+                mCustomFunctions.add(new Integer(ref));
+            } else {
+                throw new SQLiteException("failed to add custom function " + name);
+            }
+        }
+    }
+
+    private void releaseCustomFunctions() {
+        synchronized (mCustomFunctions) {
+            for (int i = 0; i < mCustomFunctions.size(); i++) {
+                Integer function = mCustomFunctions.get(i);
+                native_releaseCustomFunction(function.intValue());
+            }
+            mCustomFunctions.clear();
+        }
+    }
+
+    // list of CustomFunction references so we can clean up when the database closes
+    private final ArrayList<Integer> mCustomFunctions =
+            new ArrayList<Integer>();
+
+    private native int native_addCustomFunction(String name, int numArgs, CustomFunction function);
+    private native void native_releaseCustomFunction(int function);
+
+    /**
      * Gets the database version.
      *
      * @return the database version
      */
     public int getVersion() {
-        SQLiteStatement prog = null;
-        lock();
-        if (!isOpen()) {
-            throw new IllegalStateException("database not open");
-        }
-        try {
-            prog = new SQLiteStatement(this, "PRAGMA user_version;");
-            long version = prog.simpleQueryForLong();
-            return (int) version;
-        } finally {
-            if (prog != null) prog.close();
-            unlock();
-        }
+        return ((Long) DatabaseUtils.longForQuery(this, "PRAGMA user_version;", null)).intValue();
     }
 
     /**
@@ -948,20 +1178,8 @@
      * @return the new maximum database size
      */
     public long getMaximumSize() {
-        SQLiteStatement prog = null;
-        lock();
-        if (!isOpen()) {
-            throw new IllegalStateException("database not open");
-        }
-        try {
-            prog = new SQLiteStatement(this,
-                    "PRAGMA max_page_count;");
-            long pageCount = prog.simpleQueryForLong();
-            return pageCount * getPageSize();
-        } finally {
-            if (prog != null) prog.close();
-            unlock();
-        }
+        long pageCount = DatabaseUtils.longForQuery(this, "PRAGMA max_page_count;", null);
+        return pageCount * getPageSize();
     }
 
     /**
@@ -972,26 +1190,15 @@
      * @return the new maximum database size
      */
     public long setMaximumSize(long numBytes) {
-        SQLiteStatement prog = null;
-        lock();
-        if (!isOpen()) {
-            throw new IllegalStateException("database not open");
+        long pageSize = getPageSize();
+        long numPages = numBytes / pageSize;
+        // If numBytes isn't a multiple of pageSize, bump up a page
+        if ((numBytes % pageSize) != 0) {
+            numPages++;
         }
-        try {
-            long pageSize = getPageSize();
-            long numPages = numBytes / pageSize;
-            // If numBytes isn't a multiple of pageSize, bump up a page
-            if ((numBytes % pageSize) != 0) {
-                numPages++;
-            }
-            prog = new SQLiteStatement(this,
-                    "PRAGMA max_page_count = " + numPages);
-            long newPageCount = prog.simpleQueryForLong();
-            return newPageCount * pageSize;
-        } finally {
-            if (prog != null) prog.close();
-            unlock();
-        }
+        long newPageCount = DatabaseUtils.longForQuery(this, "PRAGMA max_page_count = " + numPages,
+                null);
+        return newPageCount * pageSize;
     }
 
     /**
@@ -1000,20 +1207,7 @@
      * @return the database page size, in bytes
      */
     public long getPageSize() {
-        SQLiteStatement prog = null;
-        lock();
-        if (!isOpen()) {
-            throw new IllegalStateException("database not open");
-        }
-        try {
-            prog = new SQLiteStatement(this,
-                    "PRAGMA page_size;");
-            long size = prog.simpleQueryForLong();
-            return size;
-        } finally {
-            if (prog != null) prog.close();
-            unlock();
-        }
+        return DatabaseUtils.longForQuery(this, "PRAGMA page_size;", null);
     }
 
     /**
@@ -1034,9 +1228,10 @@
      * @param table the table to mark as syncable
      * @param deletedTable The deleted table that corresponds to the
      *          syncable table
+     * @deprecated This method no longer serves any useful purpose and has been deprecated.
      */
+    @Deprecated
     public void markTableSyncable(String table, String deletedTable) {
-        markTableSyncable(table, "_id", table, deletedTable);
     }
 
     /**
@@ -1049,60 +1244,10 @@
      * @param foreignKey this is the column in table whose value is an _id in
      *          updateTable
      * @param updateTable this is the table that will have its _sync_dirty
+     * @deprecated This method no longer serves any useful purpose and has been deprecated.
      */
-    public void markTableSyncable(String table, String foreignKey,
-            String updateTable) {
-        markTableSyncable(table, foreignKey, updateTable, null);
-    }
-
-    /**
-     * Mark this table as syncable, with the _sync_dirty residing in another
-     * table. When an update occurs in this table the _sync_dirty field of the
-     * row in updateTable with the _id in foreignKey will be set to
-     * ensure proper syncing operation.
-     *
-     * @param table an update on this table will trigger a sync time removal
-     * @param foreignKey this is the column in table whose value is an _id in
-     *          updateTable
-     * @param updateTable this is the table that will have its _sync_dirty
-     * @param deletedTable The deleted table that corresponds to the
-     *          updateTable
-     */
-    private void markTableSyncable(String table, String foreignKey,
-            String updateTable, String deletedTable) {
-        lock();
-        try {
-            native_execSQL("SELECT _sync_dirty FROM " + updateTable
-                    + " LIMIT 0");
-            native_execSQL("SELECT " + foreignKey + " FROM " + table
-                    + " LIMIT 0");
-        } finally {
-            unlock();
-        }
-
-        SyncUpdateInfo info = new SyncUpdateInfo(updateTable, deletedTable,
-                foreignKey);
-        synchronized (mSyncUpdateInfo) {
-            mSyncUpdateInfo.put(table, info);
-        }
-    }
-
-    /**
-     * Call for each row that is updated in a cursor.
-     *
-     * @param table the table the row is in
-     * @param rowId the row ID of the updated row
-     */
-    /* package */ void rowUpdated(String table, long rowId) {
-        SyncUpdateInfo info;
-        synchronized (mSyncUpdateInfo) {
-            info = mSyncUpdateInfo.get(table);
-        }
-        if (info != null) {
-            execSQL("UPDATE " + info.masterTable
-                    + " SET _sync_dirty=1 WHERE _id=(SELECT " + info.foreignKey
-                    + " FROM " + table + " WHERE _id=" + rowId + ")");
-        }
+    @Deprecated
+    public void markTableSyncable(String table, String foreignKey, String updateTable) {
     }
 
     /**
@@ -1134,6 +1279,8 @@
      * statement and fill in those values with {@link SQLiteProgram#bindString}
      * and {@link SQLiteProgram#bindLong} each time you want to run the
      * statement. Statements may not return result sets larger than 1x1.
+     *<p>
+     * No two threads should be using the same {@link SQLiteStatement} at the same time.
      *
      * @param sql The raw SQL statement, may contain ? for unknown values to be
      *            bound later.
@@ -1141,15 +1288,8 @@
      * {@link SQLiteStatement}s are not synchronized, see the documentation for more details.
      */
     public SQLiteStatement compileStatement(String sql) throws SQLException {
-        lock();
-        if (!isOpen()) {
-            throw new IllegalStateException("database not open");
-        }
-        try {
-            return new SQLiteStatement(this, sql);
-        } finally {
-            unlock();
-        }
+        verifyDbIsOpen();
+        return new SQLiteStatement(this, sql, null);
     }
 
     /**
@@ -1226,9 +1366,7 @@
             boolean distinct, String table, String[] columns,
             String selection, String[] selectionArgs, String groupBy,
             String having, String orderBy, String limit) {
-        if (!isOpen()) {
-            throw new IllegalStateException("database not open");
-        }
+        verifyDbIsOpen();
         String sql = SQLiteQueryBuilder.buildQueryString(
                 distinct, table, columns, selection, groupBy, having, orderBy, limit);
 
@@ -1339,9 +1477,7 @@
     public Cursor rawQueryWithFactory(
             CursorFactory cursorFactory, String sql, String[] selectionArgs,
             String editTable) {
-        if (!isOpen()) {
-            throw new IllegalStateException("database not open");
-        }
+        verifyDbIsOpen();
         BlockGuard.getThreadPolicy().onReadFromDisk();
         long timeStart = 0;
 
@@ -1349,7 +1485,8 @@
             timeStart = System.currentTimeMillis();
         }
 
-        SQLiteCursorDriver driver = new SQLiteDirectCursorDriver(this, sql, editTable);
+        SQLiteDatabase db = getDbConnection(sql);
+        SQLiteCursorDriver driver = new SQLiteDirectCursorDriver(db, sql, editTable);
 
         Cursor cursor = null;
         try {
@@ -1375,6 +1512,7 @@
                                   : "<null>")  + ", count is " + count);
                 }
             }
+            releaseDbConnection(db);
         }
         return cursor;
     }
@@ -1501,84 +1639,41 @@
      */
     public long insertWithOnConflict(String table, String nullColumnHack,
             ContentValues initialValues, int conflictAlgorithm) {
-        BlockGuard.getThreadPolicy().onWriteToDisk();
-        if (!isOpen()) {
-            throw new IllegalStateException("database not open");
-        }
-
-        // Measurements show most sql lengths <= 152
-        StringBuilder sql = new StringBuilder(152);
+        StringBuilder sql = new StringBuilder();
         sql.append("INSERT");
         sql.append(CONFLICT_VALUES[conflictAlgorithm]);
         sql.append(" INTO ");
         sql.append(table);
-        // Measurements show most values lengths < 40
-        StringBuilder values = new StringBuilder(40);
+        sql.append('(');
 
-        Set<Map.Entry<String, Object>> entrySet = null;
-        if (initialValues != null && initialValues.size() > 0) {
-            entrySet = initialValues.valueSet();
-            Iterator<Map.Entry<String, Object>> entriesIter = entrySet.iterator();
-            sql.append('(');
-
-            boolean needSeparator = false;
-            while (entriesIter.hasNext()) {
-                if (needSeparator) {
-                    sql.append(", ");
-                    values.append(", ");
-                }
-                needSeparator = true;
-                Map.Entry<String, Object> entry = entriesIter.next();
-                sql.append(entry.getKey());
-                values.append('?');
+        Object[] bindArgs = null;
+        int size = (initialValues != null && initialValues.size() > 0) ? initialValues.size() : 0;
+        if (size > 0) {
+            bindArgs = new Object[size];
+            int i = 0;
+            for (String colName : initialValues.keySet()) {
+                sql.append((i > 0) ? "," : "");
+                sql.append(colName);
+                bindArgs[i++] = initialValues.get(colName);
             }
-
             sql.append(')');
+            sql.append(" VALUES (");
+            for (i = 0; i < size; i++) {
+                sql.append((i > 0) ? ",?" : "?");
+            }
         } else {
-            sql.append("(" + nullColumnHack + ") ");
-            values.append("NULL");
+            sql.append(nullColumnHack + ") VALUES (NULL");
         }
+        sql.append(')');
 
-        sql.append(" VALUES(");
-        sql.append(values);
-        sql.append(");");
-
-        lock();
-        SQLiteStatement statement = null;
+        SQLiteStatement statement = new SQLiteStatement(this, sql.toString(), bindArgs);
         try {
-            statement = compileStatement(sql.toString());
-
-            // Bind the values
-            if (entrySet != null) {
-                int size = entrySet.size();
-                Iterator<Map.Entry<String, Object>> entriesIter = entrySet.iterator();
-                for (int i = 0; i < size; i++) {
-                    Map.Entry<String, Object> entry = entriesIter.next();
-                    DatabaseUtils.bindObjectToProgram(statement, i + 1, entry.getValue());
-                }
-            }
-
-            // Run the program and then cleanup
-            statement.execute();
-
-            long insertedRowId = lastInsertRow();
-            if (insertedRowId == -1) {
-                Log.e(TAG, "Error inserting " + initialValues + " using " + sql);
-            } else {
-                if (Config.LOGD && Log.isLoggable(TAG, Log.VERBOSE)) {
-                    Log.v(TAG, "Inserting row " + insertedRowId + " from "
-                            + initialValues + " using " + sql);
-                }
-            }
-            return insertedRowId;
+            return statement.executeInsert();
         } catch (SQLiteDatabaseCorruptException e) {
             onCorruption();
             throw e;
         } finally {
-            if (statement != null) {
-                statement.close();
-            }
-            unlock();
+            statement.close();
         }
     }
 
@@ -1593,32 +1688,15 @@
      *         whereClause.
      */
     public int delete(String table, String whereClause, String[] whereArgs) {
-        BlockGuard.getThreadPolicy().onWriteToDisk();
-        lock();
-        if (!isOpen()) {
-            throw new IllegalStateException("database not open");
-        }
-        SQLiteStatement statement = null;
+        SQLiteStatement statement =  new SQLiteStatement(this, "DELETE FROM " + table +
+                (!TextUtils.isEmpty(whereClause) ? " WHERE " + whereClause : ""), whereArgs);
         try {
-            statement = compileStatement("DELETE FROM " + table
-                    + (!TextUtils.isEmpty(whereClause)
-                    ? " WHERE " + whereClause : ""));
-            if (whereArgs != null) {
-                int numArgs = whereArgs.length;
-                for (int i = 0; i < numArgs; i++) {
-                    DatabaseUtils.bindObjectToProgram(statement, i + 1, whereArgs[i]);
-                }
-            }
-            statement.execute();
-            return lastChangeCount();
+            return statement.executeUpdateDelete();
         } catch (SQLiteDatabaseCorruptException e) {
             onCorruption();
             throw e;
         } finally {
-            if (statement != null) {
-                statement.close();
-            }
-            unlock();
+            statement.close();
         }
     }
 
@@ -1649,8 +1727,8 @@
      */
     public int updateWithOnConflict(String table, ContentValues values,
             String whereClause, String[] whereArgs, int conflictAlgorithm) {
-        BlockGuard.getThreadPolicy().onWriteToDisk();
-        if (values == null || values.size() == 0) {
+        int setValuesSize = values.size();
+        if (values == null || setValuesSize == 0) {
             throw new IllegalArgumentException("Empty values");
         }
 
@@ -1660,98 +1738,68 @@
         sql.append(table);
         sql.append(" SET ");
 
-        Set<Map.Entry<String, Object>> entrySet = values.valueSet();
-        Iterator<Map.Entry<String, Object>> entriesIter = entrySet.iterator();
-
-        while (entriesIter.hasNext()) {
-            Map.Entry<String, Object> entry = entriesIter.next();
-            sql.append(entry.getKey());
+        // move all bind args to one array
+        int bindArgsSize = (whereArgs == null) ? setValuesSize : (setValuesSize + whereArgs.length);
+        Object[] bindArgs = new Object[bindArgsSize];
+        int i = 0;
+        for (String colName : values.keySet()) {
+            sql.append((i > 0) ? "," : "");
+            sql.append(colName);
+            bindArgs[i++] = values.get(colName);
             sql.append("=?");
-            if (entriesIter.hasNext()) {
-                sql.append(", ");
+        }
+        if (whereArgs != null) {
+            for (i = setValuesSize; i < bindArgsSize; i++) {
+                bindArgs[i] = whereArgs[i - setValuesSize];
             }
         }
-
         if (!TextUtils.isEmpty(whereClause)) {
             sql.append(" WHERE ");
             sql.append(whereClause);
         }
 
-        lock();
-        if (!isOpen()) {
-            throw new IllegalStateException("database not open");
-        }
-        SQLiteStatement statement = null;
+        SQLiteStatement statement = new SQLiteStatement(this, sql.toString(), bindArgs);
         try {
-            statement = compileStatement(sql.toString());
-
-            // Bind the values
-            int size = entrySet.size();
-            entriesIter = entrySet.iterator();
-            int bindArg = 1;
-            for (int i = 0; i < size; i++) {
-                Map.Entry<String, Object> entry = entriesIter.next();
-                DatabaseUtils.bindObjectToProgram(statement, bindArg, entry.getValue());
-                bindArg++;
-            }
-
-            if (whereArgs != null) {
-                size = whereArgs.length;
-                for (int i = 0; i < size; i++) {
-                    statement.bindString(bindArg, whereArgs[i]);
-                    bindArg++;
-                }
-            }
-
-            // Run the program and then cleanup
-            statement.execute();
-            int numChangedRows = lastChangeCount();
-            if (Config.LOGD && Log.isLoggable(TAG, Log.VERBOSE)) {
-                Log.v(TAG, "Updated " + numChangedRows + " using " + values + " and " + sql);
-            }
-            return numChangedRows;
+            return statement.executeUpdateDelete();
         } catch (SQLiteDatabaseCorruptException e) {
             onCorruption();
             throw e;
-        } catch (SQLException e) {
-            Log.e(TAG, "Error updating " + values + " using " + sql);
-            throw e;
         } finally {
-            if (statement != null) {
-                statement.close();
-            }
-            unlock();
+            statement.close();
         }
     }
 
     /**
-     * Execute a single SQL statement that is not a query. For example, CREATE
-     * TABLE, DELETE, INSERT, etc. Multiple statements separated by ;s are not
-     * supported. it takes a write lock
+     * Execute a single SQL statement that is NOT a SELECT
+     * or any other SQL statement that returns data.
+     * <p>
+     * It has no means to return any data (such as the number of affected rows).
+     * Instead, you're encouraged to use {@link #insert(String, String, ContentValues)},
+     * {@link #update(String, ContentValues, String, String[])}, et al, when possible.
+     * </p>
+     * <p>
+     * When using {@link #enableWriteAheadLogging()}, journal_mode is
+     * automatically managed by this class. So, do not set journal_mode
+     * using "PRAGMA journal_mode'<value>" statement if your app is using
+     * {@link #enableWriteAheadLogging()}
+     * </p>
      *
+     * @param sql the SQL statement to be executed. Multiple statements separated by semicolons are
+     * not supported.
      * @throws SQLException If the SQL string is invalid for some reason
      */
     public void execSQL(String sql) throws SQLException {
-        BlockGuard.getThreadPolicy().onWriteToDisk();
+        int stmtType = DatabaseUtils.getSqlStatementType(sql);
+        if (stmtType == DatabaseUtils.STATEMENT_ATTACH) {
+            disableWriteAheadLogging();
+        }
         long timeStart = SystemClock.uptimeMillis();
-        lock();
-        if (!isOpen()) {
-            throw new IllegalStateException("database not open");
-        }
         logTimeStat(mLastSqlStatement, timeStart, GET_LOCK_LOG_PREFIX);
-        try {
-            native_execSQL(sql);
-        } catch (SQLiteDatabaseCorruptException e) {
-            onCorruption();
-            throw e;
-        } finally {
-            unlock();
-        }
+        executeSql(sql, null);
 
         // Log commit statements along with the most recently executed
-        // SQL statement for disambiguation.  Note that instance
-        // equality to COMMIT_SQL is safe here.
-        if (sql == COMMIT_SQL) {
+        // SQL statement for disambiguation.
+        if (stmtType == DatabaseUtils.STATEMENT_COMMIT) {
             logTimeStat(mLastSqlStatement, timeStart, COMMIT_SQL);
         } else {
             logTimeStat(sql, timeStart, null);
@@ -1759,65 +1807,98 @@
     }
 
     /**
-     * Execute a single SQL statement that is not a query. For example, CREATE
-     * TABLE, DELETE, INSERT, etc. Multiple statements separated by ;s are not
-     * supported. it takes a write lock,
+     * Execute a single SQL statement that is NOT a SELECT/INSERT/UPDATE/DELETE.
+     * <p>
+     * For INSERT statements, use any of the following instead.
+     * <ul>
+     *   <li>{@link #insert(String, String, ContentValues)}</li>
+     *   <li>{@link #insertOrThrow(String, String, ContentValues)}</li>
+     *   <li>{@link #insertWithOnConflict(String, String, ContentValues, int)}</li>
+     * </ul>
+     * <p>
+     * For UPDATE statements, use any of the following instead.
+     * <ul>
+     *   <li>{@link #update(String, ContentValues, String, String[])}</li>
+     *   <li>{@link #updateWithOnConflict(String, ContentValues, String, String[], int)}</li>
+     * </ul>
+     * <p>
+     * For DELETE statements, use any of the following instead.
+     * <ul>
+     *   <li>{@link #delete(String, String, String[])}</li>
+     * </ul>
+     * <p>
+     * For example, the following are good candidates for using this method:
+     * <ul>
+     *   <li>ALTER TABLE</li>
+     *   <li>CREATE or DROP table / trigger / view / index / virtual table</li>
+     *   <li>REINDEX</li>
+     *   <li>RELEASE</li>
+     *   <li>SAVEPOINT</li>
+     *   <li>PRAGMA that returns no data</li>
+     * </ul>
+     * </p>
+     * <p>
+     * When using {@link #enableWriteAheadLogging()}, journal_mode is
+     * automatically managed by this class. So, do not set journal_mode
+     * using "PRAGMA journal_mode'<value>" statement if your app is using
+     * {@link #enableWriteAheadLogging()}
+     * </p>
      *
-     * @param sql
+     * @param sql the SQL statement to be executed. Multiple statements separated by semicolons are
+     * not supported.
      * @param bindArgs only byte[], String, Long and Double are supported in bindArgs.
      * @throws SQLException If the SQL string is invalid for some reason
      */
     public void execSQL(String sql, Object[] bindArgs) throws SQLException {
-        BlockGuard.getThreadPolicy().onWriteToDisk();
         if (bindArgs == null) {
             throw new IllegalArgumentException("Empty bindArgs");
         }
+        executeSql(sql, bindArgs);
+    }
+
+    private void executeSql(String sql, Object[] bindArgs) throws SQLException {
         long timeStart = SystemClock.uptimeMillis();
-        lock();
-        if (!isOpen()) {
-            throw new IllegalStateException("database not open");
-        }
-        SQLiteStatement statement = null;
+        SQLiteStatement statement = new SQLiteStatement(this, sql, bindArgs);
         try {
-            statement = compileStatement(sql);
-            if (bindArgs != null) {
-                int numArgs = bindArgs.length;
-                for (int i = 0; i < numArgs; i++) {
-                    DatabaseUtils.bindObjectToProgram(statement, i + 1, bindArgs[i]);
-                }
-            }
-            statement.execute();
+            statement.executeUpdateDelete();
         } catch (SQLiteDatabaseCorruptException e) {
             onCorruption();
             throw e;
         } finally {
-            if (statement != null) {
-                statement.close();
-            }
-            unlock();
+            statement.close();
         }
         logTimeStat(sql, timeStart);
     }
 
     @Override
-    protected void finalize() {
-        if (isOpen()) {
-            Log.e(TAG, "close() was never explicitly called on database '" +
-                    mPath + "' ", mStackTrace);
-            closeClosable();
-            onAllReferencesReleased();
+    protected void finalize() throws Throwable {
+        try {
+            if (isOpen()) {
+                Log.e(TAG, "close() was never explicitly called on database '" +
+                        mPath + "' ", mStackTrace);
+                closeClosable();
+                onAllReferencesReleased();
+                releaseCustomFunctions();
+            }
+        } finally {
+            super.finalize();
         }
     }
 
     /**
-     * Private constructor. See {@link #create} and {@link #openDatabase}.
+     * Private constructor.
      *
      * @param path The full path to the database
      * @param factory The factory to use when creating cursors, may be NULL.
      * @param flags 0 or {@link #NO_LOCALIZED_COLLATORS}.  If the database file already
      *              exists, mFlags will be updated appropriately.
+     * @param errorHandler The {@link DatabaseErrorHandler} to be used when sqlite reports database
+     * corruption. may be NULL.
+     * @param connectionNum 0 for main database connection handle. 1..N for pooled database
+     * connection handles.
      */
-    private SQLiteDatabase(String path, CursorFactory factory, int flags) {
+    private SQLiteDatabase(String path, CursorFactory factory, int flags,
+            DatabaseErrorHandler errorHandler, short connectionNum) {
         if (path == null) {
             throw new IllegalArgumentException("path should not be null");
         }
@@ -1826,25 +1907,11 @@
         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());
+        // Set the DatabaseErrorHandler to be used when SQLite reports corruption.
+        // If the caller sets errorHandler = null, then use default errorhandler.
+        mErrorHandler = (errorHandler == null) ? new DefaultDatabaseErrorHandler() : errorHandler;
+        mConnectionNum = connectionNum;
     }
 
     /**
@@ -1901,7 +1968,7 @@
         }
         if (durationMillis >= sQueryLogTimeInMillis) {
             samplePercent = 100;
-        } else {;
+        } else {
             samplePercent = (int) (100 * durationMillis / sQueryLogTimeInMillis) + 1;
             if (mRandom.nextInt(100) >= samplePercent) return;
         }
@@ -1970,6 +2037,20 @@
         }
     }
 
+    /* package */ void verifyDbIsOpen() {
+        if (!isOpen()) {
+            throw new IllegalStateException("database " + getPath() + " (conn# " +
+                    mConnectionNum + ") already closed");
+        }
+    }
+
+    /* package */ void verifyLockOwner() {
+        verifyDbIsOpen();
+        if (mLockingEnabled && !isDbLockedByCurrentThread()) {
+            throw new IllegalStateException("Don't have database lock!");
+        }
+    }
+
     /*
      * ============================================================================
      *
@@ -1977,22 +2058,14 @@
      * ============================================================================
      */
     /**
-     * adds the given sql and its compiled-statement-id-returned-by-sqlite to the
+     * Adds the given SQL and its compiled-statement-id-returned-by-sqlite to the
      * cache of compiledQueries attached to 'this'.
-     *
-     * if there is already a {@link SQLiteCompiledSql} in compiledQueries for the given sql,
+     * <p>
+     * If there is already a {@link SQLiteCompiledSql} in compiledQueries for the given SQL,
      * the new {@link SQLiteCompiledSql} object is NOT inserted into the cache (i.e.,the current
      * 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
@@ -2000,36 +2073,32 @@
             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() {
+    /** package-level access for testing purposes */
+    /* package */ void deallocCachedSqlStatements() {
         synchronized (mCompiledQueries) {
             for (SQLiteCompiledSql compiledSql : mCompiledQueries.values()) {
                 compiledSql.releaseSqlStatement();
@@ -2039,20 +2108,13 @@
     }
 
     /**
-     * from the compiledQueries cache, returns the compiled-statement-id for the given sql.
-     * returns null, if not found in the cache.
+     * From the compiledQueries cache, returns the compiled-statement-id for the given SQL.
+     * Returns null, if not found in the cache.
      */
     /* package */ SQLiteCompiledSql getCompiledStatementForSql(String sql) {
         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) {
@@ -2065,63 +2127,24 @@
             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.
+     * Sets the maximum size of the prepared-statement cache for this database.
      * (size of the cache = number of compiled-sql-statements stored in the cache).
+     *<p>
+     * Maximum cache size can ONLY be increased from its current size (default = 10).
+     * If this method is called with smaller size than the current maximum value,
+     * then IllegalStateException is thrown.
+     *<p>
+     * This method is thread-safe.
      *
-     * max cache size can ONLY be increased from its current size (default = 0).
-     * 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) {
@@ -2133,60 +2156,305 @@
         mMaxSqlCacheSize = cacheSize;
     }
 
-    static class ActiveDatabases {
-        private static final ActiveDatabases activeDatabases = new ActiveDatabases();
-        private HashSet<WeakReference<SQLiteDatabase>> mActiveDatabases =
-            new HashSet<WeakReference<SQLiteDatabase>>();
-        private ActiveDatabases() {} // disable instantiation of this class
-        static ActiveDatabases getInstance() {return activeDatabases;}
+    /* package */ boolean isSqlInStatementCache(String sql) {
+        synchronized (mCompiledQueries) {
+            return mCompiledQueries.containsKey(sql);
+        }
+    }
+
+    /* package */ void finalizeStatementLater(int id) {
+        if (!isOpen()) {
+            // database already closed. this statement will already have been finalized.
+            return;
+        }
+        synchronized(mClosedStatementIds) {
+            if (mClosedStatementIds.contains(id)) {
+                // this statement id is already queued up for finalization.
+                return;
+            }
+            mClosedStatementIds.add(id);
+        }
+    }
+
+    /* package */ void closePendingStatements() {
+        if (!isOpen()) {
+            // since this database is already closed, no need to finalize anything.
+            mClosedStatementIds.clear();
+            return;
+        }
+        verifyLockOwner();
+        /* to minimize synchronization on mClosedStatementIds, make a copy of the list */
+        ArrayList<Integer> list = new ArrayList<Integer>(mClosedStatementIds.size());
+        synchronized(mClosedStatementIds) {
+            list.addAll(mClosedStatementIds);
+            mClosedStatementIds.clear();
+        }
+        // finalize all the statements from the copied list
+        int size = list.size();
+        for (int i = 0; i < size; i++) {
+            native_finalize(list.get(i));
+        }
+    }
+
+    /**
+     * for testing only
+     */
+    /* package */ ArrayList<Integer> getQueuedUpStmtList() {
+        return mClosedStatementIds;
+    }
+
+    /**
+     * This method enables parallel execution of queries from multiple threads on the same database.
+     * It does this by opening multiple handles to the database and using a different
+     * database handle for each query.
+     * <p>
+     * If a transaction is in progress on one connection handle and say, a table is updated in the
+     * transaction, then query on the same table on another connection handle will block for the
+     * transaction to complete. But this method enables such queries to execute by having them
+     * return old version of the data from the table. Most often it is the data that existed in the
+     * table prior to the above transaction updates on that table.
+     * <p>
+     * Maximum number of simultaneous handles used to execute queries in parallel is
+     * dependent upon the device memory and possibly other properties.
+     * <p>
+     * After calling this method, execution of queries in parallel is enabled as long as this
+     * database handle is open. To disable execution of queries in parallel, database should
+     * be closed and reopened.
+     * <p>
+     * If a query is part of a transaction, then it is executed on the same database handle the
+     * transaction was begun.
+     * <p>
+     * If the database has any attached databases, then execution of queries in paralel is NOT
+     * possible. In such cases, a message is printed to logcat and false is returned.
+     * <p>
+     * This feature is not available for :memory: databases. In such cases,
+     * a message is printed to logcat and false is returned.
+     * <p>
+     * A typical way to use this method is the following:
+     * <pre>
+     *     SQLiteDatabase db = SQLiteDatabase.openDatabase("db_filename", cursorFactory,
+     *             CREATE_IF_NECESSARY, myDatabaseErrorHandler);
+     *     db.enableWriteAheadLogging();
+     * </pre>
+     * <p>
+     * Writers should use {@link #beginTransactionNonExclusive()} or
+     * {@link #beginTransactionWithListenerNonExclusive(SQLiteTransactionListener)}
+     * to start a trsnsaction.
+     * Non-exclusive mode allows database file to be in readable by threads executing queries.
+     * </p>
+     *
+     * @return true if write-ahead-logging is set. false otherwise
+     */
+    public synchronized boolean enableWriteAheadLogging() {
+        if (mPath.equalsIgnoreCase(MEMORY_DB_PATH)) {
+            Log.i(TAG, "can't enable WAL for memory databases.");
+            return false;
+        }
+
+        // make sure this database has NO attached databases because sqlite's write-ahead-logging
+        // doesn't work for databases with attached databases
+        if (getAttachedDbs().size() > 1) {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG,
+                        "this database: " + mPath + " has attached databases. can't  enable WAL.");
+            }
+            return false;
+        }
+        if (mConnectionPool == null) {
+            mConnectionPool = new DatabaseConnectionPool(this);
+            setJournalMode(mPath, "WAL");
+        }
+        return true;
+    }
+
+    /**
+     * This method disables the features enabled by {@link #enableWriteAheadLogging()}.
+     * @hide
+     */
+    public void disableWriteAheadLogging() {
+        synchronized (this) {
+            if (mConnectionPool == null) {
+                return;
+            }
+            mConnectionPool.close();
+            mConnectionPool = null;
+            setJournalMode(mPath, "TRUNCATE");
+        }
+    }
+
+    /* package */ SQLiteDatabase getDatabaseHandle(String sql) {
+        if (isPooledConnection()) {
+            // this is a pooled database connection
+            // use it if it is open AND if I am not currently part of a transaction
+            if (isOpen() && !amIInTransaction()) {
+                // TODO: use another connection from the pool
+                // if this connection is currently in use by some other thread
+                // AND if there are free connections in the pool
+                return this;
+            } else {
+                // the pooled connection is not open! could have been closed either due
+                // to corruption on this or some other connection to the database
+                // OR, maybe the connection pool is disabled after this connection has been
+                // allocated to me. try to get some other pooled or main database connection
+                return getParentDbConnObj().getDbConnection(sql);
+            }
+        } else {
+            // this is NOT a pooled connection. can we get one?
+            return getDbConnection(sql);
+        }
+    }
+
+    /**
+     * Sets the database connection handle pool size to the given value.
+     * Database connection handle pool is enabled when the app calls
+     * {@link #enableWriteAheadLogging()}.
+     * <p>
+     * The default connection handle pool is set by the system by taking into account various
+     * aspects of the device, such as memory, number of cores etc. It is recommended that
+     * applications use the default pool size set by the system.
+     *
+     * @param size the value the connection handle pool size should be set to.
+     */
+    public synchronized void setConnectionPoolSize(int size) {
+        if (mConnectionPool == null) {
+            throw new IllegalStateException("connection pool not enabled");
+        }
+        int i = mConnectionPool.getMaxPoolSize();
+        if (size < i) {
+            throw new IllegalArgumentException(
+                    "cannot set max pool size to a value less than the current max value(=" +
+                    i + ")");
+        }
+        mConnectionPool.setMaxPoolSize(size);
+    }
+
+    /* package */ SQLiteDatabase createPoolConnection(short connectionNum) {
+        SQLiteDatabase db = openDatabase(mPath, mFactory, mFlags, mErrorHandler, connectionNum);
+        db.mParentConnObj = this;
+        return db;
+    }
+
+    private synchronized SQLiteDatabase getParentDbConnObj() {
+        return mParentConnObj;
+    }
+
+    private boolean isPooledConnection() {
+        return this.mConnectionNum > 0;
+    }
+
+    /* package */ SQLiteDatabase getDbConnection(String sql) {
+        verifyDbIsOpen();
+        // this method should always be called with main database connection handle
+        // NEVER with pooled database connection handle
+        if (isPooledConnection()) {
+            throw new IllegalStateException("incorrect database connection handle");
+        }
+
+        // use the current connection handle if
+        // 1. if the caller is part of the ongoing transaction, if any
+        // 2. OR, if there is NO connection handle pool setup
+        if (amIInTransaction() || mConnectionPool == null) {
+            return this;
+        } else {
+            // get a connection handle from the pool
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                assert mConnectionPool != null;
+                Log.i(TAG, mConnectionPool.toString());
+            }
+            return mConnectionPool.get(sql);
+        }
+    }
+
+    private void releaseDbConnection(SQLiteDatabase db) {
+        // ignore this release call if
+        // 1. the database is closed
+        // 2. OR, if db is NOT a pooled connection handle
+        // 3. OR, if the database being released is same as 'this' (this condition means
+        //     that we should always be releasing a pooled connection handle by calling this method
+        //     from the 'main' connection handle
+        if (!isOpen() || !db.isPooledConnection() || (db == this)) {
+            return;
+        }
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            assert isPooledConnection();
+            assert mConnectionPool != null;
+            Log.d(TAG, "releaseDbConnection threadid = " + Thread.currentThread().getId() +
+                    ", releasing # " + db.mConnectionNum + ", " + getPath());
+        }
+        mConnectionPool.release(db);
     }
 
     /**
      * this method is used to collect data about ALL open databases in the current process.
-     * bugreport is a user of this data. 
+     * bugreport is a user of this data.
      */
     /* package */ static ArrayList<DbStats> getDbStats() {
         ArrayList<DbStats> dbStatsList = new ArrayList<DbStats>();
-        for (WeakReference<SQLiteDatabase> w : ActiveDatabases.getInstance().mActiveDatabases) {
+        // make a local copy of mActiveDatabases - so that this method is not competing
+        // for synchronization lock on mActiveDatabases
+        ArrayList<WeakReference<SQLiteDatabase>> tempList =
+                new ArrayList<WeakReference<SQLiteDatabase>>();
+        synchronized(mActiveDatabases) {
+            Collections.copy(tempList, mActiveDatabases);
+        }
+        for (WeakReference<SQLiteDatabase> w : tempList) {
             SQLiteDatabase db = w.get();
             if (db == null || !db.isOpen()) {
                 continue;
             }
-            // get SQLITE_DBSTATUS_LOOKASIDE_USED for the db
-            int lookasideUsed = db.native_getDbLookaside();
 
-            // get the lastnode of the dbname
-            String path = db.getPath();
-            int indx = path.lastIndexOf("/");
-            String lastnode = path.substring((indx != -1) ? ++indx : 0);
+            synchronized (db) {
+                try {
+                    // get SQLITE_DBSTATUS_LOOKASIDE_USED for the db
+                    int lookasideUsed = db.native_getDbLookaside();
 
-            // get list of attached dbs and for each db, get its size and pagesize
-            ArrayList<Pair<String, String>> attachedDbs = getAttachedDbs(db);
-            if (attachedDbs == null) {
-                continue;
-            }
-            for (int i = 0; i < attachedDbs.size(); i++) {
-                Pair<String, String> p = attachedDbs.get(i);
-                long pageCount = getPragmaVal(db, p.first + ".page_count;");
+                    // get the lastnode of the dbname
+                    String path = db.getPath();
+                    int indx = path.lastIndexOf("/");
+                    String lastnode = path.substring((indx != -1) ? ++indx : 0);
 
-                // first entry in the attached db list is always the main database
-                // don't worry about prefixing the dbname with "main"
-                String dbName;
-                if (i == 0) {
-                    dbName = lastnode;
-                } else {
-                    // lookaside is only relevant for the main db
-                    lookasideUsed = 0;
-                    dbName = "  (attached) " + p.first;
-                    // if the attached db has a path, attach the lastnode from the path to above
-                    if (p.second.trim().length() > 0) {
-                        int idx = p.second.lastIndexOf("/");
-                        dbName += " : " + p.second.substring((idx != -1) ? ++idx : 0);
+                    // get list of attached dbs and for each db, get its size and pagesize
+                    ArrayList<Pair<String, String>> attachedDbs = db.getAttachedDbs();
+                    if (attachedDbs == null) {
+                        continue;
                     }
-                }
-                if (pageCount > 0) {
-                    dbStatsList.add(new DbStats(dbName, pageCount, db.getPageSize(),
-                            lookasideUsed));
+                    for (int i = 0; i < attachedDbs.size(); i++) {
+                        Pair<String, String> p = attachedDbs.get(i);
+                        long pageCount = DatabaseUtils.longForQuery(db, "PRAGMA " + p.first
+                                + ".page_count;", null);
+
+                        // first entry in the attached db list is always the main database
+                        // don't worry about prefixing the dbname with "main"
+                        String dbName;
+                        if (i == 0) {
+                            dbName = lastnode;
+                        } else {
+                            // lookaside is only relevant for the main db
+                            lookasideUsed = 0;
+                            dbName = "  (attached) " + p.first;
+                            // if the attached db has a path, attach the lastnode from the path to above
+                            if (p.second.trim().length() > 0) {
+                                int idx = p.second.lastIndexOf("/");
+                                dbName += " : " + p.second.substring((idx != -1) ? ++idx : 0);
+                            }
+                        }
+                        if (pageCount > 0) {
+                            dbStatsList.add(new DbStats(dbName, pageCount, db.getPageSize(),
+                                    lookasideUsed, db.mNumCacheHits, db.mNumCacheMisses,
+                                    db.mCompiledQueries.size()));
+                        }
+                    }
+                    // if there are pooled connections, return the cache stats for them also.
+                    if (db.mConnectionPool != null) {
+                        for (SQLiteDatabase pDb : db.mConnectionPool.getConnectionList()) {
+                            dbStatsList.add(new DbStats("(pooled # " + pDb.mConnectionNum + ") "
+                                    + lastnode, 0, 0, 0, pDb.mNumCacheHits, pDb.mNumCacheMisses,
+                                    pDb.mCompiledQueries.size()));
+                        }
+                    }
+                } catch (SQLiteException e) {
+                    // ignore. we don't care about exceptions when we are taking adb
+                    // bugreport!
                 }
             }
         }
@@ -2194,44 +2462,76 @@
     }
 
     /**
-     * get the specified pragma value from sqlite for the specified database.
-     * only handles pragma's that return int/long.
-     * NO JAVA locks are held in this method.
-     * TODO: use this to do all pragma's in this class
+     * Returns list of full pathnames of all attached databases including the main database
+     * by executing 'pragma database_list' on the database.
+     *
+     * @return ArrayList of pairs of (database name, database file path) or null if the database
+     * is not open.
      */
-    private static long getPragmaVal(SQLiteDatabase db, String pragma) {
-        if (!db.isOpen()) {
-            return 0;
-        }
-        SQLiteStatement prog = null;
-        try {
-            prog = new SQLiteStatement(db, "PRAGMA " + pragma);
-            long val = prog.simpleQueryForLong();
-            return val;
-        } finally {
-            if (prog != null) prog.close();
-        }
-    }
-
-    /**
-     * returns list of full pathnames of all attached databases
-     * including the main database
-     * TODO: move this to {@link DatabaseUtils}
-     */
-    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;
     }
 
     /**
+     * Runs 'pragma integrity_check' on the given database (and all the attached databases)
+     * and returns true if the given database (and all its attached databases) pass integrity_check,
+     * false otherwise.
+     *<p>
+     * If the result is false, then this method logs the errors reported by the integrity_check
+     * command execution.
+     *<p>
+     * Note that 'pragma integrity_check' on a database can take a long time.
+     *
+     * @return true if the given database (and all its attached databases) pass integrity_check,
+     * false otherwise.
+     */
+    public boolean isDatabaseIntegrityOk() {
+        verifyDbIsOpen();
+        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
@@ -2239,51 +2539,34 @@
     private native void dbopen(String path, int flags);
 
     /**
-     * Native call to setup tracing of all sql statements
+     * Native call to setup tracing of all SQL statements
      *
      * @param path the full path to the database
+     * @param connectionNum connection number: 0 - N, where the main database
+     *            connection handle is numbered 0 and the connection handles in the connection
+     *            pool are numbered 1..N.
      */
-    private native void enableSqlTracing(String path);
+    private native void enableSqlTracing(String path, short connectionNum);
 
     /**
-     * Native call to setup profiling of all sql statements.
+     * Native call to setup profiling of all SQL statements.
      * currently, sqlite's profiling = printing of execution-time
-     * (wall-clock time) of each of the sql statements, as they
+     * (wall-clock time) of each of the SQL statements, as they
      * are executed.
      *
      * @param path the full path to the database
+     * @param connectionNum connection number: 0 - N, where the main database
+     *            connection handle is numbered 0 and the connection handles in the connection
+     *            pool are numbered 1..N.
      */
-    private native void enableSqlProfiling(String path);
-
-    /**
-     * Native call to execute a raw SQL statement. {@link #lock} must be held
-     * when calling this method.
-     *
-     * @param sql The raw SQL string
-     * @throws SQLException
-     */
-    /* package */ native void native_execSQL(String sql) throws SQLException;
+    private native void enableSqlProfiling(String path, short connectionNum);
 
     /**
      * Native call to set the locale.  {@link #lock} must be held when calling
      * this method.
      * @throws SQLException
      */
-    /* package */ native void native_setLocale(String loc, int flags);
-
-    /**
-     * Returns the row ID of the last row inserted into the database.
-     *
-     * @return the row ID of the last row inserted into the database.
-     */
-    /* package */ native long lastInsertRow();
-
-    /**
-     * Returns the number of changes made in the last statement executed.
-     *
-     * @return the number of changes made in the last statement executed.
-     */
-    /* package */ native int lastChangeCount();
+    private native void native_setLocale(String loc, int flags);
 
     /**
      * return the SQLITE_DBSTATUS_LOOKASIDE_USED documented here
@@ -2291,4 +2574,11 @@
      * @return int value of SQLITE_DBSTATUS_LOOKASIDE_USED
      */
     private native int native_getDbLookaside();
+
+    /**
+     * finalizes the given statement id.
+     *
+     * @param statementId statement to be finzlied by sqlite
+     */
+    private final native void native_finalize(int statementId);
 }
diff --git a/core/java/android/database/sqlite/SQLiteDatabaseLockedException.java b/core/java/android/database/sqlite/SQLiteDatabaseLockedException.java
new file mode 100644
index 0000000..f0e2d81
--- /dev/null
+++ b/core/java/android/database/sqlite/SQLiteDatabaseLockedException.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.sqlite;
+
+/**
+ * Thrown if  the database engine was unable to acquire the
+ * database locks it needs to do its job.  If the statement is a [COMMIT]
+ * or occurs outside of an explicit transaction, then you can retry the
+ * statement.  If the statement is not a [COMMIT] and occurs within a
+ * explicit transaction then you should rollback the transaction before
+ * continuing.
+ */
+public class SQLiteDatabaseLockedException extends SQLiteException {
+    public SQLiteDatabaseLockedException() {}
+
+    public SQLiteDatabaseLockedException(String error) {
+        super(error);
+    }
+}
diff --git a/core/java/android/database/sqlite/SQLiteDatatypeMismatchException.java b/core/java/android/database/sqlite/SQLiteDatatypeMismatchException.java
new file mode 100644
index 0000000..7f82535
--- /dev/null
+++ b/core/java/android/database/sqlite/SQLiteDatatypeMismatchException.java
@@ -0,0 +1,25 @@
+/*
+ * 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.
+ */
+
+package android.database.sqlite;
+
+public class SQLiteDatatypeMismatchException extends SQLiteException {
+    public SQLiteDatatypeMismatchException() {}
+
+    public SQLiteDatatypeMismatchException(String error) {
+        super(error);
+    }
+}
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..de2fca9 100644
--- a/core/java/android/database/sqlite/SQLiteDirectCursorDriver.java
+++ b/core/java/android/database/sqlite/SQLiteDirectCursorDriver.java
@@ -39,18 +39,16 @@
 
     public Cursor query(CursorFactory factory, String[] selectionArgs) {
         // Compile the query
-        SQLiteQuery query = new SQLiteQuery(mDatabase, mSql, 0, selectionArgs);
+        SQLiteQuery query = null;
 
         try {
-            // Arg binding
-            int numArgs = selectionArgs == null ? 0 : selectionArgs.length;
-            for (int i = 0; i < numArgs; i++) {
-                query.bindString(i + 1, selectionArgs[i]);
-            }
+            mDatabase.lock();
+            mDatabase.closePendingStatements();
+            query = new SQLiteQuery(mDatabase, mSql, 0, selectionArgs);
 
             // Create the cursor
             if (factory == null) {
-                mCursor = new SQLiteCursor(mDatabase, this, mEditTable, query);
+                mCursor = new SQLiteCursor(this, mEditTable, query);
             } else {
                 mCursor = factory.newCursor(mDatabase, this, mEditTable, query);
             }
@@ -61,6 +59,7 @@
         } finally {
             // Make sure this object is cleaned up if something happens
             if (query != null) query.close();
+            mDatabase.unlock();
         }
     }
 
@@ -69,10 +68,7 @@
     }
 
     public void setBindArguments(String[] bindArgs) {
-        final int numArgs = bindArgs.length;
-        for (int i = 0; i < numArgs; i++) {
-            mQuery.bindString(i + 1, bindArgs[i]);
-        }
+        mQuery.bindAllArgsAsStrings(bindArgs);
     }
 
     public void cursorDeactivated() {
diff --git a/core/java/android/database/sqlite/SQLiteOpenHelper.java b/core/java/android/database/sqlite/SQLiteOpenHelper.java
index b4615eb..d8cce21 100644
--- a/core/java/android/database/sqlite/SQLiteOpenHelper.java
+++ b/core/java/android/database/sqlite/SQLiteOpenHelper.java
@@ -17,6 +17,8 @@
 package android.database.sqlite;
 
 import android.content.Context;
+import android.database.DatabaseErrorHandler;
+import android.database.DefaultDatabaseErrorHandler;
 import android.database.sqlite.SQLiteDatabase.CursorFactory;
 import android.util.Log;
 
@@ -51,6 +53,7 @@
 
     private SQLiteDatabase mDatabase = null;
     private boolean mIsInitializing = false;
+    private final DatabaseErrorHandler mErrorHandler;
 
     /**
      * Create a helper object to create, open, and/or manage a database.
@@ -65,12 +68,37 @@
      *     {@link #onUpgrade} will be used to upgrade the database
      */
     public SQLiteOpenHelper(Context context, String name, CursorFactory factory, int version) {
+        this(context, name, factory, version, new DefaultDatabaseErrorHandler());
+    }
+
+    /**
+     * Create a helper object to create, open, and/or manage a database.
+     * The database is not actually created or opened until one of
+     * {@link #getWritableDatabase} or {@link #getReadableDatabase} is called.
+     *
+     * <p>Accepts input param: a concrete instance of {@link DatabaseErrorHandler} to be
+     * used to handle corruption when sqlite reports database corruption.</p>
+     *
+     * @param context to use to open or create the database
+     * @param name of the database file, or null for an in-memory database
+     * @param factory to use for creating cursor objects, or null for the default
+     * @param version number of the database (starting at 1); if the database is older,
+     *     {@link #onUpgrade} will be used to upgrade the database
+     * @param errorHandler the {@link DatabaseErrorHandler} to be used when sqlite reports database
+     * corruption.
+     */
+    public SQLiteOpenHelper(Context context, String name, CursorFactory factory, int version,
+            DatabaseErrorHandler errorHandler) {
         if (version < 1) throw new IllegalArgumentException("Version must be >= 1, was " + version);
+        if (errorHandler == null) {
+            throw new IllegalArgumentException("DatabaseErrorHandler param value can't be null.");
+        }
 
         mContext = context;
         mName = name;
         mFactory = factory;
         mNewVersion = version;
+        mErrorHandler = errorHandler;
     }
 
     /**
@@ -115,10 +143,14 @@
             if (mName == null) {
                 db = SQLiteDatabase.create(null);
             } else {
-                db = mContext.openOrCreateDatabase(mName, 0, mFactory);
+                db = mContext.openOrCreateDatabase(mName, 0, mFactory, mErrorHandler);
             }
 
             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 {
@@ -194,7 +226,8 @@
         try {
             mIsInitializing = true;
             String path = mContext.getDatabasePath(mName).getPath();
-            db = SQLiteDatabase.openDatabase(path, mFactory, SQLiteDatabase.OPEN_READONLY);
+            db = SQLiteDatabase.openDatabase(path, mFactory, SQLiteDatabase.OPEN_READONLY,
+                    mErrorHandler);
             if (db.getVersion() != mNewVersion) {
                 throw new SQLiteException("Can't upgrade read-only database from version " +
                         db.getVersion() + " to " + mNewVersion + ": " + path);
diff --git a/core/java/android/database/sqlite/SQLiteOutOfMemoryException.java b/core/java/android/database/sqlite/SQLiteOutOfMemoryException.java
new file mode 100644
index 0000000..98ce8b5
--- /dev/null
+++ b/core/java/android/database/sqlite/SQLiteOutOfMemoryException.java
@@ -0,0 +1,25 @@
+/*
+ * 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.sqlite;
+
+public class SQLiteOutOfMemoryException extends SQLiteException {
+    public SQLiteOutOfMemoryException() {}
+
+    public SQLiteOutOfMemoryException(String error) {
+        super(error);
+    }
+}
diff --git a/core/java/android/database/sqlite/SQLiteProgram.java b/core/java/android/database/sqlite/SQLiteProgram.java
index 4d96f12..231d8e6 100644
--- a/core/java/android/database/sqlite/SQLiteProgram.java
+++ b/core/java/android/database/sqlite/SQLiteProgram.java
@@ -16,11 +16,15 @@
 
 package android.database.sqlite;
 
+import android.database.DatabaseUtils;
+import android.database.Cursor;
 import android.util.Log;
 
+import java.util.HashMap;
+
 /**
  * A base class for compiled SQLite programs.
- *
+ *<p>
  * SQLiteProgram is not internally synchronized so code using a SQLiteProgram from multiple
  * threads should perform its own synchronization when using the SQLiteProgram.
  */
@@ -43,7 +47,7 @@
      * @deprecated do not use this
      */
     @Deprecated
-    protected int nHandle = 0;
+    protected int nHandle;
 
     /**
      * the SQLiteCompiledSql object for the given sql statement.
@@ -56,40 +60,79 @@
      * @deprecated do not use this
      */
     @Deprecated
-    protected int nStatement = 0;
+    protected int nStatement;
+
+    /**
+     * In the case of {@link SQLiteStatement}, this member stores the bindargs passed
+     * to the following methods, instead of actually doing the binding.
+     * <ul>
+     *   <li>{@link #bindBlob(int, byte[])}</li>
+     *   <li>{@link #bindDouble(int, double)}</li>
+     *   <li>{@link #bindLong(int, long)}</li>
+     *   <li>{@link #bindNull(int)}</li>
+     *   <li>{@link #bindString(int, String)}</li>
+     * </ul>
+     * <p>
+     * Each entry in the array is a Pair of
+     * <ol>
+     *   <li>bind arg position number</li>
+     *   <li>the value to be bound to the bindarg</li>
+     * </ol>
+     * <p>
+     * It is lazily initialized in the above bind methods
+     * and it is cleared in {@link #clearBindings()} method.
+     * <p>
+     * It is protected (in multi-threaded environment) by {@link SQLiteProgram}.this
+     */
+    /* package */ HashMap<Integer, Object> mBindArgs = null;
+    /* package */ final int mStatementType;
 
     /* package */ SQLiteProgram(SQLiteDatabase db, String sql) {
-        mDatabase = db;
+        this(db, sql, null, true);
+    }
+
+    /* package */ SQLiteProgram(SQLiteDatabase db, String sql, Object[] bindArgs,
+            boolean compileFlag) {
         mSql = sql.trim();
+        mStatementType = DatabaseUtils.getSqlStatementType(mSql);
         db.acquireReference();
         db.addSQLiteClosable(this);
-        this.nHandle = db.mNativeHandle;
+        mDatabase = db;
+        nHandle = db.mNativeHandle;
+        if (bindArgs != null) {
+            int size = bindArgs.length;
+            for (int i = 0; i < size; i++) {
+                this.addToBindArgs(i + 1, bindArgs[i]);
+            }
+        }
+        if (compileFlag) {
+            compileAndbindAllArgs();
+        }
+    }
 
+    private void compileSql() {
         // only cache CRUD statements
-        String prefixSql = mSql.substring(0, 6);
-        if (!prefixSql.equalsIgnoreCase("INSERT") && !prefixSql.equalsIgnoreCase("UPDATE") &&
-                !prefixSql.equalsIgnoreCase("REPLAC") &&
-                !prefixSql.equalsIgnoreCase("DELETE") && !prefixSql.equalsIgnoreCase("SELECT")) {
-            mCompiledSql = new SQLiteCompiledSql(db, sql);
+        if (mStatementType != DatabaseUtils.STATEMENT_SELECT &&
+                mStatementType != DatabaseUtils.STATEMENT_UPDATE) {
+            mCompiledSql = new SQLiteCompiledSql(mDatabase, mSql);
             nStatement = mCompiledSql.nStatement;
             // since it is not in the cache, no need to acquire() it.
             return;
         }
 
-        // it is not pragma
-        mCompiledSql = db.getCompiledStatementForSql(sql);
+        mCompiledSql = mDatabase.getCompiledStatementForSql(mSql);
         if (mCompiledSql == null) {
             // create a new compiled-sql obj
-            mCompiledSql = new SQLiteCompiledSql(db, sql);
+            mCompiledSql = new SQLiteCompiledSql(mDatabase, mSql);
 
             // add it to the cache of compiled-sqls
             // but before adding it and thus making it available for anyone else to use it,
             // make sure it is acquired by me.
             mCompiledSql.acquire();
-            db.addToCompiledQueries(sql, mCompiledSql);
+            mDatabase.addToCompiledQueries(mSql, mCompiledSql);
             if (SQLiteDebug.DEBUG_ACTIVE_CURSOR_FINALIZATION) {
                 Log.v(TAG, "Created DbObj (id#" + mCompiledSql.nStatement +
-                        ") for sql: " + sql);
+                        ") for sql: " + mSql);
             }
         } else {
             // it is already in compiled-sql cache.
@@ -100,12 +143,12 @@
                 // we can't have two different SQLiteProgam objects can't share the same
                 // CompiledSql object. create a new one.
                 // finalize it when I am done with it in "this" object.
-                mCompiledSql = new SQLiteCompiledSql(db, sql);
+                mCompiledSql = new SQLiteCompiledSql(mDatabase, mSql);
                 if (SQLiteDebug.DEBUG_ACTIVE_CURSOR_FINALIZATION) {
                     Log.v(TAG, "** possible bug ** Created NEW DbObj (id#" +
                             mCompiledSql.nStatement +
                             ") because the previously created DbObj (id#" + last +
-                            ") was not released for sql:" + sql);
+                            ") was not released for sql:" + mSql);
                 }
                 // since it is not in the cache, no need to acquire() it.
             }
@@ -116,8 +159,8 @@
     @Override
     protected void onAllReferencesReleased() {
         releaseCompiledSqlIfNotInCache();
-        mDatabase.releaseReference();
         mDatabase.removeSQLiteClosable(this);
+        mDatabase.releaseReference();
     }
 
     @Override
@@ -126,7 +169,7 @@
         mDatabase.releaseReference();
     }
 
-    private void releaseCompiledSqlIfNotInCache() {
+    /* package */ synchronized void releaseCompiledSqlIfNotInCache() {
         if (mCompiledSql == null) {
             return;
         }
@@ -135,22 +178,34 @@
                 // it is NOT in compiled-sql cache. i.e., responsibility of
                 // releasing this statement is on me.
                 mCompiledSql.releaseSqlStatement();
-                mCompiledSql = null;
-                nStatement = 0;
             } else {
                 // it is in compiled-sql cache. reset its CompiledSql#mInUse flag
                 mCompiledSql.release();
             }
-        } 
+        }
+        mCompiledSql = null;
+        nStatement = 0;
     }
 
     /**
      * Returns a unique identifier for this program.
      *
      * @return a unique identifier for this program
+     * @deprecated do not use this method. it is not guaranteed to be the same across executions of
+     * the SQL statement contained in this object.
      */
+    @Deprecated
     public final int getUniqueId() {
-        return nStatement;
+      return -1;
+    }
+
+    /**
+     * used only for testing purposes
+     */
+    /* package */ int getSqlStatementId() {
+      synchronized(this) {
+        return (mCompiledSql == null) ? 0 : nStatement;
+      }
     }
 
     /* package */ String getSqlString() {
@@ -169,6 +224,39 @@
         // TODO is there a need for this?
     }
 
+    private void bind(int type, int index, Object value) {
+        mDatabase.verifyDbIsOpen();
+        synchronized (this) {
+            addToBindArgs(index, (type == Cursor.FIELD_TYPE_NULL) ? null : value);
+            if (nStatement > 0) {
+                // bind only if the SQL statement is compiled
+                acquireReference();
+                try {
+                    switch (type) {
+                        case Cursor.FIELD_TYPE_NULL:
+                            native_bind_null(index);
+                            break;
+                        case Cursor.FIELD_TYPE_BLOB:
+                            native_bind_blob(index, (byte[]) value);
+                            break;
+                        case Cursor.FIELD_TYPE_FLOAT:
+                            native_bind_double(index, (Double) value);
+                            break;
+                        case Cursor.FIELD_TYPE_INTEGER:
+                            native_bind_long(index, (Long) value);
+                            break;
+                        case Cursor.FIELD_TYPE_STRING:
+                        default:
+                            native_bind_string(index, (String) value);
+                            break;
+                    }
+                } finally {
+                    releaseReference();
+                }
+            }
+        }
+    }
+
     /**
      * Bind a NULL value to this statement. The value remains bound until
      * {@link #clearBindings} is called.
@@ -176,34 +264,18 @@
      * @param index The 1-based index to the parameter to bind null to
      */
     public void bindNull(int index) {
-        if (!mDatabase.isOpen()) {
-            throw new IllegalStateException("database " + mDatabase.getPath() + " already closed");
-        }
-        acquireReference();
-        try {
-            native_bind_null(index);
-        } finally {
-            releaseReference();
-        }
+        bind(Cursor.FIELD_TYPE_NULL, index, null);
     }
 
     /**
      * Bind a long value to this statement. The value remains bound until
      * {@link #clearBindings} is called.
-     *
+     *addToBindArgs
      * @param index The 1-based index to the parameter to bind
      * @param value The value to bind
      */
     public void bindLong(int index, long value) {
-        if (!mDatabase.isOpen()) {
-            throw new IllegalStateException("database " + mDatabase.getPath() + " already closed");
-        }
-        acquireReference();
-        try {
-            native_bind_long(index, value);
-        } finally {
-            releaseReference();
-        }
+        bind(Cursor.FIELD_TYPE_INTEGER, index, value);
     }
 
     /**
@@ -214,15 +286,7 @@
      * @param value The value to bind
      */
     public void bindDouble(int index, double value) {
-        if (!mDatabase.isOpen()) {
-            throw new IllegalStateException("database " + mDatabase.getPath() + " already closed");
-        }
-        acquireReference();
-        try {
-            native_bind_double(index, value);
-        } finally {
-            releaseReference();
-        }
+        bind(Cursor.FIELD_TYPE_FLOAT, index, value);
     }
 
     /**
@@ -236,15 +300,7 @@
         if (value == null) {
             throw new IllegalArgumentException("the bind value at index " + index + " is null");
         }
-        if (!mDatabase.isOpen()) {
-            throw new IllegalStateException("database " + mDatabase.getPath() + " already closed");
-        }
-        acquireReference();
-        try {
-            native_bind_string(index, value);
-        } finally {
-            releaseReference();
-        }
+        bind(Cursor.FIELD_TYPE_STRING, index, value);
     }
 
     /**
@@ -258,29 +314,25 @@
         if (value == null) {
             throw new IllegalArgumentException("the bind value at index " + index + " is null");
         }
-        if (!mDatabase.isOpen()) {
-            throw new IllegalStateException("database " + mDatabase.getPath() + " already closed");
-        }
-        acquireReference();
-        try {
-            native_bind_blob(index, value);
-        } finally {
-            releaseReference();
-        }
+        bind(Cursor.FIELD_TYPE_BLOB, index, value);
     }
 
     /**
      * Clears all existing bindings. Unset bindings are treated as NULL.
      */
     public void clearBindings() {
-        if (!mDatabase.isOpen()) {
-            throw new IllegalStateException("database " + mDatabase.getPath() + " already closed");
-        }
-        acquireReference();
-        try {
-            native_clear_bindings();
-        } finally {
-            releaseReference();
+        synchronized (this) {
+            mBindArgs = null;
+            if (this.nStatement == 0) {
+                return;
+            }
+            mDatabase.verifyDbIsOpen();
+            acquireReference();
+            try {
+                native_clear_bindings();
+            } finally {
+                releaseReference();
+            }
         }
     }
 
@@ -288,14 +340,68 @@
      * Release this program's resources, making it invalid.
      */
     public void close() {
-        if (!mDatabase.isOpen()) {
+        synchronized (this) {
+            mBindArgs = null;
+            if (nHandle == 0 || !mDatabase.isOpen()) {
+                return;
+            }
+            releaseReference();
+        }
+    }
+
+    private synchronized void addToBindArgs(int index, Object value) {
+        if (mBindArgs == null) {
+            mBindArgs = new HashMap<Integer, Object>();
+        }
+        mBindArgs.put(index, value);
+    }
+
+    /* package */ synchronized void compileAndbindAllArgs() {
+        if (nStatement == 0) {
+            // SQL statement is not compiled yet. compile it now.
+            compileSql();
+        }
+        if (mBindArgs == null) {
             return;
         }
-        mDatabase.lock();
-        try {
-            releaseReference();
-        } finally {
-            mDatabase.unlock();
+        for (int index : mBindArgs.keySet()) {
+            Object value = mBindArgs.get(index);
+            if (value == null) {
+                native_bind_null(index);
+            } else if (value instanceof Double || value instanceof Float) {
+                native_bind_double(index, ((Number) value).doubleValue());
+            } else if (value instanceof Number) {
+                native_bind_long(index, ((Number) value).longValue());
+            } else if (value instanceof Boolean) {
+                Boolean bool = (Boolean)value;
+                native_bind_long(index, (bool) ? 1 : 0);
+                if (bool) {
+                    native_bind_long(index, 1);
+                } else {
+                    native_bind_long(index, 0);
+                }
+            } else if (value instanceof byte[]){
+                native_bind_blob(index, (byte[]) value);
+            } else {
+                native_bind_string(index, value.toString());
+            }
+        }
+    }
+
+    /**
+     * Given an array of String bindArgs, this method binds all of them in one single call.
+     *
+     * @param bindArgs the String array of bind args.
+     */
+    public void bindAllArgsAsStrings(String[] bindArgs) {
+        if (bindArgs == null) {
+            return;
+        }
+        int size = bindArgs.length;
+        synchronized(this) {
+            for (int i = 0; i < size; i++) {
+                bindString(i + 1, bindArgs[i]);
+            }
         }
     }
 
diff --git a/core/java/android/database/sqlite/SQLiteQuery.java b/core/java/android/database/sqlite/SQLiteQuery.java
index 905b66b..63a8ce9 100644
--- a/core/java/android/database/sqlite/SQLiteQuery.java
+++ b/core/java/android/database/sqlite/SQLiteQuery.java
@@ -18,7 +18,6 @@
 
 import android.database.CursorWindow;
 import android.os.SystemClock;
-import android.util.Log;
 
 /**
  * A SQLite program that represents a query that reads the resulting rows into a CursorWindow.
@@ -28,28 +27,37 @@
  * threads should perform its own synchronization when using the SQLiteQuery.
  */
 public class SQLiteQuery extends SQLiteProgram {
-    private static final String TAG = "Cursor";
+    private static final String TAG = "SQLiteQuery";
 
     /** The index of the unbound OFFSET parameter */
-    private int mOffsetIndex;
-    
-    /** Args to bind on requery */
-    private String[] mBindArgs;
+    private int mOffsetIndex = 0;
 
     private boolean mClosed = false;
 
     /**
      * Create a persistent query object.
-     * 
+     *
      * @param db The database that this query object is associated with
      * @param query The SQL string for this query. 
      * @param offsetIndex The 1-based index to the OFFSET parameter, 
      */
     /* package */ SQLiteQuery(SQLiteDatabase db, String query, int offsetIndex, String[] bindArgs) {
         super(db, query);
-
         mOffsetIndex = offsetIndex;
-        mBindArgs = bindArgs;
+        bindAllArgsAsStrings(bindArgs);
+    }
+
+    /**
+     * Constructor used to create new instance to replace a given instance of this class.
+     * This constructor is used when the current Query object is now associated with a different
+     * {@link SQLiteDatabase} object.
+     *
+     * @param db The database that this query object is associated with
+     * @param query the instance of {@link SQLiteQuery} to be replaced
+     */
+    /* package */ SQLiteQuery(SQLiteDatabase db, SQLiteQuery query) {
+        super(db, query.mSql);
+        this.mBindArgs = query.mBindArgs;
     }
 
     /**
@@ -72,11 +80,6 @@
                 // is not safe in this situation. the native code will ignore maxRead
                 int numRows = native_fill_window(window, window.getStartPosition(), mOffsetIndex,
                         maxRead, lastPos);
-
-                // Logging
-                if (SQLiteDebug.DEBUG_SQL_STATEMENTS) {
-                    Log.d(TAG, "fillWindow(): " + mSql);
-                }
                 mDatabase.logTimeStat(mSql, timeStart);
                 return numRows;
             } catch (IllegalStateException e){
@@ -141,51 +144,13 @@
      * Called by SQLiteCursor when it is requeried.
      */
     /* package */ void requery() {
-        if (mBindArgs != null) {
-            int len = mBindArgs.length;
-            try {
-                for (int i = 0; i < len; i++) {
-                    super.bindString(i + 1, mBindArgs[i]);
-                }
-            } catch (SQLiteMisuseException e) {
-                StringBuilder errMsg = new StringBuilder("mSql " + mSql);
-                for (int i = 0; i < len; i++) {
-                    errMsg.append(" ");
-                    errMsg.append(mBindArgs[i]);
-                }
-                errMsg.append(" ");
-                IllegalStateException leakProgram = new IllegalStateException(
-                        errMsg.toString(), e);
-                throw leakProgram;                
-            }
+        if (mClosed) {
+            throw new IllegalStateException("requerying a closed cursor");
         }
+        compileAndbindAllArgs();
     }
 
-    @Override
-    public void bindNull(int index) {
-        mBindArgs[index - 1] = null;
-        if (!mClosed) super.bindNull(index);
-    }
-
-    @Override
-    public void bindLong(int index, long value) {
-        mBindArgs[index - 1] = Long.toString(value);
-        if (!mClosed) super.bindLong(index, value);
-    }
-
-    @Override
-    public void bindDouble(int index, double value) {
-        mBindArgs[index - 1] = Double.toString(value);
-        if (!mClosed) super.bindDouble(index, value);
-    }
-
-    @Override
-    public void bindString(int index, String value) {
-        mBindArgs[index - 1] = value;
-        if (!mClosed) super.bindString(index, value);
-    }
-
-    private final native int native_fill_window(CursorWindow window, 
+    private final native int native_fill_window(CursorWindow window,
             int startPos, int offsetParam, int maxRead, int lastPos);
 
     private final native int native_column_count();
diff --git a/core/java/android/database/sqlite/SQLiteReadOnlyDatabaseException.java b/core/java/android/database/sqlite/SQLiteReadOnlyDatabaseException.java
new file mode 100644
index 0000000..5b633c6
--- /dev/null
+++ b/core/java/android/database/sqlite/SQLiteReadOnlyDatabaseException.java
@@ -0,0 +1,25 @@
+/*
+ * 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.sqlite;
+
+public class SQLiteReadOnlyDatabaseException extends SQLiteException {
+    public SQLiteReadOnlyDatabaseException() {}
+
+    public SQLiteReadOnlyDatabaseException(String error) {
+        super(error);
+    }
+}
diff --git a/core/java/android/database/sqlite/SQLiteStatement.java b/core/java/android/database/sqlite/SQLiteStatement.java
index 9e425c3..97e39d6 100644
--- a/core/java/android/database/sqlite/SQLiteStatement.java
+++ b/core/java/android/database/sqlite/SQLiteStatement.java
@@ -16,6 +16,7 @@
 
 package android.database.sqlite;
 
+import android.database.DatabaseUtils;
 import android.os.SystemClock;
 
 import dalvik.system.BlockGuard;
@@ -25,44 +26,62 @@
  * The statement cannot return multiple rows, but 1x1 result sets are allowed.
  * Don't use SQLiteStatement constructor directly, please use
  * {@link SQLiteDatabase#compileStatement(String)}
- *
+ *<p>
  * SQLiteStatement is not internally synchronized so code using a SQLiteStatement from multiple
  * threads should perform its own synchronization when using the SQLiteStatement.
  */
+@SuppressWarnings("deprecation")
 public class SQLiteStatement extends SQLiteProgram
 {
+    private static final boolean READ = true;
+    private static final boolean WRITE = false;
+
+    private SQLiteDatabase mOrigDb;
+    private int mState;
+    /** possible value for {@link #mState}. indicates that a transaction is started.} */
+    private static final int TRANS_STARTED = 1;
+    /** possible value for {@link #mState}. indicates that a lock is acquired.} */
+    private static final int LOCK_ACQUIRED = 2;
+
     /**
      * Don't use SQLiteStatement constructor directly, please use
      * {@link SQLiteDatabase#compileStatement(String)}
      * @param db
      * @param sql
      */
-    /* package */ SQLiteStatement(SQLiteDatabase db, String sql) {
-        super(db, sql);
+    /* package */ SQLiteStatement(SQLiteDatabase db, String sql, Object[] bindArgs) {
+        super(db, sql, bindArgs, false /* don't compile sql statement */);
     }
 
     /**
-     * Execute this SQL statement, if it is not a query. For example,
-     * CREATE TABLE, DELTE, INSERT, etc.
+     * Execute this SQL statement, if it is not a SELECT / INSERT / DELETE / UPDATE, for example
+     * CREATE / DROP table, view, trigger, index etc.
      *
      * @throws android.database.SQLException If the SQL string is invalid for
      *         some reason
      */
     public void execute() {
-        BlockGuard.getThreadPolicy().onWriteToDisk();
-        if (!mDatabase.isOpen()) {
-            throw new IllegalStateException("database " + mDatabase.getPath() + " already closed");
-        }
-        long timeStart = SystemClock.uptimeMillis();
-        mDatabase.lock();
+        executeUpdateDelete();
+    }
 
-        acquireReference();
-        try {
-            native_execute();
-            mDatabase.logTimeStat(mSql, timeStart);
-        } finally {
-            releaseReference();
-            mDatabase.unlock();
+    /**
+     * Execute this SQL statement, if the the number of rows affected by exection of this SQL
+     * statement is of any importance to the caller - for example, UPDATE / DELETE SQL statements.
+     *
+     * @return the number of rows affected by this SQL statement execution.
+     * @throws android.database.SQLException If the SQL string is invalid for
+     *         some reason
+     */
+    public int executeUpdateDelete() {
+        synchronized(this) {
+            long timeStart = acquireAndLock(WRITE);
+            try {
+                int numChanges = native_execute();
+                mDatabase.logTimeStat(mSql, timeStart);
+                return numChanges;
+            } finally {
+                releaseAndUnlock();
+            }
         }
     }
 
@@ -76,21 +95,15 @@
      *         some reason
      */
     public long executeInsert() {
-        BlockGuard.getThreadPolicy().onWriteToDisk();
-        if (!mDatabase.isOpen()) {
-            throw new IllegalStateException("database " + mDatabase.getPath() + " already closed");
-        }
-        long timeStart = SystemClock.uptimeMillis();
-        mDatabase.lock();
-
-        acquireReference();
-        try {
-            native_execute();
-            mDatabase.logTimeStat(mSql, timeStart);
-            return (mDatabase.lastChangeCount() > 0) ? mDatabase.lastInsertRow() : -1;
-        } finally {
-            releaseReference();
-            mDatabase.unlock();
+        synchronized(this) {
+            long timeStart = acquireAndLock(WRITE);
+            try {
+                long lastInsertedRowId = native_executeInsert();
+                mDatabase.logTimeStat(mSql, timeStart);
+                return lastInsertedRowId;
+            } finally {
+                releaseAndUnlock();
+            }
         }
     }
 
@@ -103,21 +116,15 @@
      * @throws android.database.sqlite.SQLiteDoneException if the query returns zero rows
      */
     public long simpleQueryForLong() {
-        BlockGuard.getThreadPolicy().onReadFromDisk();
-        if (!mDatabase.isOpen()) {
-            throw new IllegalStateException("database " + mDatabase.getPath() + " already closed");
-        }
-        long timeStart = SystemClock.uptimeMillis();
-        mDatabase.lock();
-
-        acquireReference();
-        try {
-            long retValue = native_1x1_long();
-            mDatabase.logTimeStat(mSql, timeStart);
-            return retValue;
-        } finally {
-            releaseReference();
-            mDatabase.unlock();
+        synchronized(this) {
+            long timeStart = acquireAndLock(READ);
+            try {
+                long retValue = native_1x1_long();
+                mDatabase.logTimeStat(mSql, timeStart);
+                return retValue;
+            } finally {
+                releaseAndUnlock();
+            }
         }
     }
 
@@ -130,25 +137,111 @@
      * @throws android.database.sqlite.SQLiteDoneException if the query returns zero rows
      */
     public String simpleQueryForString() {
-        BlockGuard.getThreadPolicy().onReadFromDisk();
-        if (!mDatabase.isOpen()) {
-            throw new IllegalStateException("database " + mDatabase.getPath() + " already closed");
-        }
-        long timeStart = SystemClock.uptimeMillis();
-        mDatabase.lock();
-
-        acquireReference();
-        try {
-            String retValue = native_1x1_string();
-            mDatabase.logTimeStat(mSql, timeStart);
-            return retValue;
-        } finally {
-            releaseReference();
-            mDatabase.unlock();
+        synchronized(this) {
+            long timeStart = acquireAndLock(READ);
+            try {
+                String retValue = native_1x1_string();
+                mDatabase.logTimeStat(mSql, timeStart);
+                return retValue;
+            } finally {
+                releaseAndUnlock();
+            }
         }
     }
 
-    private final native void native_execute();
+    /**
+     * Called before every method in this class before executing a SQL statement,
+     * this method does the following:
+     * <ul>
+     *   <li>make sure the database is open</li>
+     *   <li>get a database connection from the connection pool,if possible</li>
+     *   <li>notifies {@link BlockGuard} of read/write</li>
+     *   <li>if the SQL statement is an update, start transaction if not already in one.
+     *   otherwise, get lock on the database</li>
+     *   <li>acquire reference on this object</li>
+     *   <li>and then return the current time _before_ the database lock was acquired</li>
+     * </ul>
+     * <p>
+     * This method removes the duplicate code from the other public
+     * methods in this class.
+     */
+    private long acquireAndLock(boolean rwFlag) {
+        mState = 0;
+        // use pooled database connection handles for SELECT SQL statements
+        mDatabase.verifyDbIsOpen();
+        SQLiteDatabase db = (mStatementType != DatabaseUtils.STATEMENT_SELECT) ? mDatabase
+                : mDatabase.getDbConnection(mSql);
+        // use the database connection obtained above
+        mOrigDb = mDatabase;
+        mDatabase = db;
+        nHandle = mDatabase.mNativeHandle;
+        if (rwFlag == WRITE) {
+            BlockGuard.getThreadPolicy().onWriteToDisk();
+        } else {
+            BlockGuard.getThreadPolicy().onReadFromDisk();
+        }
+
+        /*
+         * Special case handling of SQLiteDatabase.execSQL("BEGIN transaction").
+         * we know it is execSQL("BEGIN transaction") from the caller IF there is no lock held.
+         * beginTransaction() methods in SQLiteDatabase call lockForced() before
+         * calling execSQL("BEGIN transaction").
+         */
+        if (mStatementType == DatabaseUtils.STATEMENT_BEGIN) {
+            if (!mDatabase.isDbLockedByCurrentThread()) {
+                // transaction is  NOT started by calling beginTransaction() methods in
+                // SQLiteDatabase
+                mDatabase.setTransactionUsingExecSqlFlag();
+            }
+        } else if (mStatementType == DatabaseUtils.STATEMENT_UPDATE) {
+            // got update SQL statement. if there is NO pending transaction, start one
+            if (!mDatabase.inTransaction()) {
+                mDatabase.beginTransactionNonExclusive();
+                mState = TRANS_STARTED;
+            }
+        }
+        // do I have database lock? if not, grab it.
+        if (!mDatabase.isDbLockedByCurrentThread()) {
+            mDatabase.lock();
+            mState = LOCK_ACQUIRED;
+        }
+
+        acquireReference();
+        long startTime = SystemClock.uptimeMillis();
+        mDatabase.closePendingStatements();
+        compileAndbindAllArgs();
+        return startTime;
+    }
+
+    /**
+     * this method releases locks and references acquired in {@link #acquireAndLock(boolean)}
+     */
+    private void releaseAndUnlock() {
+        releaseReference();
+        if (mState == TRANS_STARTED) {
+            try {
+                mDatabase.setTransactionSuccessful();
+            } finally {
+                mDatabase.endTransaction();
+            }
+        } else if (mState == LOCK_ACQUIRED) {
+            mDatabase.unlock();
+        }
+        if (mStatementType == DatabaseUtils.STATEMENT_COMMIT ||
+                mStatementType == DatabaseUtils.STATEMENT_ABORT) {
+            mDatabase.resetTransactionUsingExecSqlFlag();
+        }
+        clearBindings();
+        // release the compiled sql statement so that the caller's SQLiteStatement no longer
+        // has a hard reference to a database object that may get deallocated at any point.
+        releaseCompiledSqlIfNotInCache();
+        // restore the database connection handle to the original value
+        mDatabase = mOrigDb;
+        nHandle = mDatabase.mNativeHandle;
+    }
+
+    private final native int native_execute();
+    private final native long native_executeInsert();
     private final native long native_1x1_long();
     private final native String native_1x1_string();
 }
diff --git a/core/java/android/database/sqlite/SQLiteTableLockedException.java b/core/java/android/database/sqlite/SQLiteTableLockedException.java
new file mode 100644
index 0000000..8278df0
--- /dev/null
+++ b/core/java/android/database/sqlite/SQLiteTableLockedException.java
@@ -0,0 +1,25 @@
+/*
+ * 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.sqlite;
+
+public class SQLiteTableLockedException extends SQLiteException {
+    public SQLiteTableLockedException() {}
+
+    public SQLiteTableLockedException(String error) {
+        super(error);
+    }
+}
diff --git a/core/java/android/hardware/SensorEvent.java b/core/java/android/hardware/SensorEvent.java
index 70519ff..aaf3898 100644
--- a/core/java/android/hardware/SensorEvent.java
+++ b/core/java/android/hardware/SensorEvent.java
@@ -84,14 +84,14 @@
      * sensor itself (<b>Fs</b>) using the relation:
      * </p>
      * 
-     * <b><center>Ad = - ·Fs / mass</center></b>
+     * <b><center>Ad = - &#8721;Fs / mass</center></b>
      * 
      * <p>
      * In particular, the force of gravity is always influencing the measured
      * acceleration:
      * </p>
      * 
-     * <b><center>Ad = -g - ·F / mass</center></b>
+     * <b><center>Ad = -g - &#8721;F / mass</center></b>
      * 
      * <p>
      * For this reason, when the device is sitting on a table (and obviously not
diff --git a/core/java/android/hardware/Usb.java b/core/java/android/hardware/Usb.java
index 57271d4..1028f9f 100644
--- a/core/java/android/hardware/Usb.java
+++ b/core/java/android/hardware/Usb.java
@@ -55,6 +55,24 @@
     public static final String ACTION_USB_STATE =
             "android.hardware.action.USB_STATE";
 
+   /**
+     * Broadcast Action:  A broadcast for USB camera attached event.
+     *
+     * This intent is sent when a USB device supporting PTP is attached to the host USB bus.
+     * The intent's data contains a Uri for the device in the MTP provider.
+     */
+    public static final String ACTION_USB_CAMERA_ATTACHED =
+            "android.hardware.action.USB_CAMERA_ATTACHED";
+
+   /**
+     * Broadcast Action:  A broadcast for USB camera detached event.
+     *
+     * This intent is sent when a USB device supporting PTP is detached from the host USB bus.
+     * The intent's data contains a Uri for the device in the MTP provider.
+     */
+    public static final String ACTION_USB_CAMERA_DETACHED =
+            "android.hardware.action.USB_CAMERA_DETACHED";
+
     /**
      * Boolean extra indicating whether USB is connected or disconnected.
      * Used in extras for the {@link #ACTION_USB_STATE} broadcast.
diff --git a/core/java/android/inputmethodservice/KeyboardView.java b/core/java/android/inputmethodservice/KeyboardView.java
index 4b48409..ab5c78a 100644
--- a/core/java/android/inputmethodservice/KeyboardView.java
+++ b/core/java/android/inputmethodservice/KeyboardView.java
@@ -1128,7 +1128,9 @@
 
     private boolean onModifiedTouchEvent(MotionEvent me, boolean possiblePoly) {
         int touchX = (int) me.getX() - mPaddingLeft;
-        int touchY = (int) me.getY() + mVerticalCorrection - mPaddingTop;
+        int touchY = (int) me.getY() - mPaddingTop;
+        if (touchY >= -mVerticalCorrection)
+            touchY += mVerticalCorrection;
         final int action = me.getAction();
         final long eventTime = me.getEventTime();
         mOldEventTime = eventTime;
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 280ded6..6335296 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -524,5 +524,20 @@
         } catch (RemoteException e) {
             return TETHER_ERROR_SERVICE_UNAVAIL;
         }
-   }
+    }
+
+    /**
+     * Ensure the device stays awake until we connect with the next network
+     * @param forWhome The name of the network going down for logging purposes
+     * @return {@code true} on success, {@code false} on failure
+     * {@hide}
+     */
+    public boolean requestNetworkTransitionWakelock(String forWhom) {
+        try {
+            mService.requestNetworkTransitionWakelock(forWhom);
+            return true;
+        } catch (RemoteException e) {
+            return false;
+        }
+    }
 }
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/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index b05c2ed..5a14cc9 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -72,4 +72,6 @@
     String[] getTetherableUsbRegexs();
 
     String[] getTetherableWifiRegexs();
+
+    void requestNetworkTransitionWakelock(in String forWhom);
 }
diff --git a/core/java/android/net/MobileDataStateTracker.java b/core/java/android/net/MobileDataStateTracker.java
index 214510d..11cd526 100644
--- a/core/java/android/net/MobileDataStateTracker.java
+++ b/core/java/android/net/MobileDataStateTracker.java
@@ -22,12 +22,15 @@
 import android.content.IntentFilter;
 import android.os.RemoteException;
 import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
 import android.os.ServiceManager;
-import android.os.SystemProperties;
 import com.android.internal.telephony.ITelephony;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.TelephonyIntents;
 import android.net.NetworkInfo.DetailedState;
+import android.net.NetworkInfo;
+import android.net.NetworkProperties;
 import android.telephony.TelephonyManager;
 import android.util.Log;
 import android.text.TextUtils;
@@ -39,7 +42,7 @@
  *
  * {@hide}
  */
-public class MobileDataStateTracker extends NetworkStateTracker {
+public class MobileDataStateTracker implements NetworkStateTracker {
 
     private static final String TAG = "MobileDataStateTracker";
     private static final boolean DBG = true;
@@ -48,10 +51,19 @@
     private ITelephony mPhoneService;
 
     private String mApnType;
-    private String mApnTypeToWatchFor;
-    private String mApnName;
-    private boolean mEnabled;
-    private BroadcastReceiver mStateReceiver;
+    private static String[] sDnsPropNames;
+    private NetworkInfo mNetworkInfo;
+    private boolean mTeardownRequested = false;
+    private Handler mTarget;
+    private Context mContext;
+    private NetworkProperties mNetworkProperties;
+    private boolean mPrivateDnsRouteSet = false;
+    private int mDefaultGatewayAddr = 0;
+    private boolean mDefaultRouteSet = false;
+
+    // DEFAULT and HIPRI are the same connection.  If we're one of these we need to check if
+    // the other is also disconnected before we reset sockets
+    private boolean mIsDefaultOrHipri = false;
 
     /**
      * Create a new MobileDataStateTracker
@@ -62,24 +74,20 @@
      * @param tag the name of this network
      */
     public MobileDataStateTracker(Context context, Handler target, int netType, String tag) {
-        super(context, target, netType,
+        mTarget = target;
+        mContext = context;
+        mNetworkInfo = new NetworkInfo(netType,
                 TelephonyManager.getDefault().getNetworkType(), tag,
                 TelephonyManager.getDefault().getNetworkTypeName());
         mApnType = networkTypeToApnType(netType);
-        if (TextUtils.equals(mApnType, Phone.APN_TYPE_HIPRI)) {
-            mApnTypeToWatchFor = Phone.APN_TYPE_DEFAULT;
-        } else {
-            mApnTypeToWatchFor = mApnType;
+        if (netType == ConnectivityManager.TYPE_MOBILE ||
+                netType == ConnectivityManager.TYPE_MOBILE_HIPRI) {
+            mIsDefaultOrHipri = true;
         }
 
         mPhoneService = null;
-        if(netType == ConnectivityManager.TYPE_MOBILE) {
-            mEnabled = true;
-        } else {
-            mEnabled = false;
-        }
 
-        mDnsPropNames = new String[] {
+        sDnsPropNames = new String[] {
                 "net.rmnet0.dns1",
                 "net.rmnet0.dns2",
                 "net.eth0.dns1",
@@ -90,7 +98,45 @@
                 "net.gprs.dns2",
                 "net.ppp0.dns1",
                 "net.ppp0.dns2"};
+    }
 
+    /**
+     * Return the IP addresses of the DNS servers available for the mobile data
+     * network interface.
+     * @return a list of DNS addresses, with no holes.
+     */
+    public String[] getDnsPropNames() {
+        return sDnsPropNames;
+    }
+
+    public boolean isPrivateDnsRouteSet() {
+        return mPrivateDnsRouteSet;
+    }
+
+    public void privateDnsRouteSet(boolean enabled) {
+        mPrivateDnsRouteSet = enabled;
+    }
+
+    public NetworkInfo getNetworkInfo() {
+        return mNetworkInfo;
+    }
+
+    public int getDefaultGatewayAddr() {
+        return mDefaultGatewayAddr;
+    }
+
+    public boolean isDefaultRouteSet() {
+        return mDefaultRouteSet;
+    }
+
+    public void defaultRouteSet(boolean enabled) {
+        mDefaultRouteSet = enabled;
+    }
+
+    /**
+     * This is not implemented.
+     */
+    public void releaseWakeLock() {
     }
 
     /**
@@ -102,133 +148,139 @@
         filter.addAction(TelephonyIntents.ACTION_DATA_CONNECTION_FAILED);
         filter.addAction(TelephonyIntents.ACTION_SERVICE_STATE_CHANGED);
 
-        mStateReceiver = new MobileDataStateReceiver();
-        Intent intent = mContext.registerReceiver(mStateReceiver, filter);
-        if (intent != null)
-            mMobileDataState = getMobileDataState(intent);
-        else
-            mMobileDataState = Phone.DataState.DISCONNECTED;
+        mContext.registerReceiver(new MobileDataStateReceiver(), filter);
+        mMobileDataState = Phone.DataState.DISCONNECTED;
     }
 
-    private Phone.DataState getMobileDataState(Intent intent) {
-        String str = intent.getStringExtra(Phone.STATE_KEY);
-        if (str != null) {
-            String apnTypeList =
-                    intent.getStringExtra(Phone.DATA_APN_TYPES_KEY);
-            if (isApnTypeIncluded(apnTypeList)) {
-                return Enum.valueOf(Phone.DataState.class, str);
-            }
+    /**
+     * Record the roaming status of the device, and if it is a change from the previous
+     * status, send a notification to any listeners.
+     * @param isRoaming {@code true} if the device is now roaming, {@code false}
+     * if it is no longer roaming.
+     */
+    private void setRoamingStatus(boolean isRoaming) {
+        if (isRoaming != mNetworkInfo.isRoaming()) {
+            mNetworkInfo.setRoaming(isRoaming);
+            Message msg = mTarget.obtainMessage(EVENT_ROAMING_CHANGED, mNetworkInfo);
+            msg.sendToTarget();
         }
-        return Phone.DataState.DISCONNECTED;
     }
 
-    private boolean isApnTypeIncluded(String typeList) {
-        /* comma seperated list - split and check */
-        if (typeList == null)
-            return false;
-
-        String[] list = typeList.split(",");
-        for(int i=0; i< list.length; i++) {
-            if (TextUtils.equals(list[i], mApnTypeToWatchFor) ||
-                TextUtils.equals(list[i], Phone.APN_TYPE_ALL)) {
-                return true;
+    private void setSubtype(int subtype, String subtypeName) {
+        if (mNetworkInfo.isConnected()) {
+            int oldSubtype = mNetworkInfo.getSubtype();
+            if (subtype != oldSubtype) {
+                mNetworkInfo.setSubtype(subtype, subtypeName);
+                Message msg = mTarget.obtainMessage(
+                        EVENT_NETWORK_SUBTYPE_CHANGED, oldSubtype, 0, mNetworkInfo);
+                msg.sendToTarget();
             }
         }
-        return false;
     }
 
     private class MobileDataStateReceiver extends BroadcastReceiver {
+        IConnectivityManager mConnectivityManager;
+
         public void onReceive(Context context, Intent intent) {
-            synchronized(this) {
-                if (intent.getAction().equals(TelephonyIntents.
-                        ACTION_ANY_DATA_CONNECTION_STATE_CHANGED)) {
-                    Phone.DataState state = getMobileDataState(intent);
-                    String reason = intent.getStringExtra(Phone.STATE_CHANGE_REASON_KEY);
-                    String apnName = intent.getStringExtra(Phone.DATA_APN_KEY);
-                    String apnTypeList = intent.getStringExtra(Phone.DATA_APN_TYPES_KEY);
-                    mApnName = apnName;
+            if (intent.getAction().equals(TelephonyIntents.
+                    ACTION_ANY_DATA_CONNECTION_STATE_CHANGED)) {
+                String apnType = intent.getStringExtra(Phone.DATA_APN_TYPE_KEY);
 
-                    boolean unavailable = intent.getBooleanExtra(Phone.NETWORK_UNAVAILABLE_KEY,
-                            false);
-
-                    // set this regardless of the apnTypeList.  It's all the same radio/network
-                    // underneath
-                    mNetworkInfo.setIsAvailable(!unavailable);
-
-                    if (isApnTypeIncluded(apnTypeList)) {
-                        if (mEnabled == false) {
-                            // if we're not enabled but the APN Type is supported by this connection
-                            // we should record the interface name if one's provided.  If the user
-                            // turns on this network we will need the interfacename but won't get
-                            // a fresh connected message - TODO fix this when we get per-APN
-                            // notifications
-                            if (state == Phone.DataState.CONNECTED) {
-                                if (DBG) Log.d(TAG, "replacing old mInterfaceName (" +
-                                        mInterfaceName + ") with " +
-                                        intent.getStringExtra(Phone.DATA_IFACE_NAME_KEY) +
-                                        " for " + mApnType);
-                                mInterfaceName = intent.getStringExtra(Phone.DATA_IFACE_NAME_KEY);
-                            }
-                            return;
-                        }
-                    } else {
-                        return;
-                    }
-
-                    if (DBG) Log.d(TAG, mApnType + " Received state= " + state + ", old= " +
-                            mMobileDataState + ", reason= " +
-                            (reason == null ? "(unspecified)" : reason) +
-                            ", apnTypeList= " + apnTypeList);
-
-                    if (mMobileDataState != state) {
-                        mMobileDataState = state;
-                        switch (state) {
-                            case DISCONNECTED:
-                                if(isTeardownRequested()) {
-                                    mEnabled = false;
-                                    setTeardownRequested(false);
-                                }
-
-                                setDetailedState(DetailedState.DISCONNECTED, reason, apnName);
-                                if (mInterfaceName != null) {
-                                    NetworkUtils.resetConnections(mInterfaceName);
-                                }
-                                // can't do this here - ConnectivityService needs it to clear stuff
-                                // it's ok though - just leave it to be refreshed next time
-                                // we connect.
-                                //if (DBG) Log.d(TAG, "clearing mInterfaceName for "+ mApnType +
-                                //        " as it DISCONNECTED");
-                                //mInterfaceName = null;
-                                //mDefaultGatewayAddr = 0;
-                                break;
-                            case CONNECTING:
-                                setDetailedState(DetailedState.CONNECTING, reason, apnName);
-                                break;
-                            case SUSPENDED:
-                                setDetailedState(DetailedState.SUSPENDED, reason, apnName);
-                                break;
-                            case CONNECTED:
-                                mInterfaceName = intent.getStringExtra(Phone.DATA_IFACE_NAME_KEY);
-                                if (mInterfaceName == null) {
-                                    Log.d(TAG, "CONNECTED event did not supply interface name.");
-                                }
-                                setDetailedState(DetailedState.CONNECTED, reason, apnName);
-                                break;
-                        }
-                    }
-                } else if (intent.getAction().
-                        equals(TelephonyIntents.ACTION_DATA_CONNECTION_FAILED)) {
-                    mEnabled = false;
-                    String reason = intent.getStringExtra(Phone.FAILURE_REASON_KEY);
-                    String apnName = intent.getStringExtra(Phone.DATA_APN_KEY);
-                    if (DBG) Log.d(TAG, "Received " + intent.getAction() + " broadcast" +
-                            reason == null ? "" : "(" + reason + ")");
-                    setDetailedState(DetailedState.FAILED, reason, apnName);
+                if (!TextUtils.equals(apnType, mApnType)) {
+                    return;
                 }
-                TelephonyManager tm = TelephonyManager.getDefault();
-                setRoamingStatus(tm.isNetworkRoaming());
-                setSubtype(tm.getNetworkType(), tm.getNetworkTypeName());
+                Phone.DataState state = Enum.valueOf(Phone.DataState.class,
+                        intent.getStringExtra(Phone.STATE_KEY));
+                String reason = intent.getStringExtra(Phone.STATE_CHANGE_REASON_KEY);
+                String apnName = intent.getStringExtra(Phone.DATA_APN_KEY);
+
+                boolean unavailable = intent.getBooleanExtra(Phone.NETWORK_UNAVAILABLE_KEY,
+                        false);
+                mNetworkInfo.setIsAvailable(!unavailable);
+
+                if (DBG) Log.d(TAG, mApnType + " Received state= " + state + ", old= " +
+                        mMobileDataState + ", reason= " +
+                        (reason == null ? "(unspecified)" : reason));
+
+                if (mMobileDataState != state) {
+                    mMobileDataState = state;
+                    switch (state) {
+                        case DISCONNECTED:
+                            if(isTeardownRequested()) {
+                                setTeardownRequested(false);
+                            }
+
+                            setDetailedState(DetailedState.DISCONNECTED, reason, apnName);
+                            boolean doReset = true;
+                            if (mIsDefaultOrHipri == true) {
+                                // both default and hipri must go down before we reset
+                                int typeToCheck = (Phone.APN_TYPE_DEFAULT.equals(mApnType) ?
+                                    ConnectivityManager.TYPE_MOBILE_HIPRI :
+                                    ConnectivityManager.TYPE_MOBILE);
+                                if (mConnectivityManager == null) {
+                                    IBinder b = ServiceManager.getService(
+                                            mContext.CONNECTIVITY_SERVICE);
+                                    mConnectivityManager = IConnectivityManager.Stub.asInterface(b);
+                                }
+                                try {
+                                    if (mConnectivityManager != null) {
+                                        NetworkInfo info = mConnectivityManager.getNetworkInfo(
+                                                typeToCheck);
+                                        if (info.isConnected() == true) {
+                                            doReset = false;
+                                        }
+                                    }
+                                } catch (RemoteException e) {
+                                    // just go ahead with the reset
+                                    Log.e(TAG, "Exception trying to contact ConnService: "
+                                            + e);
+                                }
+                            }
+                            if (doReset && mNetworkProperties != null) {
+                                String iface = mNetworkProperties.getInterfaceName();
+                                if (iface != null) NetworkUtils.resetConnections(iface);
+                            }
+                            // TODO - check this
+                            // can't do this here - ConnectivityService needs it to clear stuff
+                            // it's ok though - just leave it to be refreshed next time
+                            // we connect.
+                            //if (DBG) Log.d(TAG, "clearing mInterfaceName for "+ mApnType +
+                            //        " as it DISCONNECTED");
+                            //mInterfaceName = null;
+                            //mDefaultGatewayAddr = 0;
+                            break;
+                        case CONNECTING:
+                            setDetailedState(DetailedState.CONNECTING, reason, apnName);
+                            break;
+                        case SUSPENDED:
+                            setDetailedState(DetailedState.SUSPENDED, reason, apnName);
+                            break;
+                        case CONNECTED:
+                            mNetworkProperties = intent.getParcelableExtra(
+                                    Phone.DATA_NETWORK_PROPERTIES_KEY);
+                            if (mNetworkProperties == null) {
+                                Log.d(TAG,
+                                        "CONNECTED event did not supply network properties.");
+                            }
+                            setDetailedState(DetailedState.CONNECTED, reason, apnName);
+                            break;
+                    }
+                }
+            } else if (intent.getAction().
+                    equals(TelephonyIntents.ACTION_DATA_CONNECTION_FAILED)) {
+                String apnType = intent.getStringExtra(Phone.DATA_APN_TYPE_KEY);
+                if (!TextUtils.equals(apnType, mApnType)) {
+                    return;
+                }
+                String reason = intent.getStringExtra(Phone.FAILURE_REASON_KEY);
+                String apnName = intent.getStringExtra(Phone.DATA_APN_KEY);
+                if (DBG) Log.d(TAG, mApnType + "Received " + intent.getAction() +
+                        " broadcast" + reason == null ? "" : "(" + reason + ")");
+                setDetailedState(DetailedState.FAILED, reason, apnName);
             }
+            TelephonyManager tm = TelephonyManager.getDefault();
+            setRoamingStatus(tm.isNetworkRoaming());
+            setSubtype(tm.getNetworkType(), tm.getNetworkTypeName());
         }
     }
 
@@ -320,70 +372,88 @@
     /**
      * Tear down mobile data connectivity, i.e., disable the ability to create
      * mobile data connections.
+     * TODO - make async and return nothing?
      */
-    @Override
     public boolean teardown() {
-        // since we won't get a notification currently (TODO - per APN notifications)
-        // we won't get a disconnect message until all APN's on the current connection's
-        // APN list are disabled.  That means privateRoutes for DNS and such will remain on -
-        // not a problem since that's all shared with whatever other APN is still on, but
-        // ugly.
         setTeardownRequested(true);
         return (setEnableApn(mApnType, false) != Phone.APN_REQUEST_FAILED);
     }
 
     /**
+     * Record the detailed state of a network, and if it is a
+     * change from the previous state, send a notification to
+     * any listeners.
+     * @param state the new @{code DetailedState}
+     */
+    private void setDetailedState(NetworkInfo.DetailedState state) {
+        setDetailedState(state, null, null);
+    }
+
+    /**
+     * Record the detailed state of a network, and if it is a
+     * change from the previous state, send a notification to
+     * any listeners.
+     * @param state the new @{code DetailedState}
+     * @param reason a {@code String} indicating a reason for the state change,
+     * if one was supplied. May be {@code null}.
+     * @param extraInfo optional {@code String} providing extra information about the state change
+     */
+    private void setDetailedState(NetworkInfo.DetailedState state, String reason, String extraInfo) {
+        if (DBG) Log.d(TAG, "setDetailed state, old ="
+                + mNetworkInfo.getDetailedState() + " and new state=" + state);
+        if (state != mNetworkInfo.getDetailedState()) {
+            boolean wasConnecting = (mNetworkInfo.getState() == NetworkInfo.State.CONNECTING);
+            String lastReason = mNetworkInfo.getReason();
+            /*
+             * If a reason was supplied when the CONNECTING state was entered, and no
+             * reason was supplied for entering the CONNECTED state, then retain the
+             * reason that was supplied when going to CONNECTING.
+             */
+            if (wasConnecting && state == NetworkInfo.DetailedState.CONNECTED && reason == null
+                    && lastReason != null)
+                reason = lastReason;
+            mNetworkInfo.setDetailedState(state, reason, extraInfo);
+            Message msg = mTarget.obtainMessage(EVENT_STATE_CHANGED, mNetworkInfo);
+            msg.sendToTarget();
+        }
+    }
+
+    private void setDetailedStateInternal(NetworkInfo.DetailedState state) {
+        mNetworkInfo.setDetailedState(state, null, null);
+    }
+
+    public void setTeardownRequested(boolean isRequested) {
+        mTeardownRequested = isRequested;
+    }
+
+    public boolean isTeardownRequested() {
+        return mTeardownRequested;
+    }
+
+    /**
      * Re-enable mobile data connectivity after a {@link #teardown()}.
+     * TODO - make async and always get a notification?
      */
     public boolean reconnect() {
+        boolean retValue = false; //connected or expect to be?
         setTeardownRequested(false);
         switch (setEnableApn(mApnType, true)) {
             case Phone.APN_ALREADY_ACTIVE:
-                // TODO - remove this when we get per-apn notifications
-                mEnabled = true;
                 // need to set self to CONNECTING so the below message is handled.
-                mMobileDataState = Phone.DataState.CONNECTING;
-                setDetailedState(DetailedState.CONNECTING, Phone.REASON_APN_CHANGED, null);
-                //send out a connected message
-                Intent intent = new Intent(TelephonyIntents.
-                        ACTION_ANY_DATA_CONNECTION_STATE_CHANGED);
-                intent.putExtra(Phone.STATE_KEY, Phone.DataState.CONNECTED.toString());
-                intent.putExtra(Phone.STATE_CHANGE_REASON_KEY, Phone.REASON_APN_CHANGED);
-                intent.putExtra(Phone.DATA_APN_TYPES_KEY, mApnTypeToWatchFor);
-                intent.putExtra(Phone.DATA_APN_KEY, mApnName);
-                intent.putExtra(Phone.DATA_IFACE_NAME_KEY, mInterfaceName);
-                intent.putExtra(Phone.NETWORK_UNAVAILABLE_KEY, false);
-                if (mStateReceiver != null) mStateReceiver.onReceive(mContext, intent);
+                retValue = true;
                 break;
             case Phone.APN_REQUEST_STARTED:
-                mEnabled = true;
                 // no need to do anything - we're already due some status update intents
+                retValue = true;
                 break;
             case Phone.APN_REQUEST_FAILED:
-                if (mPhoneService == null && mApnType == Phone.APN_TYPE_DEFAULT) {
-                    // on startup we may try to talk to the phone before it's ready
-                    // since the phone will come up enabled, go with that.
-                    // TODO - this also comes up on telephony crash: if we think mobile data is
-                    // off and the telephony stuff crashes and has to restart it will come up
-                    // enabled (making a data connection).  We will then be out of sync.
-                    // A possible solution is a broadcast when telephony restarts.
-                    mEnabled = true;
-                    return false;
-                }
-                // else fall through
             case Phone.APN_TYPE_NOT_AVAILABLE:
-                // Default is always available, but may be off due to
-                // AirplaneMode or E-Call or whatever..
-                if (mApnType != Phone.APN_TYPE_DEFAULT) {
-                    mEnabled = false;
-                }
                 break;
             default:
                 Log.e(TAG, "Error in reconnect - unexpected response.");
-                mEnabled = false;
                 break;
         }
-        return mEnabled;
+        return retValue;
     }
 
     /**
@@ -457,23 +527,9 @@
     }
 
     /**
-     * Ensure that a network route exists to deliver traffic to the specified
-     * host via the mobile data network.
-     * @param hostAddress the IP address of the host to which the route is desired,
-     * in network byte order.
-     * @return {@code true} on success, {@code false} on failure
+     * This is not supported.
      */
-    @Override
-    public boolean requestRouteToHost(int hostAddress) {
-        if (DBG) {
-            Log.d(TAG, "Requested host route to " + Integer.toHexString(hostAddress) +
-                    " for " + mApnType + "(" + mInterfaceName + ")");
-        }
-        if (mInterfaceName != null && hostAddress != -1) {
-            return NetworkUtils.addHostRoute(mInterfaceName, hostAddress) == 0;
-        } else {
-            return false;
-        }
+    public void interpretScanResultsAvailable() {
     }
 
     @Override
@@ -537,4 +593,8 @@
                 return null;
         }
     }
+
+    public NetworkProperties getNetworkProperties() {
+        return mNetworkProperties;
+    }
 }
diff --git a/core/java/android/net/NetworkInfo.java b/core/java/android/net/NetworkInfo.java
index 649cb8c..5f5e11c 100644
--- a/core/java/android/net/NetworkInfo.java
+++ b/core/java/android/net/NetworkInfo.java
@@ -97,7 +97,7 @@
         stateMap.put(DetailedState.DISCONNECTED, State.DISCONNECTED);
         stateMap.put(DetailedState.FAILED, State.DISCONNECTED);
     }
-    
+
     private int mNetworkType;
     private int mSubtype;
     private String mTypeName;
@@ -121,7 +121,10 @@
      */
     public NetworkInfo(int type) {}
 
-    NetworkInfo(int type, int subtype, String typeName, String subtypeName) {
+    /**
+     * @hide
+     */
+    public NetworkInfo(int type, int subtype, String typeName, String subtypeName) {
         if (!ConnectivityManager.isNetworkTypeValid(type)) {
             throw new IllegalArgumentException("Invalid network type: " + type);
         }
@@ -141,7 +144,9 @@
      * @return the network type
      */
     public int getType() {
-        return mNetworkType;
+        synchronized (this) {
+            return mNetworkType;
+        }
     }
 
     /**
@@ -150,12 +155,16 @@
      * @return the network subtype
      */
     public int getSubtype() {
-        return mSubtype;
+        synchronized (this) {
+            return mSubtype;
+        }
     }
 
     void setSubtype(int subtype, String subtypeName) {
-        mSubtype = subtype;
-        mSubtypeName = subtypeName;
+        synchronized (this) {
+            mSubtype = subtype;
+            mSubtypeName = subtypeName;
+        }
     }
 
     /**
@@ -164,7 +173,9 @@
      * @return the name of the network type
      */
     public String getTypeName() {
-        return mTypeName;
+        synchronized (this) {
+            return mTypeName;
+        }
     }
 
     /**
@@ -172,7 +183,9 @@
      * @return the name of the network subtype
      */
     public String getSubtypeName() {
-        return mSubtypeName;
+        synchronized (this) {
+            return mSubtypeName;
+        }
     }
 
     /**
@@ -185,7 +198,9 @@
      * of being established, {@code false} otherwise.
      */
     public boolean isConnectedOrConnecting() {
-        return mState == State.CONNECTED || mState == State.CONNECTING;
+        synchronized (this) {
+            return mState == State.CONNECTED || mState == State.CONNECTING;
+        }
     }
 
     /**
@@ -194,7 +209,9 @@
      * @return {@code true} if network connectivity exists, {@code false} otherwise.
      */
     public boolean isConnected() {
-        return mState == State.CONNECTED;
+        synchronized (this) {
+            return mState == State.CONNECTED;
+        }
     }
 
     /**
@@ -210,7 +227,9 @@
      * @return {@code true} if the network is available, {@code false} otherwise
      */
     public boolean isAvailable() {
-        return mIsAvailable;
+        synchronized (this) {
+            return mIsAvailable;
+        }
     }
 
     /**
@@ -220,7 +239,9 @@
      * @hide
      */
     public void setIsAvailable(boolean isAvailable) {
-        mIsAvailable = isAvailable;
+        synchronized (this) {
+            mIsAvailable = isAvailable;
+        }
     }
 
     /**
@@ -231,7 +252,9 @@
      * otherwise.
      */
     public boolean isFailover() {
-        return mIsFailover;
+        synchronized (this) {
+            return mIsFailover;
+        }
     }
 
     /**
@@ -241,7 +264,9 @@
      * @hide
      */
     public void setFailover(boolean isFailover) {
-        mIsFailover = isFailover;
+        synchronized (this) {
+            mIsFailover = isFailover;
+        }
     }
 
     /**
@@ -251,11 +276,15 @@
      * @return {@code true} if roaming is in effect, {@code false} otherwise.
      */
     public boolean isRoaming() {
-        return mIsRoaming;
+        synchronized (this) {
+            return mIsRoaming;
+        }
     }
 
     void setRoaming(boolean isRoaming) {
-        mIsRoaming = isRoaming;
+        synchronized (this) {
+            mIsRoaming = isRoaming;
+        }
     }
 
     /**
@@ -263,7 +292,9 @@
      * @return the coarse-grained state
      */
     public State getState() {
-        return mState;
+        synchronized (this) {
+            return mState;
+        }
     }
 
     /**
@@ -271,7 +302,9 @@
      * @return the fine-grained state
      */
     public DetailedState getDetailedState() {
-        return mDetailedState;
+        synchronized (this) {
+            return mDetailedState;
+        }
     }
 
     /**
@@ -281,12 +314,15 @@
      * if one was supplied. May be {@code null}.
      * @param extraInfo an optional {@code String} providing addditional network state
      * information passed up from the lower networking layers.
+     * @hide
      */
-    void setDetailedState(DetailedState detailedState, String reason, String extraInfo) {
-        this.mDetailedState = detailedState;
-        this.mState = stateMap.get(detailedState);
-        this.mReason = reason;
-        this.mExtraInfo = extraInfo;
+    public void setDetailedState(DetailedState detailedState, String reason, String extraInfo) {
+        synchronized (this) {
+            this.mDetailedState = detailedState;
+            this.mState = stateMap.get(detailedState);
+            this.mReason = reason;
+            this.mExtraInfo = extraInfo;
+        }
     }
 
     /**
@@ -295,7 +331,9 @@
      * @return the reason for failure, or null if not available
      */
     public String getReason() {
-        return mReason;
+        synchronized (this) {
+            return mReason;
+        }
     }
 
     /**
@@ -305,20 +343,24 @@
      * @return the extra information, or null if not available
      */
     public String getExtraInfo() {
-        return mExtraInfo;
+        synchronized (this) {
+            return mExtraInfo;
+        }
     }
 
     @Override
     public String toString() {
-        StringBuilder builder = new StringBuilder("NetworkInfo: ");
-        builder.append("type: ").append(getTypeName()).append("[").append(getSubtypeName()).
-                append("], state: ").append(mState).append("/").append(mDetailedState).
-                append(", reason: ").append(mReason == null ? "(unspecified)" : mReason).
-                append(", extra: ").append(mExtraInfo == null ? "(none)" : mExtraInfo).
-                append(", roaming: ").append(mIsRoaming).
-                append(", failover: ").append(mIsFailover).
-                append(", isAvailable: ").append(mIsAvailable);
-        return builder.toString();
+        synchronized (this) {
+            StringBuilder builder = new StringBuilder("NetworkInfo: ");
+            builder.append("type: ").append(getTypeName()).append("[").append(getSubtypeName()).
+            append("], state: ").append(mState).append("/").append(mDetailedState).
+            append(", reason: ").append(mReason == null ? "(unspecified)" : mReason).
+            append(", extra: ").append(mExtraInfo == null ? "(none)" : mExtraInfo).
+            append(", roaming: ").append(mIsRoaming).
+            append(", failover: ").append(mIsFailover).
+            append(", isAvailable: ").append(mIsAvailable);
+            return builder.toString();
+        }
     }
 
     /**
@@ -334,17 +376,19 @@
      * @hide
      */
     public void writeToParcel(Parcel dest, int flags) {
-        dest.writeInt(mNetworkType);
-        dest.writeInt(mSubtype);
-        dest.writeString(mTypeName);
-        dest.writeString(mSubtypeName);
-        dest.writeString(mState.name());
-        dest.writeString(mDetailedState.name());
-        dest.writeInt(mIsFailover ? 1 : 0);
-        dest.writeInt(mIsAvailable ? 1 : 0);
-        dest.writeInt(mIsRoaming ? 1 : 0);
-        dest.writeString(mReason);
-        dest.writeString(mExtraInfo);
+        synchronized (this) {
+            dest.writeInt(mNetworkType);
+            dest.writeInt(mSubtype);
+            dest.writeString(mTypeName);
+            dest.writeString(mSubtypeName);
+            dest.writeString(mState.name());
+            dest.writeString(mDetailedState.name());
+            dest.writeInt(mIsFailover ? 1 : 0);
+            dest.writeInt(mIsAvailable ? 1 : 0);
+            dest.writeInt(mIsRoaming ? 1 : 0);
+            dest.writeString(mReason);
+            dest.writeString(mExtraInfo);
+        }
     }
 
     /**
diff --git a/core/java/android/net/NetworkProperties.aidl b/core/java/android/net/NetworkProperties.aidl
new file mode 100644
index 0000000..07aac6e
--- /dev/null
+++ b/core/java/android/net/NetworkProperties.aidl
@@ -0,0 +1,22 @@
+/*
+**
+** Copyright (C) 2009 Qualcomm Innovation Center, Inc.  All Rights Reserved.
+** 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.net;
+
+parcelable NetworkProperties;
+
diff --git a/core/java/android/net/NetworkProperties.java b/core/java/android/net/NetworkProperties.java
new file mode 100644
index 0000000..da2a2d4
--- /dev/null
+++ b/core/java/android/net/NetworkProperties.java
@@ -0,0 +1,192 @@
+/*
+ * 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.
+ */
+
+package android.net;
+
+import android.os.Parcelable;
+import android.os.Parcel;
+import android.util.Log;
+
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.Collection;
+
+/**
+ * Describes the properties of a network interface or single address
+ * of an interface.
+ * TODO - consider adding optional fields like Apn and ApnType
+ * @hide
+ */
+public class NetworkProperties implements Parcelable {
+
+    private NetworkInterface mIface;
+    private Collection<InetAddress> mAddresses;
+    private Collection<InetAddress> mDnses;
+    private InetAddress mGateway;
+    private ProxyProperties mHttpProxy;
+
+    public NetworkProperties() {
+        clear();
+    }
+
+    public synchronized void setInterface(NetworkInterface iface) {
+        mIface = iface;
+    }
+    public synchronized NetworkInterface getInterface() {
+        return mIface;
+    }
+    public synchronized String getInterfaceName() {
+        return (mIface == null ? null : mIface.getName());
+    }
+
+    public synchronized void addAddress(InetAddress address) {
+        mAddresses.add(address);
+    }
+    public synchronized Collection<InetAddress> getAddresses() {
+        return mAddresses;
+    }
+
+    public synchronized void addDns(InetAddress dns) {
+        mDnses.add(dns);
+    }
+    public synchronized Collection<InetAddress> getDnses() {
+        return mDnses;
+    }
+
+    public synchronized void setGateway(InetAddress gateway) {
+        mGateway = gateway;
+    }
+    public synchronized InetAddress getGateway() {
+        return mGateway;
+    }
+
+    public synchronized void setHttpProxy(ProxyProperties proxy) {
+        mHttpProxy = proxy;
+    }
+    public synchronized ProxyProperties getHttpProxy() {
+        return mHttpProxy;
+    }
+
+    public synchronized void clear() {
+        mIface = null;
+        mAddresses = new ArrayList<InetAddress>();
+        mDnses = new ArrayList<InetAddress>();
+        mGateway = null;
+        mHttpProxy = null;
+    }
+
+    /**
+     * Implement the Parcelable interface
+     * @hide
+     */
+    public int describeContents() {
+        return 0;
+    }
+
+    public synchronized String toString() {
+        String ifaceName = (mIface == null ? "" : "InterfaceName: " + mIface.getName() + " ");
+
+        String ip = "IpAddresses: [";
+        for (InetAddress addr : mAddresses) ip +=  addr.toString() + ",";
+        ip += "] ";
+
+        String dns = "DnsAddresses: [";
+        for (InetAddress addr : mDnses) dns += addr.toString() + ",";
+        dns += "] ";
+
+        String proxy = (mHttpProxy == null ? "" : "HttpProxy: " + mHttpProxy.toString() + " ");
+        String gateway = (mGateway == null ? "" : "Gateway: " + mGateway.toString() + " ");
+
+        return ifaceName + ip + gateway + dns + proxy;
+    }
+
+    /**
+     * Implement the Parcelable interface.
+     * @hide
+     */
+    public synchronized void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(getInterfaceName());
+        dest.writeInt(mAddresses.size());
+        //TODO: explore an easy alternative to preserve hostname
+        // without doing a lookup
+        for(InetAddress a : mAddresses) {
+            dest.writeByteArray(a.getAddress());
+        }
+        dest.writeInt(mDnses.size());
+        for(InetAddress d : mDnses) {
+            dest.writeByteArray(d.getAddress());
+        }
+        if (mGateway != null) {
+            dest.writeByte((byte)1);
+            dest.writeByteArray(mGateway.getAddress());
+        } else {
+            dest.writeByte((byte)0);
+        }
+        if (mHttpProxy != null) {
+            dest.writeByte((byte)1);
+            dest.writeParcelable(mHttpProxy, flags);
+        } else {
+            dest.writeByte((byte)0);
+        }
+    }
+
+    /**
+     * Implement the Parcelable interface.
+     * @hide
+     */
+    public static final Creator<NetworkProperties> CREATOR =
+        new Creator<NetworkProperties>() {
+            public NetworkProperties createFromParcel(Parcel in) {
+                NetworkProperties netProp = new NetworkProperties();
+                String iface = in.readString();
+                if (iface != null) {
+                    try {
+                        netProp.setInterface(NetworkInterface.getByName(iface));
+                    } catch (Exception e) {
+                        return null;
+                    }
+                }
+                int addressCount = in.readInt();
+                for (int i=0; i<addressCount; i++) {
+                    try {
+                        netProp.addAddress(InetAddress.getByAddress(in.createByteArray()));
+                    } catch (UnknownHostException e) { }
+                }
+                addressCount = in.readInt();
+                for (int i=0; i<addressCount; i++) {
+                    try {
+                        netProp.addDns(InetAddress.getByAddress(in.createByteArray()));
+                    } catch (UnknownHostException e) { }
+                }
+                if (in.readByte() == 1) {
+                    try {
+                        netProp.setGateway(InetAddress.getByAddress(in.createByteArray()));
+                    } catch (UnknownHostException e) {}
+                }
+                if (in.readByte() == 1) {
+                    netProp.setHttpProxy((ProxyProperties)in.readParcelable(null));
+                }
+                return netProp;
+            }
+
+            public NetworkProperties[] newArray(int size) {
+                return new NetworkProperties[size];
+            }
+        };
+}
diff --git a/core/java/android/net/NetworkStateTracker.java b/core/java/android/net/NetworkStateTracker.java
index 1fb0144..44215e7 100644
--- a/core/java/android/net/NetworkStateTracker.java
+++ b/core/java/android/net/NetworkStateTracker.java
@@ -16,40 +16,15 @@
 
 package android.net;
 
-import java.io.FileWriter;
-import java.io.IOException;
-
-import android.os.Handler;
-import android.os.Message;
-import android.os.SystemProperties;
-import android.content.Context;
-import android.text.TextUtils;
-import android.util.Config;
-import android.util.Log;
-
-
 /**
- * Each subclass of this class keeps track of the state of connectivity
- * of a network interface. All state information for a network should
- * be kept in a Tracker class. This superclass manages the
- * network-type-independent aspects of network state.
+ * Interface for connectivity service to act on a network interface.
+ * All state information for a network should be kept in a Tracker class.
+ * This interface defines network-type-independent functions that should
+ * be implemented by the Tracker class.
  *
  * {@hide}
  */
-public abstract class NetworkStateTracker extends Handler {
-
-    protected NetworkInfo mNetworkInfo;
-    protected Context mContext;
-    protected Handler mTarget;
-    protected String mInterfaceName;
-    protected String[] mDnsPropNames;
-    private boolean mPrivateDnsRouteSet;
-    protected int mDefaultGatewayAddr;
-    private boolean mDefaultRouteSet;
-    private boolean mTeardownRequested;
-
-    private static boolean DBG = true;
-    private static final String TAG = "NetworkStateTracker";
+public interface NetworkStateTracker {
 
     public static final int EVENT_STATE_CHANGED = 1;
     public static final int EVENT_SCAN_RESULTS_AVAILABLE = 2;
@@ -63,306 +38,86 @@
     public static final int EVENT_ROAMING_CHANGED = 5;
     public static final int EVENT_NETWORK_SUBTYPE_CHANGED = 6;
     public static final int EVENT_RESTORE_DEFAULT_NETWORK = 7;
+    public static final int EVENT_CLEAR_NET_TRANSITION_WAKELOCK = 8;
 
-    public NetworkStateTracker(Context context,
-            Handler target,
-            int networkType,
-            int subType,
-            String typeName,
-            String subtypeName) {
-        super();
-        mContext = context;
-        mTarget = target;
-        mTeardownRequested = false;
+    /**
+     * Fetch NetworkInfo for the network
+     */
+    public NetworkInfo getNetworkInfo();
 
-        this.mNetworkInfo = new NetworkInfo(networkType, subType, typeName, subtypeName);
-    }
-
-    public NetworkInfo getNetworkInfo() {
-        return mNetworkInfo;
-    }
+    /**
+     * Fetch NetworkProperties for the network
+     */
+    public NetworkProperties getNetworkProperties();
 
     /**
      * Return the system properties name associated with the tcp buffer sizes
      * for this network.
      */
-    public abstract String getTcpBufferSizesPropName();
+    public String getTcpBufferSizesPropName();
 
     /**
-     * Return the IP addresses of the DNS servers available for the mobile data
-     * network interface.
-     * @return a list of DNS addresses, with no holes.
+     * Check if private DNS route is set for the network
      */
-    public String[] getNameServers() {
-        return getNameServerList(mDnsPropNames);
-    }
+    public boolean isPrivateDnsRouteSet();
 
     /**
-     * Return the IP addresses of the DNS servers available for this
-     * network interface.
-     * @param propertyNames the names of the system properties whose values
-     * give the IP addresses. Properties with no values are skipped.
-     * @return an array of {@code String}s containing the IP addresses
-     * of the DNS servers, in dot-notation. This may have fewer
-     * non-null entries than the list of names passed in, since
-     * some of the passed-in names may have empty values.
+     * Set a flag indicating private DNS route is set
      */
-    static protected String[] getNameServerList(String[] propertyNames) {
-        String[] dnsAddresses = new String[propertyNames.length];
-        int i, j;
-
-        for (i = 0, j = 0; i < propertyNames.length; i++) {
-            String value = SystemProperties.get(propertyNames[i]);
-            // The GSM layer sometimes sets a bogus DNS server address of
-            // 0.0.0.0
-            if (!TextUtils.isEmpty(value) && !TextUtils.equals(value, "0.0.0.0")) {
-                dnsAddresses[j++] = value;
-            }
-        }
-        return dnsAddresses;
-    }
-
-    public void addPrivateDnsRoutes() {
-        if (DBG) {
-            Log.d(TAG, "addPrivateDnsRoutes for " + this +
-                    "(" + mInterfaceName + ") - mPrivateDnsRouteSet = "+mPrivateDnsRouteSet);
-        }
-        if (mInterfaceName != null && !mPrivateDnsRouteSet) {
-            for (String addrString : getNameServers()) {
-                int addr = NetworkUtils.lookupHost(addrString);
-                if (addr != -1 && addr != 0) {
-                    if (DBG) Log.d(TAG, "  adding "+addrString+" ("+addr+")");
-                    NetworkUtils.addHostRoute(mInterfaceName, addr);
-                }
-            }
-            mPrivateDnsRouteSet = true;
-        }
-    }
-
-    public void removePrivateDnsRoutes() {
-        // TODO - we should do this explicitly but the NetUtils api doesnt
-        // support this yet - must remove all.  No worse than before
-        if (mInterfaceName != null && mPrivateDnsRouteSet) {
-            if (DBG) {
-                Log.d(TAG, "removePrivateDnsRoutes for " + mNetworkInfo.getTypeName() +
-                        " (" + mInterfaceName + ")");
-            }
-            NetworkUtils.removeHostRoutes(mInterfaceName);
-            mPrivateDnsRouteSet = false;
-        }
-    }
-
-    public void addDefaultRoute() {
-        if ((mInterfaceName != null) && (mDefaultGatewayAddr != 0) &&
-                mDefaultRouteSet == false) {
-            if (DBG) {
-                Log.d(TAG, "addDefaultRoute for " + mNetworkInfo.getTypeName() +
-                        " (" + mInterfaceName + "), GatewayAddr=" + mDefaultGatewayAddr);
-            }
-            NetworkUtils.setDefaultRoute(mInterfaceName, mDefaultGatewayAddr);
-            mDefaultRouteSet = true;
-        }
-    }
-
-    public void removeDefaultRoute() {
-        if (mInterfaceName != null && mDefaultRouteSet == true) {
-            if (DBG) {
-                Log.d(TAG, "removeDefaultRoute for " + mNetworkInfo.getTypeName() + " (" +
-                        mInterfaceName + ")");
-            }
-            NetworkUtils.removeDefaultRoute(mInterfaceName);
-            mDefaultRouteSet = false;
-        }
-    }
+    public void privateDnsRouteSet(boolean enabled);
 
     /**
-     * Reads the network specific TCP buffer sizes from SystemProperties
-     * net.tcp.buffersize.[default|wifi|umts|edge|gprs] and set them for system
-     * wide use
+     * Fetch default gateway address for the network
      */
-   public void updateNetworkSettings() {
-        String key = getTcpBufferSizesPropName();
-        String bufferSizes = SystemProperties.get(key);
-
-        if (bufferSizes.length() == 0) {
-            Log.e(TAG, key + " not found in system properties. Using defaults");
-
-            // Setting to default values so we won't be stuck to previous values
-            key = "net.tcp.buffersize.default";
-            bufferSizes = SystemProperties.get(key);
-        }
-
-        // Set values in kernel
-        if (bufferSizes.length() != 0) {
-            if (DBG) {
-                Log.v(TAG, "Setting TCP values: [" + bufferSizes
-                        + "] which comes from [" + key + "]");
-            }
-            setBufferSize(bufferSizes);
-        }
-    }
+    public int getDefaultGatewayAddr();
 
     /**
-     * Release the wakelock, if any, that may be held while handling a
-     * disconnect operation.
+     * Check if default route is set
      */
-    public void releaseWakeLock() {
-    }
+    public boolean isDefaultRouteSet();
 
     /**
-     * Writes TCP buffer sizes to /sys/kernel/ipv4/tcp_[r/w]mem_[min/def/max]
-     * which maps to /proc/sys/net/ipv4/tcp_rmem and tcpwmem
-     * 
-     * @param bufferSizes in the format of "readMin, readInitial, readMax,
-     *        writeMin, writeInitial, writeMax"
+     * Set a flag indicating default route is set for the network
      */
-    private void setBufferSize(String bufferSizes) {
-        try {
-            String[] values = bufferSizes.split(",");
-
-            if (values.length == 6) {
-              final String prefix = "/sys/kernel/ipv4/tcp_";
-                stringToFile(prefix + "rmem_min", values[0]);
-                stringToFile(prefix + "rmem_def", values[1]);
-                stringToFile(prefix + "rmem_max", values[2]);
-                stringToFile(prefix + "wmem_min", values[3]);
-                stringToFile(prefix + "wmem_def", values[4]);
-                stringToFile(prefix + "wmem_max", values[5]);
-            } else {
-                Log.e(TAG, "Invalid buffersize string: " + bufferSizes);
-            }
-        } catch (IOException e) {
-            Log.e(TAG, "Can't set tcp buffer sizes:" + e);
-        }
-    }
+    public void defaultRouteSet(boolean enabled);
 
     /**
-     * Writes string to file. Basically same as "echo -n $string > $filename"
-     * 
-     * @param filename
-     * @param string
-     * @throws IOException
+     * Indicate tear down requested from connectivity
      */
-    private void stringToFile(String filename, String string) throws IOException {
-        FileWriter out = new FileWriter(filename);
-        try {
-            out.write(string);
-        } finally {
-            out.close();
-        }
-    }
+    public void setTeardownRequested(boolean isRequested);
 
     /**
-     * Record the detailed state of a network, and if it is a
-     * change from the previous state, send a notification to
-     * any listeners.
-     * @param state the new @{code DetailedState}
+     * Check if tear down was requested
      */
-    public void setDetailedState(NetworkInfo.DetailedState state) {
-        setDetailedState(state, null, null);
-    }
+    public boolean isTeardownRequested();
 
-    /**
-     * Record the detailed state of a network, and if it is a
-     * change from the previous state, send a notification to
-     * any listeners.
-     * @param state the new @{code DetailedState}
-     * @param reason a {@code String} indicating a reason for the state change,
-     * if one was supplied. May be {@code null}.
-     * @param extraInfo optional {@code String} providing extra information about the state change
-     */
-    public void setDetailedState(NetworkInfo.DetailedState state, String reason, String extraInfo) {
-        if (DBG) Log.d(TAG, "setDetailed state, old ="+mNetworkInfo.getDetailedState()+" and new state="+state);
-        if (state != mNetworkInfo.getDetailedState()) {
-            boolean wasConnecting = (mNetworkInfo.getState() == NetworkInfo.State.CONNECTING);
-            String lastReason = mNetworkInfo.getReason();
-            /*
-             * If a reason was supplied when the CONNECTING state was entered, and no
-             * reason was supplied for entering the CONNECTED state, then retain the
-             * reason that was supplied when going to CONNECTING.
-             */
-            if (wasConnecting && state == NetworkInfo.DetailedState.CONNECTED && reason == null
-                    && lastReason != null)
-                reason = lastReason;
-            mNetworkInfo.setDetailedState(state, reason, extraInfo);
-            Message msg = mTarget.obtainMessage(EVENT_STATE_CHANGED, mNetworkInfo);
-            msg.sendToTarget();
-        }
-    }
-
-    protected void setDetailedStateInternal(NetworkInfo.DetailedState state) {
-        mNetworkInfo.setDetailedState(state, null, null);
-    }
-
-    public void setTeardownRequested(boolean isRequested) {
-        mTeardownRequested = isRequested;
-    }
-
-    public boolean isTeardownRequested() {
-        return mTeardownRequested;
-    }
-    
-    /**
-     * Send a  notification that the results of a scan for network access
-     * points has completed, and results are available.
-     */
-    protected void sendScanResultsAvailable() {
-        Message msg = mTarget.obtainMessage(EVENT_SCAN_RESULTS_AVAILABLE, mNetworkInfo);
-        msg.sendToTarget();
-    }
-
-    /**
-     * Record the roaming status of the device, and if it is a change from the previous
-     * status, send a notification to any listeners.
-     * @param isRoaming {@code true} if the device is now roaming, {@code false}
-     * if it is no longer roaming.
-     */
-    protected void setRoamingStatus(boolean isRoaming) {
-        if (isRoaming != mNetworkInfo.isRoaming()) {
-            mNetworkInfo.setRoaming(isRoaming);
-            Message msg = mTarget.obtainMessage(EVENT_ROAMING_CHANGED, mNetworkInfo);
-            msg.sendToTarget();
-        }
-    }
-
-    protected void setSubtype(int subtype, String subtypeName) {
-        if (mNetworkInfo.isConnected()) {
-            int oldSubtype = mNetworkInfo.getSubtype();
-            if (subtype != oldSubtype) {
-                mNetworkInfo.setSubtype(subtype, subtypeName);
-                Message msg = mTarget.obtainMessage(
-                        EVENT_NETWORK_SUBTYPE_CHANGED, oldSubtype, 0, mNetworkInfo);
-                msg.sendToTarget();
-            }
-        }
-    }
-
-    public abstract void startMonitoring();
+    public void startMonitoring();
 
     /**
      * Disable connectivity to a network
      * @return {@code true} if a teardown occurred, {@code false} if the
      * teardown did not occur.
      */
-    public abstract boolean teardown();
+    public boolean teardown();
 
     /**
      * Reenable connectivity to a network after a {@link #teardown()}.
+     * @return {@code true} if we're connected or expect to be connected
      */
-    public abstract boolean reconnect();
+    public boolean reconnect();
 
     /**
      * Turn the wireless radio off for a network.
      * @param turnOn {@code true} to turn the radio on, {@code false}
      */
-    public abstract boolean setRadio(boolean turnOn);
+    public boolean setRadio(boolean turnOn);
 
     /**
      * Returns an indication of whether this network is available for
      * connections. A value of {@code false} means that some quasi-permanent
      * condition prevents connectivity to this network.
      */
-    public abstract boolean isAvailable();
+    public boolean isAvailable();
 
     /**
      * Tells the underlying networking system that the caller wants to
@@ -376,7 +131,7 @@
      * implementation+feature combination, except that the value {@code -1}
      * always indicates failure.
      */
-    public abstract int startUsingNetworkFeature(String feature, int callingPid, int callingUid);
+    public int startUsingNetworkFeature(String feature, int callingPid, int callingUid);
 
     /**
      * Tells the underlying networking system that the caller is finished
@@ -390,23 +145,12 @@
      * implementation+feature combination, except that the value {@code -1}
      * always indicates failure.
      */
-    public abstract int stopUsingNetworkFeature(String feature, int callingPid, int callingUid);
-
-    /**
-     * Ensure that a network route exists to deliver traffic to the specified
-     * host via this network interface.
-     * @param hostAddress the IP address of the host to which the route is desired
-     * @return {@code true} on success, {@code false} on failure
-     */
-    public boolean requestRouteToHost(int hostAddress) {
-        return false;
-    }
+    public int stopUsingNetworkFeature(String feature, int callingPid, int callingUid);
 
     /**
      * Interprets scan results. This will be called at a safe time for
      * processing, and from a safe thread.
      */
-    public void interpretScanResultsAvailable() {
-    }
+    public void interpretScanResultsAvailable();
 
 }
diff --git a/core/java/android/net/NetworkUtils.java b/core/java/android/net/NetworkUtils.java
index a3ae01b..564bc1f 100644
--- a/core/java/android/net/NetworkUtils.java
+++ b/core/java/android/net/NetworkUtils.java
@@ -32,13 +32,37 @@
     public native static int disableInterface(String interfaceName);
 
     /** Add a route to the specified host via the named interface. */
-    public native static int addHostRoute(String interfaceName, int hostaddr);
+    public static int addHostRoute(String interfaceName, InetAddress hostaddr) {
+        int v4Int = v4StringToInt(hostaddr.getHostAddress());
+        if (v4Int != 0) {
+            return addHostRouteNative(interfaceName, v4Int);
+        } else {
+            return -1;
+        }
+    }
+    private native static int addHostRouteNative(String interfaceName, int hostaddr);
 
     /** Add a default route for the named interface. */
-    public native static int setDefaultRoute(String interfaceName, int gwayAddr);
+    public static int setDefaultRoute(String interfaceName, InetAddress gwayAddr) {
+        int v4Int = v4StringToInt(gwayAddr.getHostAddress());
+        if (v4Int != 0) {
+            return setDefaultRouteNative(interfaceName, v4Int);
+        } else {
+            return -1;
+        }
+    }
+    private native static int setDefaultRouteNative(String interfaceName, int hostaddr);
 
     /** Return the gateway address for the default route for the named interface. */
-    public native static int getDefaultRoute(String interfaceName);
+    public static InetAddress getDefaultRoute(String interfaceName) {
+        int addr = getDefaultRouteNative(interfaceName);
+        try {
+            return InetAddress.getByAddress(v4IntToArray(addr));
+        } catch (UnknownHostException e) {
+            return null;
+        }
+    }
+    private native static int getDefaultRouteNative(String interfaceName);
 
     /** Remove host routes that uses the named interface. */
     public native static int removeHostRoutes(String interfaceName);
@@ -105,27 +129,30 @@
     private native static boolean configureNative(
         String interfaceName, int ipAddress, int netmask, int gateway, int dns1, int dns2);
 
-    /**
-     * Look up a host name and return the result as an int. Works if the argument
-     * is an IP address in dot notation. Obviously, this can only be used for IPv4
-     * addresses.
-     * @param hostname the name of the host (or the IP address)
-     * @return the IP address as an {@code int} in network byte order
-     */
-    public static int lookupHost(String hostname) {
-        InetAddress inetAddress;
+    // The following two functions are glue to tie the old int-based address scheme
+    // to the new InetAddress scheme.  They should go away when we go fully to InetAddress
+    // TODO - remove when we switch fully to InetAddress
+    public static byte[] v4IntToArray(int addr) {
+        byte[] addrBytes = new byte[4];
+        addrBytes[0] = (byte)(addr & 0xff);
+        addrBytes[1] = (byte)((addr >> 8) & 0xff);
+        addrBytes[2] = (byte)((addr >> 16) & 0xff);
+        addrBytes[3] = (byte)((addr >> 24) & 0xff);
+        return addrBytes;
+    }
+
+    public static int v4StringToInt(String str) {
+        int result = 0;
+        String[] array = str.split("\\.");
+        if (array.length != 4) return 0;
         try {
-            inetAddress = InetAddress.getByName(hostname);
-        } catch (UnknownHostException e) {
-            return -1;
+            result = Integer.parseInt(array[3]);
+            result = (result << 8) + Integer.parseInt(array[2]);
+            result = (result << 8) + Integer.parseInt(array[1]);
+            result = (result << 8) + Integer.parseInt(array[0]);
+        } catch (NumberFormatException e) {
+            return 0;
         }
-        byte[] addrBytes;
-        int addr;
-        addrBytes = inetAddress.getAddress();
-        addr = ((addrBytes[3] & 0xff) << 24)
-                | ((addrBytes[2] & 0xff) << 16)
-                | ((addrBytes[1] & 0xff) << 8)
-                |  (addrBytes[0] & 0xff);
-        return addr;
+        return result;
     }
 }
diff --git a/core/java/android/net/Proxy.java b/core/java/android/net/Proxy.java
index 22c30a5..02ec137 100644
--- a/core/java/android/net/Proxy.java
+++ b/core/java/android/net/Proxy.java
@@ -16,32 +16,175 @@
 
 package android.net;
 
-import org.apache.http.HttpHost;
-
 import android.content.ContentResolver;
 import android.content.Context;
+import android.database.ContentObserver;
+import android.os.Handler;
 import android.os.SystemProperties;
 import android.provider.Settings;
-import android.util.Log;
 
 import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
 import java.net.URI;
 import java.net.UnknownHostException;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 import junit.framework.Assert;
 
+import org.apache.http.HttpHost;
+
 /**
  * A convenience class for accessing the user and default proxy
  * settings.
  */
-final public class Proxy {
+public final class Proxy {
 
     // Set to true to enable extra debugging.
-    static final private boolean DEBUG = false;
+    private static final boolean DEBUG = false;
 
-    static final public String PROXY_CHANGE_ACTION =
+    public static final String PROXY_CHANGE_ACTION =
         "android.intent.action.PROXY_CHANGE";
 
+    private static ReadWriteLock sProxyInfoLock = new ReentrantReadWriteLock();
+
+    private static SettingsObserver sGlobalProxyChangedObserver = null;
+
+    private static ProxySpec sGlobalProxySpec = null;
+
+    // Hostname / IP REGEX validation
+    // Matches blank input, ips, and domain names
+    private static final String NAME_IP_REGEX =
+        "[a-zA-Z0-9]+(\\-[a-zA-Z0-9]+)*(\\.[a-zA-Z0-9]+(\\-[a-zA-Z0-9]+)*)*";
+
+    private static final String HOSTNAME_REGEXP = "^$|^" + NAME_IP_REGEX + "$";
+
+    private static final Pattern HOSTNAME_PATTERN;
+
+    private static final String EXCLLIST_REGEXP = "$|^(.?" + NAME_IP_REGEX
+        + ")+(,(.?" + NAME_IP_REGEX + "))*$";
+
+    private static final Pattern EXCLLIST_PATTERN;
+
+    static {
+        HOSTNAME_PATTERN = Pattern.compile(HOSTNAME_REGEXP);
+        EXCLLIST_PATTERN = Pattern.compile(EXCLLIST_REGEXP);
+    }
+
+    private static class ProxySpec {
+        String[] exclusionList = null;
+        InetSocketAddress proxyAddress = null;
+        public ProxySpec() { };
+    }
+
+    private static boolean isURLInExclusionListReadLocked(String url, String[] exclusionList) {
+        if (exclusionList == null) {
+            return false;
+        }
+        Uri u = Uri.parse(url);
+        String urlDomain = u.getHost();
+        // If the domain is defined as ".android.com" or "android.com", we wish to match
+        // http://android.com as well as http://xxx.android.com , but not
+        // http://myandroid.com . This code works out the logic.
+        for (String excludedDomain : exclusionList) {
+            String dotDomain = "." + excludedDomain;
+            if (urlDomain.equals(excludedDomain)) {
+                return true;
+            }
+            if (urlDomain.endsWith(dotDomain)) {
+                return true;
+            }
+        }
+        // No match
+        return false;
+    }
+
+    private static String parseHost(String proxySpec) {
+        int i = proxySpec.indexOf(':');
+        if (i == -1) {
+            if (DEBUG) {
+                Assert.assertTrue(proxySpec.length() == 0);
+            }
+            return null;
+        }
+        return proxySpec.substring(0, i);
+    }
+
+    private static int parsePort(String proxySpec) {
+        int i = proxySpec.indexOf(':');
+        if (i == -1) {
+            if (DEBUG) {
+                Assert.assertTrue(proxySpec.length() == 0);
+            }
+            return -1;
+        }
+        if (DEBUG) {
+            Assert.assertTrue(i < proxySpec.length());
+        }
+        return Integer.parseInt(proxySpec.substring(i+1));
+    }
+
+    /**
+     * Return the proxy object to be used for the URL given as parameter.
+     * @param ctx A Context used to get the settings for the proxy host.
+     * @param url A URL to be accessed. Used to evaluate exclusion list.
+     * @return Proxy (java.net) object containing the host name. If the
+     *         user did not set a hostname it returns the default host.
+     *         A null value means that no host is to be used.
+     * {@hide}
+     */
+    public static final java.net.Proxy getProxy(Context ctx, String url) {
+        sProxyInfoLock.readLock().lock();
+        java.net.Proxy retval;
+        try {
+            if (sGlobalProxyChangedObserver == null) {
+                registerContentObserversReadLocked(ctx);
+                parseGlobalProxyInfoReadLocked(ctx);
+            }
+            if (sGlobalProxySpec != null) {
+                // Proxy defined - Apply exclusion rules
+                if (isURLInExclusionListReadLocked(url, sGlobalProxySpec.exclusionList)) {
+                    // Return no proxy
+                    retval = java.net.Proxy.NO_PROXY;
+                } else {
+                    java.net.Proxy retProxy =
+                        new java.net.Proxy(java.net.Proxy.Type.HTTP, sGlobalProxySpec.proxyAddress);
+                    sProxyInfoLock.readLock().unlock();
+                    if (isLocalHost(url)) {
+                        sProxyInfoLock.readLock().lock();
+                        retval = java.net.Proxy.NO_PROXY;
+                    } else {
+                        sProxyInfoLock.readLock().lock();
+                        retval = retProxy;
+                    }
+                }
+            } else {
+                // If network is WiFi, return no proxy.
+                // Otherwise, return the Mobile Operator proxy.
+                if (!isNetworkWifi(ctx)) {
+                    java.net.Proxy retProxy = getDefaultProxy(url);
+                    sProxyInfoLock.readLock().unlock();
+                    if (isLocalHost(url)) {
+                        sProxyInfoLock.readLock().lock();
+                        retval = java.net.Proxy.NO_PROXY;
+                    } else {
+                        sProxyInfoLock.readLock().lock();
+                        retval = retProxy;
+                    }
+                } else {
+                    retval = java.net.Proxy.NO_PROXY;
+                }
+            }
+        } finally {
+            sProxyInfoLock.readLock().unlock();
+        }
+        return retval;
+    }
+
+    // TODO: deprecate this function
     /**
      * Return the proxy host set by the user.
      * @param ctx A Context used to get the settings for the proxy host.
@@ -49,60 +192,55 @@
      *         name it returns the default host. A null value means that no
      *         host is to be used.
      */
-    static final public String getHost(Context ctx) {
-        ContentResolver contentResolver = ctx.getContentResolver();
-        Assert.assertNotNull(contentResolver);
-        String host = Settings.Secure.getString(
-                contentResolver,
-                Settings.Secure.HTTP_PROXY);
-        if (host != null) {
-            int i = host.indexOf(':');
-            if (i == -1) {
-                if (DEBUG) {
-                    Assert.assertTrue(host.length() == 0);
-                }
-                return null;
+    public static final String getHost(Context ctx) {
+        sProxyInfoLock.readLock().lock();
+        try {
+            if (sGlobalProxyChangedObserver == null) {
+                registerContentObserversReadLocked(ctx);
+                parseGlobalProxyInfoReadLocked(ctx);
             }
-            return host.substring(0, i);
+            if (sGlobalProxySpec != null) {
+                InetSocketAddress sa = sGlobalProxySpec.proxyAddress;
+                return sa.getHostName();
+            }
+            return getDefaultHost();
+        } finally {
+            sProxyInfoLock.readLock().unlock();
         }
-        return getDefaultHost();
     }
 
+    // TODO: deprecate this function
     /**
      * Return the proxy port set by the user.
      * @param ctx A Context used to get the settings for the proxy port.
      * @return The port number to use or -1 if no proxy is to be used.
      */
-    static final public int getPort(Context ctx) {
-        ContentResolver contentResolver = ctx.getContentResolver();
-        Assert.assertNotNull(contentResolver);
-        String host = Settings.Secure.getString(
-                contentResolver,
-                Settings.Secure.HTTP_PROXY);
-        if (host != null) {
-            int i = host.indexOf(':');
-            if (i == -1) {
-                if (DEBUG) {
-                    Assert.assertTrue(host.length() == 0);
-                }
-                return -1;
+    public static final int getPort(Context ctx) {
+        sProxyInfoLock.readLock().lock();
+        try {
+            if (sGlobalProxyChangedObserver == null) {
+                registerContentObserversReadLocked(ctx);
+                parseGlobalProxyInfoReadLocked(ctx);
             }
-            if (DEBUG) {
-                Assert.assertTrue(i < host.length());
+            if (sGlobalProxySpec != null) {
+                InetSocketAddress sa = sGlobalProxySpec.proxyAddress;
+                return sa.getPort();
             }
-            return Integer.parseInt(host.substring(i+1));
+            return getDefaultPort();
+        } finally {
+            sProxyInfoLock.readLock().unlock();
         }
-        return getDefaultPort();
     }
 
+    // TODO: deprecate this function
     /**
      * Return the default proxy host specified by the carrier.
      * @return String containing the host name or null if there is no proxy for
      * this carrier.
      */
-    static final public String getDefaultHost() {
+    public static final String getDefaultHost() {
         String host = SystemProperties.get("net.gprs.http-proxy");
-        if (host != null) {
+        if ((host != null) && (host.length() != 0)) {
             Uri u = Uri.parse(host);
             host = u.getHost();
             return host;
@@ -111,14 +249,15 @@
         }
     }
 
+    // TODO: deprecate this function
     /**
      * Return the default proxy port specified by the carrier.
      * @return The port number to be used with the proxy host or -1 if there is
      * no proxy for this carrier.
      */
-    static final public int getDefaultPort() {
+    public static final int getDefaultPort() {
         String host = SystemProperties.get("net.gprs.http-proxy");
-        if (host != null) {
+        if ((host != null) && (host.length() != 0)) {
             Uri u = Uri.parse(host);
             return u.getPort();
         } else {
@@ -126,6 +265,20 @@
         }
     }
 
+    private static final java.net.Proxy getDefaultProxy(String url) {
+        // TODO: This will go away when information is collected from ConnectivityManager...
+        // There are broadcast of network proxies, so they are parse manually.
+        String host = SystemProperties.get("net.gprs.http-proxy");
+        if ((host != null) && (host.length() != 0)) {
+            Uri u = Uri.parse(host);
+            return new java.net.Proxy(java.net.Proxy.Type.HTTP,
+                    new InetSocketAddress(u.getHost(), u.getPort()));
+        } else {
+            return java.net.Proxy.NO_PROXY;
+        }
+    }
+
+    // TODO: remove this function / deprecate
     /**
      * Returns the preferred proxy to be used by clients. This is a wrapper
      * around {@link android.net.Proxy#getHost()}. Currently no proxy will
@@ -138,26 +291,23 @@
      * android.permission.ACCESS_NETWORK_STATE
      * @return The preferred proxy to be used by clients, or null if there
      * is no proxy.
-     *
      * {@hide}
      */
-    static final public HttpHost getPreferredHttpHost(Context context,
+    public static final HttpHost getPreferredHttpHost(Context context,
             String url) {
-        if (!isLocalHost(url) && !isNetworkWifi(context)) {
-            final String proxyHost = Proxy.getHost(context);
-            if (proxyHost != null) {
-                return new HttpHost(proxyHost, Proxy.getPort(context), "http");
-            }
+        java.net.Proxy prefProxy = getProxy(context, url);
+        if (prefProxy.equals(java.net.Proxy.NO_PROXY)) {
+            return null;
+        } else {
+            InetSocketAddress sa = (InetSocketAddress)prefProxy.address();
+            return new HttpHost(sa.getHostName(), sa.getPort(), "http");
         }
-
-        return null;
     }
 
-    static final private boolean isLocalHost(String url) {
+    private static final boolean isLocalHost(String url) {
         if (url == null) {
             return false;
         }
-
         try {
             final URI uri = URI.create(url);
             final String host = uri.getHost();
@@ -174,15 +324,13 @@
         } catch (IllegalArgumentException iex) {
             // Ignore (URI.create)
         }
-
         return false;
     }
 
-    static final private boolean isNetworkWifi(Context context) {
+    private static final boolean isNetworkWifi(Context context) {
         if (context == null) {
             return false;
         }
-
         final ConnectivityManager connectivity = (ConnectivityManager)
             context.getSystemService(Context.CONNECTIVITY_SERVICE);
         if (connectivity != null) {
@@ -192,7 +340,126 @@
                 return true;
             }
         }
-
         return false;
     }
-};
+
+    private static class SettingsObserver extends ContentObserver {
+
+        private Context mContext;
+
+        SettingsObserver(Context ctx) {
+            super(new Handler());
+            mContext = ctx;
+        }
+
+        @Override
+        public void onChange(boolean selfChange) {
+            sProxyInfoLock.readLock().lock();
+            parseGlobalProxyInfoReadLocked(mContext);
+            sProxyInfoLock.readLock().unlock();
+        }
+    }
+
+    private static final void registerContentObserversReadLocked(Context ctx) {
+        Uri uriGlobalProxy = Settings.Secure.getUriFor(Settings.Secure.HTTP_PROXY);
+        Uri uriGlobalExclList =
+            Settings.Secure.getUriFor(Settings.Secure.HTTP_PROXY_EXCLUSION_LIST);
+
+        // No lock upgrading (from read to write) allowed
+        sProxyInfoLock.readLock().unlock();
+        sProxyInfoLock.writeLock().lock();
+        sGlobalProxyChangedObserver = new SettingsObserver(ctx);
+        // Downgrading locks (from write to read) is allowed
+        sProxyInfoLock.readLock().lock();
+        sProxyInfoLock.writeLock().unlock();
+        ctx.getContentResolver().registerContentObserver(uriGlobalProxy, false,
+                sGlobalProxyChangedObserver);
+        ctx.getContentResolver().registerContentObserver(uriGlobalExclList, false,
+                sGlobalProxyChangedObserver);
+    }
+
+    private static final void parseGlobalProxyInfoReadLocked(Context ctx) {
+        ContentResolver contentResolver = ctx.getContentResolver();
+        String proxyHost =  Settings.Secure.getString(
+                contentResolver,
+                Settings.Secure.HTTP_PROXY);
+        if ((proxyHost == null) || (proxyHost.length() == 0)) {
+            // Clear signal
+            sProxyInfoLock.readLock().unlock();
+            sProxyInfoLock.writeLock().lock();
+            sGlobalProxySpec = null;
+            sProxyInfoLock.readLock().lock();
+            sProxyInfoLock.writeLock().unlock();
+            return;
+        }
+        String exclusionListSpec = Settings.Secure.getString(
+                contentResolver,
+                Settings.Secure.HTTP_PROXY_EXCLUSION_LIST);
+        String host = parseHost(proxyHost);
+        int port = parsePort(proxyHost);
+        if (proxyHost != null) {
+            sGlobalProxySpec = new ProxySpec();
+            sGlobalProxySpec.proxyAddress = new InetSocketAddress(host, port);
+            if ((exclusionListSpec != null) && (exclusionListSpec.length() != 0)) {
+                String[] exclusionListEntries = exclusionListSpec.toLowerCase().split(",");
+                String[] processedEntries = new String[exclusionListEntries.length];
+                for (int i = 0; i < exclusionListEntries.length; i++) {
+                    String entry = exclusionListEntries[i].trim();
+                    if (entry.startsWith(".")) {
+                        entry = entry.substring(1);
+                    }
+                    processedEntries[i] = entry;
+                }
+                sProxyInfoLock.readLock().unlock();
+                sProxyInfoLock.writeLock().lock();
+                sGlobalProxySpec.exclusionList = processedEntries;
+            } else {
+                sProxyInfoLock.readLock().unlock();
+                sProxyInfoLock.writeLock().lock();
+                sGlobalProxySpec.exclusionList = null;
+            }
+        } else {
+            sProxyInfoLock.readLock().unlock();
+            sProxyInfoLock.writeLock().lock();
+            sGlobalProxySpec = null;
+        }
+        sProxyInfoLock.readLock().lock();
+        sProxyInfoLock.writeLock().unlock();
+    }
+
+    /**
+     * Validate syntax of hostname, port and exclusion list entries
+     * {@hide}
+     */
+    public static void validate(String hostname, String port, String exclList) {
+        Matcher match = HOSTNAME_PATTERN.matcher(hostname);
+        Matcher listMatch = EXCLLIST_PATTERN.matcher(exclList);
+
+        if (!match.matches()) {
+            throw new IllegalArgumentException();
+        }
+
+        if (!listMatch.matches()) {
+            throw new IllegalArgumentException();
+        }
+
+        if (hostname.length() > 0 && port.length() == 0) {
+            throw new IllegalArgumentException();
+        }
+
+        if (port.length() > 0) {
+            if (hostname.length() == 0) {
+                throw new IllegalArgumentException();
+            }
+            int portVal = -1;
+            try {
+                portVal = Integer.parseInt(port);
+            } catch (NumberFormatException ex) {
+                throw new IllegalArgumentException();
+            }
+            if (portVal <= 0 || portVal > 0xFFFF) {
+                throw new IllegalArgumentException();
+            }
+        }
+    }
+}
diff --git a/core/java/android/net/ProxyProperties.java b/core/java/android/net/ProxyProperties.java
new file mode 100644
index 0000000..6828dd4
--- /dev/null
+++ b/core/java/android/net/ProxyProperties.java
@@ -0,0 +1,108 @@
+/*
+ * 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.net;
+
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+/**
+ * A container class for the http proxy info
+ * @hide
+ */
+public class ProxyProperties implements Parcelable {
+
+    private InetAddress mProxy;
+    private int mPort;
+    private String mExclusionList;
+
+    public ProxyProperties() {
+    }
+
+    public synchronized InetAddress getAddress() {
+        return mProxy;
+    }
+    public synchronized void setAddress(InetAddress proxy) {
+        mProxy = proxy;
+    }
+
+    public synchronized int getPort() {
+        return mPort;
+    }
+    public synchronized void setPort(int port) {
+        mPort = port;
+    }
+
+    public synchronized String getExclusionList() {
+        return mExclusionList;
+    }
+    public synchronized void setExclusionList(String exclusionList) {
+        mExclusionList = exclusionList;
+    }
+
+    /**
+     * Implement the Parcelable interface
+     * @hide
+     */
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * Implement the Parcelable interface.
+     * @hide
+     */
+    public synchronized void writeToParcel(Parcel dest, int flags) {
+        if (mProxy != null) {
+            dest.writeByte((byte)1);
+            dest.writeString(mProxy.getHostName());
+            dest.writeByteArray(mProxy.getAddress());
+        } else {
+            dest.writeByte((byte)0);
+        }
+        dest.writeInt(mPort);
+        dest.writeString(mExclusionList);
+    }
+
+    /**
+     * Implement the Parcelable interface.
+     * @hide
+     */
+    public static final Creator<ProxyProperties> CREATOR =
+        new Creator<ProxyProperties>() {
+            public ProxyProperties createFromParcel(Parcel in) {
+                ProxyProperties proxyProperties = new ProxyProperties();
+                if (in.readByte() == 1) {
+                    try {
+                        proxyProperties.setAddress(InetAddress.getByAddress(in.readString(),
+                                in.createByteArray()));
+                    } catch (UnknownHostException e) {}
+                }
+                proxyProperties.setPort(in.readInt());
+                proxyProperties.setExclusionList(in.readString());
+                return proxyProperties;
+            }
+
+            public ProxyProperties[] newArray(int size) {
+                return new ProxyProperties[size];
+            }
+        };
+
+};
diff --git a/core/java/android/net/http/AndroidHttpClient.java b/core/java/android/net/http/AndroidHttpClient.java
index e07ee59..915e342 100644
--- a/core/java/android/net/http/AndroidHttpClient.java
+++ b/core/java/android/net/http/AndroidHttpClient.java
@@ -61,6 +61,7 @@
 import android.net.SSLCertificateSocketFactory;
 import android.net.SSLSessionCache;
 import android.os.Looper;
+import android.util.Base64;
 import android.util.Log;
 
 /**
@@ -81,6 +82,11 @@
 
     private static final String TAG = "AndroidHttpClient";
 
+    private static String[] textContentTypes = new String[] {
+            "text/",
+            "application/xml",
+            "application/json"
+    };
 
     /** Interceptor throws an exception if the executing thread is blocked */
     private static final HttpRequestInterceptor sThreadCheckInterceptor =
@@ -358,7 +364,7 @@
         }
         if (level < Log.VERBOSE || level > Log.ASSERT) {
             throw new IllegalArgumentException("Level is out of range ["
-                + Log.VERBOSE + ".." + Log.ASSERT + "]");    
+                + Log.VERBOSE + ".." + Log.ASSERT + "]");
         }
 
         curlConfiguration = new LoggingConfiguration(name, level);
@@ -431,12 +437,17 @@
                 if (entity.getContentLength() < 1024) {
                     ByteArrayOutputStream stream = new ByteArrayOutputStream();
                     entity.writeTo(stream);
-                    String entityString = stream.toString();
 
-                    // TODO: Check the content type, too.
-                    builder.append(" --data-ascii \"")
-                            .append(entityString)
-                            .append("\"");
+                    if (isBinaryContent(request)) {
+                        String base64 = Base64.encodeToString(stream.toByteArray(), Base64.NO_WRAP);
+                        builder.insert(0, "echo '" + base64 + "' | base64 -d > /tmp/$$.bin; ");
+                        builder.append(" --data-binary @/tmp/$$.bin");
+                    } else {
+                        String entityString = stream.toString();
+                        builder.append(" --data-ascii \"")
+                                .append(entityString)
+                                .append("\"");
+                    }
                 } else {
                     builder.append(" [TOO MUCH DATA TO INCLUDE]");
                 }
@@ -446,6 +457,30 @@
         return builder.toString();
     }
 
+    private static boolean isBinaryContent(HttpUriRequest request) {
+        Header[] headers;
+        headers = request.getHeaders(Headers.CONTENT_ENCODING);
+        if (headers != null) {
+            for (Header header : headers) {
+                if ("gzip".equalsIgnoreCase(header.getValue())) {
+                    return true;
+                }
+            }
+        }
+
+        headers = request.getHeaders(Headers.CONTENT_TYPE);
+        if (headers != null) {
+            for (Header header : headers) {
+                for (String contentType : textContentTypes) {
+                    if (header.getValue().startsWith(contentType)) {
+                        return false;
+                    }
+                }
+            }
+        }
+        return true;
+    }
+
     /**
      * Returns the date of the given HTTP date string. This method can identify
      * and parse the date formats emitted by common HTTP servers, such as
diff --git a/core/java/android/net/http/CertificateChainValidator.java b/core/java/android/net/http/CertificateChainValidator.java
index c527fe4..c36ad38 100644
--- a/core/java/android/net/http/CertificateChainValidator.java
+++ b/core/java/android/net/http/CertificateChainValidator.java
@@ -80,14 +80,10 @@
             throws IOException {
         X509Certificate[] serverCertificates = null;
 
-        // start handshake, close the socket if we fail
-        try {
-            sslSocket.setUseClientMode(true);
-            sslSocket.startHandshake();
-        } catch (IOException e) {
-            closeSocketThrowException(
-                sslSocket, e.getMessage(),
-                "failed to perform SSL handshake");
+        // get a valid SSLSession, close the socket if we fail
+        SSLSession sslSession = sslSession = sslSocket.getSession();
+        if (!sslSession.isValid()) {
+            closeSocketThrowException(sslSocket, "failed to perform SSL handshake");
         }
 
         // retrieve the chain of the server peer certificates
diff --git a/core/java/android/os/AsyncTask.java b/core/java/android/os/AsyncTask.java
index d28148c..832ce84 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
@@ -187,6 +187,17 @@
         };
 
         mFuture = new FutureTask<Result>(mWorker) {
+
+            @Override
+            protected void set(Result v) {
+                super.set(v);
+                if (isCancelled()) {
+                    Message message = sHandler.obtainMessage(MESSAGE_POST_CANCEL,
+                            new AsyncTaskResult<Result>(AsyncTask.this, (Result[]) null));
+                    message.sendToTarget();
+                }
+            }
+
             @Override
             protected void done() {
                 Message message;
@@ -402,14 +413,19 @@
      * still running. Each call to this method will trigger the execution of
      * {@link #onProgressUpdate} on the UI thread.
      *
+     * {@link #onProgressUpdate} will note be called if the task has been
+     * canceled.
+     *
      * @param values The progress values to update the UI with.
      *
      * @see #onProgressUpdate
      * @see #doInBackground
      */
     protected final void publishProgress(Progress... values) {
-        sHandler.obtainMessage(MESSAGE_POST_PROGRESS,
-                new AsyncTaskResult<Progress>(this, values)).sendToTarget();
+        if (!isCancelled()) {
+            sHandler.obtainMessage(MESSAGE_POST_PROGRESS,
+                    new AsyncTaskResult<Progress>(this, values)).sendToTarget();
+        }
     }
 
     private void finish(Result result) {
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index fdd3573..78ec638 100644
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -196,6 +196,17 @@
          * </ul>
          */
         public static final int GINGERBREAD = CUR_DEVELOPMENT;
+
+        /**
+         * Next next version of Android.
+         *
+         * <p>Applications targeting this or a later release will get these
+         * new changes in behavior:</p>
+         * <ul>
+         * <li> Alerts UI change?
+         * </ul>
+         */
+        public static final int HONEYCOMB = CUR_DEVELOPMENT;
     }
     
     /** The type of build, like "user" or "eng". */
diff --git a/core/java/android/os/Debug.java b/core/java/android/os/Debug.java
index 86f9a6b..a58e70b 100644
--- a/core/java/android/os/Debug.java
+++ b/core/java/android/os/Debug.java
@@ -731,7 +731,7 @@
     }
 
     /**
-     * Dump "hprof" data to the specified file.  This will cause a GC.
+     * Dump "hprof" data to the specified file.  This may cause a GC.
      *
      * @param fileName Full pathname of output file (e.g. "/sdcard/dump.hprof").
      * @throws UnsupportedOperationException if the VM was built without
@@ -743,11 +743,24 @@
     }
 
     /**
-     * Collect "hprof" and send it to DDMS.  This will cause a GC.
+     * Like dumpHprofData(String), but takes an already-opened
+     * FileDescriptor to which the trace is written.  The file name is also
+     * supplied simply for logging.  Makes a dup of the file descriptor.
+     *
+     * Primarily for use by the "am" shell command.
+     *
+     * @hide
+     */
+    public static void dumpHprofData(String fileName, FileDescriptor fd)
+            throws IOException {
+        VMDebug.dumpHprofData(fileName, fd);
+    }
+
+    /**
+     * Collect "hprof" and send it to DDMS.  This may cause a GC.
      *
      * @throws UnsupportedOperationException if the VM was built without
      *         HPROF support.
-     *
      * @hide
      */
     public static void dumpHprofDataDdms() {
@@ -755,6 +768,13 @@
     }
 
     /**
+     * Writes native heap data to the specified file descriptor.
+     *
+     * @hide
+     */
+    public static native void dumpNativeHeap(FileDescriptor fd);
+
+    /**
      * Returns the number of sent transactions from this process.
      * @return The number of sent transactions or -1 if it could not read t.
      */
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 96bf2d5..61cdace 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -44,6 +44,7 @@
  * Get an instance of this class by calling
  * {@link android.content.Context#getSystemService(java.lang.String)} with an argument
  * of {@link android.content.Context#STORAGE_SERVICE}.
+ *
  */
 
 public class StorageManager
@@ -206,7 +207,6 @@
      *
      * @param listener A {@link android.os.storage.StorageEventListener StorageEventListener} object.
      *
-     * @hide
      */
     public void registerListener(StorageEventListener listener) {
         if (listener == null) {
@@ -223,7 +223,6 @@
      *
      * @param listener A {@link android.os.storage.StorageEventListener StorageEventListener} object.
      *
-     * @hide
      */
     public void unregisterListener(StorageEventListener listener) {
         if (listener == null) {
@@ -244,8 +243,6 @@
 
     /**
      * Enables USB Mass Storage (UMS) on the device.
-     *
-     * @hide
      */
     public void enableUsbMassStorage() {
         try {
@@ -257,8 +254,6 @@
 
     /**
      * Disables USB Mass Storage (UMS) on the device.
-     *
-     * @hide
      */
     public void disableUsbMassStorage() {
         try {
@@ -271,8 +266,6 @@
     /**
      * Query if a USB Mass Storage (UMS) host is connected.
      * @return true if UMS host is connected.
-     *
-     * @hide
      */
     public boolean isUsbMassStorageConnected() {
         try {
@@ -286,8 +279,6 @@
     /**
      * Query if a USB Mass Storage (UMS) is enabled on the device.
      * @return true if UMS host is enabled.
-     *
-     * @hide
      */
     public boolean isUsbMassStorageEnabled() {
         try {
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
deleted file mode 100644
index 875c29e..0000000
--- a/core/java/android/pim/vcard/JapaneseUtils.java
+++ /dev/null
@@ -1,380 +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.pim.vcard;
-
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * TextUtils especially for Japanese.
- */
-/* package */ class JapaneseUtils {
-    static private final Map<Character, String> sHalfWidthMap =
-        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");
-        sHalfWidthMap.put('\u300D', "\uFF63");
-        sHalfWidthMap.put('\u301C', "~");
-        sHalfWidthMap.put('\u3041', "\uFF67");
-        sHalfWidthMap.put('\u3042', "\uFF71");
-        sHalfWidthMap.put('\u3043', "\uFF68");
-        sHalfWidthMap.put('\u3044', "\uFF72");
-        sHalfWidthMap.put('\u3045', "\uFF69");
-        sHalfWidthMap.put('\u3046', "\uFF73");
-        sHalfWidthMap.put('\u3047', "\uFF6A");
-        sHalfWidthMap.put('\u3048', "\uFF74");
-        sHalfWidthMap.put('\u3049', "\uFF6B");
-        sHalfWidthMap.put('\u304A', "\uFF75");
-        sHalfWidthMap.put('\u304B', "\uFF76");
-        sHalfWidthMap.put('\u304C', "\uFF76\uFF9E");
-        sHalfWidthMap.put('\u304D', "\uFF77");
-        sHalfWidthMap.put('\u304E', "\uFF77\uFF9E");
-        sHalfWidthMap.put('\u304F', "\uFF78");
-        sHalfWidthMap.put('\u3050', "\uFF78\uFF9E");
-        sHalfWidthMap.put('\u3051', "\uFF79");
-        sHalfWidthMap.put('\u3052', "\uFF79\uFF9E");
-        sHalfWidthMap.put('\u3053', "\uFF7A");
-        sHalfWidthMap.put('\u3054', "\uFF7A\uFF9E");
-        sHalfWidthMap.put('\u3055', "\uFF7B");
-        sHalfWidthMap.put('\u3056', "\uFF7B\uFF9E");
-        sHalfWidthMap.put('\u3057', "\uFF7C");
-        sHalfWidthMap.put('\u3058', "\uFF7C\uFF9E");
-        sHalfWidthMap.put('\u3059', "\uFF7D");
-        sHalfWidthMap.put('\u305A', "\uFF7D\uFF9E");
-        sHalfWidthMap.put('\u305B', "\uFF7E");
-        sHalfWidthMap.put('\u305C', "\uFF7E\uFF9E");
-        sHalfWidthMap.put('\u305D', "\uFF7F");
-        sHalfWidthMap.put('\u305E', "\uFF7F\uFF9E");
-        sHalfWidthMap.put('\u305F', "\uFF80");
-        sHalfWidthMap.put('\u3060', "\uFF80\uFF9E");
-        sHalfWidthMap.put('\u3061', "\uFF81");
-        sHalfWidthMap.put('\u3062', "\uFF81\uFF9E");
-        sHalfWidthMap.put('\u3063', "\uFF6F");
-        sHalfWidthMap.put('\u3064', "\uFF82");
-        sHalfWidthMap.put('\u3065', "\uFF82\uFF9E");
-        sHalfWidthMap.put('\u3066', "\uFF83");
-        sHalfWidthMap.put('\u3067', "\uFF83\uFF9E");
-        sHalfWidthMap.put('\u3068', "\uFF84");
-        sHalfWidthMap.put('\u3069', "\uFF84\uFF9E");
-        sHalfWidthMap.put('\u306A', "\uFF85");
-        sHalfWidthMap.put('\u306B', "\uFF86");
-        sHalfWidthMap.put('\u306C', "\uFF87");
-        sHalfWidthMap.put('\u306D', "\uFF88");
-        sHalfWidthMap.put('\u306E', "\uFF89");
-        sHalfWidthMap.put('\u306F', "\uFF8A");
-        sHalfWidthMap.put('\u3070', "\uFF8A\uFF9E");
-        sHalfWidthMap.put('\u3071', "\uFF8A\uFF9F");
-        sHalfWidthMap.put('\u3072', "\uFF8B");
-        sHalfWidthMap.put('\u3073', "\uFF8B\uFF9E");
-        sHalfWidthMap.put('\u3074', "\uFF8B\uFF9F");
-        sHalfWidthMap.put('\u3075', "\uFF8C");
-        sHalfWidthMap.put('\u3076', "\uFF8C\uFF9E");
-        sHalfWidthMap.put('\u3077', "\uFF8C\uFF9F");
-        sHalfWidthMap.put('\u3078', "\uFF8D");
-        sHalfWidthMap.put('\u3079', "\uFF8D\uFF9E");
-        sHalfWidthMap.put('\u307A', "\uFF8D\uFF9F");
-        sHalfWidthMap.put('\u307B', "\uFF8E");
-        sHalfWidthMap.put('\u307C', "\uFF8E\uFF9E");
-        sHalfWidthMap.put('\u307D', "\uFF8E\uFF9F");
-        sHalfWidthMap.put('\u307E', "\uFF8F");
-        sHalfWidthMap.put('\u307F', "\uFF90");
-        sHalfWidthMap.put('\u3080', "\uFF91");
-        sHalfWidthMap.put('\u3081', "\uFF92");
-        sHalfWidthMap.put('\u3082', "\uFF93");
-        sHalfWidthMap.put('\u3083', "\uFF6C");
-        sHalfWidthMap.put('\u3084', "\uFF94");
-        sHalfWidthMap.put('\u3085', "\uFF6D");
-        sHalfWidthMap.put('\u3086', "\uFF95");
-        sHalfWidthMap.put('\u3087', "\uFF6E");
-        sHalfWidthMap.put('\u3088', "\uFF96");
-        sHalfWidthMap.put('\u3089', "\uFF97");
-        sHalfWidthMap.put('\u308A', "\uFF98");
-        sHalfWidthMap.put('\u308B', "\uFF99");
-        sHalfWidthMap.put('\u308C', "\uFF9A");
-        sHalfWidthMap.put('\u308D', "\uFF9B");
-        sHalfWidthMap.put('\u308E', "\uFF9C");
-        sHalfWidthMap.put('\u308F', "\uFF9C");
-        sHalfWidthMap.put('\u3090', "\uFF72");
-        sHalfWidthMap.put('\u3091', "\uFF74");
-        sHalfWidthMap.put('\u3092', "\uFF66");
-        sHalfWidthMap.put('\u3093', "\uFF9D");
-        sHalfWidthMap.put('\u309B', "\uFF9E");
-        sHalfWidthMap.put('\u309C', "\uFF9F");
-        sHalfWidthMap.put('\u30A1', "\uFF67");
-        sHalfWidthMap.put('\u30A2', "\uFF71");
-        sHalfWidthMap.put('\u30A3', "\uFF68");
-        sHalfWidthMap.put('\u30A4', "\uFF72");
-        sHalfWidthMap.put('\u30A5', "\uFF69");
-        sHalfWidthMap.put('\u30A6', "\uFF73");
-        sHalfWidthMap.put('\u30A7', "\uFF6A");
-        sHalfWidthMap.put('\u30A8', "\uFF74");
-        sHalfWidthMap.put('\u30A9', "\uFF6B");
-        sHalfWidthMap.put('\u30AA', "\uFF75");
-        sHalfWidthMap.put('\u30AB', "\uFF76");
-        sHalfWidthMap.put('\u30AC', "\uFF76\uFF9E");
-        sHalfWidthMap.put('\u30AD', "\uFF77");
-        sHalfWidthMap.put('\u30AE', "\uFF77\uFF9E");
-        sHalfWidthMap.put('\u30AF', "\uFF78");
-        sHalfWidthMap.put('\u30B0', "\uFF78\uFF9E");
-        sHalfWidthMap.put('\u30B1', "\uFF79");
-        sHalfWidthMap.put('\u30B2', "\uFF79\uFF9E");
-        sHalfWidthMap.put('\u30B3', "\uFF7A");
-        sHalfWidthMap.put('\u30B4', "\uFF7A\uFF9E");
-        sHalfWidthMap.put('\u30B5', "\uFF7B");
-        sHalfWidthMap.put('\u30B6', "\uFF7B\uFF9E");
-        sHalfWidthMap.put('\u30B7', "\uFF7C");
-        sHalfWidthMap.put('\u30B8', "\uFF7C\uFF9E");
-        sHalfWidthMap.put('\u30B9', "\uFF7D");
-        sHalfWidthMap.put('\u30BA', "\uFF7D\uFF9E");
-        sHalfWidthMap.put('\u30BB', "\uFF7E");
-        sHalfWidthMap.put('\u30BC', "\uFF7E\uFF9E");
-        sHalfWidthMap.put('\u30BD', "\uFF7F");
-        sHalfWidthMap.put('\u30BE', "\uFF7F\uFF9E");
-        sHalfWidthMap.put('\u30BF', "\uFF80");
-        sHalfWidthMap.put('\u30C0', "\uFF80\uFF9E");
-        sHalfWidthMap.put('\u30C1', "\uFF81");
-        sHalfWidthMap.put('\u30C2', "\uFF81\uFF9E");
-        sHalfWidthMap.put('\u30C3', "\uFF6F");
-        sHalfWidthMap.put('\u30C4', "\uFF82");
-        sHalfWidthMap.put('\u30C5', "\uFF82\uFF9E");
-        sHalfWidthMap.put('\u30C6', "\uFF83");
-        sHalfWidthMap.put('\u30C7', "\uFF83\uFF9E");
-        sHalfWidthMap.put('\u30C8', "\uFF84");
-        sHalfWidthMap.put('\u30C9', "\uFF84\uFF9E");
-        sHalfWidthMap.put('\u30CA', "\uFF85");
-        sHalfWidthMap.put('\u30CB', "\uFF86");
-        sHalfWidthMap.put('\u30CC', "\uFF87");
-        sHalfWidthMap.put('\u30CD', "\uFF88");
-        sHalfWidthMap.put('\u30CE', "\uFF89");
-        sHalfWidthMap.put('\u30CF', "\uFF8A");
-        sHalfWidthMap.put('\u30D0', "\uFF8A\uFF9E");
-        sHalfWidthMap.put('\u30D1', "\uFF8A\uFF9F");
-        sHalfWidthMap.put('\u30D2', "\uFF8B");
-        sHalfWidthMap.put('\u30D3', "\uFF8B\uFF9E");
-        sHalfWidthMap.put('\u30D4', "\uFF8B\uFF9F");
-        sHalfWidthMap.put('\u30D5', "\uFF8C");
-        sHalfWidthMap.put('\u30D6', "\uFF8C\uFF9E");
-        sHalfWidthMap.put('\u30D7', "\uFF8C\uFF9F");
-        sHalfWidthMap.put('\u30D8', "\uFF8D");
-        sHalfWidthMap.put('\u30D9', "\uFF8D\uFF9E");
-        sHalfWidthMap.put('\u30DA', "\uFF8D\uFF9F");
-        sHalfWidthMap.put('\u30DB', "\uFF8E");
-        sHalfWidthMap.put('\u30DC', "\uFF8E\uFF9E");
-        sHalfWidthMap.put('\u30DD', "\uFF8E\uFF9F");
-        sHalfWidthMap.put('\u30DE', "\uFF8F");
-        sHalfWidthMap.put('\u30DF', "\uFF90");
-        sHalfWidthMap.put('\u30E0', "\uFF91");
-        sHalfWidthMap.put('\u30E1', "\uFF92");
-        sHalfWidthMap.put('\u30E2', "\uFF93");
-        sHalfWidthMap.put('\u30E3', "\uFF6C");
-        sHalfWidthMap.put('\u30E4', "\uFF94");
-        sHalfWidthMap.put('\u30E5', "\uFF6D");
-        sHalfWidthMap.put('\u30E6', "\uFF95");
-        sHalfWidthMap.put('\u30E7', "\uFF6E");
-        sHalfWidthMap.put('\u30E8', "\uFF96");
-        sHalfWidthMap.put('\u30E9', "\uFF97");
-        sHalfWidthMap.put('\u30EA', "\uFF98");
-        sHalfWidthMap.put('\u30EB', "\uFF99");
-        sHalfWidthMap.put('\u30EC', "\uFF9A");
-        sHalfWidthMap.put('\u30ED', "\uFF9B");
-        sHalfWidthMap.put('\u30EE', "\uFF9C");
-        sHalfWidthMap.put('\u30EF', "\uFF9C");
-        sHalfWidthMap.put('\u30F0', "\uFF72");
-        sHalfWidthMap.put('\u30F1', "\uFF74");
-        sHalfWidthMap.put('\u30F2', "\uFF66");
-        sHalfWidthMap.put('\u30F3', "\uFF9D");
-        sHalfWidthMap.put('\u30F4', "\uFF73\uFF9E");
-        sHalfWidthMap.put('\u30F5', "\uFF76");
-        sHalfWidthMap.put('\u30F6', "\uFF79");
-        sHalfWidthMap.put('\u30FB', "\uFF65");
-        sHalfWidthMap.put('\u30FC', "\uFF70");
-        sHalfWidthMap.put('\uFF01', "!");
-        sHalfWidthMap.put('\uFF02', "\"");
-        sHalfWidthMap.put('\uFF03', "#");
-        sHalfWidthMap.put('\uFF04', "$");
-        sHalfWidthMap.put('\uFF05', "%");
-        sHalfWidthMap.put('\uFF06', "&");
-        sHalfWidthMap.put('\uFF07', "'");
-        sHalfWidthMap.put('\uFF08', "(");
-        sHalfWidthMap.put('\uFF09', ")");
-        sHalfWidthMap.put('\uFF0A', "*");
-        sHalfWidthMap.put('\uFF0B', "+");
-        sHalfWidthMap.put('\uFF0C', ",");
-        sHalfWidthMap.put('\uFF0D', "-");
-        sHalfWidthMap.put('\uFF0E', ".");
-        sHalfWidthMap.put('\uFF0F', "/");
-        sHalfWidthMap.put('\uFF10', "0");
-        sHalfWidthMap.put('\uFF11', "1");
-        sHalfWidthMap.put('\uFF12', "2");
-        sHalfWidthMap.put('\uFF13', "3");
-        sHalfWidthMap.put('\uFF14', "4");
-        sHalfWidthMap.put('\uFF15', "5");
-        sHalfWidthMap.put('\uFF16', "6");
-        sHalfWidthMap.put('\uFF17', "7");
-        sHalfWidthMap.put('\uFF18', "8");
-        sHalfWidthMap.put('\uFF19', "9");
-        sHalfWidthMap.put('\uFF1A', ":");
-        sHalfWidthMap.put('\uFF1B', ";");
-        sHalfWidthMap.put('\uFF1C', "<");
-        sHalfWidthMap.put('\uFF1D', "=");
-        sHalfWidthMap.put('\uFF1E', ">");
-        sHalfWidthMap.put('\uFF1F', "?");
-        sHalfWidthMap.put('\uFF20', "@");
-        sHalfWidthMap.put('\uFF21', "A");
-        sHalfWidthMap.put('\uFF22', "B");
-        sHalfWidthMap.put('\uFF23', "C");
-        sHalfWidthMap.put('\uFF24', "D");
-        sHalfWidthMap.put('\uFF25', "E");
-        sHalfWidthMap.put('\uFF26', "F");
-        sHalfWidthMap.put('\uFF27', "G");
-        sHalfWidthMap.put('\uFF28', "H");
-        sHalfWidthMap.put('\uFF29', "I");
-        sHalfWidthMap.put('\uFF2A', "J");
-        sHalfWidthMap.put('\uFF2B', "K");
-        sHalfWidthMap.put('\uFF2C', "L");
-        sHalfWidthMap.put('\uFF2D', "M");
-        sHalfWidthMap.put('\uFF2E', "N");
-        sHalfWidthMap.put('\uFF2F', "O");
-        sHalfWidthMap.put('\uFF30', "P");
-        sHalfWidthMap.put('\uFF31', "Q");
-        sHalfWidthMap.put('\uFF32', "R");
-        sHalfWidthMap.put('\uFF33', "S");
-        sHalfWidthMap.put('\uFF34', "T");
-        sHalfWidthMap.put('\uFF35', "U");
-        sHalfWidthMap.put('\uFF36', "V");
-        sHalfWidthMap.put('\uFF37', "W");
-        sHalfWidthMap.put('\uFF38', "X");
-        sHalfWidthMap.put('\uFF39', "Y");
-        sHalfWidthMap.put('\uFF3A', "Z");
-        sHalfWidthMap.put('\uFF3B', "[");
-        sHalfWidthMap.put('\uFF3C', "\\");
-        sHalfWidthMap.put('\uFF3D', "]");
-        sHalfWidthMap.put('\uFF3E', "^");
-        sHalfWidthMap.put('\uFF3F', "_");
-        sHalfWidthMap.put('\uFF41', "a");
-        sHalfWidthMap.put('\uFF42', "b");
-        sHalfWidthMap.put('\uFF43', "c");
-        sHalfWidthMap.put('\uFF44', "d");
-        sHalfWidthMap.put('\uFF45', "e");
-        sHalfWidthMap.put('\uFF46', "f");
-        sHalfWidthMap.put('\uFF47', "g");
-        sHalfWidthMap.put('\uFF48', "h");
-        sHalfWidthMap.put('\uFF49', "i");
-        sHalfWidthMap.put('\uFF4A', "j");
-        sHalfWidthMap.put('\uFF4B', "k");
-        sHalfWidthMap.put('\uFF4C', "l");
-        sHalfWidthMap.put('\uFF4D', "m");
-        sHalfWidthMap.put('\uFF4E', "n");
-        sHalfWidthMap.put('\uFF4F', "o");
-        sHalfWidthMap.put('\uFF50', "p");
-        sHalfWidthMap.put('\uFF51', "q");
-        sHalfWidthMap.put('\uFF52', "r");
-        sHalfWidthMap.put('\uFF53', "s");
-        sHalfWidthMap.put('\uFF54', "t");
-        sHalfWidthMap.put('\uFF55', "u");
-        sHalfWidthMap.put('\uFF56', "v");
-        sHalfWidthMap.put('\uFF57', "w");
-        sHalfWidthMap.put('\uFF58', "x");
-        sHalfWidthMap.put('\uFF59', "y");
-        sHalfWidthMap.put('\uFF5A', "z");
-        sHalfWidthMap.put('\uFF5B', "{");
-        sHalfWidthMap.put('\uFF5C', "|");
-        sHalfWidthMap.put('\uFF5D', "}");
-        sHalfWidthMap.put('\uFF5E', "~");
-        sHalfWidthMap.put('\uFF61', "\uFF61");
-        sHalfWidthMap.put('\uFF62', "\uFF62");
-        sHalfWidthMap.put('\uFF63', "\uFF63");
-        sHalfWidthMap.put('\uFF64', "\uFF64");
-        sHalfWidthMap.put('\uFF65', "\uFF65");
-        sHalfWidthMap.put('\uFF66', "\uFF66");
-        sHalfWidthMap.put('\uFF67', "\uFF67");
-        sHalfWidthMap.put('\uFF68', "\uFF68");
-        sHalfWidthMap.put('\uFF69', "\uFF69");
-        sHalfWidthMap.put('\uFF6A', "\uFF6A");
-        sHalfWidthMap.put('\uFF6B', "\uFF6B");
-        sHalfWidthMap.put('\uFF6C', "\uFF6C");
-        sHalfWidthMap.put('\uFF6D', "\uFF6D");
-        sHalfWidthMap.put('\uFF6E', "\uFF6E");
-        sHalfWidthMap.put('\uFF6F', "\uFF6F");
-        sHalfWidthMap.put('\uFF70', "\uFF70");
-        sHalfWidthMap.put('\uFF71', "\uFF71");
-        sHalfWidthMap.put('\uFF72', "\uFF72");
-        sHalfWidthMap.put('\uFF73', "\uFF73");
-        sHalfWidthMap.put('\uFF74', "\uFF74");
-        sHalfWidthMap.put('\uFF75', "\uFF75");
-        sHalfWidthMap.put('\uFF76', "\uFF76");
-        sHalfWidthMap.put('\uFF77', "\uFF77");
-        sHalfWidthMap.put('\uFF78', "\uFF78");
-        sHalfWidthMap.put('\uFF79', "\uFF79");
-        sHalfWidthMap.put('\uFF7A', "\uFF7A");
-        sHalfWidthMap.put('\uFF7B', "\uFF7B");
-        sHalfWidthMap.put('\uFF7C', "\uFF7C");
-        sHalfWidthMap.put('\uFF7D', "\uFF7D");
-        sHalfWidthMap.put('\uFF7E', "\uFF7E");
-        sHalfWidthMap.put('\uFF7F', "\uFF7F");
-        sHalfWidthMap.put('\uFF80', "\uFF80");
-        sHalfWidthMap.put('\uFF81', "\uFF81");
-        sHalfWidthMap.put('\uFF82', "\uFF82");
-        sHalfWidthMap.put('\uFF83', "\uFF83");
-        sHalfWidthMap.put('\uFF84', "\uFF84");
-        sHalfWidthMap.put('\uFF85', "\uFF85");
-        sHalfWidthMap.put('\uFF86', "\uFF86");
-        sHalfWidthMap.put('\uFF87', "\uFF87");
-        sHalfWidthMap.put('\uFF88', "\uFF88");
-        sHalfWidthMap.put('\uFF89', "\uFF89");
-        sHalfWidthMap.put('\uFF8A', "\uFF8A");
-        sHalfWidthMap.put('\uFF8B', "\uFF8B");
-        sHalfWidthMap.put('\uFF8C', "\uFF8C");
-        sHalfWidthMap.put('\uFF8D', "\uFF8D");
-        sHalfWidthMap.put('\uFF8E', "\uFF8E");
-        sHalfWidthMap.put('\uFF8F', "\uFF8F");
-        sHalfWidthMap.put('\uFF90', "\uFF90");
-        sHalfWidthMap.put('\uFF91', "\uFF91");
-        sHalfWidthMap.put('\uFF92', "\uFF92");
-        sHalfWidthMap.put('\uFF93', "\uFF93");
-        sHalfWidthMap.put('\uFF94', "\uFF94");
-        sHalfWidthMap.put('\uFF95', "\uFF95");
-        sHalfWidthMap.put('\uFF96', "\uFF96");
-        sHalfWidthMap.put('\uFF97', "\uFF97");
-        sHalfWidthMap.put('\uFF98', "\uFF98");
-        sHalfWidthMap.put('\uFF99', "\uFF99");
-        sHalfWidthMap.put('\uFF9A', "\uFF9A");
-        sHalfWidthMap.put('\uFF9B', "\uFF9B");
-        sHalfWidthMap.put('\uFF9C', "\uFF9C");
-        sHalfWidthMap.put('\uFF9D', "\uFF9D");
-        sHalfWidthMap.put('\uFF9E', "\uFF9E");
-        sHalfWidthMap.put('\uFF9F', "\uFF9F");
-        sHalfWidthMap.put('\uFFE5', "\u005C\u005C");
-    }
-
-    /**
-     * Return half-width version of that character if possible. Return 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) {
-        if (sHalfWidthMap.containsKey(ch)) {
-            return sHalfWidthMap.get(ch);
-        } else {
-            return null;
-        }
-    }
-}
diff --git a/core/java/android/pim/vcard/VCardBuilder.java b/core/java/android/pim/vcard/VCardBuilder.java
deleted file mode 100644
index 1da6d7a..0000000
--- a/core/java/android/pim/vcard/VCardBuilder.java
+++ /dev/null
@@ -1,1932 +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.pim.vcard;
-
-import android.content.ContentValues;
-import android.provider.ContactsContract.CommonDataKinds.Email;
-import android.provider.ContactsContract.CommonDataKinds.Event;
-import android.provider.ContactsContract.CommonDataKinds.Im;
-import android.provider.ContactsContract.CommonDataKinds.Nickname;
-import android.provider.ContactsContract.CommonDataKinds.Note;
-import android.provider.ContactsContract.CommonDataKinds.Organization;
-import android.provider.ContactsContract.CommonDataKinds.Phone;
-import android.provider.ContactsContract.CommonDataKinds.Photo;
-import android.provider.ContactsContract.CommonDataKinds.Relation;
-import android.provider.ContactsContract.CommonDataKinds.StructuredName;
-import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
-import android.provider.ContactsContract.CommonDataKinds.Website;
-import android.telephony.PhoneNumberUtils;
-import android.text.TextUtils;
-import android.util.CharsetUtils;
-import android.util.Log;
-
-import org.apache.commons.codec.binary.Base64;
-
-import java.io.UnsupportedEncodingException;
-import java.nio.charset.UnsupportedCharsetException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * The class which lets users create their own vCard String.
- */
-public class VCardBuilder {
-    private static final String LOG_TAG = "VCardBuilder";
-
-    // If you add the other element, please check all the columns are able to be
-    // converted to String.
-    //
-    // e.g. BLOB is not what we can handle here now.
-    private static final Set<String> sAllowedAndroidPropertySet =
-            Collections.unmodifiableSet(new HashSet<String>(Arrays.asList(
-                    Nickname.CONTENT_ITEM_TYPE, Event.CONTENT_ITEM_TYPE,
-                    Relation.CONTENT_ITEM_TYPE)));
-
-    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;
-
-    private static final String VCARD_DATA_VCARD = "VCARD";
-    private static final String VCARD_DATA_PUBLIC = "PUBLIC";
-
-    private static final String VCARD_PARAM_SEPARATOR = ";";
-    private static final String VCARD_END_OF_LINE = "\r\n";
-    private static final String VCARD_DATA_SEPARATOR = ":";
-    private static final String VCARD_ITEM_SEPARATOR = ";";
-    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 SHIFT_JIS = "SHIFT_JIS";
-    private static final String UTF_8 = "UTF-8";
-
-    private final int mVCardType;
-
-    private final boolean mIsV30;
-    private final boolean mIsJapaneseMobilePhone;
-    private final boolean mOnlyOneNoteFieldIsAvailable;
-    private final boolean mIsDoCoMo;
-    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 mVCardCharsetParameter;
-
-    private StringBuilder mBuilder;
-    private boolean mEndAppended;
-
-    public VCardBuilder(final int vcardType) {
-        mVCardType = vcardType;
-
-        mIsV30 = VCardConfig.isV30(vcardType);
-        mShouldUseQuotedPrintable = VCardConfig.shouldUseQuotedPrintable(vcardType);
-        mIsDoCoMo = VCardConfig.isDoCoMo(vcardType);
-        mIsJapaneseMobilePhone = VCardConfig.needsToConvertPhoneticString(vcardType);
-        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);
-
-        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;
-            }
-            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;
-        }
-        clear();
-    }
-
-    public void clear() {
-        mBuilder = new StringBuilder();
-        mEndAppended = false;
-        appendLine(VCardConstants.PROPERTY_BEGIN, VCARD_DATA_VCARD);
-        if (mIsV30) {
-            appendLine(VCardConstants.PROPERTY_VERSION, VCardConstants.VERSION_V30);
-        } else {
-            appendLine(VCardConstants.PROPERTY_VERSION, VCardConstants.VERSION_V21);
-        }
-    }
-
-    private boolean containsNonEmptyName(final ContentValues contentValues) {
-        final String familyName = contentValues.getAsString(StructuredName.FAMILY_NAME);
-        final String middleName = contentValues.getAsString(StructuredName.MIDDLE_NAME);
-        final String givenName = contentValues.getAsString(StructuredName.GIVEN_NAME);
-        final String prefix = contentValues.getAsString(StructuredName.PREFIX);
-        final String suffix = contentValues.getAsString(StructuredName.SUFFIX);
-        final String phoneticFamilyName =
-                contentValues.getAsString(StructuredName.PHONETIC_FAMILY_NAME);
-        final String phoneticMiddleName =
-                contentValues.getAsString(StructuredName.PHONETIC_MIDDLE_NAME);
-        final String phoneticGivenName =
-                contentValues.getAsString(StructuredName.PHONETIC_GIVEN_NAME);
-        final String displayName = contentValues.getAsString(StructuredName.DISPLAY_NAME);
-        return !(TextUtils.isEmpty(familyName) && TextUtils.isEmpty(middleName) &&
-                TextUtils.isEmpty(givenName) && TextUtils.isEmpty(prefix) &&
-                TextUtils.isEmpty(suffix) && TextUtils.isEmpty(phoneticFamilyName) &&
-                TextUtils.isEmpty(phoneticMiddleName) && TextUtils.isEmpty(phoneticGivenName) &&
-                TextUtils.isEmpty(displayName));
-    }
-
-    private ContentValues getPrimaryContentValue(final List<ContentValues> contentValuesList) {
-        ContentValues primaryContentValues = null;
-        ContentValues subprimaryContentValues = null;
-        for (ContentValues contentValues : contentValuesList) {
-            if (contentValues == null){
-                continue;
-            }
-            Integer isSuperPrimary = contentValues.getAsInteger(StructuredName.IS_SUPER_PRIMARY);
-            if (isSuperPrimary != null && isSuperPrimary > 0) {
-                // We choose "super primary" ContentValues.
-                primaryContentValues = contentValues;
-                break;
-            } else if (primaryContentValues == null) {
-                // We choose the first "primary" ContentValues
-                // if "super primary" ContentValues does not exist.
-                final Integer isPrimary = contentValues.getAsInteger(StructuredName.IS_PRIMARY);
-                if (isPrimary != null && isPrimary > 0 &&
-                        containsNonEmptyName(contentValues)) {
-                    primaryContentValues = contentValues;
-                    // Do not break, since there may be ContentValues with "super primary"
-                    // afterword.
-                } else if (subprimaryContentValues == null &&
-                        containsNonEmptyName(contentValues)) {
-                    subprimaryContentValues = contentValues;
-                }
-            }
-        }
-
-        if (primaryContentValues == null) {
-            if (subprimaryContentValues != null) {
-                // We choose the first ContentValues if any "primary" ContentValues does not exist.
-                primaryContentValues = subprimaryContentValues;
-            } else {
-                Log.e(LOG_TAG, "All ContentValues given from database is empty.");
-                primaryContentValues = new ContentValues();
-            }
-        }
-
-        return primaryContentValues;
-    }
-
-    /**
-     * For safety, we'll emit just one value around StructuredName, as external importers
-     * may get confused with multiple "N", "FN", etc. properties, though it is valid in
-     * vCard spec.
-     */
-    public VCardBuilder appendNameProperties(final List<ContentValues> contentValuesList) {
-        if (contentValuesList == null || contentValuesList.isEmpty()) {
-            if (mIsDoCoMo) {
-                appendLine(VCardConstants.PROPERTY_N, "");
-            } else if (mIsV30) {
-                // vCard 3.0 requires "N" and "FN" properties.
-                appendLine(VCardConstants.PROPERTY_N, "");
-                appendLine(VCardConstants.PROPERTY_FN, "");
-            }
-            return this;
-        }
-
-        final ContentValues contentValues = getPrimaryContentValue(contentValuesList);
-        final String familyName = contentValues.getAsString(StructuredName.FAMILY_NAME);
-        final String middleName = contentValues.getAsString(StructuredName.MIDDLE_NAME);
-        final String givenName = contentValues.getAsString(StructuredName.GIVEN_NAME);
-        final String prefix = contentValues.getAsString(StructuredName.PREFIX);
-        final String suffix = contentValues.getAsString(StructuredName.SUFFIX);
-        final String displayName = contentValues.getAsString(StructuredName.DISPLAY_NAME);
-
-        if (!TextUtils.isEmpty(familyName) || !TextUtils.isEmpty(givenName)) {
-            final boolean reallyAppendCharsetParameterToName =
-                    shouldAppendCharsetParam(familyName, givenName, middleName, prefix, suffix);
-            final boolean reallyUseQuotedPrintableToName =
-                    (!mRefrainsQPToNameProperties &&
-                            !(VCardUtils.containsOnlyNonCrLfPrintableAscii(familyName) &&
-                                    VCardUtils.containsOnlyNonCrLfPrintableAscii(givenName) &&
-                                    VCardUtils.containsOnlyNonCrLfPrintableAscii(middleName) &&
-                                    VCardUtils.containsOnlyNonCrLfPrintableAscii(prefix) &&
-                                    VCardUtils.containsOnlyNonCrLfPrintableAscii(suffix)));
-
-            final String formattedName;
-            if (!TextUtils.isEmpty(displayName)) {
-                formattedName = displayName;
-            } else {
-                formattedName = VCardUtils.constructNameFromElements(
-                        VCardConfig.getNameOrderType(mVCardType),
-                        familyName, middleName, givenName, prefix, suffix);
-            }
-            final boolean reallyAppendCharsetParameterToFN =
-                    shouldAppendCharsetParam(formattedName);
-            final boolean reallyUseQuotedPrintableToFN =
-                    !mRefrainsQPToNameProperties &&
-                    !VCardUtils.containsOnlyNonCrLfPrintableAscii(formattedName);
-
-            final String encodedFamily;
-            final String encodedGiven;
-            final String encodedMiddle;
-            final String encodedPrefix;
-            final String encodedSuffix;
-            if (reallyUseQuotedPrintableToName) {
-                encodedFamily = encodeQuotedPrintable(familyName);
-                encodedGiven = encodeQuotedPrintable(givenName);
-                encodedMiddle = encodeQuotedPrintable(middleName);
-                encodedPrefix = encodeQuotedPrintable(prefix);
-                encodedSuffix = encodeQuotedPrintable(suffix);
-            } else {
-                encodedFamily = escapeCharacters(familyName);
-                encodedGiven = escapeCharacters(givenName);
-                encodedMiddle = escapeCharacters(middleName);
-                encodedPrefix = escapeCharacters(prefix);
-                encodedSuffix = escapeCharacters(suffix);
-            }
-
-            final String encodedFormattedname =
-                    (reallyUseQuotedPrintableToFN ?
-                            encodeQuotedPrintable(formattedName) : escapeCharacters(formattedName));
-
-            mBuilder.append(VCardConstants.PROPERTY_N);
-            if (mIsDoCoMo) {
-                if (reallyAppendCharsetParameterToName) {
-                    mBuilder.append(VCARD_PARAM_SEPARATOR);
-                    mBuilder.append(mVCardCharsetParameter);
-                }
-                if (reallyUseQuotedPrintableToName) {
-                    mBuilder.append(VCARD_PARAM_SEPARATOR);
-                    mBuilder.append(VCARD_PARAM_ENCODING_QP);
-                }
-                mBuilder.append(VCARD_DATA_SEPARATOR);
-                // DoCoMo phones require that all the elements in the "family name" field.
-                mBuilder.append(formattedName);
-                mBuilder.append(VCARD_ITEM_SEPARATOR);
-                mBuilder.append(VCARD_ITEM_SEPARATOR);
-                mBuilder.append(VCARD_ITEM_SEPARATOR);
-                mBuilder.append(VCARD_ITEM_SEPARATOR);
-            } else {
-                if (reallyAppendCharsetParameterToName) {
-                    mBuilder.append(VCARD_PARAM_SEPARATOR);
-                    mBuilder.append(mVCardCharsetParameter);
-                }
-                if (reallyUseQuotedPrintableToName) {
-                    mBuilder.append(VCARD_PARAM_SEPARATOR);
-                    mBuilder.append(VCARD_PARAM_ENCODING_QP);
-                }
-                mBuilder.append(VCARD_DATA_SEPARATOR);
-                mBuilder.append(encodedFamily);
-                mBuilder.append(VCARD_ITEM_SEPARATOR);
-                mBuilder.append(encodedGiven);
-                mBuilder.append(VCARD_ITEM_SEPARATOR);
-                mBuilder.append(encodedMiddle);
-                mBuilder.append(VCARD_ITEM_SEPARATOR);
-                mBuilder.append(encodedPrefix);
-                mBuilder.append(VCARD_ITEM_SEPARATOR);
-                mBuilder.append(encodedSuffix);
-            }
-            mBuilder.append(VCARD_END_OF_LINE);
-
-            // FN property
-            mBuilder.append(VCardConstants.PROPERTY_FN);
-            if (reallyAppendCharsetParameterToFN) {
-                mBuilder.append(VCARD_PARAM_SEPARATOR);
-                mBuilder.append(mVCardCharsetParameter);
-            }
-            if (reallyUseQuotedPrintableToFN) {
-                mBuilder.append(VCARD_PARAM_SEPARATOR);
-                mBuilder.append(VCARD_PARAM_ENCODING_QP);
-            }
-            mBuilder.append(VCARD_DATA_SEPARATOR);
-            mBuilder.append(encodedFormattedname);
-            mBuilder.append(VCARD_END_OF_LINE);
-        } else if (!TextUtils.isEmpty(displayName)) {
-            final boolean reallyUseQuotedPrintableToDisplayName =
-                (!mRefrainsQPToNameProperties &&
-                        !VCardUtils.containsOnlyNonCrLfPrintableAscii(displayName));
-            final String encodedDisplayName =
-                    reallyUseQuotedPrintableToDisplayName ?
-                            encodeQuotedPrintable(displayName) :
-                                escapeCharacters(displayName);
-
-            mBuilder.append(VCardConstants.PROPERTY_N);
-            if (shouldAppendCharsetParam(displayName)) {
-                mBuilder.append(VCARD_PARAM_SEPARATOR);
-                mBuilder.append(mVCardCharsetParameter);
-            }
-            if (reallyUseQuotedPrintableToDisplayName) {
-                mBuilder.append(VCARD_PARAM_SEPARATOR);
-                mBuilder.append(VCARD_PARAM_ENCODING_QP);
-            }
-            mBuilder.append(VCARD_DATA_SEPARATOR);
-            mBuilder.append(encodedDisplayName);
-            mBuilder.append(VCARD_ITEM_SEPARATOR);
-            mBuilder.append(VCARD_ITEM_SEPARATOR);
-            mBuilder.append(VCARD_ITEM_SEPARATOR);
-            mBuilder.append(VCARD_ITEM_SEPARATOR);
-            mBuilder.append(VCARD_END_OF_LINE);
-            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.
-            if (shouldAppendCharsetParam(displayName)) {
-                mBuilder.append(VCARD_PARAM_SEPARATOR);
-                mBuilder.append(mVCardCharsetParameter);
-            }
-            mBuilder.append(VCARD_DATA_SEPARATOR);
-            mBuilder.append(encodedDisplayName);
-            mBuilder.append(VCARD_END_OF_LINE);
-        } else if (mIsV30) {
-            // vCard 3.0 specification requires these fields.
-            appendLine(VCardConstants.PROPERTY_N, "");
-            appendLine(VCardConstants.PROPERTY_FN, "");
-        } else if (mIsDoCoMo) {
-            appendLine(VCardConstants.PROPERTY_N, "");
-        }
-
-        appendPhoneticNameFields(contentValues);
-        return this;
-    }
-
-    private void appendPhoneticNameFields(final ContentValues contentValues) {
-        final String phoneticFamilyName;
-        final String phoneticMiddleName;
-        final String phoneticGivenName;
-        {
-            final String tmpPhoneticFamilyName =
-                contentValues.getAsString(StructuredName.PHONETIC_FAMILY_NAME);
-            final String tmpPhoneticMiddleName =
-                contentValues.getAsString(StructuredName.PHONETIC_MIDDLE_NAME);
-            final String tmpPhoneticGivenName =
-                contentValues.getAsString(StructuredName.PHONETIC_GIVEN_NAME);
-            if (mNeedsToConvertPhoneticString) {
-                phoneticFamilyName = VCardUtils.toHalfWidthString(tmpPhoneticFamilyName);
-                phoneticMiddleName = VCardUtils.toHalfWidthString(tmpPhoneticMiddleName);
-                phoneticGivenName = VCardUtils.toHalfWidthString(tmpPhoneticGivenName);
-            } else {
-                phoneticFamilyName = tmpPhoneticFamilyName;
-                phoneticMiddleName = tmpPhoneticMiddleName;
-                phoneticGivenName = tmpPhoneticGivenName;
-            }
-        }
-
-        if (TextUtils.isEmpty(phoneticFamilyName)
-                && TextUtils.isEmpty(phoneticMiddleName)
-                && TextUtils.isEmpty(phoneticGivenName)) {
-            if (mIsDoCoMo) {
-                mBuilder.append(VCardConstants.PROPERTY_SOUND);
-                mBuilder.append(VCARD_PARAM_SEPARATOR);
-                mBuilder.append(VCardConstants.PARAM_TYPE_X_IRMC_N);
-                mBuilder.append(VCARD_DATA_SEPARATOR);
-                mBuilder.append(VCARD_ITEM_SEPARATOR);
-                mBuilder.append(VCARD_ITEM_SEPARATOR);
-                mBuilder.append(VCARD_ITEM_SEPARATOR);
-                mBuilder.append(VCARD_ITEM_SEPARATOR);
-                mBuilder.append(VCARD_END_OF_LINE);
-            }
-            return;
-        }
-
-        // Try to emit the field(s) related to phonetic name.
-        if (mIsV30) {
-            final String sortString = VCardUtils
-                    .constructNameFromElements(mVCardType,
-                            phoneticFamilyName, phoneticMiddleName, phoneticGivenName);
-            mBuilder.append(VCardConstants.PROPERTY_SORT_STRING);
-            if (shouldAppendCharsetParam(sortString)) {
-                mBuilder.append(VCARD_PARAM_SEPARATOR);
-                mBuilder.append(mVCardCharsetParameter);
-            }
-            mBuilder.append(VCARD_DATA_SEPARATOR);
-            mBuilder.append(escapeCharacters(sortString));
-            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
-            //       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.
-            //
-            //       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;;;
-            mBuilder.append(VCardConstants.PROPERTY_SOUND);
-            mBuilder.append(VCARD_PARAM_SEPARATOR);
-            mBuilder.append(VCardConstants.PARAM_TYPE_X_IRMC_N);
-
-            boolean reallyUseQuotedPrintable =
-                (!mRefrainsQPToNameProperties
-                        && !(VCardUtils.containsOnlyNonCrLfPrintableAscii(
-                                phoneticFamilyName)
-                                && VCardUtils.containsOnlyNonCrLfPrintableAscii(
-                                        phoneticMiddleName)
-                                && VCardUtils.containsOnlyNonCrLfPrintableAscii(
-                                        phoneticGivenName)));
-
-            final String encodedPhoneticFamilyName;
-            final String encodedPhoneticMiddleName;
-            final String encodedPhoneticGivenName;
-            if (reallyUseQuotedPrintable) {
-                encodedPhoneticFamilyName = encodeQuotedPrintable(phoneticFamilyName);
-                encodedPhoneticMiddleName = encodeQuotedPrintable(phoneticMiddleName);
-                encodedPhoneticGivenName = encodeQuotedPrintable(phoneticGivenName);
-            } else {
-                encodedPhoneticFamilyName = escapeCharacters(phoneticFamilyName);
-                encodedPhoneticMiddleName = escapeCharacters(phoneticMiddleName);
-                encodedPhoneticGivenName = escapeCharacters(phoneticGivenName);
-            }
-
-            if (shouldAppendCharsetParam(encodedPhoneticFamilyName,
-                    encodedPhoneticMiddleName, encodedPhoneticGivenName)) {
-                mBuilder.append(VCARD_PARAM_SEPARATOR);
-                mBuilder.append(mVCardCharsetParameter);
-            }
-            mBuilder.append(VCARD_DATA_SEPARATOR);
-            {
-                boolean first = true;
-                if (!TextUtils.isEmpty(encodedPhoneticFamilyName)) {
-                    mBuilder.append(encodedPhoneticFamilyName);
-                    first = false;
-                }
-                if (!TextUtils.isEmpty(encodedPhoneticMiddleName)) {
-                    if (first) {
-                        first = false;
-                    } else {
-                        mBuilder.append(' ');
-                    }
-                    mBuilder.append(encodedPhoneticMiddleName);
-                }
-                if (!TextUtils.isEmpty(encodedPhoneticGivenName)) {
-                    if (!first) {
-                        mBuilder.append(' ');
-                    }
-                    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_END_OF_LINE);
-        }
-
-        if (mUsesDefactProperty) {
-            if (!TextUtils.isEmpty(phoneticGivenName)) {
-                final boolean reallyUseQuotedPrintable =
-                    (mShouldUseQuotedPrintable &&
-                            !VCardUtils.containsOnlyNonCrLfPrintableAscii(phoneticGivenName));
-                final String encodedPhoneticGivenName;
-                if (reallyUseQuotedPrintable) {
-                    encodedPhoneticGivenName = encodeQuotedPrintable(phoneticGivenName);
-                } else {
-                    encodedPhoneticGivenName = escapeCharacters(phoneticGivenName);
-                }
-                mBuilder.append(VCardConstants.PROPERTY_X_PHONETIC_FIRST_NAME);
-                if (shouldAppendCharsetParam(phoneticGivenName)) {
-                    mBuilder.append(VCARD_PARAM_SEPARATOR);
-                    mBuilder.append(mVCardCharsetParameter);
-                }
-                if (reallyUseQuotedPrintable) {
-                    mBuilder.append(VCARD_PARAM_SEPARATOR);
-                    mBuilder.append(VCARD_PARAM_ENCODING_QP);
-                }
-                mBuilder.append(VCARD_DATA_SEPARATOR);
-                mBuilder.append(encodedPhoneticGivenName);
-                mBuilder.append(VCARD_END_OF_LINE);
-            }
-            if (!TextUtils.isEmpty(phoneticMiddleName)) {
-                final boolean reallyUseQuotedPrintable =
-                    (mShouldUseQuotedPrintable &&
-                            !VCardUtils.containsOnlyNonCrLfPrintableAscii(phoneticMiddleName));
-                final String encodedPhoneticMiddleName;
-                if (reallyUseQuotedPrintable) {
-                    encodedPhoneticMiddleName = encodeQuotedPrintable(phoneticMiddleName);
-                } else {
-                    encodedPhoneticMiddleName = escapeCharacters(phoneticMiddleName);
-                }
-                mBuilder.append(VCardConstants.PROPERTY_X_PHONETIC_MIDDLE_NAME);
-                if (shouldAppendCharsetParam(phoneticMiddleName)) {
-                    mBuilder.append(VCARD_PARAM_SEPARATOR);
-                    mBuilder.append(mVCardCharsetParameter);
-                }
-                if (reallyUseQuotedPrintable) {
-                    mBuilder.append(VCARD_PARAM_SEPARATOR);
-                    mBuilder.append(VCARD_PARAM_ENCODING_QP);
-                }
-                mBuilder.append(VCARD_DATA_SEPARATOR);
-                mBuilder.append(encodedPhoneticMiddleName);
-                mBuilder.append(VCARD_END_OF_LINE);
-            }
-            if (!TextUtils.isEmpty(phoneticFamilyName)) {
-                final boolean reallyUseQuotedPrintable =
-                    (mShouldUseQuotedPrintable &&
-                            !VCardUtils.containsOnlyNonCrLfPrintableAscii(phoneticFamilyName));
-                final String encodedPhoneticFamilyName;
-                if (reallyUseQuotedPrintable) {
-                    encodedPhoneticFamilyName = encodeQuotedPrintable(phoneticFamilyName);
-                } else {
-                    encodedPhoneticFamilyName = escapeCharacters(phoneticFamilyName);
-                }
-                mBuilder.append(VCardConstants.PROPERTY_X_PHONETIC_LAST_NAME);
-                if (shouldAppendCharsetParam(phoneticFamilyName)) {
-                    mBuilder.append(VCARD_PARAM_SEPARATOR);
-                    mBuilder.append(mVCardCharsetParameter);
-                }
-                if (reallyUseQuotedPrintable) {
-                    mBuilder.append(VCARD_PARAM_SEPARATOR);
-                    mBuilder.append(VCARD_PARAM_ENCODING_QP);
-                }
-                mBuilder.append(VCARD_DATA_SEPARATOR);
-                mBuilder.append(encodedPhoneticFamilyName);
-                mBuilder.append(VCARD_END_OF_LINE);
-            }
-        }
-    }
-
-    public VCardBuilder appendNickNames(final List<ContentValues> contentValuesList) {
-        final boolean useAndroidProperty;
-        if (mIsV30) {
-            useAndroidProperty = false;
-        } else if (mUsesAndroidProperty) {
-            useAndroidProperty = true;
-        } else {
-            // There's no way to add this field.
-            return this;
-        }
-        if (contentValuesList != null) {
-            for (ContentValues contentValues : contentValuesList) {
-                final String nickname = contentValues.getAsString(Nickname.NAME);
-                if (TextUtils.isEmpty(nickname)) {
-                    continue;
-                }
-                if (useAndroidProperty) {
-                    appendAndroidSpecificProperty(Nickname.CONTENT_ITEM_TYPE, contentValues);
-                } else {
-                    appendLineWithCharsetAndQPDetection(VCardConstants.PROPERTY_NICKNAME, nickname);
-                }
-            }
-        }
-        return this;
-    }
-
-    public VCardBuilder appendPhones(final List<ContentValues> contentValuesList) {
-        boolean phoneLineExists = false;
-        if (contentValuesList != null) {
-            Set<String> phoneSet = new HashSet<String>();
-            for (ContentValues contentValues : contentValuesList) {
-                final Integer typeAsObject = contentValues.getAsInteger(Phone.TYPE);
-                final String label = contentValues.getAsString(Phone.LABEL);
-                final Integer isPrimaryAsInteger = contentValues.getAsInteger(Phone.IS_PRIMARY);
-                final boolean isPrimary = (isPrimaryAsInteger != null ?
-                        (isPrimaryAsInteger > 0) : false);
-                String phoneNumber = contentValues.getAsString(Phone.NUMBER);
-                if (phoneNumber != null) {
-                    phoneNumber = phoneNumber.trim();
-                }
-                if (TextUtils.isEmpty(phoneNumber)) {
-                    continue;
-                }
-
-                // 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 {
-                    final List<String> phoneNumberList = splitAndTrimPhoneNumbers(phoneNumber);
-                    if (phoneNumberList.isEmpty()) {
-                        continue;
-                    }
-                    phoneLineExists = true;
-                    for (String actualPhoneNumber : phoneNumberList) {
-                        if (!phoneSet.contains(actualPhoneNumber)) {
-                            final int format = VCardUtils.getPhoneNumberFormat(mVCardType);
-                            final String formattedPhoneNumber =
-                                    PhoneNumberUtils.formatNumber(actualPhoneNumber, format);
-                            phoneSet.add(actualPhoneNumber);
-                            appendTelLine(type, label, formattedPhoneNumber, isPrimary);
-                        }
-                    }  // for (String actualPhoneNumber : phoneNumberList) {
-                }
-            }
-        }
-
-        if (!phoneLineExists && mIsDoCoMo) {
-            appendTelLine(Phone.TYPE_HOME, "", "", false);
-        }
-
-        return this;
-    }
-
-    /**
-     * <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);
-            if (Character.isDigit(ch) || ch == '+') {
-                builder.append(ch);
-            } else if ((ch == ';' || ch == '\n') && builder.length() > 0) {
-                phoneList.add(builder.toString());
-                builder = new StringBuilder();
-            }
-        }
-        if (builder.length() > 0) {
-            phoneList.add(builder.toString());
-        }
-
-        return phoneList;
-    }
-
-    public VCardBuilder appendEmails(final List<ContentValues> contentValuesList) {
-        boolean emailAddressExists = false;
-        if (contentValuesList != null) {
-            final Set<String> addressSet = new HashSet<String>();
-            for (ContentValues contentValues : contentValuesList) {
-                String emailAddress = contentValues.getAsString(Email.DATA);
-                if (emailAddress != null) {
-                    emailAddress = emailAddress.trim();
-                }
-                if (TextUtils.isEmpty(emailAddress)) {
-                    continue;
-                }
-                Integer typeAsObject = contentValues.getAsInteger(Email.TYPE);
-                final int type = (typeAsObject != null ?
-                        typeAsObject : DEFAULT_EMAIL_TYPE);
-                final String label = contentValues.getAsString(Email.LABEL);
-                Integer isPrimaryAsInteger = contentValues.getAsInteger(Email.IS_PRIMARY);
-                final boolean isPrimary = (isPrimaryAsInteger != null ?
-                        (isPrimaryAsInteger > 0) : false);
-                emailAddressExists = true;
-                if (!addressSet.contains(emailAddress)) {
-                    addressSet.add(emailAddress);
-                    appendEmailLine(type, label, emailAddress, isPrimary);
-                }
-            }
-        }
-
-        if (!emailAddressExists && mIsDoCoMo) {
-            appendEmailLine(Email.TYPE_HOME, "", "", false);
-        }
-
-        return this;
-    }
-
-    public VCardBuilder appendPostals(final List<ContentValues> contentValuesList) {
-        if (contentValuesList == null || contentValuesList.isEmpty()) {
-            if (mIsDoCoMo) {
-                mBuilder.append(VCardConstants.PROPERTY_ADR);
-                mBuilder.append(VCARD_PARAM_SEPARATOR);
-                mBuilder.append(VCardConstants.PARAM_TYPE_HOME);
-                mBuilder.append(VCARD_DATA_SEPARATOR);
-                mBuilder.append(VCARD_END_OF_LINE);
-            }
-        } else {
-            if (mIsDoCoMo) {
-                appendPostalsForDoCoMo(contentValuesList);
-            } else {
-                appendPostalsForGeneric(contentValuesList);
-            }
-        }
-
-        return this;
-    }
-
-    private static final Map<Integer, Integer> sPostalTypePriorityMap;
-
-    static {
-        sPostalTypePriorityMap = new HashMap<Integer, Integer>();
-        sPostalTypePriorityMap.put(StructuredPostal.TYPE_HOME, 0);
-        sPostalTypePriorityMap.put(StructuredPostal.TYPE_WORK, 1);
-        sPostalTypePriorityMap.put(StructuredPostal.TYPE_OTHER, 2);
-        sPostalTypePriorityMap.put(StructuredPostal.TYPE_CUSTOM, 3);
-    }
-
-    /**
-     * Tries to append just one line. If there's no appropriate address
-     * information, append an empty line.
-     */
-    private void appendPostalsForDoCoMo(final List<ContentValues> contentValuesList) {
-        int currentPriority = Integer.MAX_VALUE;
-        int currentType = Integer.MAX_VALUE;
-        ContentValues currentContentValues = null;
-        for (final ContentValues contentValues : contentValuesList) {
-            if (contentValues == null) {
-                continue;
-            }
-            final Integer typeAsInteger = contentValues.getAsInteger(StructuredPostal.TYPE);
-            final Integer priorityAsInteger = sPostalTypePriorityMap.get(typeAsInteger);
-            final int priority =
-                    (priorityAsInteger != null ? priorityAsInteger : Integer.MAX_VALUE);
-            if (priority < currentPriority) {
-                currentPriority = priority;
-                currentType = typeAsInteger;
-                currentContentValues = contentValues;
-                if (priority == 0) {
-                    break;
-                }
-            }
-        }
-
-        if (currentContentValues == null) {
-            Log.w(LOG_TAG, "Should not come here. Must have at least one postal data.");
-            return;
-        }
-
-        final String label = currentContentValues.getAsString(StructuredPostal.LABEL);
-        appendPostalLine(currentType, label, currentContentValues, false, true);
-    }
-
-    private void appendPostalsForGeneric(final List<ContentValues> contentValuesList) {
-        for (final ContentValues contentValues : contentValuesList) {
-            if (contentValues == null) {
-                continue;
-            }
-            final Integer typeAsInteger = contentValues.getAsInteger(StructuredPostal.TYPE);
-            final int type = (typeAsInteger != null ?
-                    typeAsInteger : DEFAULT_POSTAL_TYPE);
-            final String label = contentValues.getAsString(StructuredPostal.LABEL);
-            final Integer isPrimaryAsInteger =
-                contentValues.getAsInteger(StructuredPostal.IS_PRIMARY);
-            final boolean isPrimary = (isPrimaryAsInteger != null ?
-                    (isPrimaryAsInteger > 0) : false);
-            appendPostalLine(type, label, contentValues, isPrimary, false);
-        }
-    }
-
-    private static class PostalStruct {
-        final boolean reallyUseQuotedPrintable;
-        final boolean appendCharset;
-        final String addressData;
-        public PostalStruct(final boolean reallyUseQuotedPrintable,
-                final boolean appendCharset, final String addressData) {
-            this.reallyUseQuotedPrintable = reallyUseQuotedPrintable;
-            this.appendCharset = appendCharset;
-            this.addressData = addressData;
-        }
-    }
-
-    /**
-     * @return null when there's no information available to construct the data.
-     */
-    private PostalStruct tryConstructPostalStruct(ContentValues contentValues) {
-        // adr-value    = 0*6(text-value ";") text-value
-        //              ; PO Box, Extended Address, Street, Locality, Region, Postal
-        //              ; Code, Country Name
-        final String rawPoBox = contentValues.getAsString(StructuredPostal.POBOX);
-        final String rawNeighborhood = contentValues.getAsString(StructuredPostal.NEIGHBORHOOD);
-        final String rawStreet = contentValues.getAsString(StructuredPostal.STREET);
-        final String rawLocality = contentValues.getAsString(StructuredPostal.CITY);
-        final String rawRegion = contentValues.getAsString(StructuredPostal.REGION);
-        final String rawPostalCode = contentValues.getAsString(StructuredPostal.POSTCODE);
-        final String rawCountry = contentValues.getAsString(StructuredPostal.COUNTRY);
-        final String[] rawAddressArray = new String[]{
-                rawPoBox, rawNeighborhood, rawStreet, rawLocality,
-                rawRegion, rawPostalCode, rawCountry};
-        if (!VCardUtils.areAllEmpty(rawAddressArray)) {
-            final boolean reallyUseQuotedPrintable =
-                (mShouldUseQuotedPrintable &&
-                        !VCardUtils.containsOnlyNonCrLfPrintableAscii(rawAddressArray));
-            final boolean appendCharset =
-                !VCardUtils.containsOnlyPrintableAscii(rawAddressArray);
-            final String encodedPoBox;
-            final String encodedStreet;
-            final String encodedLocality;
-            final String encodedRegion;
-            final String encodedPostalCode;
-            final String encodedCountry;
-            final String encodedNeighborhood;
-
-            final String rawLocality2;
-            // This looks inefficient since we encode rawLocality and rawNeighborhood twice,
-            // but this is intentional.
-            //
-            // QP encoding may add line feeds when needed and the result of
-            // - encodeQuotedPrintable(rawLocality + " " + rawNeighborhood)
-            // may be different from
-            // - encodedLocality + " " + encodedNeighborhood.
-            //
-            // We use safer way.
-            if (TextUtils.isEmpty(rawLocality)) {
-                if (TextUtils.isEmpty(rawNeighborhood)) {
-                    rawLocality2 = "";
-                } else {
-                    rawLocality2 = rawNeighborhood;
-                }
-            } else {
-                if (TextUtils.isEmpty(rawNeighborhood)) {
-                    rawLocality2 = rawLocality;
-                } else {
-                    rawLocality2 = rawLocality + " " + rawNeighborhood;
-                }
-            }
-            if (reallyUseQuotedPrintable) {
-                encodedPoBox = encodeQuotedPrintable(rawPoBox);
-                encodedStreet = encodeQuotedPrintable(rawStreet);
-                encodedLocality = encodeQuotedPrintable(rawLocality2);
-                encodedRegion = encodeQuotedPrintable(rawRegion);
-                encodedPostalCode = encodeQuotedPrintable(rawPostalCode);
-                encodedCountry = encodeQuotedPrintable(rawCountry);
-            } else {
-                encodedPoBox = escapeCharacters(rawPoBox);
-                encodedStreet = escapeCharacters(rawStreet);
-                encodedLocality = escapeCharacters(rawLocality2);
-                encodedRegion = escapeCharacters(rawRegion);
-                encodedPostalCode = escapeCharacters(rawPostalCode);
-                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);
-            return new PostalStruct(
-                    reallyUseQuotedPrintable, appendCharset, addressBuffer.toString());
-        } else {  // VCardUtils.areAllEmpty(rawAddressArray) == true
-            // Try to use FORMATTED_ADDRESS instead.
-            final String rawFormattedAddress =
-                contentValues.getAsString(StructuredPostal.FORMATTED_ADDRESS);
-            if (TextUtils.isEmpty(rawFormattedAddress)) {
-                return null;
-            }
-            final boolean reallyUseQuotedPrintable =
-                (mShouldUseQuotedPrintable &&
-                        !VCardUtils.containsOnlyNonCrLfPrintableAscii(rawFormattedAddress));
-            final boolean appendCharset =
-                !VCardUtils.containsOnlyPrintableAscii(rawFormattedAddress);
-            final String encodedFormattedAddress;
-            if (reallyUseQuotedPrintable) {
-                encodedFormattedAddress = encodeQuotedPrintable(rawFormattedAddress);
-            } else {
-                encodedFormattedAddress = escapeCharacters(rawFormattedAddress);
-            }
-
-            // 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);
-            return new PostalStruct(
-                    reallyUseQuotedPrintable, appendCharset, addressBuffer.toString());
-        }
-    }
-
-    public VCardBuilder appendIms(final List<ContentValues> contentValuesList) {
-        if (contentValuesList != null) {
-            for (ContentValues contentValues : contentValuesList) {
-                final Integer protocolAsObject = contentValues.getAsInteger(Im.PROTOCOL);
-                if (protocolAsObject == null) {
-                    continue;
-                }
-                final String propertyName = VCardUtils.getPropertyNameForIm(protocolAsObject);
-                if (propertyName == null) {
-                    continue;
-                }
-                String data = contentValues.getAsString(Im.DATA);
-                if (data != null) {
-                    data = data.trim();
-                }
-                if (TextUtils.isEmpty(data)) {
-                    continue;
-                }
-                final String typeAsString;
-                {
-                    final Integer typeAsInteger = contentValues.getAsInteger(Im.TYPE);
-                    switch (typeAsInteger != null ? typeAsInteger : Im.TYPE_OTHER) {
-                        case Im.TYPE_HOME: {
-                            typeAsString = VCardConstants.PARAM_TYPE_HOME;
-                            break;
-                        }
-                        case Im.TYPE_WORK: {
-                            typeAsString = VCardConstants.PARAM_TYPE_WORK;
-                            break;
-                        }
-                        case Im.TYPE_CUSTOM: {
-                            final String label = contentValues.getAsString(Im.LABEL);
-                            typeAsString = (label != null ? "X-" + label : null);
-                            break;
-                        }
-                        case Im.TYPE_OTHER:  // Ignore
-                        default: {
-                            typeAsString = null;
-                            break;
-                        }
-                    }
-                }
-
-                final List<String> parameterList = new ArrayList<String>();
-                if (!TextUtils.isEmpty(typeAsString)) {
-                    parameterList.add(typeAsString);
-                }
-                final Integer isPrimaryAsInteger = contentValues.getAsInteger(Im.IS_PRIMARY);
-                final boolean isPrimary = (isPrimaryAsInteger != null ?
-                        (isPrimaryAsInteger > 0) : false);
-                if (isPrimary) {
-                    parameterList.add(VCardConstants.PARAM_TYPE_PREF);
-                }
-
-                appendLineWithCharsetAndQPDetection(propertyName, parameterList, data);
-            }
-        }
-        return this;
-    }
-
-    public VCardBuilder appendWebsites(final List<ContentValues> contentValuesList) {
-        if (contentValuesList != null) {
-            for (ContentValues contentValues : contentValuesList) {
-                String website = contentValues.getAsString(Website.URL);
-                if (website != null) {
-                    website = website.trim();
-                }
-
-                // Note: vCard 3.0 does not allow any parameter addition toward "URL"
-                //       property, while there's no document in vCard 2.1.
-                if (!TextUtils.isEmpty(website)) {
-                    appendLineWithCharsetAndQPDetection(VCardConstants.PROPERTY_URL, website);
-                }
-            }
-        }
-        return this;
-    }
-
-    public VCardBuilder appendOrganizations(final List<ContentValues> contentValuesList) {
-        if (contentValuesList != null) {
-            for (ContentValues contentValues : contentValuesList) {
-                String company = contentValues.getAsString(Organization.COMPANY);
-                if (company != null) {
-                    company = company.trim();
-                }
-                String department = contentValues.getAsString(Organization.DEPARTMENT);
-                if (department != null) {
-                    department = department.trim();
-                }
-                String title = contentValues.getAsString(Organization.TITLE);
-                if (title != null) {
-                    title = title.trim();
-                }
-
-                StringBuilder orgBuilder = new StringBuilder();
-                if (!TextUtils.isEmpty(company)) {
-                    orgBuilder.append(company);
-                }
-                if (!TextUtils.isEmpty(department)) {
-                    if (orgBuilder.length() > 0) {
-                        orgBuilder.append(';');
-                    }
-                    orgBuilder.append(department);
-                }
-                final String orgline = orgBuilder.toString();
-                appendLine(VCardConstants.PROPERTY_ORG, orgline,
-                        !VCardUtils.containsOnlyPrintableAscii(orgline),
-                        (mShouldUseQuotedPrintable &&
-                                !VCardUtils.containsOnlyNonCrLfPrintableAscii(orgline)));
-
-                if (!TextUtils.isEmpty(title)) {
-                    appendLine(VCardConstants.PROPERTY_TITLE, title,
-                            !VCardUtils.containsOnlyPrintableAscii(title),
-                            (mShouldUseQuotedPrintable &&
-                                    !VCardUtils.containsOnlyNonCrLfPrintableAscii(title)));
-                }
-            }
-        }
-        return this;
-    }
-
-    public VCardBuilder appendPhotos(final List<ContentValues> contentValuesList) {
-        if (contentValuesList != null) {
-            for (ContentValues contentValues : contentValuesList) {
-                if (contentValues == null) {
-                    continue;
-                }
-                byte[] data = contentValues.getAsByteArray(Photo.PHOTO);
-                if (data == null) {
-                    continue;
-                }
-                final String photoType = VCardUtils.guessImageType(data);
-                if (photoType == null) {
-                    Log.d(LOG_TAG, "Unknown photo type. Ignored.");
-                    continue;
-                }
-                final String photoString = new String(Base64.encodeBase64(data));
-                if (!TextUtils.isEmpty(photoString)) {
-                    appendPhotoLine(photoString, photoType);
-                }
-            }
-        }
-        return this;
-    }
-
-    public VCardBuilder appendNotes(final List<ContentValues> contentValuesList) {
-        if (contentValuesList != null) {
-            if (mOnlyOneNoteFieldIsAvailable) {
-                final StringBuilder noteBuilder = new StringBuilder();
-                boolean first = true;
-                for (final ContentValues contentValues : contentValuesList) {
-                    String note = contentValues.getAsString(Note.NOTE);
-                    if (note == null) {
-                        note = "";
-                    }
-                    if (note.length() > 0) {
-                        if (first) {
-                            first = false;
-                        } else {
-                            noteBuilder.append('\n');
-                        }
-                        noteBuilder.append(note);
-                    }
-                }
-                final String noteStr = noteBuilder.toString();
-                // This means we scan noteStr completely twice, which is redundant.
-                // But for now, we assume this is not so time-consuming..
-                final boolean shouldAppendCharsetInfo =
-                    !VCardUtils.containsOnlyPrintableAscii(noteStr);
-                final boolean reallyUseQuotedPrintable =
-                        (mShouldUseQuotedPrintable &&
-                            !VCardUtils.containsOnlyNonCrLfPrintableAscii(noteStr));
-                appendLine(VCardConstants.PROPERTY_NOTE, noteStr,
-                        shouldAppendCharsetInfo, reallyUseQuotedPrintable);
-            } else {
-                for (ContentValues contentValues : contentValuesList) {
-                    final String noteStr = contentValues.getAsString(Note.NOTE);
-                    if (!TextUtils.isEmpty(noteStr)) {
-                        final boolean shouldAppendCharsetInfo =
-                                !VCardUtils.containsOnlyPrintableAscii(noteStr);
-                        final boolean reallyUseQuotedPrintable =
-                                (mShouldUseQuotedPrintable &&
-                                    !VCardUtils.containsOnlyNonCrLfPrintableAscii(noteStr));
-                        appendLine(VCardConstants.PROPERTY_NOTE, noteStr,
-                                shouldAppendCharsetInfo, reallyUseQuotedPrintable);
-                    }
-                }
-            }
-        }
-        return this;
-    }
-
-    public VCardBuilder appendEvents(final List<ContentValues> contentValuesList) {
-        if (contentValuesList != null) {
-            String primaryBirthday = null;
-            String secondaryBirthday = null;
-            for (final ContentValues contentValues : contentValuesList) {
-                if (contentValues == null) {
-                    continue;
-                }
-                final Integer eventTypeAsInteger = contentValues.getAsInteger(Event.TYPE);
-                final int eventType;
-                if (eventTypeAsInteger != null) {
-                    eventType = eventTypeAsInteger;
-                } else {
-                    eventType = Event.TYPE_OTHER;
-                }
-                if (eventType == Event.TYPE_BIRTHDAY) {
-                    final String birthdayCandidate = contentValues.getAsString(Event.START_DATE);
-                    if (birthdayCandidate == null) {
-                        continue;
-                    }
-                    final Integer isSuperPrimaryAsInteger =
-                        contentValues.getAsInteger(Event.IS_SUPER_PRIMARY);
-                    final boolean isSuperPrimary = (isSuperPrimaryAsInteger != null ?
-                            (isSuperPrimaryAsInteger > 0) : false);
-                    if (isSuperPrimary) {
-                        // "super primary" birthday should the prefered one.
-                        primaryBirthday = birthdayCandidate;
-                        break;
-                    }
-                    final Integer isPrimaryAsInteger =
-                        contentValues.getAsInteger(Event.IS_PRIMARY);
-                    final boolean isPrimary = (isPrimaryAsInteger != null ?
-                            (isPrimaryAsInteger > 0) : false);
-                    if (isPrimary) {
-                        // We don't break here since "super primary" birthday may exist later.
-                        primaryBirthday = birthdayCandidate;
-                    } else if (secondaryBirthday == null) {
-                        // First entry is set to the "secondary" candidate.
-                        secondaryBirthday = birthdayCandidate;
-                    }
-                } else if (mUsesAndroidProperty) {
-                    // Event types other than Birthday is not supported by vCard.
-                    appendAndroidSpecificProperty(Event.CONTENT_ITEM_TYPE, contentValues);
-                }
-            }
-            if (primaryBirthday != null) {
-                appendLineWithCharsetAndQPDetection(VCardConstants.PROPERTY_BDAY,
-                        primaryBirthday.trim());
-            } else if (secondaryBirthday != null){
-                appendLineWithCharsetAndQPDetection(VCardConstants.PROPERTY_BDAY,
-                        secondaryBirthday.trim());
-            }
-        }
-        return this;
-    }
-
-    public VCardBuilder appendRelation(final List<ContentValues> contentValuesList) {
-        if (mUsesAndroidProperty && contentValuesList != null) {
-            for (final ContentValues contentValues : contentValuesList) {
-                if (contentValues == null) {
-                    continue;
-                }
-                appendAndroidSpecificProperty(Relation.CONTENT_ITEM_TYPE, contentValues);
-            }
-        }
-        return this;
-    }
-
-    public void appendPostalLine(final int type, final String label,
-            final ContentValues contentValues,
-            final boolean isPrimary, final boolean emitLineEveryTime) {
-        final boolean reallyUseQuotedPrintable;
-        final boolean appendCharset;
-        final String addressValue;
-        {
-            PostalStruct postalStruct = tryConstructPostalStruct(contentValues);
-            if (postalStruct == null) {
-                if (emitLineEveryTime) {
-                    reallyUseQuotedPrintable = false;
-                    appendCharset = false;
-                    addressValue = "";
-                } else {
-                    return;
-                }
-            } else {
-                reallyUseQuotedPrintable = postalStruct.reallyUseQuotedPrintable;
-                appendCharset = postalStruct.appendCharset;
-                addressValue = postalStruct.addressData;
-            }
-        }
-
-        List<String> parameterList = new ArrayList<String>();
-        if (isPrimary) {
-            parameterList.add(VCardConstants.PARAM_TYPE_PREF);
-        }
-        switch (type) {
-            case StructuredPostal.TYPE_HOME: {
-                parameterList.add(VCardConstants.PARAM_TYPE_HOME);
-                break;
-            }
-            case StructuredPostal.TYPE_WORK: {
-                parameterList.add(VCardConstants.PARAM_TYPE_WORK);
-                break;
-            }
-            case StructuredPostal.TYPE_CUSTOM: {
-                if (!TextUtils.isEmpty(label)
-                        && VCardUtils.containsOnlyAlphaDigitHyphen(label)) {
-                    // We're not sure whether the label is valid in the spec
-                    // ("IANA-token" in the vCard 3.0 is unclear...)
-                    // Just  for safety, we add "X-" at the beggining of each label.
-                    // Also checks the label obeys with vCard 3.0 spec.
-                    parameterList.add("X-" + label);
-                }
-                break;
-            }
-            case StructuredPostal.TYPE_OTHER: {
-                break;
-            }
-            default: {
-                Log.e(LOG_TAG, "Unknown StructuredPostal type: " + type);
-                break;
-            }
-        }
-
-        mBuilder.append(VCardConstants.PROPERTY_ADR);
-        if (!parameterList.isEmpty()) {
-            mBuilder.append(VCARD_PARAM_SEPARATOR);
-            appendTypeParameters(parameterList);
-        }
-        if (appendCharset) {
-            // Strictly, vCard 3.0 does not allow exporters to emit charset information,
-            // but we will add it since the information should be useful for importers,
-            //
-            // Assume no parser does not emit error with this parameter in vCard 3.0.
-            mBuilder.append(VCARD_PARAM_SEPARATOR);
-            mBuilder.append(mVCardCharsetParameter);
-        }
-        if (reallyUseQuotedPrintable) {
-            mBuilder.append(VCARD_PARAM_SEPARATOR);
-            mBuilder.append(VCARD_PARAM_ENCODING_QP);
-        }
-        mBuilder.append(VCARD_DATA_SEPARATOR);
-        mBuilder.append(addressValue);
-        mBuilder.append(VCARD_END_OF_LINE);
-    }
-
-    public void appendEmailLine(final int type, final String label,
-            final String rawValue, final boolean isPrimary) {
-        final String typeAsString;
-        switch (type) {
-            case Email.TYPE_CUSTOM: {
-                if (VCardUtils.isMobilePhoneLabel(label)) {
-                    typeAsString = VCardConstants.PARAM_TYPE_CELL;
-                } else if (!TextUtils.isEmpty(label)
-                        && VCardUtils.containsOnlyAlphaDigitHyphen(label)) {
-                    typeAsString = "X-" + label;
-                } else {
-                    typeAsString = null;
-                }
-                break;
-            }
-            case Email.TYPE_HOME: {
-                typeAsString = VCardConstants.PARAM_TYPE_HOME;
-                break;
-            }
-            case Email.TYPE_WORK: {
-                typeAsString = VCardConstants.PARAM_TYPE_WORK;
-                break;
-            }
-            case Email.TYPE_OTHER: {
-                typeAsString = null;
-                break;
-            }
-            case Email.TYPE_MOBILE: {
-                typeAsString = VCardConstants.PARAM_TYPE_CELL;
-                break;
-            }
-            default: {
-                Log.e(LOG_TAG, "Unknown Email type: " + type);
-                typeAsString = null;
-                break;
-            }
-        }
-
-        final List<String> parameterList = new ArrayList<String>();
-        if (isPrimary) {
-            parameterList.add(VCardConstants.PARAM_TYPE_PREF);
-        }
-        if (!TextUtils.isEmpty(typeAsString)) {
-            parameterList.add(typeAsString);
-        }
-
-        appendLineWithCharsetAndQPDetection(VCardConstants.PROPERTY_EMAIL, parameterList,
-                rawValue);
-    }
-
-    public void appendTelLine(final Integer typeAsInteger, final String label,
-            final String encodedValue, boolean isPrimary) {
-        mBuilder.append(VCardConstants.PROPERTY_TEL);
-        mBuilder.append(VCARD_PARAM_SEPARATOR);
-
-        final int type;
-        if (typeAsInteger == null) {
-            type = Phone.TYPE_OTHER;
-        } else {
-            type = typeAsInteger;
-        }
-
-        ArrayList<String> parameterList = new ArrayList<String>();
-        switch (type) {
-            case Phone.TYPE_HOME: {
-                parameterList.addAll(
-                        Arrays.asList(VCardConstants.PARAM_TYPE_HOME));
-                break;
-            }
-            case Phone.TYPE_WORK: {
-                parameterList.addAll(
-                        Arrays.asList(VCardConstants.PARAM_TYPE_WORK));
-                break;
-            }
-            case Phone.TYPE_FAX_HOME: {
-                parameterList.addAll(
-                        Arrays.asList(VCardConstants.PARAM_TYPE_HOME, VCardConstants.PARAM_TYPE_FAX));
-                break;
-            }
-            case Phone.TYPE_FAX_WORK: {
-                parameterList.addAll(
-                        Arrays.asList(VCardConstants.PARAM_TYPE_WORK, VCardConstants.PARAM_TYPE_FAX));
-                break;
-            }
-            case Phone.TYPE_MOBILE: {
-                parameterList.add(VCardConstants.PARAM_TYPE_CELL);
-                break;
-            }
-            case Phone.TYPE_PAGER: {
-                if (mIsDoCoMo) {
-                    // Not sure about the reason, but previous implementation had
-                    // used "VOICE" instead of "PAGER"
-                    parameterList.add(VCardConstants.PARAM_TYPE_VOICE);
-                } else {
-                    parameterList.add(VCardConstants.PARAM_TYPE_PAGER);
-                }
-                break;
-            }
-            case Phone.TYPE_OTHER: {
-                parameterList.add(VCardConstants.PARAM_TYPE_VOICE);
-                break;
-            }
-            case Phone.TYPE_CAR: {
-                parameterList.add(VCardConstants.PARAM_TYPE_CAR);
-                break;
-            }
-            case Phone.TYPE_COMPANY_MAIN: {
-                // There's no relevant field in vCard (at least 2.1).
-                parameterList.add(VCardConstants.PARAM_TYPE_WORK);
-                isPrimary = true;
-                break;
-            }
-            case Phone.TYPE_ISDN: {
-                parameterList.add(VCardConstants.PARAM_TYPE_ISDN);
-                break;
-            }
-            case Phone.TYPE_MAIN: {
-                isPrimary = true;
-                break;
-            }
-            case Phone.TYPE_OTHER_FAX: {
-                parameterList.add(VCardConstants.PARAM_TYPE_FAX);
-                break;
-            }
-            case Phone.TYPE_TELEX: {
-                parameterList.add(VCardConstants.PARAM_TYPE_TLX);
-                break;
-            }
-            case Phone.TYPE_WORK_MOBILE: {
-                parameterList.addAll(
-                        Arrays.asList(VCardConstants.PARAM_TYPE_WORK, VCardConstants.PARAM_TYPE_CELL));
-                break;
-            }
-            case Phone.TYPE_WORK_PAGER: {
-                parameterList.add(VCardConstants.PARAM_TYPE_WORK);
-                // See above.
-                if (mIsDoCoMo) {
-                    parameterList.add(VCardConstants.PARAM_TYPE_VOICE);
-                } else {
-                    parameterList.add(VCardConstants.PARAM_TYPE_PAGER);
-                }
-                break;
-            }
-            case Phone.TYPE_MMS: {
-                parameterList.add(VCardConstants.PARAM_TYPE_MSG);
-                break;
-            }
-            case Phone.TYPE_CUSTOM: {
-                if (TextUtils.isEmpty(label)) {
-                    // Just ignore the custom type.
-                    parameterList.add(VCardConstants.PARAM_TYPE_VOICE);
-                } else if (VCardUtils.isMobilePhoneLabel(label)) {
-                    parameterList.add(VCardConstants.PARAM_TYPE_CELL);
-                } else {
-                    final String upperLabel = label.toUpperCase();
-                    if (VCardUtils.isValidInV21ButUnknownToContactsPhoteType(upperLabel)) {
-                        parameterList.add(upperLabel);
-                    } else if (VCardUtils.containsOnlyAlphaDigitHyphen(label)) {
-                        // Note: Strictly, vCard 2.1 does not allow "X-" parameter without
-                        //       "TYPE=" string.
-                        parameterList.add("X-" + label);
-                    }
-                }
-                break;
-            }
-            case Phone.TYPE_RADIO:
-            case Phone.TYPE_TTY_TDD:
-            default: {
-                break;
-            }
-        }
-
-        if (isPrimary) {
-            parameterList.add(VCardConstants.PARAM_TYPE_PREF);
-        }
-
-        if (parameterList.isEmpty()) {
-            appendUncommonPhoneType(mBuilder, type);
-        } else {
-            appendTypeParameters(parameterList);
-        }
-
-        mBuilder.append(VCARD_DATA_SEPARATOR);
-        mBuilder.append(encodedValue);
-        mBuilder.append(VCARD_END_OF_LINE);
-    }
-
-    /**
-     * Appends phone type string which may not be available in some devices.
-     */
-    private void appendUncommonPhoneType(final StringBuilder builder, final Integer type) {
-        if (mIsDoCoMo) {
-            // The previous implementation for DoCoMo had been conservative
-            // about miscellaneous types.
-            builder.append(VCardConstants.PARAM_TYPE_VOICE);
-        } else {
-            String phoneType = VCardUtils.getPhoneTypeString(type);
-            if (phoneType != null) {
-                appendTypeParameter(phoneType);
-            } else {
-                Log.e(LOG_TAG, "Unknown or unsupported (by vCard) Phone type: " + type);
-            }
-        }
-    }
-
-    /**
-     * @param encodedValue Must be encoded by BASE64 
-     * @param photoType
-     */
-    public void appendPhotoLine(final String encodedValue, final String photoType) {
-        StringBuilder tmpBuilder = new StringBuilder();
-        tmpBuilder.append(VCardConstants.PROPERTY_PHOTO);
-        tmpBuilder.append(VCARD_PARAM_SEPARATOR);
-        if (mIsV30) {
-            tmpBuilder.append(VCARD_PARAM_ENCODING_BASE64_V30);
-        } else {
-            tmpBuilder.append(VCARD_PARAM_ENCODING_BASE64_V21);
-        }
-        tmpBuilder.append(VCARD_PARAM_SEPARATOR);
-        appendTypeParameter(tmpBuilder, photoType);
-        tmpBuilder.append(VCARD_DATA_SEPARATOR);
-        tmpBuilder.append(encodedValue);
-
-        final String tmpStr = tmpBuilder.toString();
-        tmpBuilder = new StringBuilder();
-        int lineCount = 0;
-        final int length = tmpStr.length();
-        final int maxNumForFirstLine = VCardConstants.MAX_CHARACTER_NUMS_BASE64_V30
-                - VCARD_END_OF_LINE.length();
-        final int maxNumInGeneral = maxNumForFirstLine - VCARD_WS.length();
-        int maxNum = maxNumForFirstLine;
-        for (int i = 0; i < length; i++) {
-            tmpBuilder.append(tmpStr.charAt(i));
-            lineCount++;
-            if (lineCount > maxNum) {
-                tmpBuilder.append(VCARD_END_OF_LINE);
-                tmpBuilder.append(VCARD_WS);
-                maxNum = maxNumInGeneral;
-                lineCount = 0;
-            }
-        }
-        mBuilder.append(tmpBuilder.toString());
-        mBuilder.append(VCARD_END_OF_LINE);
-        mBuilder.append(VCARD_END_OF_LINE);
-    }
-
-    public void appendAndroidSpecificProperty(final String mimeType, ContentValues contentValues) {
-        if (!sAllowedAndroidPropertySet.contains(mimeType)) {
-            return;
-        }
-        final List<String> rawValueList = new ArrayList<String>();
-        for (int i = 1; i <= VCardConstants.MAX_DATA_COLUMN; i++) {
-            String value = contentValues.getAsString("data" + i);
-            if (value == null) {
-                value = "";
-            }
-            rawValueList.add(value);
-        }
-
-        boolean needCharset =
-            (mShouldAppendCharsetParam &&
-                    !VCardUtils.containsOnlyNonCrLfPrintableAscii(rawValueList));
-        boolean reallyUseQuotedPrintable =
-            (mShouldUseQuotedPrintable &&
-                    !VCardUtils.containsOnlyNonCrLfPrintableAscii(rawValueList));
-        mBuilder.append(VCardConstants.PROPERTY_X_ANDROID_CUSTOM);
-        if (needCharset) {
-            mBuilder.append(VCARD_PARAM_SEPARATOR);
-            mBuilder.append(mVCardCharsetParameter);
-        }
-        if (reallyUseQuotedPrintable) {
-            mBuilder.append(VCARD_PARAM_SEPARATOR);
-            mBuilder.append(VCARD_PARAM_ENCODING_QP);
-        }
-        mBuilder.append(VCARD_DATA_SEPARATOR);
-        mBuilder.append(mimeType);  // Should not be encoded.
-        for (String rawValue : rawValueList) {
-            final String encodedValue;
-            if (reallyUseQuotedPrintable) {
-                encodedValue = encodeQuotedPrintable(rawValue);
-            } else {
-                // TODO: one line may be too huge, which may be invalid in vCard 3.0
-                //        (which says "When generating a content line, lines longer than
-                //        75 characters SHOULD be folded"), though several
-                //        (even well-known) applications do not care this.
-                encodedValue = escapeCharacters(rawValue);
-            }
-            mBuilder.append(VCARD_ITEM_SEPARATOR);
-            mBuilder.append(encodedValue);
-        }
-        mBuilder.append(VCARD_END_OF_LINE);
-    }
-
-    public void appendLineWithCharsetAndQPDetection(final String propertyName,
-            final String rawValue) {
-        appendLineWithCharsetAndQPDetection(propertyName, null, rawValue);
-    }
-
-    public void appendLineWithCharsetAndQPDetection(
-            final String propertyName, final List<String> rawValueList) {
-        appendLineWithCharsetAndQPDetection(propertyName, null, rawValueList);
-    }
-
-    public void appendLineWithCharsetAndQPDetection(final String propertyName,
-            final List<String> parameterList, final String rawValue) {
-        final boolean needCharset =
-                !VCardUtils.containsOnlyPrintableAscii(rawValue);
-        final boolean reallyUseQuotedPrintable =
-                (mShouldUseQuotedPrintable &&
-                        !VCardUtils.containsOnlyNonCrLfPrintableAscii(rawValue));
-        appendLine(propertyName, parameterList,
-                rawValue, needCharset, reallyUseQuotedPrintable);
-    }
-
-    public void appendLineWithCharsetAndQPDetection(final String propertyName,
-            final List<String> parameterList, final List<String> rawValueList) {
-        boolean needCharset =
-            (mShouldAppendCharsetParam &&
-                    !VCardUtils.containsOnlyNonCrLfPrintableAscii(rawValueList));
-        boolean reallyUseQuotedPrintable =
-            (mShouldUseQuotedPrintable &&
-                    !VCardUtils.containsOnlyNonCrLfPrintableAscii(rawValueList));
-        appendLine(propertyName, parameterList, rawValueList,
-                needCharset, reallyUseQuotedPrintable);
-    }
-
-    /**
-     * Appends one line with a given property name and value.  
-     */
-    public void appendLine(final String propertyName, final String rawValue) {
-        appendLine(propertyName, rawValue, false, false);
-    }
-
-    public void appendLine(final String propertyName, final List<String> rawValueList) {
-        appendLine(propertyName, rawValueList, false, false);
-    }
-
-    public void appendLine(final String propertyName,
-            final String rawValue, final boolean needCharset,
-            boolean reallyUseQuotedPrintable) {
-        appendLine(propertyName, null, rawValue, needCharset, reallyUseQuotedPrintable);
-    }
-
-    public void appendLine(final String propertyName, final List<String> parameterList,
-            final String rawValue) {
-        appendLine(propertyName, parameterList, rawValue, false, false);
-    }
-
-    public void appendLine(final String propertyName, final List<String> parameterList,
-            final String rawValue, final boolean needCharset,
-            boolean reallyUseQuotedPrintable) {
-        mBuilder.append(propertyName);
-        if (parameterList != null && parameterList.size() > 0) {
-            mBuilder.append(VCARD_PARAM_SEPARATOR);
-            appendTypeParameters(parameterList);
-        }
-        if (needCharset) {
-            mBuilder.append(VCARD_PARAM_SEPARATOR);
-            mBuilder.append(mVCardCharsetParameter);
-        }
-
-        final String encodedValue;
-        if (reallyUseQuotedPrintable) {
-            mBuilder.append(VCARD_PARAM_SEPARATOR);
-            mBuilder.append(VCARD_PARAM_ENCODING_QP);
-            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.
-            encodedValue = escapeCharacters(rawValue);
-        }
-
-        mBuilder.append(VCARD_DATA_SEPARATOR);
-        mBuilder.append(encodedValue);
-        mBuilder.append(VCARD_END_OF_LINE);
-    }
-
-    public void appendLine(final String propertyName, final List<String> rawValueList,
-            final boolean needCharset, boolean needQuotedPrintable) {
-        appendLine(propertyName, null, rawValueList, needCharset, needQuotedPrintable);
-    }
-
-    public void appendLine(final String propertyName, final List<String> parameterList,
-            final List<String> rawValueList, final boolean needCharset,
-            final boolean needQuotedPrintable) {
-        mBuilder.append(propertyName);
-        if (parameterList != null && parameterList.size() > 0) {
-            mBuilder.append(VCARD_PARAM_SEPARATOR);
-            appendTypeParameters(parameterList);
-        }
-        if (needCharset) {
-            mBuilder.append(VCARD_PARAM_SEPARATOR);
-            mBuilder.append(mVCardCharsetParameter);
-        }
-        if (needQuotedPrintable) {
-            mBuilder.append(VCARD_PARAM_SEPARATOR);
-            mBuilder.append(VCARD_PARAM_ENCODING_QP);
-        }
-
-        mBuilder.append(VCARD_DATA_SEPARATOR);
-        boolean first = true;
-        for (String rawValue : rawValueList) {
-            final String encodedValue;
-            if (needQuotedPrintable) {
-                encodedValue = encodeQuotedPrintable(rawValue);
-            } else {
-                // TODO: one line may be too huge, which may be invalid in vCard 3.0
-                //        (which says "When generating a content line, lines longer than
-                //        75 characters SHOULD be folded"), though several
-                //        (even well-known) applications do not care this.
-                encodedValue = escapeCharacters(rawValue);
-            }
-
-            if (first) {
-                first = false;
-            } else {
-                mBuilder.append(VCARD_ITEM_SEPARATOR);
-            }
-            mBuilder.append(encodedValue);
-        }
-        mBuilder.append(VCARD_END_OF_LINE);
-    }
-
-    /**
-     * VCARD_PARAM_SEPARATOR must be appended before this method being called.
-     */
-    private void appendTypeParameters(final List<String> types) {
-        // We may have to make this comma separated form like "TYPE=DOM,WORK" in the future,
-        // which would be recommended way in vcard 3.0 though not valid in vCard 2.1.
-        boolean first = true;
-        for (final String typeValue : types) {
-            // Note: vCard 3.0 specifies the different type of acceptable type Strings, but
-            //       we don't emit that kind of vCard 3.0 specific type since there should be
-            //       high probabilyty in which external importers cannot understand them.
-            //
-            // e.g. TYPE="\u578B\u306B\u3087" (vCard 3.0 allows non-Ascii characters if they
-            //      are quoted.)
-            if (!VCardUtils.isV21Word(typeValue)) {
-                continue;
-            }
-            if (first) {
-                first = false;
-            } else {
-                mBuilder.append(VCARD_PARAM_SEPARATOR);
-            }
-            appendTypeParameter(typeValue);
-        }
-    }
-
-    /**
-     * VCARD_PARAM_SEPARATOR must be appended before this method being called.
-     */
-    private void appendTypeParameter(final String type) {
-        appendTypeParameter(mBuilder, type);
-    }
-
-    private void appendTypeParameter(final StringBuilder builder, final String type) {
-        // Refrain from using appendType() so that "TYPE=" is not be appended when the
-        // device is DoCoMo's (just for safety).
-        //
-        // Note: In vCard 3.0, Type strings also can be like this: "TYPE=HOME,PREF"
-        if ((mIsV30 || mAppendTypeParamName) && !mIsDoCoMo) {
-            builder.append(VCardConstants.PARAM_TYPE).append(VCARD_PARAM_EQUAL);
-        }
-        builder.append(type);
-    }
-
-    /**
-     * Returns true when the property line should contain charset parameter
-     * information. This method may return true even when vCard version is 3.0.
-     *
-     * Strictly, adding charset information is invalid in VCard 3.0.
-     * However we'll add the info only when charset we use is not UTF-8
-     * in vCard 3.0 format, since parser side may be able to use the charset
-     * via this field, though we may encounter another problem by adding it.
-     *
-     * e.g. Japanese mobile phones use Shift_Jis while RFC 2426
-     * recommends UTF-8. By adding this field, parsers may be able
-     * to know this text is NOT UTF-8 but Shift_Jis.
-     */
-    private boolean shouldAppendCharsetParam(String...propertyValueList) {
-        if (!mShouldAppendCharsetParam) {
-            return false;
-        }
-        for (String propertyValue : propertyValueList) {
-            if (!VCardUtils.containsOnlyPrintableAscii(propertyValue)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    private String encodeQuotedPrintable(final String str) {
-        if (TextUtils.isEmpty(str)) {
-            return "";
-        }
-
-        final StringBuilder builder = new StringBuilder();
-        int index = 0;
-        int lineCount = 0;
-        byte[] strArray = null;
-
-        try {
-            strArray = str.getBytes(mCharsetString);
-        } catch (UnsupportedEncodingException e) {
-            Log.e(LOG_TAG, "Charset " + mCharsetString + " cannot be used. "
-                    + "Try default charset");
-            strArray = str.getBytes();
-        }
-        while (index < strArray.length) {
-            builder.append(String.format("=%02X", strArray[index]));
-            index += 1;
-            lineCount += 3;
-
-            if (lineCount >= 67) {
-                // Specification requires CRLF must be inserted before the
-                // length of the line
-                // becomes more than 76.
-                // Assuming that the next character is a multi-byte character,
-                // it will become
-                // 6 bytes.
-                // 76 - 6 - 3 = 67
-                builder.append("=\r\n");
-                lineCount = 0;
-            }
-        }
-
-        return builder.toString();
-    }
-
-    /**
-     * Append '\' to the characters which should be escaped. The character set is different
-     * not only between vCard 2.1 and vCard 3.0 but also among each device.
-     *
-     * Note that Quoted-Printable string must not be input here.
-     */
-    @SuppressWarnings("fallthrough")
-    private String escapeCharacters(final String unescaped) {
-        if (TextUtils.isEmpty(unescaped)) {
-            return "";
-        }
-
-        final StringBuilder tmpBuilder = new StringBuilder();
-        final int length = unescaped.length();
-        for (int i = 0; i < length; i++) {
-            final char ch = unescaped.charAt(i);
-            switch (ch) {
-                case ';': {
-                    tmpBuilder.append('\\');
-                    tmpBuilder.append(';');
-                    break;
-                }
-                case '\r': {
-                    if (i + 1 < length) {
-                        char nextChar = unescaped.charAt(i);
-                        if (nextChar == '\n') {
-                            break;
-                        } else {
-                            // fall through
-                        }
-                    } else {
-                        // fall through
-                    }
-                }
-                case '\n': {
-                    // In vCard 2.1, there's no specification about this, while
-                    // vCard 3.0 explicitly requires this should be encoded to "\n".
-                    tmpBuilder.append("\\n");
-                    break;
-                }
-                case '\\': {
-                    if (mIsV30) {
-                        tmpBuilder.append("\\\\");
-                        break;
-                    } else {
-                        // fall through
-                    }
-                }
-                case '<':
-                case '>': {
-                    if (mIsDoCoMo) {
-                        tmpBuilder.append('\\');
-                        tmpBuilder.append(ch);
-                    } else {
-                        tmpBuilder.append(ch);
-                    }
-                    break;
-                }
-                case ',': {
-                    if (mIsV30) {
-                        tmpBuilder.append("\\,");
-                    } else {
-                        tmpBuilder.append(ch);
-                    }
-                    break;
-                }
-                default: {
-                    tmpBuilder.append(ch);
-                    break;
-                }
-            }
-        }
-        return tmpBuilder.toString();
-    }
-
-    @Override
-    public String toString() {
-        if (!mEndAppended) {
-            if (mIsDoCoMo) {
-                appendLine(VCardConstants.PROPERTY_X_CLASS, VCARD_DATA_PUBLIC);
-                appendLine(VCardConstants.PROPERTY_X_REDUCTION, "");
-                appendLine(VCardConstants.PROPERTY_X_NO, "");
-                appendLine(VCardConstants.PROPERTY_X_DCM_HMN_MODE, "");
-            }
-            appendLine(VCardConstants.PROPERTY_END, VCARD_DATA_VCARD);
-            mEndAppended = true;
-        }
-        return mBuilder.toString();
-    }
-}
diff --git a/core/java/android/pim/vcard/VCardComposer.java b/core/java/android/pim/vcard/VCardComposer.java
deleted file mode 100644
index 0e8b665..0000000
--- a/core/java/android/pim/vcard/VCardComposer.java
+++ /dev/null
@@ -1,592 +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.pim.vcard;
-
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.Entity;
-import android.content.EntityIterator;
-import android.content.Entity.NamedContentValues;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteException;
-import android.net.Uri;
-import android.pim.vcard.exception.VCardException;
-import android.provider.ContactsContract.Contacts;
-import android.provider.ContactsContract.Data;
-import android.provider.ContactsContract.RawContacts;
-import android.provider.ContactsContract.RawContactsEntity;
-import android.provider.ContactsContract.CommonDataKinds.Email;
-import android.provider.ContactsContract.CommonDataKinds.Event;
-import android.provider.ContactsContract.CommonDataKinds.Im;
-import android.provider.ContactsContract.CommonDataKinds.Nickname;
-import android.provider.ContactsContract.CommonDataKinds.Note;
-import android.provider.ContactsContract.CommonDataKinds.Organization;
-import android.provider.ContactsContract.CommonDataKinds.Phone;
-import android.provider.ContactsContract.CommonDataKinds.Photo;
-import android.provider.ContactsContract.CommonDataKinds.Relation;
-import android.provider.ContactsContract.CommonDataKinds.StructuredName;
-import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
-import android.provider.ContactsContract.CommonDataKinds.Website;
-import android.util.CharsetUtils;
-import android.util.Log;
-
-import java.io.BufferedWriter;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.io.OutputStreamWriter;
-import java.io.UnsupportedEncodingException;
-import java.io.Writer;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.nio.charset.UnsupportedCharsetException;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * <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.
- * </p>
- *
- * <p>
- * Usually, this class should be used like this.
- * </p>
- *
- * <pre class="prettyprint">VCardComposer composer = null;
- * try {
- *     composer = new VCardComposer(context);
- *     composer.addHandler(
- *             composer.new HandlerForOutputStream(outputStream));
- *     if (!composer.init()) {
- *         // Do something handling the situation.
- *         return;
- *     }
- *     while (!composer.isAfterLast()) {
- *         if (mCanceled) {
- *             // Assume a user may cancel this operation during the export.
- *             return;
- *         }
- *         if (!composer.createOneEntry()) {
- *             // Do something handling the error situation.
- *             return;
- *         }
- *     }
- * } finally {
- *     if (composer != null) {
- *         composer.terminate();
- *     }
- * } </pre>
- */
-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";
-
-    public static final String FAILURE_REASON_NO_ENTRY =
-        "There's no exportable in the database";
-
-    public static final String FAILURE_REASON_NOT_INITIALIZED =
-        "The vCard composer object is not correctly initialized";
-
-    /** Should be visible only from developers... (no need to translate, hopefully) */
-    public static final String FAILURE_REASON_UNSUPPORTED_URI =
-        "The Uri vCard composer received is not supported by the composer.";
-
-    public static final String NO_ERROR = "No error";
-
-    public static final String VCARD_TYPE_STRING_DOCOMO = "docomo";
-
-    private static final String SHIFT_JIS = "SHIFT_JIS";
-    private static final String UTF_8 = "UTF-8";
-
-    /**
-     * Special URI for testing.
-     */
-    public static final String VCARD_TEST_AUTHORITY = "com.android.unit_tests.vcard";
-    public static final Uri VCARD_TEST_AUTHORITY_URI =
-        Uri.parse("content://" + VCARD_TEST_AUTHORITY);
-    public static final Uri CONTACTS_TEST_CONTENT_URI =
-        Uri.withAppendedPath(VCARD_TEST_AUTHORITY_URI, "contacts");
-
-    private static final Map<Integer, String> sImMap;
-
-    static {
-        sImMap = new HashMap<Integer, String>();
-        sImMap.put(Im.PROTOCOL_AIM, VCardConstants.PROPERTY_X_AIM);
-        sImMap.put(Im.PROTOCOL_MSN, VCardConstants.PROPERTY_X_MSN);
-        sImMap.put(Im.PROTOCOL_YAHOO, VCardConstants.PROPERTY_X_YAHOO);
-        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.
-    }
-
-    public static interface OneEntryHandler {
-        public boolean onInit(Context context);
-        public boolean onEntryCreated(String vcard);
-        public void onTerminate();
-    }
-
-    /**
-     * <p>
-     * An useful example handler, which emits VCard String to outputstream one by one.
-     * </p>
-     * <p>
-     * The input OutputStream object is closed() on {@link #onTerminate()}.
-     * Must not close the stream outside.
-     * </p>
-     */
-    public 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 boolean mOnTerminateIsCalled = false;
-
-        /**
-         * Input stream will be closed on the detruction of this object.
-         */
-        public HandlerForOutputStream(OutputStream outputStream) {
-            mOutputStream = outputStream;
-        }
-
-        public boolean onInit(Context context) {
-            try {
-                mWriter = new BufferedWriter(new OutputStreamWriter(
-                        mOutputStream, mCharsetString));
-            } catch (UnsupportedEncodingException e1) {
-                Log.e(LOG_TAG, "Unsupported charset: " + mCharsetString);
-                mErrorReason = "Encoding is not supported (usually this does not happen!): "
-                        + mCharsetString;
-                return false;
-            }
-
-            if (mIsDoCoMo) {
-                try {
-                    // Create one empty entry.
-                    mWriter.write(createOneEntryInternal("-1", null));
-                } catch (VCardException e) {
-                    Log.e(LOG_TAG, "VCardException has been thrown during on Init(): " +
-                            e.getMessage());
-                    return false;
-                } catch (IOException e) {
-                    Log.e(LOG_TAG,
-                            "IOException occurred during exportOneContactData: "
-                                    + e.getMessage());
-                    mErrorReason = "IOException occurred: " + e.getMessage();
-                    return false;
-                }
-            }
-            return true;
-        }
-
-        public boolean onEntryCreated(String vcard) {
-            try {
-                mWriter.write(vcard);
-            } catch (IOException e) {
-                Log.e(LOG_TAG,
-                        "IOException occurred during exportOneContactData: "
-                                + e.getMessage());
-                mErrorReason = "IOException occurred: " + e.getMessage();
-                return false;
-            }
-            return true;
-        }
-
-        public void onTerminate() {
-            mOnTerminateIsCalled = true;
-            if (mWriter != null) {
-                try {
-                    // Flush and sync the data so that a user is able to pull
-                    // the SDCard just after
-                    // the export.
-                    mWriter.flush();
-                    if (mOutputStream != null
-                            && mOutputStream instanceof FileOutputStream) {
-                            ((FileOutputStream) mOutputStream).getFD().sync();
-                    }
-                } catch (IOException e) {
-                    Log.d(LOG_TAG,
-                            "IOException during closing the output stream: "
-                                    + e.getMessage());
-                } finally {
-                    try {
-                        mWriter.close();
-                    } catch (IOException e) {
-                    }
-                }
-            }
-        }
-
-        @Override
-        public void finalize() {
-            if (!mOnTerminateIsCalled) {
-                onTerminate();
-            }
-        }
-    }
-
-    private final Context mContext;
-    private final int mVCardType;
-    private final boolean mCareHandlerErrors;
-    private final ContentResolver mContentResolver;
-
-    private final boolean mIsDoCoMo;
-    private final boolean mUsesShiftJis;
-    private Cursor mCursor;
-    private int mIdColumn;
-
-    private final String mCharsetString;
-    private boolean mTerminateIsCalled;
-    private final List<OneEntryHandler> mHandlerList;
-
-    private String mErrorReason = NO_ERROR;
-
-    private static final String[] sContactsProjection = new String[] {
-        Contacts._ID,
-    };
-
-    public VCardComposer(Context context) {
-        this(context, VCardConfig.VCARD_TYPE_DEFAULT, true);
-    }
-
-    public VCardComposer(Context context, int vcardType) {
-        this(context, vcardType, true);
-    }
-
-    public VCardComposer(Context context, String vcardTypeStr, boolean careHandlerErrors) {
-        this(context, VCardConfig.getVCardTypeFromString(vcardTypeStr), careHandlerErrors);
-    }
-
-    /**
-     * Construct for supporting call log entry vCard composing.
-     */
-    public VCardComposer(final Context context, final int vcardType,
-            final boolean careHandlerErrors) {
-        mContext = context;
-        mVCardType = vcardType;
-        mCareHandlerErrors = careHandlerErrors;
-        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;
-            }
-            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;
-        }
-    }
-
-    /**
-     * Must be called before {@link #init()}.
-     */
-    public void addHandler(OneEntryHandler handler) {
-        if (handler != null) {
-            mHandlerList.add(handler);
-        }
-    }
-
-    /**
-     * @return Returns true when initialization is successful and all the other
-     *          methods are available. Returns false otherwise.
-     */
-    public boolean init() {
-        return init(null, null);
-    }
-
-    public boolean init(final String selection, final String[] selectionArgs) {
-        return init(Contacts.CONTENT_URI, selection, selectionArgs, null);
-    }
-
-    /**
-     * Note that this is unstable interface, may be deleted in the future.
-     */
-    public boolean init(final Uri contentUri, final String selection,
-            final String[] selectionArgs, final String sortOrder) {
-        if (contentUri == null) {
-            return false;
-        }
-
-        if (mCareHandlerErrors) {
-            List<OneEntryHandler> finishedList = new ArrayList<OneEntryHandler>(
-                    mHandlerList.size());
-            for (OneEntryHandler handler : mHandlerList) {
-                if (!handler.onInit(mContext)) {
-                    for (OneEntryHandler finished : finishedList) {
-                        finished.onTerminate();
-                    }
-                    return false;
-                }
-            }
-        } else {
-            // Just ignore the false returned from onInit().
-            for (OneEntryHandler handler : mHandlerList) {
-                handler.onInit(mContext);
-            }
-        }
-
-        final String[] projection;
-        if (Contacts.CONTENT_URI.equals(contentUri) ||
-                CONTACTS_TEST_CONTENT_URI.equals(contentUri)) {
-            projection = sContactsProjection;
-        } else {
-            mErrorReason = FAILURE_REASON_UNSUPPORTED_URI;
-            return false;
-        }
-        mCursor = mContentResolver.query(
-                contentUri, projection, selection, selectionArgs, sortOrder);
-
-        if (mCursor == null) {
-            mErrorReason = FAILURE_REASON_FAILED_TO_GET_DATABASE_INFO;
-            return false;
-        }
-
-        if (getCount() == 0 || !mCursor.moveToFirst()) {
-            try {
-                mCursor.close();
-            } catch (SQLiteException e) {
-                Log.e(LOG_TAG, "SQLiteException on Cursor#close(): " + e.getMessage());
-            } finally {
-                mCursor = null;
-                mErrorReason = FAILURE_REASON_NO_ENTRY;
-            }
-            return false;
-        }
-
-        mIdColumn = mCursor.getColumnIndex(Contacts._ID);
-
-        return true;
-    }
-
-    public boolean createOneEntry() {
-        return createOneEntry(null);
-    }
-
-    /**
-     * @param getEntityIteratorMethod For Dependency Injection.
-     * @hide just for testing.
-     */
-    public boolean createOneEntry(Method getEntityIteratorMethod) {
-        if (mCursor == null || mCursor.isAfterLast()) {
-            mErrorReason = FAILURE_REASON_NOT_INITIALIZED;
-            return false;
-        }
-        String vcard;
-        try {
-            if (mIdColumn >= 0) {
-                vcard = createOneEntryInternal(mCursor.getString(mIdColumn),
-                        getEntityIteratorMethod);
-            } else {
-                Log.e(LOG_TAG, "Incorrect mIdColumn: " + mIdColumn);
-                return true;
-            }
-        } catch (VCardException e) {
-            Log.e(LOG_TAG, "VCardException has been thrown: " + e.getMessage());
-            return false;
-        } catch (OutOfMemoryError error) {
-            // Maybe some data (e.g. photo) is too big to have in memory. But it
-            // should be rare.
-            Log.e(LOG_TAG, "OutOfMemoryError occured. Ignore the entry.");
-            System.gc();
-            // TODO: should tell users what happened?
-            return true;
-        } finally {
-            mCursor.moveToNext();
-        }
-
-        // This function does not care the OutOfMemoryError on the handler side
-        // :-P
-        if (mCareHandlerErrors) {
-            List<OneEntryHandler> finishedList = new ArrayList<OneEntryHandler>(
-                    mHandlerList.size());
-            for (OneEntryHandler handler : mHandlerList) {
-                if (!handler.onEntryCreated(vcard)) {
-                    return false;
-                }
-            }
-        } else {
-            for (OneEntryHandler handler : mHandlerList) {
-                handler.onEntryCreated(vcard);
-            }
-        }
-
-        return true;
-    }
-
-    private String createOneEntryInternal(final String contactId,
-            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.
-        // e.g. If all the data in the contact of the given contact id are not exportable ones,
-        //      they are hidden from the view of this method, though contact id itself exists.
-        EntityIterator entityIterator = null;
-        try {
-            final Uri uri = RawContactsEntity.CONTENT_URI.buildUpon()
-                    .appendQueryParameter(Data.FOR_EXPORT_ONLY, "1")
-                    .build();
-            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
-                try {
-                    entityIterator = (EntityIterator)getEntityIteratorMethod.invoke(null,
-                            mContentResolver, uri, selection, selectionArgs, null);
-                } catch (IllegalArgumentException e) {
-                    Log.e(LOG_TAG, "IllegalArgumentException has been thrown: " +
-                            e.getMessage());
-                } catch (IllegalAccessException e) {
-                    Log.e(LOG_TAG, "IllegalAccessException has been thrown: " +
-                            e.getMessage());
-                } catch (InvocationTargetException e) {
-                    Log.e(LOG_TAG, "InvocationTargetException has been thrown: ");
-                    StackTraceElement[] stackTraceElements = e.getCause().getStackTrace();
-                    for (StackTraceElement element : stackTraceElements) {
-                        Log.e(LOG_TAG, "    at " + element.toString());
-                    }
-                    throw new VCardException("InvocationTargetException has been thrown: " +
-                            e.getCause().getMessage());
-                }
-            } else {
-                entityIterator = RawContacts.newEntityIterator(mContentResolver.query(
-                        uri, null, selection, selectionArgs, null));
-            }
-
-            if (entityIterator == null) {
-                Log.e(LOG_TAG, "EntityIterator is null");
-                return "";
-            }
-
-            if (!entityIterator.hasNext()) {
-                Log.w(LOG_TAG, "Data does not exist. contactId: " + contactId);
-                return "";
-            }
-
-            while (entityIterator.hasNext()) {
-                Entity entity = entityIterator.next();
-                for (NamedContentValues namedContentValues : entity.getSubValues()) {
-                    ContentValues contentValues = namedContentValues.values;
-                    String key = contentValues.getAsString(Data.MIMETYPE);
-                    if (key != null) {
-                        List<ContentValues> contentValuesList =
-                                contentValuesListMap.get(key);
-                        if (contentValuesList == null) {
-                            contentValuesList = new ArrayList<ContentValues>();
-                            contentValuesListMap.put(key, contentValuesList);
-                        }
-                        contentValuesList.add(contentValues);
-                    }
-                }
-            }
-        } finally {
-            if (entityIterator != null) {
-                entityIterator.close();
-            }
-        }
-
-        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));
-        }
-        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() {
-        for (OneEntryHandler handler : mHandlerList) {
-            handler.onTerminate();
-        }
-
-        if (mCursor != null) {
-            try {
-                mCursor.close();
-            } catch (SQLiteException e) {
-                Log.e(LOG_TAG, "SQLiteException on Cursor#close(): " + e.getMessage());
-            }
-            mCursor = null;
-        }
-
-        mTerminateIsCalled = true;
-    }
-
-    @Override
-    public void finalize() {
-        if (!mTerminateIsCalled) {
-            terminate();
-        }
-    }
-
-    public int getCount() {
-        if (mCursor == null) {
-            return 0;
-        }
-        return mCursor.getCount();
-    }
-
-    public boolean isAfterLast() {
-        if (mCursor == null) {
-            return false;
-        }
-        return mCursor.isAfterLast();
-    }
-
-    /**
-     * @return Return the error reason if possible.
-     */
-    public String getErrorReason() {
-        return mErrorReason;
-    }
-}
diff --git a/core/java/android/pim/vcard/VCardConfig.java b/core/java/android/pim/vcard/VCardConfig.java
deleted file mode 100644
index 8219840..0000000
--- a/core/java/android/pim/vcard/VCardConfig.java
+++ /dev/null
@@ -1,477 +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.pim.vcard;
-
-import android.telephony.PhoneNumberUtils;
-import android.util.Log;
-
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * The class representing VCard related configurations. Useful static methods are not in this class
- * but in VCardUtils.
- */
-public class VCardConfig {
-    private static final String LOG_TAG = "VCardConfig";
-
-    /* package */ static final int LOG_LEVEL_NONE = 0;
-    /* package */ static final int LOG_LEVEL_PERFORMANCE_MEASUREMENT = 0x1;
-    /* package */ static final int LOG_LEVEL_SHOW_WARNING = 0x2;
-    /* package */ static final int LOG_LEVEL_VERBOSE =
-        LOG_LEVEL_PERFORMANCE_MEASUREMENT | LOG_LEVEL_SHOW_WARNING;
-
-    /* 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;
-
-    // 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";
-    
-    public static final int FLAG_V21 = 0;
-    public static final int FLAG_V30 = 1;
-
-    // 0x2 is reserved for the future use ...
-
-    public static final int NAME_ORDER_DEFAULT = 0;
-    public static final int NAME_ORDER_EUROPE = 0x4;
-    public static final int NAME_ORDER_JAPANESE = 0x8;
-    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;
-
-    /**
-     * 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.
-     * 
-     * 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.
-     * 
-     * 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.
-     */
-    private static final int FLAG_USE_ANDROID_PROPERTY = 0x80000000;
-    
-    /**
-     * 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. 
-     * 
-     * 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.
-     * 
-     * 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.
-     */
-    private static final int FLAG_USE_DEFACT_PROPERTY = 0x40000000;
-
-    /**
-     * 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.
-     */
-    private static final int FLAG_DOCOMO = 0x20000000;
-
-    /**
-     * <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>
-     * 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>
-     * 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>
-     * We are afraid of the case where some vCard importer also forget handling QP presuming QP is
-     * not used in such fields.
-     * </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>
-     * Again, we should not use this flag at all for complying vCard 2.1 spec.
-     * </P>
-     * <P>
-     * In vCard 3.0, Quoted-Printable is explicitly "prohibitted", so we don't need to care this
-     * kind of problem (hopefully).
-     * </P>
-     */
-    public static final int FLAG_REFRAIN_QP_TO_NAME_PROPERTIES = 0x10000000;
-
-    /**
-     * <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>
-     * 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>
-     */
-    public static final int FLAG_CONVERT_PHONETIC_NAME_STRINGS = 0x0800000;
-
-    /**
-     * <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>
-     * 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>
-     * 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>
-     */
-    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.
-     */
-    public static final int FLAG_REFRAIN_IMAGE_EXPORT = 0x02000000;
-
-    /**
-     * <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_PHONE_NUMBER_FORMATTING = 0x02000000;
-
-    //// 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>
-     * 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>
-     */
-    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);
-
-    /* package */ static String VCARD_TYPE_V21_GENERIC_UTF8_STR = "v21_generic";
-    
-    /**
-     * <P>
-     * General vCard format with the version 3.0. Uses UTF-8 for the charset.
-     * </P>
-     * <P>
-     * Not fully ready yet. Use with caution when you use this.
-     * </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);
-
-    /* package */ static final String VCARD_TYPE_V30_GENERIC_UTF8_STR = "v30_generic";
-    
-    /**
-     * <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>
-     */
-    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";
-    
-    /**
-     * <P>
-     * General vCard format with the version 3.0 with some Europe convension. Uses UTF-8.
-     * </P>
-     * <P>
-     * Not ready yet. Use with caution when you use this.
-     * </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);
-    
-    /* package */ static final String VCARD_TYPE_V30_EUROPE_STR = "v30_europe";
-
-    /**
-     * <P>
-     * The vCard 2.1 format for miscellaneous Japanese devices, using UTF-8 as default charset.
-     * </P>
-     * <P>
-     * Not ready yet. Use with caution when you use this.
-     * </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);
-
-    /* package */ static final String VCARD_TYPE_V21_JAPANESE_UTF8_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>
-     * The vCard 3.0 format for miscellaneous Japanese devices, using UTF-8 as default charset.
-     * </P>
-     * <P>
-     * Not ready yet. Use with caution when you use this.
-     * </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);
-
-    /* package */ static final String VCARD_TYPE_V30_JAPANESE_UTF8_STR = "v30_japanese_utf8";
-
-    /**
-     * <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>
-     */
-    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);
-
-    /* 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>
-     * <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>
-     */
-    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;
-
-    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_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_MOBILE);
-        sJapaneseMobileTypeSet.add(VCARD_TYPE_DOCOMO);
-    }
-
-    public static int getVCardTypeFromString(final String vcardTypeString) {
-        final String loweredKey = vcardTypeString.toLowerCase();
-        if (sVCardTypeMap.containsKey(loweredKey)) {
-            return sVCardTypeMap.get(loweredKey);
-        } else if ("default".equalsIgnoreCase(vcardTypeString)) {
-            return VCARD_TYPE_DEFAULT;
-        } else {
-            Log.e(LOG_TAG, "Unknown vCard type String: \"" + vcardTypeString + "\"");
-            return VCARD_TYPE_DEFAULT;
-        }
-    }
-
-    public static boolean isV30(final int vcardType) {
-        return ((vcardType & FLAG_V30) != 0);  
-    }
-
-    public static boolean shouldUseQuotedPrintable(final int vcardType) {
-        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;
-    }
-
-    public static boolean usesAndroidSpecificProperty(final int vcardType) {
-        return ((vcardType & FLAG_USE_ANDROID_PROPERTY) != 0);
-    }
-
-    public static boolean usesDefactProperty(final int vcardType) {
-        return ((vcardType & FLAG_USE_DEFACT_PROPERTY) != 0);
-    }
-
-    public static boolean showPerformanceLog() {
-        return (VCardConfig.LOG_LEVEL & VCardConfig.LOG_LEVEL_PERFORMANCE_MEASUREMENT) != 0;
-    }
-
-    public static boolean shouldRefrainQPToNameProperties(final int vcardType) {
-       return (!shouldUseQuotedPrintable(vcardType) ||
-               ((vcardType & FLAG_REFRAIN_QP_TO_NAME_PROPERTIES) != 0));
-    }
-
-    public static boolean appendTypeParamName(final int vcardType) {
-        return (isV30(vcardType) || ((vcardType & FLAG_APPEND_TYPE_PARAM) != 0));
-    }
-
-    /**
-     * @return true if the device is Japanese and some Japanese convension is
-     * applied to creating "formatted" something like FORMATTED_ADDRESS.
-     */
-    public static boolean isJapaneseDevice(final int vcardType) {
-        // TODO: Some mask will be required so that this method wrongly interpret
-        //        Japanese"-like" vCard type.
-        //        e.g. VCARD_TYPE_V21_JAPANESE_SJIS | FLAG_APPEND_TYPE_PARAMS
-        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);
-    }
-
-    public static boolean onlyOneNoteFieldIsAvailable(final int vcardType) {
-        return vcardType == VCARD_TYPE_DOCOMO;
-    }
-
-    public static boolean isDoCoMo(final int vcardType) {
-        return ((vcardType & FLAG_DOCOMO) != 0);
-    }
-
-    private VCardConfig() {
-    }
-}
diff --git a/core/java/android/pim/vcard/VCardConstants.java b/core/java/android/pim/vcard/VCardConstants.java
deleted file mode 100644
index 8c07126..0000000
--- a/core/java/android/pim/vcard/VCardConstants.java
+++ /dev/null
@@ -1,152 +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.pim.vcard;
-
-/**
- * Constants used in both exporter and importer code.
- */
-public class VCardConstants {
-    public static final String VERSION_V21 = "2.1";
-    public static final String VERSION_V30 = "3.0";
-
-    // The property names valid both in vCard 2.1 and 3.0.
-    public static final String PROPERTY_BEGIN = "BEGIN";
-    public static final String PROPERTY_VERSION = "VERSION";
-    public static final String PROPERTY_N = "N";
-    public static final String PROPERTY_FN = "FN";
-    public static final String PROPERTY_ADR = "ADR";
-    public static final String PROPERTY_EMAIL = "EMAIL";
-    public static final String PROPERTY_NOTE = "NOTE";
-    public static final String PROPERTY_ORG = "ORG";
-    public static final String PROPERTY_SOUND = "SOUND";  // Not fully supported.
-    public static final String PROPERTY_TEL = "TEL";
-    public static final String PROPERTY_TITLE = "TITLE";
-    public static final String PROPERTY_ROLE = "ROLE";
-    public static final String PROPERTY_PHOTO = "PHOTO";
-    public static final String PROPERTY_LOGO = "LOGO";
-    public static final String PROPERTY_URL = "URL";
-    public static final String PROPERTY_BDAY = "BDAY";  // Birthday
-    public static final String PROPERTY_END = "END";
-
-    // Valid property names not supported (not appropriately handled) by our vCard importer now.
-    public static final String PROPERTY_REV = "REV";
-    public static final String PROPERTY_AGENT = "AGENT";
-
-    // Available in vCard 3.0. Shoud not use when composing vCard 2.1 file.
-    public static final String PROPERTY_NAME = "NAME";
-    public static final String PROPERTY_NICKNAME = "NICKNAME";
-    public static final String PROPERTY_SORT_STRING = "SORT-STRING";
-    
-    // De-fact property values expressing phonetic names.
-    public static final String PROPERTY_X_PHONETIC_FIRST_NAME = "X-PHONETIC-FIRST-NAME";
-    public static final String PROPERTY_X_PHONETIC_MIDDLE_NAME = "X-PHONETIC-MIDDLE-NAME";
-    public static final String PROPERTY_X_PHONETIC_LAST_NAME = "X-PHONETIC-LAST-NAME";
-
-    // Properties both ContactsStruct in Eclair and de-fact vCard extensions
-    // shown in http://en.wikipedia.org/wiki/VCard support are defined here.
-    public static final String PROPERTY_X_AIM = "X-AIM";
-    public static final String PROPERTY_X_MSN = "X-MSN";
-    public static final String PROPERTY_X_YAHOO = "X-YAHOO";
-    public static final String PROPERTY_X_ICQ = "X-ICQ";
-    public static final String PROPERTY_X_JABBER = "X-JABBER";
-    public static final String PROPERTY_X_GOOGLE_TALK = "X-GOOGLE-TALK";
-    public static final String PROPERTY_X_SKYPE_USERNAME = "X-SKYPE-USERNAME";
-    // Properties only ContactsStruct has. We alse use this.
-    public static final String PROPERTY_X_QQ = "X-QQ";
-    public static final String PROPERTY_X_NETMEETING = "X-NETMEETING";
-
-    // Phone number for Skype, available as usual phone.
-    public static final String PROPERTY_X_SKYPE_PSTNNUMBER = "X-SKYPE-PSTNNUMBER";
-
-    // Property for Android-specific fields.
-    public static final String PROPERTY_X_ANDROID_CUSTOM = "X-ANDROID-CUSTOM";
-
-    // Properties for DoCoMo vCard.
-    public static final String PROPERTY_X_CLASS = "X-CLASS";
-    public static final String PROPERTY_X_REDUCTION = "X-REDUCTION";
-    public static final String PROPERTY_X_NO = "X-NO";
-    public static final String PROPERTY_X_DCM_HMN_MODE = "X-DCM-HMN-MODE";
-
-    public static final String PARAM_TYPE = "TYPE";
-
-    public static final String PARAM_TYPE_HOME = "HOME";
-    public static final String PARAM_TYPE_WORK = "WORK";
-    public static final String PARAM_TYPE_FAX = "FAX";
-    public static final String PARAM_TYPE_CELL = "CELL";
-    public static final String PARAM_TYPE_VOICE = "VOICE";
-    public static final String PARAM_TYPE_INTERNET = "INTERNET";
-
-    // Abbreviation of "prefered" according to vCard 2.1 specification.
-    // We interpret this value as "primary" property during import/export.
-    //
-    // Note: Both vCard specs does not mention anything about the requirement for this parameter,
-    //       but there may be some vCard importer which will get confused with more than
-    //       one "PREF"s in one property name, while Android accepts them.
-    public static final String PARAM_TYPE_PREF = "PREF";
-
-    // Phone type parameters valid in vCard and known to ContactsContract, but not so common.
-    public static final String PARAM_TYPE_CAR = "CAR";
-    public static final String PARAM_TYPE_ISDN = "ISDN";
-    public static final String PARAM_TYPE_PAGER = "PAGER";
-    public static final String PARAM_TYPE_TLX = "TLX";  // Telex
-
-    // Phone types existing in vCard 2.1 but not known to ContactsContract.
-    public static final String PARAM_TYPE_MODEM = "MODEM";
-    public static final String PARAM_TYPE_MSG = "MSG";
-    public static final String PARAM_TYPE_BBS = "BBS";
-    public static final String PARAM_TYPE_VIDEO = "VIDEO";
-
-    // 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.
-    public static final String PARAM_PHONE_EXTRA_TYPE_CALLBACK = "CALLBACK";
-    public static final String PARAM_PHONE_EXTRA_TYPE_RADIO = "RADIO";
-    public static final String PARAM_PHONE_EXTRA_TYPE_TTY_TDD = "TTY-TDD";
-    public static final String PARAM_PHONE_EXTRA_TYPE_ASSISTANT = "ASSISTANT";
-    // vCard composer translates this type to "WORK" + "PREF". Just for parsing.
-    public static final String PARAM_PHONE_EXTRA_TYPE_COMPANY_MAIN = "COMPANY-MAIN";
-    // vCard composer translates this type to "VOICE" Just for parsing.
-    public static final String PARAM_PHONE_EXTRA_TYPE_OTHER = "OTHER";
-
-    // TYPE parameters for postal addresses.
-    public static final String PARAM_ADR_TYPE_PARCEL = "PARCEL";
-    public static final String PARAM_ADR_TYPE_DOM = "DOM";
-    public static final String PARAM_ADR_TYPE_INTL = "INTL";
-
-    // TYPE parameters not officially valid but used in some vCard exporter.
-    // 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,
-        // which is specifically invalid but should be always properly accepted, and emitted
-        // in some special case (for that device/application).
-        public static final String PROPERTY_X_GOOGLE_TALK_WITH_SPACE = "X-GOOGLE TALK";
-    }
-
-    /* package */ static final int MAX_DATA_COLUMN = 15;
-
-    /* package */ static final int MAX_CHARACTER_NUMS_QP = 76;
-    static final int MAX_CHARACTER_NUMS_BASE64_V30 = 75;
-
-    private VCardConstants() {
-    }
-}
\ No newline at end of file
diff --git a/core/java/android/pim/vcard/VCardEntry.java b/core/java/android/pim/vcard/VCardEntry.java
deleted file mode 100644
index 7c7e9b8..0000000
--- a/core/java/android/pim/vcard/VCardEntry.java
+++ /dev/null
@@ -1,1447 +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.pim.vcard;
-
-import android.accounts.Account;
-import android.content.ContentProviderOperation;
-import android.content.ContentProviderResult;
-import android.content.ContentResolver;
-import android.content.OperationApplicationException;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.RemoteException;
-import android.provider.ContactsContract;
-import android.provider.ContactsContract.Contacts;
-import android.provider.ContactsContract.Data;
-import android.provider.ContactsContract.Groups;
-import android.provider.ContactsContract.RawContacts;
-import android.provider.ContactsContract.CommonDataKinds.Email;
-import android.provider.ContactsContract.CommonDataKinds.Event;
-import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
-import android.provider.ContactsContract.CommonDataKinds.Im;
-import android.provider.ContactsContract.CommonDataKinds.Nickname;
-import android.provider.ContactsContract.CommonDataKinds.Note;
-import android.provider.ContactsContract.CommonDataKinds.Organization;
-import android.provider.ContactsContract.CommonDataKinds.Phone;
-import android.provider.ContactsContract.CommonDataKinds.Photo;
-import android.provider.ContactsContract.CommonDataKinds.StructuredName;
-import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
-import android.provider.ContactsContract.CommonDataKinds.Website;
-import android.telephony.PhoneNumberUtils;
-import android.text.TextUtils;
-import android.util.Log;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-
-/**
- * This class bridges between data structure of Contact app and VCard data.
- */
-public class VCardEntry {
-    private static final String LOG_TAG = "VCardEntry";
-
-    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 {
-        sImMap.put(VCardConstants.PROPERTY_X_AIM, Im.PROTOCOL_AIM);
-        sImMap.put(VCardConstants.PROPERTY_X_MSN, Im.PROTOCOL_MSN);
-        sImMap.put(VCardConstants.PROPERTY_X_YAHOO, Im.PROTOCOL_YAHOO);
-        sImMap.put(VCardConstants.PROPERTY_X_ICQ, Im.PROTOCOL_ICQ);
-        sImMap.put(VCardConstants.PROPERTY_X_JABBER, Im.PROTOCOL_JABBER);
-        sImMap.put(VCardConstants.PROPERTY_X_SKYPE_USERNAME, Im.PROTOCOL_SKYPE);
-        sImMap.put(VCardConstants.PROPERTY_X_GOOGLE_TALK, Im.PROTOCOL_GOOGLE_TALK);
-        sImMap.put(VCardConstants.ImportOnly.PROPERTY_X_GOOGLE_TALK_WITH_SPACE,
-                Im.PROTOCOL_GOOGLE_TALK);
-    }
-
-    static public 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.
-        public boolean isPrimary;
-        public PhoneData(int type, String data, String label, boolean isPrimary) {
-            this.type = type;
-            this.data = data;
-            this.label = label;
-            this.isPrimary = isPrimary;
-        }
-
-        @Override
-        public boolean equals(Object obj) {
-            if (!(obj instanceof PhoneData)) {
-                return false;
-            }
-            PhoneData phoneData = (PhoneData)obj;
-            return (type == phoneData.type && data.equals(phoneData.data) &&
-                    label.equals(phoneData.label) && isPrimary == phoneData.isPrimary);
-        }
-
-        @Override
-        public String toString() {
-            return String.format("type: %d, data: %s, label: %s, isPrimary: %s",
-                    type, data, label, isPrimary);
-        }
-    }
-
-    static public 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;
-            this.data = data;
-            this.label = label;
-            this.isPrimary = isPrimary;
-        }
-
-        @Override
-        public boolean equals(Object obj) {
-            if (!(obj instanceof EmailData)) {
-                return false;
-            }
-            EmailData emailData = (EmailData)obj;
-            return (type == emailData.type && data.equals(emailData.data) &&
-                    label.equals(emailData.label) && isPrimary == emailData.isPrimary);
-        }
-
-        @Override
-        public String toString() {
-            return String.format("type: %d, data: %s, label: %s, isPrimary: %s",
-                    type, data, label, isPrimary);
-        }
-    }
-
-    static public class PostalData {
-        // Determined by vCard spec.
-        // 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;
-        public final String extendedAddress;
-        public final String street;
-        public final String localty;
-        public final String region;
-        public final String postalCode;
-        public final String country;
-        public final int type;
-        public final String label;
-        public boolean isPrimary;
-
-        public PostalData(final int type, final List<String> propValueList,
-                final String label, boolean isPrimary) {
-            this.type = type;
-            dataArray = new String[ADDR_MAX_DATA_SIZE];
-
-            int size = propValueList.size();
-            if (size > ADDR_MAX_DATA_SIZE) {
-                size = ADDR_MAX_DATA_SIZE;
-            }
-
-            // adr-value = 0*6(text-value ";") text-value
-            //           ; PO Box, Extended Address, Street, Locality, Region, Postal
-            //           ; Code, Country Name
-            //
-            // Use Iterator assuming List may be LinkedList, though actually it is
-            // always ArrayList in the current implementation.
-            int i = 0;
-            for (String addressElement : propValueList) {
-                dataArray[i] = addressElement;
-                if (++i >= size) {
-                    break;
-                }
-            }
-            while (i < ADDR_MAX_DATA_SIZE) {
-                dataArray[i++] = null;
-            }
-
-            this.pobox = dataArray[0];
-            this.extendedAddress = dataArray[1];
-            this.street = dataArray[2];
-            this.localty = dataArray[3];
-            this.region = dataArray[4];
-            this.postalCode = dataArray[5];
-            this.country = dataArray[6];
-            this.label = label;
-            this.isPrimary = isPrimary;
-        }
-
-        @Override
-        public boolean equals(Object obj) {
-            if (!(obj instanceof PostalData)) {
-                return false;
-            }
-            final PostalData postalData = (PostalData)obj;
-            return (Arrays.equals(dataArray, postalData.dataArray) &&
-                    (type == postalData.type &&
-                            (type == StructuredPostal.TYPE_CUSTOM ?
-                                    (label == postalData.label) : true)) &&
-                    (isPrimary == postalData.isPrimary));
-        }
-
-        public String getFormattedAddress(final int vcardType) {
-            StringBuilder builder = new StringBuilder();
-            boolean empty = true;
-            if (VCardConfig.isJapaneseDevice(vcardType)) {
-                // In Japan, the order is reversed.
-                for (int i = ADDR_MAX_DATA_SIZE - 1; i >= 0; i--) {
-                    String addressPart = dataArray[i];
-                    if (!TextUtils.isEmpty(addressPart)) {
-                        if (!empty) {
-                            builder.append(' ');
-                        } else {
-                            empty = false;
-                        }
-                        builder.append(addressPart);
-                    }
-                }
-            } else {
-                for (int i = 0; i < ADDR_MAX_DATA_SIZE; i++) {
-                    String addressPart = dataArray[i];
-                    if (!TextUtils.isEmpty(addressPart)) {
-                        if (!empty) {
-                            builder.append(' ');
-                        } else {
-                            empty = false;
-                        }
-                        builder.append(addressPart);
-                    }
-                }
-            }
-
-            return builder.toString().trim();
-        }
-
-        @Override
-        public String toString() {
-            return String.format("type: %d, label: %s, isPrimary: %s",
-                    type, label, isPrimary);
-        }
-    }
-
-    static public 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".
-        public String companyName;
-        public String departmentName;
-        public String titleName;
-        public boolean isPrimary;
-
-        public OrganizationData(int type,
-                String companyName,
-                String departmentName,
-                String titleName,
-                boolean isPrimary) {
-            this.type = type;
-            this.companyName = companyName;
-            this.departmentName = departmentName;
-            this.titleName = titleName;
-            this.isPrimary = isPrimary;
-        }
-
-        @Override
-        public boolean equals(Object obj) {
-            if (!(obj instanceof OrganizationData)) {
-                return false;
-            }
-            OrganizationData organization = (OrganizationData)obj;
-            return (type == organization.type &&
-                    TextUtils.equals(companyName, organization.companyName) &&
-                    TextUtils.equals(departmentName, organization.departmentName) &&
-                    TextUtils.equals(titleName, organization.titleName) &&
-                    isPrimary == organization.isPrimary);
-        }
-
-        public String getFormattedString() {
-            final StringBuilder builder = new StringBuilder();
-            if (!TextUtils.isEmpty(companyName)) {
-                builder.append(companyName);
-            }
-
-            if (!TextUtils.isEmpty(departmentName)) {
-                if (builder.length() > 0) {
-                    builder.append(", ");
-                }
-                builder.append(departmentName);
-            }
-
-            if (!TextUtils.isEmpty(titleName)) {
-                if (builder.length() > 0) {
-                    builder.append(", ");
-                }
-                builder.append(titleName);
-            }
-
-            return builder.toString();
-        }
-
-        @Override
-        public String toString() {
-            return String.format(
-                    "type: %d, company: %s, department: %s, title: %s, isPrimary: %s",
-                    type, companyName, departmentName, titleName, isPrimary);
-        }
-    }
-
-    static public class ImData {
-        public final int protocol;
-        public final String customProtocol;
-        public final int type;
-        public final String data;
-        public final boolean isPrimary;
-
-        public ImData(final int protocol, final String customProtocol, final int type,
-                final String data, final boolean isPrimary) {
-            this.protocol = protocol;
-            this.customProtocol = customProtocol;
-            this.type = type;
-            this.data = data;
-            this.isPrimary = isPrimary;
-        }
-
-        @Override
-        public boolean equals(Object obj) {
-            if (!(obj instanceof ImData)) {
-                return false;
-            }
-            ImData imData = (ImData)obj;
-            return (type == imData.type && protocol == imData.protocol
-                    && (customProtocol != null ? customProtocol.equals(imData.customProtocol) :
-                        (imData.customProtocol == null))
-                    && (data != null ? data.equals(imData.data) : (imData.data == null))
-                    && isPrimary == imData.isPrimary);
-        }
-
-        @Override
-        public String toString() {
-            return String.format(
-                    "type: %d, protocol: %d, custom_protcol: %s, data: %s, isPrimary: %s",
-                    type, protocol, customProtocol, data, isPrimary);
-        }
-    }
-
-    public static class PhotoData {
-        public static final String FORMAT_FLASH = "SWF";
-        public final int type;
-        public final String formatName;  // used when type is not defined in ContactsContract.
-        public final byte[] photoBytes;
-        public final boolean isPrimary;
-
-        public PhotoData(int type, String formatName, byte[] photoBytes, boolean isPrimary) {
-            this.type = type;
-            this.formatName = formatName;
-            this.photoBytes = photoBytes;
-            this.isPrimary = isPrimary;
-        }
-
-        @Override
-        public boolean equals(Object obj) {
-            if (!(obj instanceof PhotoData)) {
-                return false;
-            }
-            PhotoData photoData = (PhotoData)obj;
-            return (type == photoData.type &&
-                    (formatName == null ? (photoData.formatName == null) :
-                            formatName.equals(photoData.formatName)) &&
-                    (Arrays.equals(photoBytes, photoData.photoBytes)) &&
-                    (isPrimary == photoData.isPrimary));
-        }
-
-        @Override
-        public String toString() {
-            return String.format("type: %d, format: %s: size: %d, isPrimary: %s",
-                    type, formatName, photoBytes.length, isPrimary);
-        }
-    }
-
-    /* package */ static class Property {
-        private String mPropertyName;
-        private Map<String, Collection<String>> mParameterMap =
-            new HashMap<String, Collection<String>>();
-        private List<String> mPropertyValueList = new ArrayList<String>();
-        private byte[] mPropertyBytes;
-
-        public void setPropertyName(final String propertyName) {
-            mPropertyName = propertyName;
-        }
-
-        public void addParameter(final String paramName, final String paramValue) {
-            Collection<String> values;
-            if (!mParameterMap.containsKey(paramName)) {
-                if (paramName.equals("TYPE")) {
-                    values = new HashSet<String>();
-                } else {
-                    values = new ArrayList<String>();
-                }
-                mParameterMap.put(paramName, values);
-            } else {
-                values = mParameterMap.get(paramName);
-            }
-            values.add(paramValue);
-        }
-
-        public void addToPropertyValueList(final String propertyValue) {
-            mPropertyValueList.add(propertyValue);
-        }
-
-        public void setPropertyBytes(final byte[] propertyBytes) {
-            mPropertyBytes = propertyBytes;
-        }
-
-        public final Collection<String> getParameters(String type) {
-            return mParameterMap.get(type);
-        }
-
-        public final List<String> getPropertyValueList() {
-            return mPropertyValueList;
-        }
-
-        public void clear() {
-            mPropertyName = null;
-            mParameterMap.clear();
-            mPropertyValueList.clear();
-            mPropertyBytes = null;
-        }
-    }
-
-    private String mFamilyName;
-    private String mGivenName;
-    private String mMiddleName;
-    private String mPrefix;
-    private String mSuffix;
-
-    // Used only when no family nor given name is found.
-    private String mFullName;
-
-    private String mPhoneticFamilyName;
-    private String mPhoneticGivenName;
-    private String mPhoneticMiddleName;
-
-    private String mPhoneticFullName;
-
-    private List<String> mNickNameList;
-
-    private String mDisplayName;
-
-    private String mBirthday;
-
-    private List<String> mNoteList;
-    private List<PhoneData> mPhoneList;
-    private List<EmailData> mEmailList;
-    private List<PostalData> mPostalList;
-    private List<OrganizationData> mOrganizationList;
-    private List<ImData> mImList;
-    private List<PhotoData> mPhotoList;
-    private List<String> mWebsiteList;
-    private List<List<String>> mAndroidCustomPropertyList;
-
-    private final int mVCardType;
-    private final Account mAccount;
-
-    public VCardEntry() {
-        this(VCardConfig.VCARD_TYPE_V21_GENERIC_UTF8);
-    }
-
-    public VCardEntry(int vcardType) {
-        this(vcardType, null);
-    }
-
-    public VCardEntry(int vcardType, Account account) {
-        mVCardType = vcardType;
-        mAccount = account;
-    }
-
-    private void addPhone(int type, String data, String label, boolean isPrimary) {
-        if (mPhoneList == null) {
-            mPhoneList = new ArrayList<PhoneData>();
-        }
-        final StringBuilder builder = new StringBuilder();
-        final String trimed = data.trim();
-        final String formattedNumber;
-        if (type == Phone.TYPE_PAGER || VCardConfig.refrainPhoneNumberFormatting(mVCardType)) {
-            formattedNumber = trimed;
-        } else {
-            final int length = trimed.length();
-            for (int i = 0; i < length; i++) {
-                char ch = trimed.charAt(i);
-                if (('0' <= ch && ch <= '9') || (i == 0 && ch == '+')) {
-                    builder.append(ch);
-                }
-            }
-
-            // Use NANP in default when there's no information about locale.
-            final int formattingType = VCardUtils.getPhoneNumberFormat(mVCardType);
-            formattedNumber = PhoneNumberUtils.formatNumber(builder.toString(), formattingType);
-        }
-        PhoneData phoneData = new PhoneData(type, formattedNumber, label, isPrimary);
-        mPhoneList.add(phoneData);
-    }
-
-    private void addNickName(final String nickName) {
-        if (mNickNameList == null) {
-            mNickNameList = new ArrayList<String>();
-        }
-        mNickNameList.add(nickName);
-    }
-
-    private void addEmail(int type, String data, String label, boolean isPrimary){
-        if (mEmailList == null) {
-            mEmailList = new ArrayList<EmailData>();
-        }
-        mEmailList.add(new EmailData(type, data, label, isPrimary));
-    }
-
-    private void addPostal(int type, List<String> propValueList, String label, boolean isPrimary){
-        if (mPostalList == null) {
-            mPostalList = new ArrayList<PostalData>(0);
-        }
-        mPostalList.add(new PostalData(type, propValueList, label, isPrimary));
-    }
-
-    /**
-     * Should be called via {@link #handleOrgValue(int, List, boolean)} or
-     * {@link #handleTitleValue(String)}.
-     */
-    private void addNewOrganization(int type, final String companyName,
-            final String departmentName,
-            final String titleName, boolean isPrimary) {
-        if (mOrganizationList == null) {
-            mOrganizationList = new ArrayList<OrganizationData>();
-        }
-        mOrganizationList.add(new OrganizationData(type, companyName,
-                departmentName, titleName, isPrimary));
-    }
-
-    private static final List<String> sEmptyList =
-            Collections.unmodifiableList(new ArrayList<String>(0));
-
-    /**
-     * Set "ORG" related values to the appropriate data. If there's more than one
-     * {@link OrganizationData} objects, this input data are attached to the last one which
-     * does not have valid values (not including empty but only null). If there's no
-     * {@link OrganizationData} object, a new {@link OrganizationData} is created,
-     * whose title is set to null.
-     */
-    private void handleOrgValue(final int type, List<String> orgList, boolean isPrimary) {
-        if (orgList == null) {
-            orgList = sEmptyList;
-        }
-        final String companyName;
-        final String departmentName;
-        final int size = orgList.size();
-        switch (size) {
-            case 0: {
-                companyName = "";
-                departmentName = null;
-                break;
-            }
-            case 1: {
-                companyName = orgList.get(0);
-                departmentName = null;
-                break;
-            }
-            default: {  // More than 1.
-                companyName = orgList.get(0);
-                // We're not sure which is the correct string for department.
-                // In order to keep all the data, concatinate the rest of elements.
-                StringBuilder builder = new StringBuilder();
-                for (int i = 1; i < size; i++) {
-                    if (i > 1) {
-                        builder.append(' ');
-                    }
-                    builder.append(orgList.get(i));
-                }
-                departmentName = builder.toString();
-            }
-        }
-        if (mOrganizationList == null) {
-            // Create new first organization entry, with "null" title which may be
-            // added via handleTitleValue().
-            addNewOrganization(type, companyName, departmentName, null, isPrimary);
-            return;
-        }
-        for (OrganizationData organizationData : mOrganizationList) {
-            // Not use TextUtils.isEmpty() since ORG was set but the elements might be empty.
-            // e.g. "ORG;PREF:;" -> Both companyName and departmentName become empty but not null.
-            if (organizationData.companyName == null &&
-                    organizationData.departmentName == null) {
-                // Probably the "TITLE" property comes before the "ORG" property via
-                // handleTitleLine().
-                organizationData.companyName = companyName;
-                organizationData.departmentName = departmentName;
-                organizationData.isPrimary = isPrimary;
-                return;
-            }
-        }
-        // No OrganizatioData is available. Create another one, with "null" title, which may be
-        // added via handleTitleValue().
-        addNewOrganization(type, companyName, departmentName, null, isPrimary);
-    }
-
-    /**
-     * Set "title" value to the appropriate data. If there's more than one
-     * OrganizationData objects, this input is attached to the last one which does not
-     * have valid title value (not including empty but only null). If there's no
-     * OrganizationData object, a new OrganizationData is created, whose company name is
-     * set to null.
-     */
-    private void handleTitleValue(final String title) {
-        if (mOrganizationList == null) {
-            // Create new first organization entry, with "null" other info, which may be
-            // added via handleOrgValue().
-            addNewOrganization(DEFAULT_ORGANIZATION_TYPE, null, null, title, false);
-            return;
-        }
-        for (OrganizationData organizationData : mOrganizationList) {
-            if (organizationData.titleName == null) {
-                organizationData.titleName = title;
-                return;
-            }
-        }
-        // No Organization is available. Create another one, with "null" other info, which may be
-        // added via handleOrgValue().
-        addNewOrganization(DEFAULT_ORGANIZATION_TYPE, null, null, title, false);
-    }
-
-    private void addIm(int protocol, String customProtocol, int type,
-            String propValue, boolean isPrimary) {
-        if (mImList == null) {
-            mImList = new ArrayList<ImData>();
-        }
-        mImList.add(new ImData(protocol, customProtocol, type, propValue, isPrimary));
-    }
-
-    private void addNote(final String note) {
-        if (mNoteList == null) {
-            mNoteList = new ArrayList<String>(1);
-        }
-        mNoteList.add(note);
-    }
-
-    private void addPhotoBytes(String formatName, byte[] photoBytes, boolean isPrimary) {
-        if (mPhotoList == null) {
-            mPhotoList = new ArrayList<PhotoData>(1);
-        }
-        final PhotoData photoData = new PhotoData(0, null, photoBytes, isPrimary);
-        mPhotoList.add(photoData);
-    }
-
-    @SuppressWarnings("fallthrough")
-    private void handleNProperty(List<String> elems) {
-        // Family, Given, Middle, Prefix, Suffix. (1 - 5)
-        int size;
-        if (elems == null || (size = elems.size()) < 1) {
-            return;
-        }
-        if (size > 5) {
-            size = 5;
-        }
-
-        switch (size) {
-            // fallthrough
-            case 5: mSuffix = elems.get(4);
-            case 4: mPrefix = elems.get(3);
-            case 3: mMiddleName = elems.get(2);
-            case 2: mGivenName = elems.get(1);
-            default: mFamilyName = elems.get(0);
-        }
-    }
-
-    /**
-     * Note: Some Japanese mobile phones use this field for phonetic name,
-     *       since vCard 2.1 does not have "SORT-STRING" type.
-     *       Also, in some cases, the field has some ';'s in it.
-     *       Assume the ';' means the same meaning in N property
-     */
-    @SuppressWarnings("fallthrough")
-    private void handlePhoneticNameFromSound(List<String> elems) {
-        if (!(TextUtils.isEmpty(mPhoneticFamilyName) &&
-                TextUtils.isEmpty(mPhoneticMiddleName) &&
-                TextUtils.isEmpty(mPhoneticGivenName))) {
-            // This means the other properties like "X-PHONETIC-FIRST-NAME" was already found.
-            // Ignore "SOUND;X-IRMC-N".
-            return;
-        }
-
-        int size;
-        if (elems == null || (size = elems.size()) < 1) {
-            return;
-        }
-
-        // Assume that the order is "Family, Given, Middle".
-        // This is not from specification but mere assumption. Some Japanese phones use this order.
-        if (size > 3) {
-            size = 3;
-        }
-
-        if (elems.get(0).length() > 0) {
-            boolean onlyFirstElemIsNonEmpty = true;
-            for (int i = 1; i < size; i++) {
-                if (elems.get(i).length() > 0) {
-                    onlyFirstElemIsNonEmpty = false;
-                    break;
-                }
-            }
-            if (onlyFirstElemIsNonEmpty) {
-                final String[] namesArray = elems.get(0).split(" ");
-                final int nameArrayLength = namesArray.length;
-                if (nameArrayLength == 3) {
-                    // Assume the string is "Family Middle Given".
-                    mPhoneticFamilyName = namesArray[0];
-                    mPhoneticMiddleName = namesArray[1];
-                    mPhoneticGivenName = namesArray[2];
-                } else if (nameArrayLength == 2) {
-                    // Assume the string is "Family Given" based on the Japanese mobile
-                    // phones' preference.
-                    mPhoneticFamilyName = namesArray[0];
-                    mPhoneticGivenName = namesArray[1];
-                } else {
-                    mPhoneticFullName = elems.get(0);
-                }
-                return;
-            }
-        }
-
-        switch (size) {
-            // fallthrough
-            case 3: mPhoneticMiddleName = elems.get(2);
-            case 2: mPhoneticGivenName = elems.get(1);
-            default: mPhoneticFamilyName = elems.get(0);
-        }
-    }
-
-    public void addProperty(final Property property) {
-        final String propName = property.mPropertyName;
-        final Map<String, Collection<String>> paramMap = property.mParameterMap;
-        final List<String> propValueList = property.mPropertyValueList;
-        byte[] propBytes = property.mPropertyBytes;
-
-        if (propValueList.size() == 0) {
-            return;
-        }
-        final String propValue = listToString(propValueList).trim();
-
-        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) {
-            // 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;
-        } else if (propName.equals(VCardConstants.PROPERTY_N)) {
-            handleNProperty(propValueList);
-        } else if (propName.equals(VCardConstants.PROPERTY_SORT_STRING)) {
-            mPhoneticFullName = propValue;
-        } else if (propName.equals(VCardConstants.PROPERTY_NICKNAME) ||
-                propName.equals(VCardConstants.ImportOnly.PROPERTY_X_NICKNAME)) {
-            addNickName(propValue);
-        } else if (propName.equals(VCardConstants.PROPERTY_SOUND)) {
-            Collection<String> typeCollection = paramMap.get(VCardConstants.PARAM_TYPE);
-            if (typeCollection != null
-                    && typeCollection.contains(VCardConstants.PARAM_TYPE_X_IRMC_N)) {
-                // As of 2009-10-08, Parser side does not split a property value into separated
-                // values using ';' (in other words, propValueList.size() == 1),
-                // which is correct behavior from the view of vCard 2.1.
-                // But we want it to be separated, so do the separation here.
-                final List<String> phoneticNameList =
-                        VCardUtils.constructListFromValue(propValue,
-                                VCardConfig.isV30(mVCardType));
-                handlePhoneticNameFromSound(phoneticNameList);
-            } else {
-                // Ignore this field since Android cannot understand what it is.
-            }
-        } else if (propName.equals(VCardConstants.PROPERTY_ADR)) {
-            boolean valuesAreAllEmpty = true;
-            for (String value : propValueList) {
-                if (value.length() > 0) {
-                    valuesAreAllEmpty = false;
-                    break;
-                }
-            }
-            if (valuesAreAllEmpty) {
-                return;
-            }
-
-            int type = -1;
-            String label = "";
-            boolean isPrimary = false;
-            Collection<String> typeCollection = paramMap.get(VCardConstants.PARAM_TYPE);
-            if (typeCollection != null) {
-                for (String typeString : typeCollection) {
-                    typeString = typeString.toUpperCase();
-                    if (typeString.equals(VCardConstants.PARAM_TYPE_PREF)) {
-                        isPrimary = true;
-                    } else if (typeString.equals(VCardConstants.PARAM_TYPE_HOME)) {
-                        type = StructuredPostal.TYPE_HOME;
-                        label = "";
-                    } else if (typeString.equals(VCardConstants.PARAM_TYPE_WORK) ||
-                            typeString.equalsIgnoreCase(VCardConstants.PARAM_EXTRA_TYPE_COMPANY)) {
-                        // "COMPANY" seems emitted by Windows Mobile, which is not
-                        // specifically supported by vCard 2.1. We assume this is same
-                        // as "WORK".
-                        type = StructuredPostal.TYPE_WORK;
-                        label = "";
-                    } else if (typeString.equals(VCardConstants.PARAM_ADR_TYPE_PARCEL) ||
-                            typeString.equals(VCardConstants.PARAM_ADR_TYPE_DOM) ||
-                            typeString.equals(VCardConstants.PARAM_ADR_TYPE_INTL)) {
-                        // We do not have any appropriate way to store this information.
-                    } else {
-                        if (typeString.startsWith("X-") && type < 0) {
-                            typeString = typeString.substring(2);
-                        }
-                        // vCard 3.0 allows iana-token. Also some vCard 2.1 exporters
-                        // emit non-standard types. We do not handle their values now.
-                        type = StructuredPostal.TYPE_CUSTOM;
-                        label = typeString;
-                    }
-                }
-            }
-            // We use "HOME" as default
-            if (type < 0) {
-                type = StructuredPostal.TYPE_HOME;
-            }
-
-            addPostal(type, propValueList, label, isPrimary);
-        } else if (propName.equals(VCardConstants.PROPERTY_EMAIL)) {
-            int type = -1;
-            String label = null;
-            boolean isPrimary = false;
-            Collection<String> typeCollection = paramMap.get(VCardConstants.PARAM_TYPE);
-            if (typeCollection != null) {
-                for (String typeString : typeCollection) {
-                    typeString = typeString.toUpperCase();
-                    if (typeString.equals(VCardConstants.PARAM_TYPE_PREF)) {
-                        isPrimary = true;
-                    } else if (typeString.equals(VCardConstants.PARAM_TYPE_HOME)) {
-                        type = Email.TYPE_HOME;
-                    } else if (typeString.equals(VCardConstants.PARAM_TYPE_WORK)) {
-                        type = Email.TYPE_WORK;
-                    } else if (typeString.equals(VCardConstants.PARAM_TYPE_CELL)) {
-                        type = Email.TYPE_MOBILE;
-                    } else {
-                        if (typeString.startsWith("X-") && type < 0) {
-                            typeString = typeString.substring(2);
-                        }
-                        // vCard 3.0 allows iana-token.
-                        // We may have INTERNET (specified in vCard spec),
-                        // SCHOOL, etc.
-                        type = Email.TYPE_CUSTOM;
-                        label = typeString;
-                    }
-                }
-            }
-            if (type < 0) {
-                type = Email.TYPE_OTHER;
-            }
-            addEmail(type, propValue, label, isPrimary);
-        } else if (propName.equals(VCardConstants.PROPERTY_ORG)) {
-            // vCard specification does not specify other types.
-            final int type = Organization.TYPE_WORK;
-            boolean isPrimary = false;
-            Collection<String> typeCollection = paramMap.get(VCardConstants.PARAM_TYPE);
-            if (typeCollection != null) {
-                for (String typeString : typeCollection) {
-                    if (typeString.equals(VCardConstants.PARAM_TYPE_PREF)) {
-                        isPrimary = true;
-                    }
-                }
-            }
-            handleOrgValue(type, propValueList, isPrimary);
-        } else if (propName.equals(VCardConstants.PROPERTY_TITLE)) {
-            handleTitleValue(propValue);
-        } else if (propName.equals(VCardConstants.PROPERTY_ROLE)) {
-            // This conflicts with TITLE. Ignore for now...
-            // handleTitleValue(propValue);
-        } else if (propName.equals(VCardConstants.PROPERTY_PHOTO) ||
-                propName.equals(VCardConstants.PROPERTY_LOGO)) {
-            Collection<String> paramMapValue = paramMap.get("VALUE");
-            if (paramMapValue != null && paramMapValue.contains("URL")) {
-                // Currently we do not have appropriate example for testing this case.
-            } else {
-                final Collection<String> typeCollection = paramMap.get("TYPE");
-                String formatName = null;
-                boolean isPrimary = false;
-                if (typeCollection != null) {
-                    for (String typeValue : typeCollection) {
-                        if (VCardConstants.PARAM_TYPE_PREF.equals(typeValue)) {
-                            isPrimary = true;
-                        } else if (formatName == null){
-                            formatName = typeValue;
-                        }
-                    }
-                }
-                addPhotoBytes(formatName, propBytes, isPrimary);
-            }
-        } else if (propName.equals(VCardConstants.PROPERTY_TEL)) {
-            final Collection<String> typeCollection = paramMap.get(VCardConstants.PARAM_TYPE);
-            final Object typeObject =
-                VCardUtils.getPhoneTypeFromStrings(typeCollection, propValue);
-            final int type;
-            final String label;
-            if (typeObject instanceof Integer) {
-                type = (Integer)typeObject;
-                label = null;
-            } else {
-                type = Phone.TYPE_CUSTOM;
-                label = typeObject.toString();
-            }
-
-            final boolean isPrimary;
-            if (typeCollection != null && typeCollection.contains(VCardConstants.PARAM_TYPE_PREF)) {
-                isPrimary = true;
-            } else {
-                isPrimary = false;
-            }
-            addPhone(type, propValue, label, isPrimary);
-        } else if (propName.equals(VCardConstants.PROPERTY_X_SKYPE_PSTNNUMBER)) {
-            // The phone number available via Skype.
-            Collection<String> typeCollection = paramMap.get(VCardConstants.PARAM_TYPE);
-            final int type = Phone.TYPE_OTHER;
-            final boolean isPrimary;
-            if (typeCollection != null && typeCollection.contains(VCardConstants.PARAM_TYPE_PREF)) {
-                isPrimary = true;
-            } else {
-                isPrimary = false;
-            }
-            addPhone(type, propValue, null, isPrimary);
-        } else if (sImMap.containsKey(propName)) {
-            final int protocol = sImMap.get(propName);
-            boolean isPrimary = false;
-            int type = -1;
-            final Collection<String> typeCollection = paramMap.get(VCardConstants.PARAM_TYPE);
-            if (typeCollection != null) {
-                for (String typeString : typeCollection) {
-                    if (typeString.equals(VCardConstants.PARAM_TYPE_PREF)) {
-                        isPrimary = true;
-                    } else if (type < 0) {
-                        if (typeString.equalsIgnoreCase(VCardConstants.PARAM_TYPE_HOME)) {
-                            type = Im.TYPE_HOME;
-                        } else if (typeString.equalsIgnoreCase(VCardConstants.PARAM_TYPE_WORK)) {
-                            type = Im.TYPE_WORK;
-                        }
-                    }
-                }
-            }
-            if (type < 0) {
-                type = Phone.TYPE_HOME;
-            }
-            addIm(protocol, null, type, propValue, isPrimary);
-        } else if (propName.equals(VCardConstants.PROPERTY_NOTE)) {
-            addNote(propValue);
-        } else if (propName.equals(VCardConstants.PROPERTY_URL)) {
-            if (mWebsiteList == null) {
-                mWebsiteList = new ArrayList<String>(1);
-            }
-            mWebsiteList.add(propValue);
-        } else if (propName.equals(VCardConstants.PROPERTY_BDAY)) {
-            mBirthday = propValue;
-        } else if (propName.equals(VCardConstants.PROPERTY_X_PHONETIC_FIRST_NAME)) {
-            mPhoneticGivenName = propValue;
-        } else if (propName.equals(VCardConstants.PROPERTY_X_PHONETIC_MIDDLE_NAME)) {
-            mPhoneticMiddleName = propValue;
-        } else if (propName.equals(VCardConstants.PROPERTY_X_PHONETIC_LAST_NAME)) {
-            mPhoneticFamilyName = propValue;
-        } else if (propName.equals(VCardConstants.PROPERTY_X_ANDROID_CUSTOM)) {
-            final List<String> customPropertyList =
-                VCardUtils.constructListFromValue(propValue,
-                        VCardConfig.isV30(mVCardType));
-            handleAndroidCustomProperty(customPropertyList);
-        /*} else if (propName.equals("REV")) {
-            // Revision of this VCard entry. I think we can ignore this.
-        } else if (propName.equals("UID")) {
-        } else if (propName.equals("KEY")) {
-            // Type is X509 or PGP? I don't know how to handle this...
-        } else if (propName.equals("MAILER")) {
-        } else if (propName.equals("TZ")) {
-        } else if (propName.equals("GEO")) {
-        } else if (propName.equals("CLASS")) {
-            // vCard 3.0 only.
-            // e.g. CLASS:CONFIDENTIAL
-        } else if (propName.equals("PROFILE")) {
-            // VCard 3.0 only. Must be "VCARD". I think we can ignore this.
-        } else if (propName.equals("CATEGORIES")) {
-            // VCard 3.0 only.
-            // e.g. CATEGORIES:INTERNET,IETF,INDUSTRY,INFORMATION TECHNOLOGY
-        } else if (propName.equals("SOURCE")) {
-            // VCard 3.0 only.
-        } else if (propName.equals("PRODID")) {
-            // VCard 3.0 only.
-            // To specify the identifier for the product that created
-            // the vCard object.*/
-        } else {
-            // Unknown X- words and IANA token.
-        }
-    }
-
-    private void handleAndroidCustomProperty(final List<String> customPropertyList) {
-        if (mAndroidCustomPropertyList == null) {
-            mAndroidCustomPropertyList = new ArrayList<List<String>>();
-        }
-        mAndroidCustomPropertyList.add(customPropertyList);
-    }
-
-    /**
-     * Construct the display name. The constructed data must not be null.
-     */
-    private void constructDisplayName() {
-        // FullName (created via "FN" or "NAME" field) is prefered.
-        if (!TextUtils.isEmpty(mFullName)) {
-            mDisplayName = mFullName;
-        } else if (!(TextUtils.isEmpty(mFamilyName) && TextUtils.isEmpty(mGivenName))) {
-            mDisplayName = VCardUtils.constructNameFromElements(mVCardType,
-                    mFamilyName, mMiddleName, mGivenName, mPrefix, mSuffix);
-        } else if (!(TextUtils.isEmpty(mPhoneticFamilyName) &&
-                TextUtils.isEmpty(mPhoneticGivenName))) {
-            mDisplayName = VCardUtils.constructNameFromElements(mVCardType,
-                    mPhoneticFamilyName, mPhoneticMiddleName, mPhoneticGivenName);
-        } else if (mEmailList != null && mEmailList.size() > 0) {
-            mDisplayName = mEmailList.get(0).data;
-        } else if (mPhoneList != null && mPhoneList.size() > 0) {
-            mDisplayName = mPhoneList.get(0).data;
-        } else if (mPostalList != null && mPostalList.size() > 0) {
-            mDisplayName = mPostalList.get(0).getFormattedAddress(mVCardType);
-        } else if (mOrganizationList != null && mOrganizationList.size() > 0) {
-            mDisplayName = mOrganizationList.get(0).getFormattedString();
-        }
-
-        if (mDisplayName == null) {
-            mDisplayName = "";
-        }
-    }
-
-    /**
-     * Consolidate several fielsds (like mName) using name candidates,
-     */
-    public void consolidateFields() {
-        constructDisplayName();
-
-        if (mPhoneticFullName != null) {
-            mPhoneticFullName = mPhoneticFullName.trim();
-        }
-    }
-
-    public Uri pushIntoContentResolver(ContentResolver resolver) {
-        ArrayList<ContentProviderOperation> operationList =
-            new ArrayList<ContentProviderOperation>();
-        // After applying the batch the first result's Uri is returned so it is important that
-        // the RawContact is the first operation that gets inserted into the list
-        ContentProviderOperation.Builder builder =
-            ContentProviderOperation.newInsert(RawContacts.CONTENT_URI);
-        String myGroupsId = null;
-        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);
-        }
-        operationList.add(builder.build());
-
-        if (!nameFieldsAreEmpty()) {
-            builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
-            builder.withValueBackReference(StructuredName.RAW_CONTACT_ID, 0);
-            builder.withValue(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE);
-
-            builder.withValue(StructuredName.GIVEN_NAME, mGivenName);
-            builder.withValue(StructuredName.FAMILY_NAME, mFamilyName);
-            builder.withValue(StructuredName.MIDDLE_NAME, mMiddleName);
-            builder.withValue(StructuredName.PREFIX, mPrefix);
-            builder.withValue(StructuredName.SUFFIX, mSuffix);
-
-            if (!(TextUtils.isEmpty(mPhoneticGivenName)
-                    && TextUtils.isEmpty(mPhoneticFamilyName)
-                    && TextUtils.isEmpty(mPhoneticMiddleName))) {
-                builder.withValue(StructuredName.PHONETIC_GIVEN_NAME, mPhoneticGivenName);
-                builder.withValue(StructuredName.PHONETIC_FAMILY_NAME, mPhoneticFamilyName);
-                builder.withValue(StructuredName.PHONETIC_MIDDLE_NAME, mPhoneticMiddleName);
-            } else if (!TextUtils.isEmpty(mPhoneticFullName)) {
-                builder.withValue(StructuredName.PHONETIC_GIVEN_NAME, mPhoneticFullName);
-            }
-
-            builder.withValue(StructuredName.DISPLAY_NAME, getDisplayName());
-            operationList.add(builder.build());
-        }
-
-        if (mNickNameList != null && mNickNameList.size() > 0) {
-            for (String nickName : mNickNameList) {
-                builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
-                builder.withValueBackReference(Nickname.RAW_CONTACT_ID, 0);
-                builder.withValue(Data.MIMETYPE, Nickname.CONTENT_ITEM_TYPE);
-                builder.withValue(Nickname.TYPE, Nickname.TYPE_DEFAULT);
-                builder.withValue(Nickname.NAME, nickName);
-                operationList.add(builder.build());
-            }
-        }
-
-        if (mPhoneList != null) {
-            for (PhoneData phoneData : mPhoneList) {
-                builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
-                builder.withValueBackReference(Phone.RAW_CONTACT_ID, 0);
-                builder.withValue(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
-
-                builder.withValue(Phone.TYPE, phoneData.type);
-                if (phoneData.type == Phone.TYPE_CUSTOM) {
-                    builder.withValue(Phone.LABEL, phoneData.label);
-                }
-                builder.withValue(Phone.NUMBER, phoneData.data);
-                if (phoneData.isPrimary) {
-                    builder.withValue(Phone.IS_PRIMARY, 1);
-                }
-                operationList.add(builder.build());
-            }
-        }
-
-        if (mOrganizationList != null) {
-            for (OrganizationData organizationData : mOrganizationList) {
-                builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
-                builder.withValueBackReference(Organization.RAW_CONTACT_ID, 0);
-                builder.withValue(Data.MIMETYPE, Organization.CONTENT_ITEM_TYPE);
-                builder.withValue(Organization.TYPE, organizationData.type);
-                if (organizationData.companyName != null) {
-                    builder.withValue(Organization.COMPANY, organizationData.companyName);
-                }
-                if (organizationData.departmentName != null) {
-                    builder.withValue(Organization.DEPARTMENT, organizationData.departmentName);
-                }
-                if (organizationData.titleName != null) {
-                    builder.withValue(Organization.TITLE, organizationData.titleName);
-                }
-                if (organizationData.isPrimary) {
-                    builder.withValue(Organization.IS_PRIMARY, 1);
-                }
-                operationList.add(builder.build());
-            }
-        }
-
-        if (mEmailList != null) {
-            for (EmailData emailData : mEmailList) {
-                builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
-                builder.withValueBackReference(Email.RAW_CONTACT_ID, 0);
-                builder.withValue(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE);
-
-                builder.withValue(Email.TYPE, emailData.type);
-                if (emailData.type == Email.TYPE_CUSTOM) {
-                    builder.withValue(Email.LABEL, emailData.label);
-                }
-                builder.withValue(Email.DATA, emailData.data);
-                if (emailData.isPrimary) {
-                    builder.withValue(Data.IS_PRIMARY, 1);
-                }
-                operationList.add(builder.build());
-            }
-        }
-
-        if (mPostalList != null) {
-            for (PostalData postalData : mPostalList) {
-                builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
-                VCardUtils.insertStructuredPostalDataUsingContactsStruct(
-                        mVCardType, builder, postalData);
-                operationList.add(builder.build());
-            }
-        }
-
-        if (mImList != null) {
-            for (ImData imData : mImList) {
-                builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
-                builder.withValueBackReference(Im.RAW_CONTACT_ID, 0);
-                builder.withValue(Data.MIMETYPE, Im.CONTENT_ITEM_TYPE);
-                builder.withValue(Im.TYPE, imData.type);
-                builder.withValue(Im.PROTOCOL, imData.protocol);
-                if (imData.protocol == Im.PROTOCOL_CUSTOM) {
-                    builder.withValue(Im.CUSTOM_PROTOCOL, imData.customProtocol);
-                }
-                if (imData.isPrimary) {
-                    builder.withValue(Data.IS_PRIMARY, 1);
-                }
-            }
-        }
-
-        if (mNoteList != null) {
-            for (String note : mNoteList) {
-                builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
-                builder.withValueBackReference(Note.RAW_CONTACT_ID, 0);
-                builder.withValue(Data.MIMETYPE, Note.CONTENT_ITEM_TYPE);
-                builder.withValue(Note.NOTE, note);
-                operationList.add(builder.build());
-            }
-        }
-
-        if (mPhotoList != null) {
-            for (PhotoData photoData : mPhotoList) {
-                builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
-                builder.withValueBackReference(Photo.RAW_CONTACT_ID, 0);
-                builder.withValue(Data.MIMETYPE, Photo.CONTENT_ITEM_TYPE);
-                builder.withValue(Photo.PHOTO, photoData.photoBytes);
-                if (photoData.isPrimary) {
-                    builder.withValue(Photo.IS_PRIMARY, 1);
-                }
-                operationList.add(builder.build());
-            }
-        }
-
-        if (mWebsiteList != null) {
-            for (String website : mWebsiteList) {
-                builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
-                builder.withValueBackReference(Website.RAW_CONTACT_ID, 0);
-                builder.withValue(Data.MIMETYPE, Website.CONTENT_ITEM_TYPE);
-                builder.withValue(Website.URL, website);
-                // There's no information about the type of URL in vCard.
-                // We use TYPE_HOMEPAGE for safety.
-                builder.withValue(Website.TYPE, Website.TYPE_HOMEPAGE);
-                operationList.add(builder.build());
-            }
-        }
-
-        if (!TextUtils.isEmpty(mBirthday)) {
-            builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
-            builder.withValueBackReference(Event.RAW_CONTACT_ID, 0);
-            builder.withValue(Data.MIMETYPE, Event.CONTENT_ITEM_TYPE);
-            builder.withValue(Event.START_DATE, mBirthday);
-            builder.withValue(Event.TYPE, Event.TYPE_BIRTHDAY);
-            operationList.add(builder.build());
-        }
-
-        if (mAndroidCustomPropertyList != null) {
-            for (List<String> customPropertyList : mAndroidCustomPropertyList) {
-                int size = customPropertyList.size();
-                if (size < 2 || TextUtils.isEmpty(customPropertyList.get(0))) {
-                    continue;
-                } else if (size > VCardConstants.MAX_DATA_COLUMN + 1) {
-                    size = VCardConstants.MAX_DATA_COLUMN + 1;
-                    customPropertyList =
-                        customPropertyList.subList(0, VCardConstants.MAX_DATA_COLUMN + 2);
-                }
-
-                int i = 0;
-                for (final String customPropertyValue : customPropertyList) {
-                    if (i == 0) {
-                        final String mimeType = customPropertyValue;
-                        builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
-                        builder.withValueBackReference(GroupMembership.RAW_CONTACT_ID, 0);
-                        builder.withValue(Data.MIMETYPE, mimeType);
-                    } else {  // 1 <= i && i <= MAX_DATA_COLUMNS
-                        if (!TextUtils.isEmpty(customPropertyValue)) {
-                            builder.withValue("data" + i, customPropertyValue);
-                        }
-                    }
-
-                    i++;
-                }
-                operationList.add(builder.build());
-            }
-        }
-
-        if (myGroupsId != null) {
-            builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
-            builder.withValueBackReference(GroupMembership.RAW_CONTACT_ID, 0);
-            builder.withValue(Data.MIMETYPE, GroupMembership.CONTENT_ITEM_TYPE);
-            builder.withValue(GroupMembership.GROUP_SOURCE_ID, myGroupsId);
-            operationList.add(builder.build());
-        }
-
-        try {
-            ContentProviderResult[] results = resolver.applyBatch(
-                        ContactsContract.AUTHORITY, operationList);
-            // the first result is always the raw_contact. return it's uri so
-            // that it can be found later. do null checking for badly behaving
-            // ContentResolvers
-            return (results == null || results.length == 0 || results[0] == null)
-                ? null
-                : results[0].uri;
-        } catch (RemoteException e) {
-            Log.e(LOG_TAG, String.format("%s: %s", e.toString(), e.getMessage()));
-            return null;
-        } catch (OperationApplicationException e) {
-            Log.e(LOG_TAG, String.format("%s: %s", e.toString(), e.getMessage()));
-            return null;
-        }
-    }
-
-    public static VCardEntry buildFromResolver(ContentResolver resolver) {
-        return buildFromResolver(resolver, Contacts.CONTENT_URI);
-    }
-
-    public static VCardEntry buildFromResolver(ContentResolver resolver, Uri uri) {
-
-        return null;
-    }
-
-    private boolean nameFieldsAreEmpty() {
-        return (TextUtils.isEmpty(mFamilyName)
-                && TextUtils.isEmpty(mMiddleName)
-                && TextUtils.isEmpty(mGivenName)
-                && TextUtils.isEmpty(mPrefix)
-                && TextUtils.isEmpty(mSuffix)
-                && TextUtils.isEmpty(mFullName)
-                && TextUtils.isEmpty(mPhoneticFamilyName)
-                && TextUtils.isEmpty(mPhoneticMiddleName)
-                && TextUtils.isEmpty(mPhoneticGivenName)
-                && TextUtils.isEmpty(mPhoneticFullName));
-    }
-
-    public boolean isIgnorable() {
-        return getDisplayName().length() == 0;
-    }
-
-    private String listToString(List<String> list){
-        final int size = list.size();
-        if (size > 1) {
-            StringBuilder builder = new StringBuilder();
-            int i = 0;
-            for (String type : list) {
-                builder.append(type);
-                if (i < size - 1) {
-                    builder.append(";");
-                }
-            }
-            return builder.toString();
-        } else if (size == 1) {
-            return list.get(0);
-        } else {
-            return "";
-        }
-    }
-
-    // All getter methods should be used carefully, since they may change
-    // in the future as of 2009-10-05, on which I cannot be sure this structure
-    // is completely consolidated.
-    //
-    // Also note that these getter methods should be used only after
-    // all properties being pushed into this object. If not, incorrect
-    // value will "be stored in the local cache and" be returned to you.
-
-    public String getFamilyName() {
-        return mFamilyName;
-    }
-
-    public String getGivenName() {
-        return mGivenName;
-    }
-
-    public String getMiddleName() {
-        return mMiddleName;
-    }
-
-    public String getPrefix() {
-        return mPrefix;
-    }
-
-    public String getSuffix() {
-        return mSuffix;
-    }
-
-    public String getFullName() {
-        return mFullName;
-    }
-
-    public String getPhoneticFamilyName() {
-        return mPhoneticFamilyName;
-    }
-
-    public String getPhoneticGivenName() {
-        return mPhoneticGivenName;
-    }
-
-    public String getPhoneticMiddleName() {
-        return mPhoneticMiddleName;
-    }
-
-    public String getPhoneticFullName() {
-        return mPhoneticFullName;
-    }
-
-    public final List<String> getNickNameList() {
-        return mNickNameList;
-    }
-
-    public String getBirthday() {
-        return mBirthday;
-    }
-
-    public final List<String> getNotes() {
-        return mNoteList;
-    }
-
-    public final List<PhoneData> getPhoneList() {
-        return mPhoneList;
-    }
-
-    public final List<EmailData> getEmailList() {
-        return mEmailList;
-    }
-
-    public final List<PostalData> getPostalList() {
-        return mPostalList;
-    }
-
-    public final List<OrganizationData> getOrganizationList() {
-        return mOrganizationList;
-    }
-
-    public final List<ImData> getImList() {
-        return mImList;
-    }
-
-    public final List<PhotoData> getPhotoList() {
-        return mPhotoList;
-    }
-
-    public final List<String> getWebsiteList() {
-        return mWebsiteList;
-    }
-
-    public String getDisplayName() {
-        if (mDisplayName == null) {
-            constructDisplayName();
-        }
-        return mDisplayName;
-    }
-}
diff --git a/core/java/android/pim/vcard/VCardEntryCommitter.java b/core/java/android/pim/vcard/VCardEntryCommitter.java
deleted file mode 100644
index 59a2baf..0000000
--- a/core/java/android/pim/vcard/VCardEntryCommitter.java
+++ /dev/null
@@ -1,68 +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.pim.vcard;
-
-import android.content.ContentResolver;
-import android.net.Uri;
-import android.util.Log;
-
-import java.util.ArrayList;
-
-/**
- * <P>
- * {@link VCardEntryHandler} implementation which commits the entry to ContentResolver.
- * </P>
- * <P>
- * Note:<BR />
- * Each vCard may contain big photo images encoded by BASE64,
- * If we store all vCard entries in memory, OutOfMemoryError may be thrown.
- * Thus, this class push each VCard entry into ContentResolver immediately.
- * </P>
- */
-public class VCardEntryCommitter implements VCardEntryHandler {
-    public static String LOG_TAG = "VCardEntryComitter";
-
-    private final ContentResolver mContentResolver;
-    private long mTimeToCommit;
-    private ArrayList<Uri> mCreatedUris = new ArrayList<Uri>();
-
-    public VCardEntryCommitter(ContentResolver resolver) {
-        mContentResolver = resolver;
-    }
-
-    public void onStart() {
-    }
-
-    public void onEnd() {
-        if (VCardConfig.showPerformanceLog()) {
-            Log.d(LOG_TAG, String.format("time to commit entries: %d ms", mTimeToCommit));
-        }
-    }
-
-    public void onEntryCreated(final VCardEntry contactStruct) {
-        long start = System.currentTimeMillis();
-        mCreatedUris.add(contactStruct.pushIntoContentResolver(mContentResolver));
-        mTimeToCommit += System.currentTimeMillis() - start;
-    }
-
-    /**
-     * Returns the list of created Uris. This list should not be modified by the caller as it is
-     * not a clone.
-     */
-   public ArrayList<Uri> getCreatedUris() {
-        return mCreatedUris;
-    }
-}
\ No newline at end of file
diff --git a/core/java/android/pim/vcard/VCardEntryConstructor.java b/core/java/android/pim/vcard/VCardEntryConstructor.java
deleted file mode 100644
index 290ca2b..0000000
--- a/core/java/android/pim/vcard/VCardEntryConstructor.java
+++ /dev/null
@@ -1,305 +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.pim.vcard;
-
-import android.accounts.Account;
-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;
-import java.nio.charset.Charset;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-
-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 String mParamType;
-    
-    /**
-     * The charset using which {@link VCardInterpreter} parses the text.
-     */
-    private String mInputCharset;
-
-    /**
-     * 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;
-    
-    /** For measuring performance. */
-    private long mTimePushIntoContentResolver;
-
-    final private List<VCardEntryHandler> mEntryHandlers = new ArrayList<VCardEntryHandler>();
-
-    public VCardEntryConstructor() {
-        this(null, null, false, VCardConfig.VCARD_TYPE_V21_GENERIC_UTF8, null);
-    }
-
-    public VCardEntryConstructor(final int vcardType) {
-        this(null, null, false, vcardType, null);
-    }
-
-    public VCardEntryConstructor(final String charset, final boolean strictLineBreakParsing,
-            final int vcardType, final Account account) {
-        this(null, charset, strictLineBreakParsing, vcardType, account);
-    }
-
-    public VCardEntryConstructor(final String inputCharset, final String charsetForDetodedBytes,
-            final boolean strictLineBreakParsing, final int vcardType,
-            final Account account) {
-        if (inputCharset != null) {
-            mInputCharset = inputCharset;
-        } else {
-            mInputCharset = VCardConfig.DEFAULT_CHARSET;
-        }
-        if (charsetForDetodedBytes != null) {
-            mCharsetForDecodedBytes = charsetForDetodedBytes;
-        } else {
-            mCharsetForDecodedBytes = DEFAULT_CHARSET_FOR_DECODED_BYTES;
-        }
-        mStrictLineBreakParsing = strictLineBreakParsing;
-        mVCardType = vcardType;
-        mAccount = account;
-    }
-
-    public void addEntryHandler(VCardEntryHandler entryHandler) {
-        mEntryHandlers.add(entryHandler);
-    }
-    
-    public void start() {
-        for (VCardEntryHandler entryHandler : mEntryHandlers) {
-            entryHandler.onStart();
-        }
-    }
-
-    public void end() {
-        for (VCardEntryHandler entryHandler : mEntryHandlers) {
-            entryHandler.onEnd();
-        }
-    }
-
-    /**
-     * Called when the parse failed between {@link #startEntry()} and {@link #endEntry()}.
-     */
-    public void clear() {
-        mCurrentContactStruct = 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) {
-            Log.e(LOG_TAG, "Nested VCard code is not supported now.");
-        }
-        mCurrentContactStruct = new VCardEntry(mVCardType, mAccount);
-    }
-
-    public void endEntry() {
-        mCurrentContactStruct.consolidateFields();
-        for (VCardEntryHandler entryHandler : mEntryHandlers) {
-            entryHandler.onEntryCreated(mCurrentContactStruct);
-        }
-        mCurrentContactStruct = null;
-    }
-
-    public void startProperty() {
-        mCurrentProperty.clear();
-    }
-
-    public void endProperty() {
-        mCurrentContactStruct.addProperty(mCurrentProperty);
-    }
-    
-    public void propertyName(String name) {
-        mCurrentProperty.setPropertyName(name);
-    }
-
-    public void propertyGroup(String group) {
-    }
-
-    public void propertyParamType(String type) {
-        if (mParamType != null) {
-            Log.e(LOG_TAG, "propertyParamType() is called more than once " +
-                    "before propertyParamValue() is called");
-        }
-        mParamType = type;
-    }
-
-    public void propertyParamValue(String value) {
-        if (mParamType == null) {
-            // From vCard 2.1 specification. vCard 3.0 formally does not allow this case.
-            mParamType = "TYPE";
-        }
-        mCurrentProperty.addParameter(mParamType, value);
-        mParamType = null;
-    }
-
-    private String encodeString(String originalString, String charsetForDecodedBytes) {
-        if (mInputCharset.equalsIgnoreCase(charsetForDecodedBytes)) {
-            return originalString;
-        }
-        Charset charset = Charset.forName(mInputCharset);
-        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()];
-        byteBuffer.get(bytes);
-        try {
-            return new String(bytes, charsetForDecodedBytes);
-        } catch (UnsupportedEncodingException e) {
-            Log.e(LOG_TAG, "Failed to encode: charset=" + charsetForDecodedBytes);
-            return null;
-        }
-    }
-
-    private String handleOneValue(String value, String charsetForDecodedBytes, 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);
-                }
-            }
-            // Unknown encoding. Fall back to default.
-        }
-        return encodeString(value, charsetForDecodedBytes);
-    }
-    
-    public void propertyValues(List<String> values) {
-        if (values == null || values.isEmpty()) {
-            return;
-        }
-
-        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;
-        }
-
-        for (final String value : values) {
-            mCurrentProperty.addToPropertyValueList(
-                    handleOneValue(value, charsetForDecodedBytes, encoding));
-        }
-    }
-
-    public void showPerformanceInfo() {
-        Log.d(LOG_TAG, "time for insert ContactStruct to database: " + 
-                mTimePushIntoContentResolver + " ms");
-    }
-}
diff --git a/core/java/android/pim/vcard/VCardEntryCounter.java b/core/java/android/pim/vcard/VCardEntryCounter.java
deleted file mode 100644
index 7bab50d..0000000
--- a/core/java/android/pim/vcard/VCardEntryCounter.java
+++ /dev/null
@@ -1,63 +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.pim.vcard;
-
-import java.util.List;
-
-/**
- * The class which just counts the number of vCard entries in the specified input.
- */
-public class VCardEntryCounter implements VCardInterpreter {
-    private int mCount;
-
-    public int getCount() {
-        return mCount;
-    }
-
-    public void start() {
-    }
-
-    public void end() {
-    }
-
-    public void startEntry() {
-    }
-
-    public void endEntry() {
-        mCount++;
-    }
-
-    public void startProperty() {
-    }
-
-    public void endProperty() {
-    }
-
-    public void propertyGroup(String group) {
-    }
-
-    public void propertyName(String name) {
-    }
-
-    public void propertyParamType(String type) {
-    }
-
-    public void propertyParamValue(String value) {
-    }
-
-    public void propertyValues(List<String> values) {
-    }    
-}
diff --git a/core/java/android/pim/vcard/VCardEntryHandler.java b/core/java/android/pim/vcard/VCardEntryHandler.java
deleted file mode 100644
index 83a67fe..0000000
--- a/core/java/android/pim/vcard/VCardEntryHandler.java
+++ /dev/null
@@ -1,38 +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.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}.
- */
-public interface VCardEntryHandler {
-    /**
-     * Called when the parsing started.
-     */
-    public void onStart();
-
-    /**
-     * The method called when one VCard entry is successfully created
-     */
-    public void onEntryCreated(final VCardEntry entry);
-
-    /**
-     * Called when the parsing ended.
-     * Able to be use this method for showing performance log, etc.
-     */
-    public void onEnd();
-}
diff --git a/core/java/android/pim/vcard/VCardInterpreter.java b/core/java/android/pim/vcard/VCardInterpreter.java
deleted file mode 100644
index b5237c0..0000000
--- a/core/java/android/pim/vcard/VCardInterpreter.java
+++ /dev/null
@@ -1,102 +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.pim.vcard;
-
-import java.util.List;
-
-/**
- * <P>
- * The interface which should be implemented by the classes which have to analyze each
- * vCard entry more minutely than {@link VCardEntry} class analysis.
- * </P>
- * <P>
- * Here, there are several terms specific to vCard (and this library).
- * </P>
- * <P>
- * The term "entry" is one vCard representation in the input, which should start with "BEGIN:VCARD"
- * and end with "END:VCARD".
- * </P>
- * <P>
- * The term "property" is one line in vCard entry, which consists of "group", "property name",
- * "parameter(param) names and values", and "property values".
- * </P>
- * <P>
- * e.g. group1.propName;paramName1=paramValue1;paramName2=paramValue2;propertyValue1;propertyValue2...
- * </P>
- */
-public interface VCardInterpreter {
-    /**
-     * Called when vCard interpretation started.
-     */
-    void start();
-
-    /**
-     * Called when vCard interpretation finished.
-     */
-    void end();
-
-    /** 
-     * Called when parsing one vCard entry started.
-     * More specifically, this method is called when "BEGIN:VCARD" is read.
-     */
-    void startEntry();
-
-    /**
-     * Called when parsing one vCard entry ended.
-     * More specifically, this method is called when "END:VCARD" is read.
-     * Note that {@link #startEntry()} may be called since
-     * vCard (especially 2.1) allows nested vCard.
-     */
-    void endEntry();
-
-    /**
-     * Called when reading one property started.
-     */
-    void startProperty();
-
-    /**
-     * Called when reading one property ended.
-     */
-    void endProperty();
-
-    /**
-     * @param group A group name. This method may be called more than once or may not be
-     * called at all, depending on how many gruoups are appended to the property.
-     */
-    void propertyGroup(String group);
-
-    /**
-     * @param name A property name like "N", "FN", "ADR", etc.
-     */
-    void propertyName(String name);
-
-    /**
-     * @param type A parameter name like "ENCODING", "CHARSET", etc.
-     */
-    void propertyParamType(String type);
-
-    /**
-     * @param value A parameter value. This method may be called without
-     * {@link #propertyParamType(String)} being called (when the vCard is vCard 2.1).
-     */
-    void propertyParamValue(String value);
-
-    /**
-     * @param values List of property values. The size of values would be 1 unless
-     * coressponding property name is "N", "ADR", or "ORG".
-     */
-    void propertyValues(List<String> values);
-}
diff --git a/core/java/android/pim/vcard/VCardInterpreterCollection.java b/core/java/android/pim/vcard/VCardInterpreterCollection.java
deleted file mode 100644
index 99f81f7..0000000
--- a/core/java/android/pim/vcard/VCardInterpreterCollection.java
+++ /dev/null
@@ -1,102 +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.pim.vcard;
-
-import java.util.Collection;
-import java.util.List;
-
-/**
- * The {@link VCardInterpreter} implementation which aggregates more than one
- * {@link VCardInterpreter} objects and make a user object treat them as one
- * {@link VCardInterpreter} object.
- */
-public class VCardInterpreterCollection implements VCardInterpreter {
-    private final Collection<VCardInterpreter> mInterpreterCollection;
-    
-    public VCardInterpreterCollection(Collection<VCardInterpreter> interpreterCollection) {
-        mInterpreterCollection = interpreterCollection;
-    }
-
-    public Collection<VCardInterpreter> getCollection() {
-        return mInterpreterCollection;
-    }
-
-    public void start() {
-        for (VCardInterpreter builder : mInterpreterCollection) {
-            builder.start();
-        }
-    }
-
-    public void end() {
-        for (VCardInterpreter builder : mInterpreterCollection) {
-            builder.end();
-        }
-    }
-
-    public void startEntry() {
-        for (VCardInterpreter builder : mInterpreterCollection) {
-            builder.startEntry();
-        }
-    }
-
-    public void endEntry() {
-        for (VCardInterpreter builder : mInterpreterCollection) {
-            builder.endEntry();
-        }
-    }
-
-    public void startProperty() {
-        for (VCardInterpreter builder : mInterpreterCollection) {
-            builder.startProperty();
-        }
-    }
-
-    public void endProperty() {
-        for (VCardInterpreter builder : mInterpreterCollection) {
-            builder.endProperty();
-        }
-    }
-
-    public void propertyGroup(String group) {
-        for (VCardInterpreter builder : mInterpreterCollection) {
-            builder.propertyGroup(group);
-        }
-    }
-
-    public void propertyName(String name) {
-        for (VCardInterpreter builder : mInterpreterCollection) {
-            builder.propertyName(name);
-        }
-    }
-
-    public void propertyParamType(String type) {
-        for (VCardInterpreter builder : mInterpreterCollection) {
-            builder.propertyParamType(type);
-        }
-    }
-
-    public void propertyParamValue(String value) {
-        for (VCardInterpreter builder : mInterpreterCollection) {
-            builder.propertyParamValue(value);
-        }
-    }
-
-    public void propertyValues(List<String> values) {
-        for (VCardInterpreter builder : mInterpreterCollection) {
-            builder.propertyValues(values);
-        }
-    }
-}
diff --git a/core/java/android/pim/vcard/VCardParser.java b/core/java/android/pim/vcard/VCardParser.java
deleted file mode 100644
index 57c52a6..0000000
--- a/core/java/android/pim/vcard/VCardParser.java
+++ /dev/null
@@ -1,101 +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.pim.vcard;
-
-import android.pim.vcard.exception.VCardException;
-
-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;
-    }
-
-    /**
-     * <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>
-     *
-     * @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)
-            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
-     */
-    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;
-    }
-}
diff --git a/core/java/android/pim/vcard/VCardParser_V21.java b/core/java/android/pim/vcard/VCardParser_V21.java
deleted file mode 100644
index fe8cfb0..0000000
--- a/core/java/android/pim/vcard/VCardParser_V21.java
+++ /dev/null
@@ -1,936 +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.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.HashSet;
-import java.util.Set;
-
-/**
- * This class is used to parse vCard. Please refer to vCard Specification 2.1 for more detail.
- */
-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"));
-
-    /**
-     * Though vCard 2.1 specification does not allow "B" encoding, some data may have it.
-     * We allow it for safety...
-     */
-    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;
-
-    /** 
-     * 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;
-
-    public VCardParser_V21() {
-        this(null);
-    }
-
-    public VCardParser_V21(VCardSourceDetector detector) {
-        this(detector != null ? detector.getEstimatedType() : VCardConfig.PARSE_TYPE_UNKNOWN);
-    }
-
-    public VCardParser_V21(int parseType) {
-        super(parseType);
-        if (parseType == VCardConfig.PARSE_TYPE_FOMA) {
-            mNestCount = 1;
-        }
-    }
-
-    /**
-     * 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)
-            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 + "\"");
-    }
-
-    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;
-    }
-}
diff --git a/core/java/android/pim/vcard/VCardParser_V30.java b/core/java/android/pim/vcard/VCardParser_V30.java
deleted file mode 100644
index 4ecfe97..0000000
--- a/core/java/android/pim/vcard/VCardParser_V30.java
+++ /dev/null
@@ -1,358 +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.pim.vcard;
-
-import android.pim.vcard.exception.VCardException;
-import android.util.Log;
-
-import java.io.IOException;
-import java.util.Arrays;
-import java.util.HashSet;
-
-/**
- * The class used to parse vCard 3.0.
- * Please refer to vCard Specification 3.0 (http://tools.ietf.org/html/rfc2426).
- */
-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(
-                    "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;
-
-    /**
-     * True when the caller wants the parser to be strict about the input.
-     * Currently this is only for testing.
-     */
-    private final boolean mStrictParsing;
-
-    public VCardParser_V30() {
-        super();
-        mStrictParsing = false;
-    }
-
-    /**
-     * @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 parseMode) {
-        super(parseMode);
-        mStrictParsing = false;
-    }
-
-    @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)
-            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(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);
-        }        
-    }
-}
diff --git a/core/java/android/pim/vcard/VCardSourceDetector.java b/core/java/android/pim/vcard/VCardSourceDetector.java
deleted file mode 100644
index 7297c50..0000000
--- a/core/java/android/pim/vcard/VCardSourceDetector.java
+++ /dev/null
@@ -1,129 +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.pim.vcard;
-
-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
- */
-public class VCardSourceDetector implements VCardInterpreter {
-    private static Set<String> APPLE_SIGNS = new HashSet<String>(Arrays.asList(
-            "X-PHONETIC-FIRST-NAME", "X-PHONETIC-MIDDLE-NAME", "X-PHONETIC-LAST-NAME",
-            "X-ABADR", "X-ABUID"));
-    
-    private static Set<String> JAPANESE_MOBILE_PHONE_SIGNS = new HashSet<String>(Arrays.asList(
-            "X-GNO", "X-GN", "X-REDUCTION"));
-    
-    private static Set<String> WINDOWS_MOBILE_PHONE_SIGNS = new HashSet<String>(Arrays.asList(
-            "X-MICROSOFT-ASST_TEL", "X-MICROSOFT-ASSISTANT", "X-MICROSOFT-OFFICELOC"));
-    
-    // Note: these signes appears before the signs of the other type (e.g. "X-GN").
-    // In other words, Japanese FOMA mobile phones are detected as FOMA, not JAPANESE_MOBILE_PHONES.
-    private static Set<String> FOMA_SIGNS = new HashSet<String>(Arrays.asList(
-            "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;
-    // Some mobile phones (like FOMA) tells us the charset of the data.
-    private boolean mNeedParseSpecifiedCharset;
-    private String mSpecifiedCharset;
-    
-    public void start() {
-    }
-    
-    public void end() {
-    }
-
-    public void startEntry() {
-    }    
-
-    public void startProperty() {
-        mNeedParseSpecifiedCharset = false;
-    }
-    
-    public void endProperty() {
-    }
-
-    public void endEntry() {
-    }
-
-    public void propertyGroup(String group) {
-    }
-    
-    public void propertyName(String name) {
-        if (name.equalsIgnoreCase(TYPE_FOMA_CHARSET_SIGN)) {
-            mType = VCardConfig.PARSE_TYPE_FOMA;
-            mNeedParseSpecifiedCharset = true;
-            return;
-        }
-        if (mType != VCardConfig.PARSE_TYPE_UNKNOWN) {
-            return;
-        }
-        if (WINDOWS_MOBILE_PHONE_SIGNS.contains(name)) {
-            mType = VCardConfig.PARSE_TYPE_WINDOWS_MOBILE_JP;
-        } else if (FOMA_SIGNS.contains(name)) {
-            mType = VCardConfig.PARSE_TYPE_FOMA;
-        } else if (JAPANESE_MOBILE_PHONE_SIGNS.contains(name)) {
-            mType = VCardConfig.PARSE_TYPE_MOBILE_PHONE_JP;
-        } else if (APPLE_SIGNS.contains(name)) {
-            mType = VCardConfig.PARSE_TYPE_APPLE;
-        }
-    }
-
-    public void propertyParamType(String type) {
-    }
-
-    public void propertyParamValue(String value) {
-    }
-
-    public void propertyValues(List<String> values) {
-        if (mNeedParseSpecifiedCharset && values.size() > 0) {
-            mSpecifiedCharset = values.get(0);
-        }
-    }
-
-    /* package */ int getEstimatedType() {
-        return mType;
-    }
-    
-    /**
-     * Return charset String guessed from the source's properties.
-     * This method must be called after parsing target file(s).
-     * @return Charset String. Null is returned if guessing the source fails.
-     */
-    public String getEstimatedCharset() {
-        if (mSpecifiedCharset != null) {
-            return mSpecifiedCharset;
-        }
-        switch (mType) {
-            case VCardConfig.PARSE_TYPE_WINDOWS_MOBILE_JP:
-            case VCardConfig.PARSE_TYPE_FOMA:
-            case VCardConfig.PARSE_TYPE_MOBILE_PHONE_JP:
-                return "SHIFT_JIS";
-            case VCardConfig.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
deleted file mode 100644
index 11b112b..0000000
--- a/core/java/android/pim/vcard/VCardUtils.java
+++ /dev/null
@@ -1,545 +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.pim.vcard;
-
-import android.content.ContentProviderOperation;
-import android.provider.ContactsContract.Data;
-import android.provider.ContactsContract.CommonDataKinds.Im;
-import android.provider.ContactsContract.CommonDataKinds.Phone;
-import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
-import android.telephony.PhoneNumberUtils;
-import android.text.TextUtils;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * Utilities for VCard handling codes.
- */
-public class 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. 
-    private static final Map<Integer, String> sKnownPhoneTypesMap_ItoS;
-    private static final Set<String> sPhoneTypesUnknownToContactsSet;
-    private static final Map<String, Integer> sKnownPhoneTypeMap_StoI;
-    private static final Map<Integer, String> sKnownImPropNameMap_ItoS;
-    private static final Set<String> sMobilePhoneLabelSet;
-
-    static {
-        sKnownPhoneTypesMap_ItoS = new HashMap<Integer, String>();
-        sKnownPhoneTypeMap_StoI = new HashMap<String, Integer>();
-
-        sKnownPhoneTypesMap_ItoS.put(Phone.TYPE_CAR, VCardConstants.PARAM_TYPE_CAR);
-        sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_TYPE_CAR, Phone.TYPE_CAR);
-        sKnownPhoneTypesMap_ItoS.put(Phone.TYPE_PAGER, VCardConstants.PARAM_TYPE_PAGER);
-        sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_TYPE_PAGER, Phone.TYPE_PAGER);
-        sKnownPhoneTypesMap_ItoS.put(Phone.TYPE_ISDN, VCardConstants.PARAM_TYPE_ISDN);
-        sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_TYPE_ISDN, Phone.TYPE_ISDN);
-        
-        sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_TYPE_HOME, Phone.TYPE_HOME);
-        sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_TYPE_WORK, Phone.TYPE_WORK);
-        sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_TYPE_CELL, Phone.TYPE_MOBILE);
-                
-        sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_PHONE_EXTRA_TYPE_OTHER, Phone.TYPE_OTHER);
-        sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_PHONE_EXTRA_TYPE_CALLBACK,
-                Phone.TYPE_CALLBACK);
-        sKnownPhoneTypeMap_StoI.put(
-                VCardConstants.PARAM_PHONE_EXTRA_TYPE_COMPANY_MAIN, Phone.TYPE_COMPANY_MAIN);
-        sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_PHONE_EXTRA_TYPE_RADIO, Phone.TYPE_RADIO);
-        sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_PHONE_EXTRA_TYPE_TTY_TDD,
-                Phone.TYPE_TTY_TDD);
-        sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_PHONE_EXTRA_TYPE_ASSISTANT,
-                Phone.TYPE_ASSISTANT);
-
-        sPhoneTypesUnknownToContactsSet = new HashSet<String>();
-        sPhoneTypesUnknownToContactsSet.add(VCardConstants.PARAM_TYPE_MODEM);
-        sPhoneTypesUnknownToContactsSet.add(VCardConstants.PARAM_TYPE_MSG);
-        sPhoneTypesUnknownToContactsSet.add(VCardConstants.PARAM_TYPE_BBS);
-        sPhoneTypesUnknownToContactsSet.add(VCardConstants.PARAM_TYPE_VIDEO);
-
-        sKnownImPropNameMap_ItoS = new HashMap<Integer, String>();
-        sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_AIM, VCardConstants.PROPERTY_X_AIM);
-        sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_MSN, VCardConstants.PROPERTY_X_MSN);
-        sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_YAHOO, VCardConstants.PROPERTY_X_YAHOO);
-        sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_SKYPE, VCardConstants.PROPERTY_X_SKYPE_USERNAME);
-        sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_GOOGLE_TALK,
-                VCardConstants.PROPERTY_X_GOOGLE_TALK);
-        sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_ICQ, VCardConstants.PROPERTY_X_ICQ);
-        sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_JABBER, VCardConstants.PROPERTY_X_JABBER);
-        sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_QQ, VCardConstants.PROPERTY_X_QQ);
-        sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_NETMEETING, VCardConstants.PROPERTY_X_NETMEETING);
-
-        // \u643A\u5E2F\u96FB\u8A71 = Full-width Hiragana "Keitai-Denwa" (mobile phone)
-        // \u643A\u5E2F = Full-width Hiragana "Keitai" (mobile phone)
-        // \u30B1\u30A4\u30BF\u30A4 = Full-width Katakana "Keitai" (mobile phone)
-        // \uFF79\uFF72\uFF80\uFF72 = Half-width Katakana "Keitai" (mobile phone)
-        sMobilePhoneLabelSet = new HashSet<String>(Arrays.asList(
-                "MOBILE", "\u643A\u5E2F\u96FB\u8A71", "\u643A\u5E2F", "\u30B1\u30A4\u30BF\u30A4",
-                "\uFF79\uFF72\uFF80\uFF72"));
-    }
-
-    public static String getPhoneTypeString(Integer type) {
-        return sKnownPhoneTypesMap_ItoS.get(type);
-    }
-
-    /**
-     * Returns Interger when the given types can be parsed as known type. Returns String object
-     * when not, which should be set to label. 
-     */
-    public static Object getPhoneTypeFromStrings(Collection<String> types,
-            String number) {
-        if (number == null) {
-            number = "";
-        }
-        int type = -1;
-        String label = null;
-        boolean isFax = false;
-        boolean hasPref = false;
-        
-        if (types != null) {
-            for (String typeString : types) {
-                if (typeString == null) {
-                    continue;
-                }
-                typeString = typeString.toUpperCase();
-                if (typeString.equals(VCardConstants.PARAM_TYPE_PREF)) {
-                    hasPref = true;
-                } else if (typeString.equals(VCardConstants.PARAM_TYPE_FAX)) {
-                    isFax = true;
-                } else {
-                    if (typeString.startsWith("X-") && type < 0) {
-                        typeString = typeString.substring(2);
-                    }
-                    if (typeString.length() == 0) {
-                        continue;
-                    }
-                    final Integer tmp = sKnownPhoneTypeMap_StoI.get(typeString);
-                    if (tmp != null) {
-                        final int typeCandidate = tmp;
-                        // TYPE_PAGER is prefered when the number contains @ surronded by
-                        // a pager number and a domain name.
-                        // e.g.
-                        // o 1111@domain.com
-                        // x @domain.com
-                        // x 1111@
-                        final int indexOfAt = number.indexOf("@");
-                        if ((typeCandidate == Phone.TYPE_PAGER
-                                && 0 < indexOfAt && indexOfAt < number.length() - 1)
-                                || type < 0
-                                || type == Phone.TYPE_CUSTOM) {
-                            type = tmp;
-                        }
-                    } else if (type < 0) {
-                        type = Phone.TYPE_CUSTOM;
-                        label = typeString;
-                    }
-                }
-            }
-        }
-        if (type < 0) {
-            if (hasPref) {
-                type = Phone.TYPE_MAIN;
-            } else {
-                // default to TYPE_HOME
-                type = Phone.TYPE_HOME;
-            }
-        }
-        if (isFax) {
-            if (type == Phone.TYPE_HOME) {
-                type = Phone.TYPE_FAX_HOME;
-            } else if (type == Phone.TYPE_WORK) {
-                type = Phone.TYPE_FAX_WORK;
-            } else if (type == Phone.TYPE_OTHER) {
-                type = Phone.TYPE_OTHER_FAX;
-            }
-        }
-        if (type == Phone.TYPE_CUSTOM) {
-            return label;
-        } else {
-            return type;
-        }
-    }
-
-    @SuppressWarnings("deprecation")
-    public static boolean isMobilePhoneLabel(final String label) {
-        // For backward compatibility.
-        // Detail: Until Donut, there isn't TYPE_MOBILE for email while there is now.
-        //         To support mobile type at that time, this custom label had been used.
-        return (android.provider.Contacts.ContactMethodsColumns.MOBILE_EMAIL_TYPE_NAME.equals(label)
-                || sMobilePhoneLabelSet.contains(label));
-    }
-
-    public static boolean isValidInV21ButUnknownToContactsPhoteType(final String label) {
-        return sPhoneTypesUnknownToContactsSet.contains(label);
-    }
-
-    public static String getPropertyNameForIm(final int protocol) {
-        return sKnownImPropNameMap_ItoS.get(protocol);
-    }
-
-    public static String[] sortNameElements(final int vcardType,
-            final String familyName, final String middleName, final String givenName) {
-        final String[] list = new String[3];
-        final int nameOrderType = VCardConfig.getNameOrderType(vcardType);
-        switch (nameOrderType) {
-            case VCardConfig.NAME_ORDER_JAPANESE: {
-                if (containsOnlyPrintableAscii(familyName) &&
-                        containsOnlyPrintableAscii(givenName)) {
-                    list[0] = givenName;
-                    list[1] = middleName;
-                    list[2] = familyName;
-                } else {
-                    list[0] = familyName;
-                    list[1] = middleName;
-                    list[2] = givenName;
-                }
-                break;
-            }
-            case VCardConfig.NAME_ORDER_EUROPE: {
-                list[0] = middleName;
-                list[1] = givenName;
-                list[2] = familyName;
-                break;
-            }
-            default: {
-                list[0] = givenName;
-                list[1] = middleName;
-                list[2] = familyName;
-                break;
-            }
-        }
-        return list;
-    }
-
-    public static int getPhoneNumberFormat(final int vcardType) {
-        if (VCardConfig.isJapaneseDevice(vcardType)) {
-            return PhoneNumberUtils.FORMAT_JAPAN;
-        } else {
-            return PhoneNumberUtils.FORMAT_NANP;
-        }
-    }
-
-    /**
-     * Inserts postal data into the builder object.
-     * 
-     * Note that the data structure of ContactsContract is different from that defined in vCard.
-     * So some conversion may be performed in this method.
-     */
-    public static void insertStructuredPostalDataUsingContactsStruct(int vcardType,
-            final ContentProviderOperation.Builder builder,
-            final VCardEntry.PostalData postalData) {
-        builder.withValueBackReference(StructuredPostal.RAW_CONTACT_ID, 0);
-        builder.withValue(Data.MIMETYPE, StructuredPostal.CONTENT_ITEM_TYPE);
-
-        builder.withValue(StructuredPostal.TYPE, postalData.type);
-        if (postalData.type == StructuredPostal.TYPE_CUSTOM) {
-            builder.withValue(StructuredPostal.LABEL, postalData.label);
-        }
-
-        final String streetString;
-        if (TextUtils.isEmpty(postalData.street)) {
-            if (TextUtils.isEmpty(postalData.extendedAddress)) {
-                streetString = null;
-            } else {
-                streetString = postalData.extendedAddress;
-            }
-        } else {
-            if (TextUtils.isEmpty(postalData.extendedAddress)) {
-                streetString = postalData.street;
-            } else {
-                streetString = postalData.street + " " + postalData.extendedAddress;
-            }
-        }
-        builder.withValue(StructuredPostal.POBOX, postalData.pobox);
-        builder.withValue(StructuredPostal.STREET, streetString);
-        builder.withValue(StructuredPostal.CITY, postalData.localty);
-        builder.withValue(StructuredPostal.REGION, postalData.region);
-        builder.withValue(StructuredPostal.POSTCODE, postalData.postalCode);
-        builder.withValue(StructuredPostal.COUNTRY, postalData.country);
-
-        builder.withValue(StructuredPostal.FORMATTED_ADDRESS,
-                postalData.getFormattedAddress(vcardType));
-        if (postalData.isPrimary) {
-            builder.withValue(Data.IS_PRIMARY, 1);
-        }
-    }
-
-    public static String constructNameFromElements(final int vcardType,
-            final String familyName, final String middleName, final String givenName) {
-        return constructNameFromElements(vcardType, familyName, middleName, givenName,
-                null, null);
-    }
-
-    public static String constructNameFromElements(final int vcardType,
-            final String familyName, final String middleName, final String givenName,
-            final String prefix, final String suffix) {
-        final StringBuilder builder = new StringBuilder();
-        final String[] nameList = sortNameElements(vcardType, familyName, middleName, givenName);
-        boolean first = true;
-        if (!TextUtils.isEmpty(prefix)) {
-            first = false;
-            builder.append(prefix);
-        }
-        for (final String namePart : nameList) {
-            if (!TextUtils.isEmpty(namePart)) {
-                if (first) {
-                    first = false;
-                } else {
-                    builder.append(' ');
-                }
-                builder.append(namePart);
-            }
-        }
-        if (!TextUtils.isEmpty(suffix)) {
-            if (!first) {
-                builder.append(' ');
-            }
-            builder.append(suffix);
-        }
-        return builder.toString();
-    }
-
-    public static List<String> constructListFromValue(final String value,
-            final boolean isV30) {
-        final List<String> list = new ArrayList<String>();
-        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);
-                final String unescapedString =
-                    (isV30 ? VCardParser_V30.unescapeCharacter(nextCh) :
-                        VCardParser_V21.unescapeCharacter(nextCh));
-                if (unescapedString != null) {
-                    builder.append(unescapedString);
-                    i++;
-                } else {
-                    builder.append(ch);
-                }
-            } else if (ch == ';') {
-                list.add(builder.toString());
-                builder = new StringBuilder();
-            } else {
-                builder.append(ch);
-            }
-        }
-        list.add(builder.toString());
-        return list;
-    }
-
-    public static boolean containsOnlyPrintableAscii(final String...values) {
-        if (values == null) {
-            return true;
-        }
-        return containsOnlyPrintableAscii(Arrays.asList(values));
-    }
-
-    public static boolean containsOnlyPrintableAscii(final Collection<String> values) {
-        if (values == null) {
-            return true;
-        }
-        for (final String value : values) {
-            if (TextUtils.isEmpty(value)) {
-                continue;
-            }
-            if (!TextUtils.isPrintableAsciiOnly(value)) {
-                return false;
-            }
-        }
-        return true;
-    }
-
-    /**
-     * This is useful when checking the string should be encoded into quoted-printable
-     * or not, which is required by vCard 2.1.
-     * See the definition of "7bit" in vCard 2.1 spec for more information.
-     */
-    public static boolean containsOnlyNonCrLfPrintableAscii(final String...values) {
-        if (values == null) {
-            return true;
-        }
-        return containsOnlyNonCrLfPrintableAscii(Arrays.asList(values));
-    }
-
-    public static boolean containsOnlyNonCrLfPrintableAscii(final Collection<String> values) {
-        if (values == null) {
-            return true;
-        }
-        final int asciiFirst = 0x20;
-        final int asciiLast = 0x7E;  // included
-        for (final String value : values) {
-            if (TextUtils.isEmpty(value)) {
-                continue;
-            }
-            final int length = value.length();
-            for (int i = 0; i < length; i = value.offsetByCodePoints(i, 1)) {
-                final int c = value.codePointAt(i);
-                if (!(asciiFirst <= c && c <= asciiLast)) {
-                    return false;
-                }
-            }
-        }
-        return true;
-    }
-
-    private static final Set<Character> sUnAcceptableAsciiInV21WordSet =
-        new HashSet<Character>(Arrays.asList('[', ']', '=', ':', '.', ',', ' '));
-
-    /**
-     * This is useful since vCard 3.0 often requires the ("X-") properties and groups
-     * should contain only alphabets, digits, and hyphen.
-     * 
-     * 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. 
-     */
-    public static boolean containsOnlyAlphaDigitHyphen(final String...values) {
-        if (values == null) {
-            return true;
-        }
-        return containsOnlyAlphaDigitHyphen(Arrays.asList(values));
-    }
-
-    public static boolean containsOnlyAlphaDigitHyphen(final Collection<String> values) {
-        if (values == null) {
-            return true;
-        }
-        final int upperAlphabetFirst = 0x41;  // A
-        final int upperAlphabetAfterLast = 0x5b;  // [
-        final int lowerAlphabetFirst = 0x61;  // a
-        final int lowerAlphabetAfterLast = 0x7b;  // {
-        final int digitFirst = 0x30;  // 0
-        final int digitAfterLast = 0x3A;  // :
-        final int hyphen = '-';
-        for (final String str : values) {
-            if (TextUtils.isEmpty(str)) {
-                continue;
-            }
-            final int length = str.length();
-            for (int i = 0; i < length; i = str.offsetByCodePoints(i, 1)) {
-                int codepoint = str.codePointAt(i);
-                if (!((lowerAlphabetFirst <= codepoint && codepoint < lowerAlphabetAfterLast) ||
-                    (upperAlphabetFirst <= codepoint && codepoint < upperAlphabetAfterLast) ||
-                    (digitFirst <= codepoint && codepoint < digitAfterLast) ||
-                    (codepoint == hyphen))) {
-                    return false;
-                }
-            }
-        }
-        return true;
-    }
-
-    /**
-     * <P>
-     * Returns true when the given String is categorized as "word" specified in vCard spec 2.1.
-     * </P>
-     * <P>
-     * vCard 2.1 specifies:<BR />
-     * word = &lt;any printable 7bit us-ascii except []=:., &gt;
-     * </P>
-     */
-    public static boolean isV21Word(final String value) {
-        if (TextUtils.isEmpty(value)) {
-            return true;
-        }
-        final int asciiFirst = 0x20;
-        final int asciiLast = 0x7E;  // included
-        final int length = value.length();
-        for (int i = 0; i < length; i = value.offsetByCodePoints(i, 1)) {
-            final int c = value.codePointAt(i);
-            if (!(asciiFirst <= c && c <= asciiLast) ||
-                    sUnAcceptableAsciiInV21WordSet.contains((char)c)) {
-                return false;
-            }
-        }
-        return true;
-    }
-
-    public static String toHalfWidthString(final String orgString) {
-        if (TextUtils.isEmpty(orgString)) {
-            return null;
-        }
-        final StringBuilder builder = new StringBuilder();
-        final int length = orgString.length();
-        for (int i = 0; i < length; i = orgString.offsetByCodePoints(i, 1)) {
-            // All Japanese character is able to be expressed by char.
-            // Do not need to use String#codepPointAt().
-            final char ch = orgString.charAt(i);
-            final String halfWidthText = JapaneseUtils.tryGetHalfWidthText(ch);
-            if (halfWidthText != null) {
-                builder.append(halfWidthText);
-            } else {
-                builder.append(ch);
-            }
-        }
-        return builder.toString();
-    }
-
-    /**
-     * Guesses the format of input image. Currently just the first few bytes are used.
-     * The type "GIF", "PNG", or "JPEG" is returned when possible. Returns null when
-     * the guess failed.
-     * @param input Image as byte array.
-     * @return The image type or null when the type cannot be determined.
-     */
-    public static String guessImageType(final byte[] input) {
-        if (input == null) {
-            return null;
-        }
-        if (input.length >= 3 && input[0] == 'G' && input[1] == 'I' && input[2] == 'F') {
-            return "GIF";
-        } else if (input.length >= 4 && input[0] == (byte) 0x89
-                && input[1] == 'P' && input[2] == 'N' && input[3] == 'G') {
-            // Note: vCard 2.1 officially does not support PNG, but we may have it and
-            //       using X- word like "X-PNG" may not let importers know it is PNG.
-            //       So we use the String "PNG" as is...
-            return "PNG";
-        } else if (input.length >= 2 && input[0] == (byte) 0xff
-                && input[1] == (byte) 0xd8) {
-            return "JPEG";
-        } else {
-            return null;
-        }
-    }
-
-    /**
-     * @return True when all the given values are null or empty Strings.
-     */
-    public static boolean areAllEmpty(final String...values) {
-        if (values == null) {
-            return true;
-        }
-
-        for (final String value : values) {
-            if (!TextUtils.isEmpty(value)) {
-                return false;
-            }
-        }
-        return true;
-    }
-
-    private VCardUtils() {
-    }
-}
diff --git a/core/java/android/pim/vcard/exception/VCardAgentNotSupportedException.java b/core/java/android/pim/vcard/exception/VCardAgentNotSupportedException.java
deleted file mode 100644
index e72c7df..0000000
--- a/core/java/android/pim/vcard/exception/VCardAgentNotSupportedException.java
+++ /dev/null
@@ -1,27 +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.pim.vcard.exception;
-
-public class VCardAgentNotSupportedException extends VCardNotSupportedException {
-    public VCardAgentNotSupportedException() {
-        super();
-    }
-
-    public VCardAgentNotSupportedException(String message) {
-        super(message);
-    }
-
-}
\ No newline at end of file
diff --git a/core/java/android/pim/vcard/exception/VCardException.java b/core/java/android/pim/vcard/exception/VCardException.java
deleted file mode 100644
index e557219..0000000
--- a/core/java/android/pim/vcard/exception/VCardException.java
+++ /dev/null
@@ -1,35 +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.pim.vcard.exception;
-
-public class VCardException extends java.lang.Exception {
-    /**
-     * Constructs a VCardException object
-     */
-    public VCardException() {
-        super();
-    }
-
-    /**
-     * Constructs a VCardException object
-     *
-     * @param message the error message
-     */
-    public VCardException(String message) {
-        super(message);
-    }
-
-}
diff --git a/core/java/android/pim/vcard/exception/VCardInvalidCommentLineException.java b/core/java/android/pim/vcard/exception/VCardInvalidCommentLineException.java
deleted file mode 100644
index 67db62c..0000000
--- a/core/java/android/pim/vcard/exception/VCardInvalidCommentLineException.java
+++ /dev/null
@@ -1,32 +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.pim.vcard.exception;
-
-/**
- * Thrown when the vCard has some line starting with '#'. In the specification,
- * both vCard 2.1 and vCard 3.0 does not allow such line, but some actual exporter emit
- * such lines.
- */
-public class VCardInvalidCommentLineException extends VCardInvalidLineException {
-    public VCardInvalidCommentLineException() {
-        super();
-    }
-
-    public VCardInvalidCommentLineException(final String message) {
-        super(message);
-    }
-}
diff --git a/core/java/android/pim/vcard/exception/VCardInvalidLineException.java b/core/java/android/pim/vcard/exception/VCardInvalidLineException.java
deleted file mode 100644
index 330153e..0000000
--- a/core/java/android/pim/vcard/exception/VCardInvalidLineException.java
+++ /dev/null
@@ -1,32 +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.pim.vcard.exception;
-
-/**
- * Thrown when the vCard has some line starting with '#'. In the specification,
- * both vCard 2.1 and vCard 3.0 does not allow such line, but some actual exporter emit
- * such lines.
- */
-public class VCardInvalidLineException extends VCardException {
-    public VCardInvalidLineException() {
-        super();
-    }
-
-    public VCardInvalidLineException(final String message) {
-        super(message);
-    }
-}
diff --git a/core/java/android/pim/vcard/exception/VCardNestedException.java b/core/java/android/pim/vcard/exception/VCardNestedException.java
deleted file mode 100644
index 503c2fb..0000000
--- a/core/java/android/pim/vcard/exception/VCardNestedException.java
+++ /dev/null
@@ -1,29 +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.pim.vcard.exception;
-
-/**
- * VCardException thrown when VCard is nested without VCardParser's being notified.
- */
-public class VCardNestedException extends VCardNotSupportedException {
-    public VCardNestedException() {
-        super();
-    }
-    public VCardNestedException(String message) {
-        super(message);
-    }
-}
diff --git a/core/java/android/pim/vcard/exception/VCardNotSupportedException.java b/core/java/android/pim/vcard/exception/VCardNotSupportedException.java
deleted file mode 100644
index 616aa7763..0000000
--- a/core/java/android/pim/vcard/exception/VCardNotSupportedException.java
+++ /dev/null
@@ -1,33 +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.pim.vcard.exception;
-
-/**
- * The exception which tells that the input VCard is probably valid from the view of
- * specification but not supported in the current framework for now.
- * 
- * This is a kind of a good news from the view of development.
- * It may be good to ask users to send a report with the VCard example
- * for the future development.
- */
-public class VCardNotSupportedException extends VCardException {
-    public VCardNotSupportedException() {
-        super();
-    }
-    public VCardNotSupportedException(String message) {
-        super(message);
-    }
-}
\ No newline at end of file
diff --git a/core/java/android/pim/vcard/exception/VCardVersionException.java b/core/java/android/pim/vcard/exception/VCardVersionException.java
deleted file mode 100644
index 9fe8b7f..0000000
--- a/core/java/android/pim/vcard/exception/VCardVersionException.java
+++ /dev/null
@@ -1,29 +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.pim.vcard.exception;
-
-/**
- * VCardException used only when the version of the vCard is different. 
- */
-public class VCardVersionException extends VCardException {
-    public VCardVersionException() {
-        super();
-    }
-    public VCardVersionException(String message) {
-        super(message);
-    }
-}
diff --git a/core/java/android/pim/vcard/exception/package.html b/core/java/android/pim/vcard/exception/package.html
deleted file mode 100644
index 26b8a32..0000000
--- a/core/java/android/pim/vcard/exception/package.html
+++ /dev/null
@@ -1,5 +0,0 @@
-<HTML>
-<BODY>
-{@hide}
-</BODY>
-</HTML>
\ No newline at end of file
diff --git a/core/java/android/pim/vcard/package.html b/core/java/android/pim/vcard/package.html
deleted file mode 100644
index 26b8a32..0000000
--- a/core/java/android/pim/vcard/package.html
+++ /dev/null
@@ -1,5 +0,0 @@
-<HTML>
-<BODY>
-{@hide}
-</BODY>
-</HTML>
\ No newline at end of file
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..117e507 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
@@ -54,6 +56,7 @@
  * @attr ref android.R.styleable#Preference_title
  * @attr ref android.R.styleable#Preference_summary
  * @attr ref android.R.styleable#Preference_order
+ * @attr ref android.R.styleable#Preference_fragment
  * @attr ref android.R.styleable#Preference_layout
  * @attr ref android.R.styleable#Preference_widgetLayout
  * @attr ref android.R.styleable#Preference_enabled
@@ -86,6 +89,7 @@
     private CharSequence mSummary;
     private String mKey;
     private Intent mIntent;
+    private String mFragment;
     private boolean mEnabled = true;
     private boolean mSelectable = true;
     private boolean mRequiresKey;
@@ -208,6 +212,10 @@
                     mOrder = a.getInt(attr, mOrder);
                     break;
 
+                case com.android.internal.R.styleable.Preference_fragment:
+                    mFragment = a.getString(attr);
+                    break;
+
                 case com.android.internal.R.styleable.Preference_layout:
                     mLayoutResId = a.getResourceId(attr, mLayoutResId);
                     break;
@@ -313,6 +321,24 @@
     }
 
     /**
+     * Sets the class name of a fragment to be shown when this Preference is clicked.
+     *
+     * @param fragment The class name of the fragment associated with this Preference.
+     */
+    public void setFragment(String fragment) {
+        mFragment = fragment;
+    }
+
+    /**
+     * Return the fragment class name associated with this Preference.
+     *
+     * @return The fragment class name last set via {@link #setFragment} or XML.
+     */
+    public String getFragment() {
+        return mFragment;
+    }
+
+    /**
      * Sets the layout resource that is inflated as the {@link View} to be shown
      * for this Preference. In most cases, the default layout is sufficient for
      * custom Preference objects and only the widget layout needs to be changed.
@@ -1250,6 +1276,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..e13c3e8 100644
--- a/core/java/android/preference/PreferenceActivity.java
+++ b/core/java/android/preference/PreferenceActivity.java
@@ -16,83 +16,151 @@
 
 package android.preference;
 
-import android.app.Activity;
+import com.android.internal.util.XmlUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.app.Fragment;
 import android.app.ListActivity;
+import android.content.Context;
 import android.content.Intent;
-import android.content.SharedPreferences;
+import android.content.res.Configuration;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.graphics.drawable.Drawable;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Message;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.Xml;
+import android.view.LayoutInflater;
 import android.view.View;
+import android.view.ViewGroup;
+import android.view.View.OnClickListener;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
 
 /**
- * Shows a hierarchy of {@link Preference} objects as
- * lists, possibly spanning multiple screens. These preferences will
- * automatically save to {@link SharedPreferences} as the user interacts with
- * them. To retrieve an instance of {@link SharedPreferences} that the
- * preference hierarchy in this activity will use, call
- * {@link PreferenceManager#getDefaultSharedPreferences(android.content.Context)}
- * with a context in the same package as this activity.
- * <p>
- * Furthermore, the preferences shown will follow the visual style of system
- * preferences. It is easy to create a hierarchy of preferences (that can be
- * shown on multiple screens) via XML. For these reasons, it is recommended to
- * use this activity (as a superclass) to deal with preferences in applications.
- * <p>
- * A {@link PreferenceScreen} object should be at the top of the preference
- * hierarchy. Furthermore, subsequent {@link PreferenceScreen} in the hierarchy
- * denote a screen break--that is the preferences contained within subsequent
- * {@link PreferenceScreen} should be shown on another screen. The preference
- * framework handles showing these other screens from the preference hierarchy.
- * <p>
- * The preference hierarchy can be formed in multiple ways:
- * <li> From an XML file specifying the hierarchy
- * <li> From different {@link Activity Activities} that each specify its own
- * preferences in an XML file via {@link Activity} meta-data
- * <li> From an object hierarchy rooted with {@link PreferenceScreen}
- * <p>
- * To inflate from XML, use the {@link #addPreferencesFromResource(int)}. The
- * root element should be a {@link PreferenceScreen}. Subsequent elements can point
- * to actual {@link Preference} subclasses. As mentioned above, subsequent
- * {@link PreferenceScreen} in the hierarchy will result in the screen break.
- * <p>
- * To specify an {@link Intent} to query {@link Activity Activities} that each
- * have preferences, use {@link #addPreferencesFromIntent}. Each
- * {@link Activity} can specify meta-data in the manifest (via the key
- * {@link PreferenceManager#METADATA_KEY_PREFERENCES}) that points to an XML
- * resource. These XML resources will be inflated into a single preference
- * hierarchy and shown by this activity.
- * <p>
- * To specify an object hierarchy rooted with {@link PreferenceScreen}, use
- * {@link #setPreferenceScreen(PreferenceScreen)}.
- * <p>
- * 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
+ * This is the base class for an activity to show a hierarchy of preferences
+ * to the user.  Prior to {@link android.os.Build.VERSION_CODES#HONEYCOMB}
+ * this class only allowed the display of a single set of preference; this
+ * functionality should now be found in the new {@link PreferenceFragment}
+ * class.  If you are using PreferenceActivity in its old mode, the documentation
+ * there applies to the deprecated APIs here.
+ *
+ * <p>This activity shows one or more headers of preferences, each of with
+ * is associated with a {@link PreferenceFragment} to display the preferences
+ * of that header.  The actual layout and display of these associations can
+ * however vary; currently there are two major approaches it may take:
+ *
+ * <ul>
+ * <li>On a small screen it may display only the headers as a single list
+ * when first launched.  Selecting one of the header items will re-launch
+ * the activity with it only showing the PreferenceFragment of that header.
+ * <li>On a large screen in may display both the headers and current
+ * PreferenceFragment together as panes.  Selecting a header item switches
+ * to showing the correct PreferenceFragment for that item.
+ * </ul>
+ *
+ * <p>Subclasses of PreferenceActivity should implement
+ * {@link #onBuildHeaders} to populate the header list with the desired
+ * items.  Doing this implicitly switches the class into its new "headers
+ * + fragments" mode rather than the old style of just showing a single
+ * preferences list.
+ *
+ * <a name="SampleCode"></a>
+ * <h3>Sample Code</h3>
+ *
+ * <p>The following sample code shows a simple preference activity that
+ * has two different sets of preferences.  The implementation, consisting
+ * of the activity itself as well as its two preference fragments is:</p>
+ *
+ * {@sample development/samples/ApiDemos/src/com/example/android/apis/preference/PreferenceWithHeaders.java
+ *      activity}
+ *
+ * <p>The preference_headers resource describes the headers to be displayed
+ * and the fragments associated with them.  It is:
+ *
+ * {@sample development/samples/ApiDemos/res/xml/preference_headers.xml headers}
+ *
+ * <p>The first header is shown by Prefs1Fragment, which populates itself
+ * from the following XML resource:</p>
+ *
+ * {@sample development/samples/ApiDemos/res/xml/fragmented_preferences.xml preferences}
+ *
+ * <p>Note that this XML resource contains a preference screen holding another
+ * fragment, the Prefs1FragmentInner implemented here.  This allows the user
+ * to traverse down a hierarchy of preferences; pressing back will pop each
+ * fragment off the stack to return to the previous preferences.
+ *
+ * <p>See {@link PreferenceFragment} for information on implementing the
+ * fragments themselves.
  */
 public abstract class PreferenceActivity extends ListActivity implements
-        PreferenceManager.OnPreferenceTreeClickListener {
-    
+        PreferenceManager.OnPreferenceTreeClickListener,
+        PreferenceFragment.OnPreferenceStartFragmentCallback {
+    private static final String TAG = "PreferenceActivity";
+
     private static final String PREFERENCES_TAG = "android:preferences";
-    
+
+    private static final String EXTRA_PREFS_SHOW_FRAGMENT = ":android:show_fragment";
+
+    private static final String EXTRA_PREFS_NO_HEADERS = ":android:no_headers";
+
+    private static final String BACK_STACK_PREFS = ":android:prefs";
+
+    // 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";
+
+    // --- State for new mode when showing a list of headers + prefs fragment
+
+    private final ArrayList<Header> mHeaders = new ArrayList<Header>();
+
+    private HeaderAdapter mAdapter;
+
+    private View mPrefsContainer;
+
+    private boolean mSinglePane;
+
+    // --- State for old mode when showing a single preference list
+
     private PreferenceManager mPreferenceManager;
-    
+
     private Bundle mSavedInstanceState;
 
+    // --- Common state
+
+    private Button mNextButton;
+
     /**
      * 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;
@@ -100,51 +168,332 @@
         }
     };
 
+    private class HeaderViewHolder {
+        ImageView icon;
+        TextView title;
+        TextView summary;
+    }
+
+    private class HeaderAdapter extends ArrayAdapter<Header> {
+        private LayoutInflater mInflater;
+
+        public HeaderAdapter(Context context, List<Header> objects) {
+            super(context, 0, objects);
+            mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+        }
+
+        @Override
+        public View getView(int position, View convertView, ViewGroup parent) {
+            HeaderViewHolder holder;
+            View view;
+
+            if (convertView == null) {
+                view = mInflater.inflate(com.android.internal.R.layout.preference_list_item,
+                        parent, false);
+                holder = new HeaderViewHolder();
+                holder.icon = (ImageView)view.findViewById(
+                        com.android.internal.R.id.icon);
+                holder.title = (TextView)view.findViewById(
+                        com.android.internal.R.id.title);
+                holder.summary = (TextView)view.findViewById(
+                        com.android.internal.R.id.summary);
+                view.setTag(holder);
+            } else {
+                view = convertView;
+                holder = (HeaderViewHolder)view.getTag();
+            }
+
+            Header header = getItem(position);
+            if (header.icon != null) holder.icon.setImageDrawable(header.icon);
+            else if (header.iconRes != 0) holder.icon.setImageResource(header.iconRes);
+            if (header.title != null) holder.title.setText(header.title);
+            if (header.summary != null) holder.summary.setText(header.summary);
+
+            return view;
+        }
+    }
+
+    /**
+     * Description of a single Header item that the user can select.
+     */
+    public static class Header {
+        /**
+         * Title of the header that is shown to the user.
+         * @attr ref android.R.styleable#PreferenceHeader_title
+         */
+        CharSequence title;
+
+        /**
+         * Optional summary describing what this header controls.
+         * @attr ref android.R.styleable#PreferenceHeader_summary
+         */
+        CharSequence summary;
+
+        /**
+         * Optional icon resource to show for this header.
+         * @attr ref android.R.styleable#PreferenceHeader_icon
+         */
+        int iconRes;
+
+        /**
+         * Optional icon drawable to show for this header.  (If this is non-null,
+         * the iconRes will be ignored.)
+         */
+        Drawable icon;
+
+        /**
+         * Full class name of the fragment to display when this header is
+         * selected.
+         * @attr ref android.R.styleable#PreferenceHeader_fragment
+         */
+        String fragment;
+    }
+
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
         setContentView(com.android.internal.R.layout.preference_list_content);
-        
-        mPreferenceManager = onCreatePreferenceManager();
+
+        mPrefsContainer = findViewById(com.android.internal.R.id.prefs);
+        boolean hidingHeaders = onIsHidingHeaders();
+        mSinglePane = hidingHeaders || !onIsMultiPane();
+        String initialFragment = getIntent().getStringExtra(EXTRA_PREFS_SHOW_FRAGMENT);
+
+        if (initialFragment != null && mSinglePane) {
+            // If we are just showing a fragment, we want to run in
+            // new fragment mode, but don't need to compute and show
+            // the headers.
+            getListView().setVisibility(View.GONE);
+            mPrefsContainer.setVisibility(View.VISIBLE);
+            switchToHeader(initialFragment);
+
+        } else {
+            // We need to try to build the headers.
+            onBuildHeaders(mHeaders);
+
+            // If there are headers, then at this point we need to show
+            // them and, depending on the screen, we may also show in-line
+            // the currently selected preference fragment.
+            if (mHeaders.size() > 0) {
+                mAdapter = new HeaderAdapter(this, mHeaders);
+                setListAdapter(mAdapter);
+                if (!mSinglePane) {
+                    mPrefsContainer.setVisibility(View.VISIBLE);
+                    switchToHeader(initialFragment != null
+                            ? initialFragment : onGetInitialFragment());
+                }
+
+            // If there are no headers, we are in the old "just show a screen
+            // of preferences" mode.
+            } else {
+                mPreferenceManager = new PreferenceManager(this, FIRST_REQUEST_CODE);
+                mPreferenceManager.setOnPreferenceTreeClickListener(this);
+            }
+        }
+
         getListView().setScrollBarStyle(View.SCROLLBARS_INSIDE_OVERLAY);
+
+        // 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);
+                }
+            }
+        }
+    }
+
+    /**
+     * Called to determine if the activity should run in multi-pane mode.
+     * The default implementation returns true if the screen is large
+     * enough.
+     */
+    public boolean onIsMultiPane() {
+        Configuration config = getResources().getConfiguration();
+        if ((config.screenLayout&Configuration.SCREENLAYOUT_SIZE_MASK)
+                == Configuration.SCREENLAYOUT_SIZE_XLARGE
+                && config.orientation == Configuration.ORIENTATION_LANDSCAPE) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Called to determine whether the header list should be hidden.  The
+     * default implementation hides the list if the activity is being re-launched
+     * when not in multi-pane mode.
+     */
+    public boolean onIsHidingHeaders() {
+        return getIntent().getBooleanExtra(EXTRA_PREFS_NO_HEADERS, false);
+    }
+
+    /**
+     * Called to determine the initial fragment to be shown.  The default
+     * implementation simply returns the fragment of the first header.
+     */
+    public String onGetInitialFragment() {
+        return mHeaders.get(0).fragment;
+    }
+
+    /**
+     * Called when the activity needs its list of headers build.  By
+     * implementing this and adding at least one item to the list, you
+     * will cause the activity to run in its modern fragment mode.  Note
+     * that this function may not always be called; for example, if the
+     * activity has been asked to display a particular fragment without
+     * the header list, there is no need to build the headers.
+     *
+     * <p>Typical implementations will use {@link #loadHeadersFromResource}
+     * to fill in the list from a resource.
+     *
+     * @param target The list in which to place the headers.
+     */
+    public void onBuildHeaders(List<Header> target) {
+    }
+
+    /**
+     * Parse the given XML file as a header description, adding each
+     * parsed Header into the target list.
+     *
+     * @param resid The XML resource to load and parse.
+     * @param target The list in which the parsed headers should be placed.
+     */
+    public void loadHeadersFromResource(int resid, List<Header> target) {
+        XmlResourceParser parser = null;
+        try {
+            parser = getResources().getXml(resid);
+            AttributeSet attrs = Xml.asAttributeSet(parser);
+
+            int type;
+            while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
+                    && type != XmlPullParser.START_TAG) {
+            }
+
+            String nodeName = parser.getName();
+            if (!"PreferenceHeaders".equals(nodeName)) {
+                throw new RuntimeException(
+                        "XML document must start with <PreferenceHeaders> tag; found"
+                        + nodeName + " at " + parser.getPositionDescription());
+            }
+
+            int outerDepth = parser.getDepth();
+            while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
+                   && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+                if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                    continue;
+                }
+
+                nodeName = parser.getName();
+                if ("Header".equals(nodeName)) {
+                    Header header = new Header();
+
+                    TypedArray sa = getResources().obtainAttributes(attrs,
+                            com.android.internal.R.styleable.PreferenceHeader);
+                    header.title = sa.getText(
+                            com.android.internal.R.styleable.PreferenceHeader_title);
+                    header.summary = sa.getText(
+                            com.android.internal.R.styleable.PreferenceHeader_summary);
+                    header.iconRes = sa.getResourceId(
+                            com.android.internal.R.styleable.PreferenceHeader_icon, 0);
+                    header.fragment = sa.getString(
+                            com.android.internal.R.styleable.PreferenceHeader_fragment);
+                    sa.recycle();
+
+                    target.add(header);
+
+                    XmlUtils.skipCurrentTag(parser);
+                } else {
+                    XmlUtils.skipCurrentTag(parser);
+                }
+            }
+
+        } catch (XmlPullParserException e) {
+            throw new RuntimeException("Error parsing headers", e);
+        } catch (IOException e) {
+            throw new RuntimeException("Error parsing headers", e);
+        } finally {
+            if (parser != null) parser.close();
+        }
+
     }
 
     @Override
     protected void onStop() {
         super.onStop();
-        
-        mPreferenceManager.dispatchActivityStop();
+
+        if (mPreferenceManager != null) {
+            mPreferenceManager.dispatchActivityStop();
+        }
     }
 
     @Override
     protected void onDestroy() {
         super.onDestroy();
-        
-        mPreferenceManager.dispatchActivityDestroy();
+
+        if (mPreferenceManager != null) {
+            mPreferenceManager.dispatchActivityDestroy();
+        }
     }
 
     @Override
     protected void onSaveInstanceState(Bundle outState) {
         super.onSaveInstanceState(outState);
 
-        final PreferenceScreen preferenceScreen = getPreferenceScreen();
-        if (preferenceScreen != null) {
-            Bundle container = new Bundle();
-            preferenceScreen.saveHierarchyState(container);
-            outState.putBundle(PREFERENCES_TAG, container);
+        if (mPreferenceManager != null) {
+            final PreferenceScreen preferenceScreen = getPreferenceScreen();
+            if (preferenceScreen != null) {
+                Bundle container = new Bundle();
+                preferenceScreen.saveHierarchyState(container);
+                outState.putBundle(PREFERENCES_TAG, container);
+            }
         }
     }
 
     @Override
     protected void onRestoreInstanceState(Bundle state) {
-        Bundle container = state.getBundle(PREFERENCES_TAG);
-        if (container != null) {
-            final PreferenceScreen preferenceScreen = getPreferenceScreen();
-            if (preferenceScreen != null) {
-                preferenceScreen.restoreHierarchyState(container);
-                mSavedInstanceState = state;
-                return;
+        if (mPreferenceManager != null) {
+            Bundle container = state.getBundle(PREFERENCES_TAG);
+            if (container != null) {
+                final PreferenceScreen preferenceScreen = getPreferenceScreen();
+                if (preferenceScreen != null) {
+                    preferenceScreen.restoreHierarchyState(container);
+                    mSavedInstanceState = state;
+                    return;
+                }
             }
         }
 
@@ -156,14 +505,93 @@
     @Override
     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
         super.onActivityResult(requestCode, resultCode, data);
-        
-        mPreferenceManager.dispatchActivityResult(requestCode, resultCode, data);
+
+        if (mPreferenceManager != null) {
+            mPreferenceManager.dispatchActivityResult(requestCode, resultCode, data);
+        }
     }
 
     @Override
     public void onContentChanged() {
         super.onContentChanged();
-        postBindPreferences();
+
+        if (mPreferenceManager != null) {
+            postBindPreferences();
+        }
+    }
+
+    @Override
+    protected void onListItemClick(ListView l, View v, int position, long id) {
+        super.onListItemClick(l, v, position, id);
+
+        if (mAdapter != null) {
+            onHeaderClick(mHeaders.get(position), position);
+        }
+    }
+
+    /**
+     * Called when the user selects an item in the header list.  The default
+     * implementation will call either {@link #startWithFragment(String)}
+     * or {@link #switchToHeader(String)} as appropriate.
+     *
+     * @param header The header that was selected.
+     * @param position The header's position in the list.
+     */
+    public void onHeaderClick(Header header, int position) {
+        if (mSinglePane) {
+            startWithFragment(header.fragment);
+        } else {
+            switchToHeader(header.fragment);
+        }
+    }
+
+    /**
+     * Start a new instance of this activity, showing only the given
+     * preference fragment.  When launched in this mode, the header list
+     * will be hidden and the given preference fragment will be instantiated
+     * and fill the entire activity.
+     *
+     * @param fragmentName The name of the fragment to display.
+     */
+    public void startWithFragment(String fragmentName) {
+        Intent intent = new Intent(Intent.ACTION_MAIN);
+        intent.setClass(this, getClass());
+        intent.putExtra(EXTRA_PREFS_SHOW_FRAGMENT, fragmentName);
+        intent.putExtra(EXTRA_PREFS_NO_HEADERS, true);
+        startActivity(intent);
+    }
+
+    /**
+     * When in two-pane mode, switch the fragment pane to show the given
+     * preference fragment.
+     *
+     * @param fragmentName The name of the fragment to display.
+     */
+    public void switchToHeader(String fragmentName) {
+        popBackStack(BACK_STACK_PREFS, POP_BACK_STACK_INCLUSIVE);
+
+        Fragment f;
+        try {
+            f = Fragment.instantiate(this, fragmentName);
+        } catch (Exception e) {
+            Log.w(TAG, "Failure instantiating fragment " + fragmentName, e);
+            return;
+        }
+        openFragmentTransaction().replace(com.android.internal.R.id.prefs, f).commit();
+    }
+
+    @Override
+    public boolean onPreferenceStartFragment(PreferenceFragment caller, Preference pref) {
+        Fragment f;
+        try {
+            f = Fragment.instantiate(this, pref.getFragment());
+        } catch (Exception e) {
+            Log.w(TAG, "Failure instantiating fragment " + pref.getFragment(), e);
+            return false;
+        }
+        openFragmentTransaction().replace(com.android.internal.R.id.prefs, f)
+                .addToBackStack(BACK_STACK_PREFS).commit();
+        return true;
     }
 
     /**
@@ -176,7 +604,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,38 +615,41 @@
             }
         }
     }
-    
-    /**
-     * Creates the {@link PreferenceManager}.
-     * 
-     * @return The {@link PreferenceManager} used by this activity.
-     */
-    private PreferenceManager onCreatePreferenceManager() {
-        PreferenceManager preferenceManager = new PreferenceManager(this, FIRST_REQUEST_CODE);
-        preferenceManager.setOnPreferenceTreeClickListener(this);
-        return preferenceManager;
-    }
-    
+
     /**
      * Returns the {@link PreferenceManager} used by this activity.
      * @return The {@link PreferenceManager}.
+     *
+     * @deprecated This function is not relevant for a modern fragment-based
+     * PreferenceActivity.
      */
+    @Deprecated
     public PreferenceManager getPreferenceManager() {
         return mPreferenceManager;
     }
-    
+
     private void requirePreferenceManager() {
         if (mPreferenceManager == null) {
-            throw new RuntimeException("This should be called after super.onCreate.");
+            if (mAdapter == null) {
+                throw new RuntimeException("This should be called after super.onCreate.");
+            }
+            throw new RuntimeException(
+                    "Modern two-pane PreferenceActivity requires use of a PreferenceFragment");
         }
     }
 
     /**
      * Sets the root of the preference hierarchy that this activity is showing.
-     * 
+     *
      * @param preferenceScreen The root {@link PreferenceScreen} of the preference hierarchy.
+     *
+     * @deprecated This function is not relevant for a modern fragment-based
+     * PreferenceActivity.
      */
+    @Deprecated
     public void setPreferenceScreen(PreferenceScreen preferenceScreen) {
+        requirePreferenceManager();
+
         if (mPreferenceManager.setPreferences(preferenceScreen) && preferenceScreen != null) {
             postBindPreferences();
             CharSequence title = getPreferenceScreen().getTitle();
@@ -228,61 +659,84 @@
             }
         }
     }
-    
+
     /**
      * Gets the root of the preference hierarchy that this activity is showing.
-     * 
+     *
      * @return The {@link PreferenceScreen} that is the root of the preference
      *         hierarchy.
+     *
+     * @deprecated This function is not relevant for a modern fragment-based
+     * PreferenceActivity.
      */
+    @Deprecated
     public PreferenceScreen getPreferenceScreen() {
-        return mPreferenceManager.getPreferenceScreen();
+        if (mPreferenceManager != null) {
+            return mPreferenceManager.getPreferenceScreen();
+        }
+        return null;
     }
-    
+
     /**
      * Adds preferences from activities that match the given {@link Intent}.
-     * 
+     *
      * @param intent The {@link Intent} to query activities.
+     *
+     * @deprecated This function is not relevant for a modern fragment-based
+     * PreferenceActivity.
      */
+    @Deprecated
     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.
+     *
+     * @deprecated This function is not relevant for a modern fragment-based
+     * PreferenceActivity.
      */
+    @Deprecated
     public void addPreferencesFromResource(int preferencesResId) {
         requirePreferenceManager();
-        
+
         setPreferenceScreen(mPreferenceManager.inflateFromResource(this, preferencesResId,
                 getPreferenceScreen()));
     }
 
     /**
      * {@inheritDoc}
+     *
+     * @deprecated This function is not relevant for a modern fragment-based
+     * PreferenceActivity.
      */
+    @Deprecated
     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)
+     *
+     * @deprecated This function is not relevant for a modern fragment-based
+     * PreferenceActivity.
      */
+    @Deprecated
     public Preference findPreference(CharSequence key) {
-        
+
         if (mPreferenceManager == null) {
             return null;
         }
-        
+
         return mPreferenceManager.findPreference(key);
     }
 
@@ -292,5 +746,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/preference/PreferenceFragment.java b/core/java/android/preference/PreferenceFragment.java
new file mode 100644
index 0000000..a5395e2
--- /dev/null
+++ b/core/java/android/preference/PreferenceFragment.java
@@ -0,0 +1,345 @@
+/*
+ * 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.Activity;
+import android.app.Fragment;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ListView;
+
+/**
+ * Shows a hierarchy of {@link Preference} objects as
+ * lists. These preferences will
+ * automatically save to {@link SharedPreferences} as the user interacts with
+ * them. To retrieve an instance of {@link SharedPreferences} that the
+ * preference hierarchy in this fragment will use, call
+ * {@link PreferenceManager#getDefaultSharedPreferences(android.content.Context)}
+ * with a context in the same package as this fragment.
+ * <p>
+ * Furthermore, the preferences shown will follow the visual style of system
+ * preferences. It is easy to create a hierarchy of preferences (that can be
+ * shown on multiple screens) via XML. For these reasons, it is recommended to
+ * use this fragment (as a superclass) to deal with preferences in applications.
+ * <p>
+ * A {@link PreferenceScreen} object should be at the top of the preference
+ * hierarchy. Furthermore, subsequent {@link PreferenceScreen} in the hierarchy
+ * denote a screen break--that is the preferences contained within subsequent
+ * {@link PreferenceScreen} should be shown on another screen. The preference
+ * framework handles showing these other screens from the preference hierarchy.
+ * <p>
+ * The preference hierarchy can be formed in multiple ways:
+ * <li> From an XML file specifying the hierarchy
+ * <li> From different {@link Activity Activities} that each specify its own
+ * preferences in an XML file via {@link Activity} meta-data
+ * <li> From an object hierarchy rooted with {@link PreferenceScreen}
+ * <p>
+ * To inflate from XML, use the {@link #addPreferencesFromResource(int)}. The
+ * root element should be a {@link PreferenceScreen}. Subsequent elements can point
+ * to actual {@link Preference} subclasses. As mentioned above, subsequent
+ * {@link PreferenceScreen} in the hierarchy will result in the screen break.
+ * <p>
+ * To specify an {@link Intent} to query {@link Activity Activities} that each
+ * have preferences, use {@link #addPreferencesFromIntent}. Each
+ * {@link Activity} can specify meta-data in the manifest (via the key
+ * {@link PreferenceManager#METADATA_KEY_PREFERENCES}) that points to an XML
+ * resource. These XML resources will be inflated into a single preference
+ * hierarchy and shown by this fragment.
+ * <p>
+ * To specify an object hierarchy rooted with {@link PreferenceScreen}, use
+ * {@link #setPreferenceScreen(PreferenceScreen)}.
+ * <p>
+ * As a convenience, this fragment implements a click listener for any
+ * preference in the current hierarchy, see
+ * {@link #onPreferenceTreeClick(PreferenceScreen, Preference)}.
+ *
+ * <a name="SampleCode"></a>
+ * <h3>Sample Code</h3>
+ *
+ * <p>The following sample code shows a simple preference fragment that is
+ * populated from a resource.  The resource it loads is:</p>
+ *
+ * {@sample development/samples/ApiDemos/res/xml/preferences.xml preferences}
+ *
+ * <p>The fragment implementation itself simply populates the preferences
+ * when created.  Note that the preferences framework takes care of loading
+ * the current values out of the app preferences and writing them when changed:</p>
+ *
+ * {@sample development/samples/ApiDemos/src/com/example/android/apis/preference/FragmentPreferences.java
+ *      fragment}
+ *
+ * @see Preference
+ * @see PreferenceScreen
+ */
+public abstract class PreferenceFragment extends Fragment implements
+        PreferenceManager.OnPreferenceTreeClickListener {
+
+    private static final String PREFERENCES_TAG = "android:preferences";
+
+    private PreferenceManager mPreferenceManager;
+    private ListView mList;
+    private boolean mHavePrefs;
+    private boolean mInitDone;
+
+    /**
+     * 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;
+            }
+        }
+    };
+
+    final private Runnable mRequestFocus = new Runnable() {
+        public void run() {
+            mList.focusableViewAvailable(mList);
+        }
+    };
+
+    /**
+     * Interface that PreferenceFragment's containing activity should
+     * implement to be able to process preference items that wish to
+     * switch to a new fragment.
+     */
+    public interface OnPreferenceStartFragmentCallback {
+        /**
+         * Called when the user has clicked on a Preference that has
+         * a fragment class name associated with it.  The implementation
+         * to should instantiate and switch to an instance of the given
+         * fragment.
+         */
+        boolean onPreferenceStartFragment(PreferenceFragment caller, Preference pref);
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        mPreferenceManager = new PreferenceManager(getActivity(), FIRST_REQUEST_CODE);
+        mPreferenceManager.setOnPreferenceTreeClickListener(this);
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+        return inflater.inflate(com.android.internal.R.layout.preference_list_content,
+                container, false);
+    }
+
+    @Override
+    public void onActivityCreated(Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+        getListView().setScrollBarStyle(View.SCROLLBARS_INSIDE_OVERLAY);
+
+        if (mHavePrefs) {
+            bindPreferences();
+        }
+
+        mInitDone = true;
+
+        if (savedInstanceState != null) {
+            Bundle container = savedInstanceState.getBundle(PREFERENCES_TAG);
+            if (container != null) {
+                final PreferenceScreen preferenceScreen = getPreferenceScreen();
+                if (preferenceScreen != null) {
+                    preferenceScreen.restoreHierarchyState(container);
+                }
+            }
+        }
+    }
+
+    @Override
+    public void onStop() {
+        super.onStop();
+        mPreferenceManager.dispatchActivityStop();
+    }
+
+    @Override
+    public void onDestroyView() {
+        mList = null;
+        mHandler.removeCallbacks(mRequestFocus);
+        super.onDestroyView();
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        mPreferenceManager.dispatchActivityDestroy();
+    }
+
+    @Override
+    public void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+
+        final PreferenceScreen preferenceScreen = getPreferenceScreen();
+        if (preferenceScreen != null) {
+            Bundle container = new Bundle();
+            preferenceScreen.saveHierarchyState(container);
+            outState.putBundle(PREFERENCES_TAG, container);
+        }
+    }
+
+    @Override
+    public void onActivityResult(int requestCode, int resultCode, Intent data) {
+        super.onActivityResult(requestCode, resultCode, data);
+
+        mPreferenceManager.dispatchActivityResult(requestCode, resultCode, data);
+    }
+
+    /**
+     * Returns the {@link PreferenceManager} used by this fragment.
+     * @return The {@link PreferenceManager}.
+     */
+    public PreferenceManager getPreferenceManager() {
+        return mPreferenceManager;
+    }
+
+    /**
+     * Sets the root of the preference hierarchy that this fragment is showing.
+     *
+     * @param preferenceScreen The root {@link PreferenceScreen} of the preference hierarchy.
+     */
+    public void setPreferenceScreen(PreferenceScreen preferenceScreen) {
+        if (mPreferenceManager.setPreferences(preferenceScreen) && preferenceScreen != null) {
+            mHavePrefs = true;
+            if (mInitDone) {
+                postBindPreferences();
+            }
+        }
+    }
+
+    /**
+     * Gets the root of the preference hierarchy that this fragment 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(getActivity(),
+                preferencesResId, getPreferenceScreen()));
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen,
+            Preference preference) {
+        if (preference.getFragment() != null &&
+                getActivity() instanceof OnPreferenceStartFragmentCallback) {
+            return ((OnPreferenceStartFragmentCallback)getActivity()).onPreferenceStartFragment(
+                    this, 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);
+    }
+
+    private void requirePreferenceManager() {
+        if (mPreferenceManager == null) {
+            throw new RuntimeException("This should be called after super.onCreate.");
+        }
+    }
+
+    private void postBindPreferences() {
+        if (mHandler.hasMessages(MSG_BIND_PREFERENCES)) return;
+        mHandler.obtainMessage(MSG_BIND_PREFERENCES).sendToTarget();
+    }
+
+    private void bindPreferences() {
+        final PreferenceScreen preferenceScreen = getPreferenceScreen();
+        if (preferenceScreen != null) {
+            preferenceScreen.bind(getListView());
+        }
+    }
+
+    private ListView getListView() {
+        ensureList();
+        return mList;
+    }
+
+    private void ensureList() {
+        if (mList != null) {
+            return;
+        }
+        View root = getView();
+        if (root == null) {
+            throw new IllegalStateException("Content view not yet created");
+        }
+        View rawListView = root.findViewById(android.R.id.list);
+        if (!(rawListView instanceof ListView)) {
+            throw new RuntimeException(
+                    "Content has view with id attribute 'android.R.id.list' "
+                    + "that is not a ListView class");
+        }
+        mList = (ListView)rawListView;
+        if (mList == null) {
+            throw new RuntimeException(
+                    "Your content must have a ListView whose id attribute is " +
+                    "'android.R.id.list'");
+        }
+        mHandler.post(mRequestFocus);
+    }
+}
diff --git a/core/java/android/preference/PreferenceScreen.java b/core/java/android/preference/PreferenceScreen.java
index 95e54324..f34f4a3 100644
--- a/core/java/android/preference/PreferenceScreen.java
+++ b/core/java/android/preference/PreferenceScreen.java
@@ -136,7 +136,7 @@
     
     @Override
     protected void onClick() {
-        if (getIntent() != null || getPreferenceCount() == 0) {
+        if (getIntent() != null || getFragment() != null || getPreferenceCount() == 0) {
             return;
         }
         
diff --git a/core/java/android/provider/Browser.java b/core/java/android/provider/Browser.java
index 2fba1d79..991ce9e 100644
--- a/core/java/android/provider/Browser.java
+++ b/core/java/android/provider/Browser.java
@@ -22,17 +22,21 @@
 import android.content.Intent;
 import android.database.Cursor;
 import android.database.DatabaseUtils;
+import android.graphics.BitmapFactory;
 import android.net.Uri;
-import android.os.AsyncTask;
 import android.util.Log;
 import android.webkit.WebIconDatabase;
 
-import java.util.Date;
-
 public class Browser {
     private static final String LOGTAG = "browser";
-    public static final Uri BOOKMARKS_URI =
-        Uri.parse("content://browser/bookmarks");
+
+    /**
+     * A table containing both bookmarks and history items. The columns of the table are defined in
+     * {@link BookmarkColumns}. Reading this table requires the
+     * {@link android.Manifest.permission#READ_HISTORY_BOOKMARKS} permission and writing to it
+     * requires the {@link android.Manifest.permission#WRITE_HISTORY_BOOKMARKS} permission.
+     */
+    public static final Uri BOOKMARKS_URI = Uri.parse("content://browser/bookmarks");
 
     /**
      * The name of extra data when starting Browser with ACTION_VIEW or
@@ -49,12 +53,11 @@
      * application.
      * <p>
      * The value is a unique identification string that will be used to
-     * indentify the calling application. The Browser will attempt to reuse the
+     * identify the calling application. The Browser will attempt to reuse the
      * same window each time the application launches the Browser with the same
      * identifier.
      */
-    public static final String EXTRA_APPLICATION_ID =
-        "com.android.browser.application_id";
+    public static final String EXTRA_APPLICATION_ID = "com.android.browser.application_id";
 
     /**
      * The name of the extra data in the VIEW intent. The data are key/value
@@ -68,10 +71,17 @@
     /* if you change column order you must also change indices
        below */
     public static final String[] HISTORY_PROJECTION = new String[] {
-        BookmarkColumns._ID, BookmarkColumns.URL, BookmarkColumns.VISITS,
-        BookmarkColumns.DATE, BookmarkColumns.BOOKMARK, BookmarkColumns.TITLE,
-        BookmarkColumns.FAVICON, BookmarkColumns.THUMBNAIL,
-        BookmarkColumns.TOUCH_ICON, BookmarkColumns.USER_ENTERED };
+            BookmarkColumns._ID, // 0
+            BookmarkColumns.URL, // 1
+            BookmarkColumns.VISITS, // 2
+            BookmarkColumns.DATE, // 3
+            BookmarkColumns.BOOKMARK, // 4
+            BookmarkColumns.TITLE, // 5
+            BookmarkColumns.FAVICON, // 6
+            BookmarkColumns.THUMBNAIL, // 7
+            BookmarkColumns.TOUCH_ICON, // 8
+            BookmarkColumns.USER_ENTERED, // 9
+    };
 
     /* these indices dependent on HISTORY_PROJECTION */
     public static final int HISTORY_PROJECTION_ID_INDEX = 0;
@@ -92,19 +102,33 @@
 
     /* columns needed to determine whether to truncate history */
     public static final String[] TRUNCATE_HISTORY_PROJECTION = new String[] {
-        BookmarkColumns._ID, BookmarkColumns.DATE, };
+            BookmarkColumns._ID,
+            BookmarkColumns.DATE,
+    };
+
     public static final int TRUNCATE_HISTORY_PROJECTION_ID_INDEX = 0;
 
     /* truncate this many history items at a time */
     public static final int TRUNCATE_N_OLDEST = 5;
 
-    public static final Uri SEARCHES_URI =
-        Uri.parse("content://browser/searches");
+    /**
+     * A table containing a log of browser searches. The columns of the table are defined in
+     * {@link SearchColumns}. Reading this table requires the
+     * {@link android.Manifest.permission#READ_HISTORY_BOOKMARKS} permission and writing to it
+     * requires the {@link android.Manifest.permission#WRITE_HISTORY_BOOKMARKS} permission.
+     */
+    public static final Uri SEARCHES_URI = Uri.parse("content://browser/searches");
 
-    /* if you change column order you must also change indices
-       below */
+    /**
+     * A projection of {@link #SEARCHES_URI} that contains {@link SearchColumns#_ID},
+     * {@link SearchColumns#SEARCH}, and {@link SearchColumns#DATE}.
+     */
     public static final String[] SEARCHES_PROJECTION = new String[] {
-        SearchColumns._ID, SearchColumns.SEARCH, SearchColumns.DATE };
+            // if you change column order you must also change indices below
+            SearchColumns._ID, // 0
+            SearchColumns.SEARCH, // 1
+            SearchColumns.DATE, // 2
+    };
 
     /* these indices dependent on SEARCHES_PROJECTION */
     public static final int SEARCHES_PROJECTION_SEARCH_INDEX = 1;
@@ -121,9 +145,10 @@
     private static final int MAX_HISTORY_COUNT = 250;
 
     /**
-     *  Open the AddBookmark activity to save a bookmark.  Launch with
-     *  and/or url, which can be edited by the user before saving.
-     *  @param c        Context used to launch the AddBookmark activity.
+     *  Open an activity to save a bookmark. Launch with a title
+     *  and/or a url, both of which can be edited by the user before saving.
+     *
+     *  @param c        Context used to launch the activity to add a bookmark.
      *  @param title    Title for the bookmark. Can be null or empty string.
      *  @param url      Url for the bookmark. Can be null or empty string.
      */
@@ -152,8 +177,15 @@
      */
     public final static String EXTRA_SHARE_FAVICON = "share_favicon";
 
-    public static final void sendString(Context c, String s) {
-        sendString(c, s, c.getString(com.android.internal.R.string.sendText));
+    /**
+     * Sends the given string using an Intent with {@link Intent#ACTION_SEND} and a mime type
+     * of text/plain. The string is put into {@link Intent#EXTRA_TEXT}.
+     *
+     * @param context the context used to start the activity
+     * @param string the string to send
+     */
+    public static final void sendString(Context context, String string) {
+        sendString(context, string, context.getString(com.android.internal.R.string.sendText));
     }
 
     /**
@@ -181,20 +213,26 @@
     }
 
     /**
-     *  Return a cursor pointing to a list of all the bookmarks.
+     *  Return a cursor pointing to a list of all the bookmarks. The cursor will have a single
+     *  column, {@link BookmarkColumns#URL}.
+     *  <p>
      *  Requires {@link android.Manifest.permission#READ_HISTORY_BOOKMARKS}
+     *
      *  @param cr   The ContentResolver used to access the database.
      */
     public static final Cursor getAllBookmarks(ContentResolver cr) throws 
             IllegalStateException {
         return cr.query(BOOKMARKS_URI,
                 new String[] { BookmarkColumns.URL }, 
-                "bookmark = 1", null, null);
+                BookmarkColumns.BOOKMARK + " = 1", null, null);
     }
 
     /**
-     *  Return a cursor pointing to a list of all visited site urls.
+     *  Return a cursor pointing to a list of all visited site urls. The cursor will
+     *  have a single column, {@link BookmarkColumns#URL}.
+     *  <p>
      *  Requires {@link android.Manifest.permission#READ_HISTORY_BOOKMARKS}
+     *
      *  @param cr   The ContentResolver used to access the database.
      */
     public static final Cursor getAllVisitedUrls(ContentResolver cr) throws
@@ -266,7 +304,7 @@
      */
     public static final void updateVisitedHistory(ContentResolver cr,
                                                   String url, boolean real) {
-        long now = new Date().getTime();
+        long now = System.currentTimeMillis();
         Cursor c = null;
         try {
             c = getVisitedLike(cr, url);
@@ -534,7 +572,7 @@
      * @param search    The string to add to the searches database.
      */
     public static final void addSearchUrl(ContentResolver cr, String search) {
-        long now = new Date().getTime();
+        long now = System.currentTimeMillis();
         Cursor c = null;
         try {
             c = cr.query(
@@ -558,6 +596,7 @@
             if (c != null) c.close();
         }
     }
+
     /**
      * Remove all searches from the search database.
      *  Requires {@link android.Manifest.permission#WRITE_HISTORY_BOOKMARKS}
@@ -590,31 +629,90 @@
                 .bulkRequestIconForPageUrl(cr, where, listener);
     }
 
+    /**
+     * Column definitions for the mixed bookmark and history items available
+     * at {@link #BOOKMARKS_URI}.
+     */
     public static class BookmarkColumns implements BaseColumns {
+        /**
+         * The URL of the bookmark or history item.
+         * <p>Type: TEXT (URL)</p>
+         */
         public static final String URL = "url";
+
+        /**
+         * The number of time the item has been visited.
+         * <p>Type: NUMBER</p>
+         */
         public static final String VISITS = "visits";
+
+        /**
+         * The date the item was last visited, in milliseconds since the epoch.
+         * <p>Type: NUMBER (date in milliseconds since January 1, 1970)</p>
+         */
         public static final String DATE = "date";
+
+        /**
+         * Flag indicating that an item is a bookmark. A value of 1 indicates a bookmark, a value
+         * of 0 indicates a history item.
+         * <p>Type: INTEGER (boolean)</p>
+         */
         public static final String BOOKMARK = "bookmark";
+
+        /**
+         * The user visible title of the bookmark or history item.
+         * <p>Type: TEXT</p>
+         */
         public static final String TITLE = "title";
+
+        /**
+         * The date the item created, in milliseconds since the epoch.
+         * <p>Type: NUMBER (date in milliseconds since January 1, 1970)</p>
+         */
         public static final String CREATED = "created";
+
+        /**
+         * The favicon of the bookmark. Must decode via {@link BitmapFactory#decodeByteArray}.
+         * <p>Type: BLOB (image)</p>
+         */
         public static final String FAVICON = "favicon";
+
         /**
          * @hide
          */
         public static final String THUMBNAIL = "thumbnail";
+
         /**
          * @hide
          */
         public static final String TOUCH_ICON = "touch_icon";
+
         /**
          * @hide
          */
         public static final String USER_ENTERED = "user_entered";
     }
 
+    /**
+     * Column definitions for the search history table, available at {@link #SEARCHES_URI}.
+     */
     public static class SearchColumns implements BaseColumns {
+        /**
+         * Not used.
+         * @deprecated
+         */
+        @Deprecated
         public static final String URL = "url";
+
+        /**
+         * The user entered search term.
+         */
         public static final String SEARCH = "search";
+
+        /**
+         * The date the search was performed, in milliseconds since the epoch.
+         * <p>Type: NUMBER (date in milliseconds since January 1, 1970)</p>
+         */
         public static final String DATE = "date";
     }
 }
diff --git a/core/java/android/provider/Calendar.java b/core/java/android/provider/Calendar.java
index 9a09805..a6394e5 100644
--- a/core/java/android/provider/Calendar.java
+++ b/core/java/android/provider/Calendar.java
@@ -76,11 +76,106 @@
      */
     public static final String CALLER_IS_SYNCADAPTER = "caller_is_syncadapter";
 
+
+    /**
+     * Generic columns for use by sync adapters. The specific functions of
+     * these columns are private to the sync adapter. Other clients of the API
+     * should not attempt to either read or write this column.
+     */
+    protected interface BaseSyncColumns {
+
+        /** Generic column for use by sync adapters. */
+        public static final String SYNC1 = "sync1";
+        /** Generic column for use by sync adapters. */
+        public static final String SYNC2 = "sync2";
+        /** Generic column for use by sync adapters. */
+        public static final String SYNC3 = "sync3";
+        /** Generic column for use by sync adapters. */
+        public static final String SYNC4 = "sync4";
+        /** Generic column for use by sync adapters. */
+        public static final String SYNC5 = "sync5";
+    }
+
+    /**
+     * Columns for Sync information used by Calendars and Events tables.
+     */
+    public interface SyncColumns extends BaseSyncColumns {
+        /**
+         * The account that was used to sync the entry to the device.
+         * <P>Type: TEXT</P>
+         */
+        public static final String _SYNC_ACCOUNT = "_sync_account";
+
+        /**
+         * The type of the account that was used to sync the entry to the device.
+         * <P>Type: TEXT</P>
+         */
+        public static final String _SYNC_ACCOUNT_TYPE = "_sync_account_type";
+
+        /**
+         * The unique ID for a row assigned by the sync source. NULL if the row has never been synced.
+         * <P>Type: TEXT</P>
+         */
+        public static final String _SYNC_ID = "_sync_id";
+
+        /**
+         * The last time, from the sync source's point of view, that this row has been synchronized.
+         * <P>Type: INTEGER (long)</P>
+         */
+        public static final String _SYNC_TIME = "_sync_time";
+
+        /**
+         * The version of the row, as assigned by the server.
+         * <P>Type: TEXT</P>
+         */
+        public static final String _SYNC_VERSION = "_sync_version";
+
+        /**
+         * For use by sync adapter at its discretion; not modified by CalendarProvider
+         * Note that this column was formerly named _SYNC_LOCAL_ID.  We are using it to avoid a
+         * schema change.
+         * TODO Replace this with something more general in the future.
+         * <P>Type: INTEGER (long)</P>
+         */
+        public static final String _SYNC_DATA = "_sync_local_id";
+
+        /**
+         * Used only in persistent providers, and only during merging.
+         * <P>Type: INTEGER (long)</P>
+         */
+        public static final String _SYNC_MARK = "_sync_mark";
+
+        /**
+         * Used to indicate that local, unsynced, changes are present.
+         * <P>Type: INTEGER (long)</P>
+         */
+        public static final String _SYNC_DIRTY = "_sync_dirty";
+
+    }
+
+    /**
+     * Columns from the Account information used by Calendars and Events tables.
+     */
+    public interface AccountColumns {
+        /**
+         * The name of the account instance to which this row belongs, which when paired with
+         * {@link #ACCOUNT_TYPE} identifies a specific account.
+         * <P>Type: TEXT</P>
+         */
+        public static final String ACCOUNT_NAME = "account_name";
+
+        /**
+         * The type of account to which this row belongs, which when paired with
+         * {@link #ACCOUNT_NAME} identifies a specific account.
+         * <P>Type: TEXT</P>
+         */
+        public static final String ACCOUNT_TYPE = "account_type";
+    }
+
     /**
      * Columns from the Calendars table that other tables join into themselves.
      */
-    public interface CalendarsColumns
-    {
+    public interface CalendarsColumns {
         /**
          * The color of the calendar
          * <P>Type: INTEGER (color value)</P>
@@ -135,75 +230,103 @@
         public static final String SYNC_STATE = "sync_state";
 
         /**
-         * The account that was used to sync the entry to the device.
-         * <P>Type: TEXT</P>
+         * Whether the row has been deleted.  A deleted row should be ignored.
+         * <P>Type: INTEGER (boolean)</P>
          */
-        public static final String _SYNC_ACCOUNT = "_sync_account";
-
-        /**
-         * The type of the account that was used to sync the entry to the device.
-         * <P>Type: TEXT</P>
-         */
-        public static final String _SYNC_ACCOUNT_TYPE = "_sync_account_type";
-
-        /**
-         * The unique ID for a row assigned by the sync source. NULL if the row has never been synced.
-         * <P>Type: TEXT</P>
-         */
-        public static final String _SYNC_ID = "_sync_id";
-
-        /**
-         * The last time, from the sync source's point of view, that this row has been synchronized.
-         * <P>Type: INTEGER (long)</P>
-         */
-        public static final String _SYNC_TIME = "_sync_time";
-
-        /**
-         * The version of the row, as assigned by the server.
-         * <P>Type: TEXT</P>
-         */
-        public static final String _SYNC_VERSION = "_sync_version";
-
-        /**
-         * For use by sync adapter at its discretion; not modified by CalendarProvider
-         * Note that this column was formerly named _SYNC_LOCAL_ID.  We are using it to avoid a
-         * schema change.
-         * TODO Replace this with something more general in the future.
-         * <P>Type: INTEGER (long)</P>
-         */
-        public static final String _SYNC_DATA = "_sync_local_id";
-
-        /**
-         * Used only in persistent providers, and only during merging.
-         * <P>Type: INTEGER (long)</P>
-         */
-        public static final String _SYNC_MARK = "_sync_mark";
-
-        /**
-         * Used to indicate that local, unsynced, changes are present.
-         * <P>Type: INTEGER (long)</P>
-         */
-        public static final String _SYNC_DIRTY = "_sync_dirty";
-
-        /**
-         * The name of the account instance to which this row belongs, which when paired with
-         * {@link #ACCOUNT_TYPE} identifies a specific account.
-         * <P>Type: TEXT</P>
-         */
-        public static final String ACCOUNT_NAME = "account_name";
-
-        /**
-         * The type of account to which this row belongs, which when paired with
-         * {@link #ACCOUNT_NAME} identifies a specific account.
-         * <P>Type: TEXT</P>
-         */
-        public static final String ACCOUNT_TYPE = "account_type";
+        public static final String DELETED = "deleted";
     }
 
     /**
+     * Class that represents a Calendar Entity. There is one entry per calendar.
+     */
+    public static class CalendarsEntity implements BaseColumns, SyncColumns, CalendarsColumns {
+
+        public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY +
+                "/calendar_entities");
+
+        public static EntityIterator newEntityIterator(Cursor cursor, ContentResolver resolver) {
+            return new EntityIteratorImpl(cursor, resolver);
+        }
+
+        public static EntityIterator newEntityIterator(Cursor cursor,
+                ContentProviderClient provider) {
+            return new EntityIteratorImpl(cursor, provider);
+        }
+
+        private static class EntityIteratorImpl extends CursorEntityIterator {
+            private final ContentResolver mResolver;
+            private final ContentProviderClient mProvider;
+
+            public EntityIteratorImpl(Cursor cursor, ContentResolver resolver) {
+                super(cursor);
+                mResolver = resolver;
+                mProvider = null;
+            }
+
+            public EntityIteratorImpl(Cursor cursor, ContentProviderClient provider) {
+                super(cursor);
+                mResolver = null;
+                mProvider = provider;
+            }
+
+            @Override
+            public Entity getEntityAndIncrementCursor(Cursor cursor) throws RemoteException {
+                // we expect the cursor is already at the row we need to read from
+                final long calendarId = cursor.getLong(cursor.getColumnIndexOrThrow(_ID));
+
+                // Create the content value
+                ContentValues cv = new ContentValues();
+                cv.put(_ID, calendarId);
+
+                DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, _SYNC_ACCOUNT);
+                DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, _SYNC_ACCOUNT_TYPE);
+
+                DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, _SYNC_ID);
+                DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, _SYNC_VERSION);
+                DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, _SYNC_TIME);
+                DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, _SYNC_DATA);
+                DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, cv, _SYNC_DIRTY);
+                DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, cv, _SYNC_MARK);
+
+                DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, Calendars.SYNC1);
+                DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, Calendars.SYNC2);
+                DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, Calendars.SYNC3);
+                DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, Calendars.SYNC4);
+                DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, Calendars.SYNC5);
+
+                DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, Calendars.NAME);
+                DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv,
+                        Calendars.DISPLAY_NAME);
+                DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, Calendars.COLOR);
+                DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, ACCESS_LEVEL);
+                DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, SELECTED);
+                DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, SYNC_EVENTS);
+                DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, Calendars.LOCATION);
+                DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, TIMEZONE);
+                DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv,
+                        Calendars.OWNER_ACCOUNT);
+                DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv,
+                        Calendars.ORGANIZER_CAN_RESPOND);
+
+                DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, DELETED);
+
+                // Create the Entity from the ContentValue
+                Entity entity = new Entity(cv);
+
+                // Set cursor to next row
+                cursor.moveToNext();
+
+                // Return the created Entity
+                return entity;
+            }
+        }
+     }
+
+    /**
      * Contains a list of available calendars.
      */
-    public static class Calendars implements BaseColumns, CalendarsColumns
+    public static class Calendars implements BaseColumns, SyncColumns, AccountColumns,
+            CalendarsColumns
     {
         private static final String WHERE_DELETE_FOR_ACCOUNT = Calendars._SYNC_ACCOUNT + "=?"
                 + " AND " + Calendars._SYNC_ACCOUNT_TYPE + "=?";
@@ -258,6 +381,24 @@
         public static final String URL = "url";
 
         /**
+         * The URL for the calendar itself
+         * <P>Type: TEXT (URL)</P>
+         */
+        public static final String SELF_URL = "selfUrl";
+
+        /**
+         * The URL for the calendar to be edited
+         * <P>Type: TEXT (URL)</P>
+         */
+        public static final String EDIT_URL = "editUrl";
+
+        /**
+         * The URL for the calendar events
+         * <P>Type: TEXT (URL)</P>
+         */
+        public static final String EVENTS_URL = "eventsUrl";
+
+        /**
          * The name of the calendar
          * <P>Type: TEXT</P>
          */
@@ -276,12 +417,6 @@
         public static final String LOCATION = "location";
 
         /**
-         * Should the calendar be hidden in the calendar selection panel?
-         * <P>Type: INTEGER (boolean)</P>
-         */
-        public static final String HIDDEN = "hidden";
-
-        /**
          * The owner account for this calendar, based on the calendar feed.
          * This will be different from the _SYNC_ACCOUNT for delegated calendars.
          * <P>Type: String</P>
@@ -296,6 +431,9 @@
         public static final String ORGANIZER_CAN_RESPOND = "organizerCanRespond";
     }
 
+    /**
+     * Columns from the Attendees table that other tables join into themselves.
+     */
     public interface AttendeesColumns {
 
         /**
@@ -361,8 +499,7 @@
     /**
      * Columns from the Events table that other tables join into themselves.
      */
-    public interface EventsColumns
-    {
+    public interface EventsColumns {
         /**
          * The calendar the event belongs to
          * <P>Type: INTEGER (foreign key to the Calendars table)</P>
@@ -438,6 +575,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 +599,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>
          */
@@ -598,7 +753,8 @@
     /**
      * Contains one entry per calendar event. Recurring events show up as a single entry.
      */
-    public static final class EventsEntity implements BaseColumns, EventsColumns, CalendarsColumns {
+    public static final class EventsEntity implements BaseColumns, SyncColumns, AccountColumns,
+            EventsColumns {
         /**
          * The content:// style URL for this table
          */
@@ -703,8 +859,8 @@
                 DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, _SYNC_DATA);
                 DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, cv, _SYNC_DIRTY);
                 DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, _SYNC_VERSION);
-                DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, DELETED);
-                DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, Calendars.URL);
+                DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, EventsColumns.DELETED);
+                DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, Calendars.SYNC1);
 
                 Entity entity = new Entity(cv);
                 Cursor subCursor;
@@ -795,7 +951,8 @@
     /**
      * Contains one entry per calendar event. Recurring events show up as a single entry.
      */
-    public static final class Events implements BaseColumns, EventsColumns, CalendarsColumns {
+    public static final class Events implements BaseColumns, SyncColumns, AccountColumns,
+            EventsColumns {
 
         private static final String[] FETCH_ENTRY_COLUMNS =
                 new String[] { Events._SYNC_ACCOUNT, Events._SYNC_ID };
@@ -860,6 +1017,15 @@
         }
 
         public static final Cursor query(ContentResolver cr, String[] projection,
+                                         long begin, long end, String searchQuery) {
+            Uri.Builder builder = CONTENT_SEARCH_URI.buildUpon();
+            ContentUris.appendId(builder, begin);
+            ContentUris.appendId(builder, end);
+            return cr.query(builder.build(), projection, WHERE_CALENDARS_SELECTED,
+                         new String[] { searchQuery }, DEFAULT_SORT_ORDER);
+        }
+
+        public static final Cursor query(ContentResolver cr, String[] projection,
                                          long begin, long end, String where, String orderBy) {
             Uri.Builder builder = CONTENT_URI.buildUpon();
             ContentUris.appendId(builder, begin);
@@ -873,6 +1039,21 @@
                          null, orderBy == null ? DEFAULT_SORT_ORDER : orderBy);
         }
 
+        public static final Cursor query(ContentResolver cr, String[] projection, long begin,
+                long end, String searchQuery, String where, String orderBy) {
+            Uri.Builder builder = CONTENT_SEARCH_URI.buildUpon();
+            ContentUris.appendId(builder, begin);
+            ContentUris.appendId(builder, end);
+            builder = builder.appendPath(searchQuery);
+            if (TextUtils.isEmpty(where)) {
+                where = WHERE_CALENDARS_SELECTED;
+            } else {
+                where = "(" + where + ") AND " + WHERE_CALENDARS_SELECTED;
+            }
+            return cr.query(builder.build(), projection, where, null,
+                    orderBy == null ? DEFAULT_SORT_ORDER : orderBy);
+        }
+
         /**
          * The content:// style URL for this table
          */
@@ -880,6 +1061,10 @@
                 "/instances/when");
         public static final Uri CONTENT_BY_DAY_URI =
             Uri.parse("content://" + AUTHORITY + "/instances/whenbyday");
+        public static final Uri CONTENT_SEARCH_URI = Uri.parse("content://" + AUTHORITY +
+                "/instances/search");
+        public static final Uri CONTENT_SEARCH_BY_DAY_URI =
+            Uri.parse("content://" + AUTHORITY + "/instances/searchbyday");
 
         /**
          * The default sort order for this table.
@@ -896,7 +1081,6 @@
          * required for correctness, it just adds a nice touch.
          */
         public static final String SORT_CALENDAR_VIEW = "begin ASC, end DESC, title ASC";
-
         /**
          * The beginning time of the instance, in UTC milliseconds
          * <P>Type: INTEGER (long; millis since epoch)</P>
@@ -981,7 +1165,7 @@
         public static final String MAX_EVENTDAYS = "maxEventDays";
     }
 
-    public static final class CalendarMetaData implements CalendarMetaDataColumns {
+    public static final class CalendarMetaData implements CalendarMetaDataColumns, BaseColumns {
     }
 
     public interface EventDaysColumns {
@@ -1379,4 +1563,43 @@
         public static final Uri CONTENT_URI =
                 Uri.withAppendedPath(Calendar.CONTENT_URI, CONTENT_DIRECTORY);
     }
+
+    /**
+     * Columns from the EventsRawTimes table
+     */
+    public interface EventsRawTimesColumns {
+        /**
+         * The corresponding event id
+         * <P>Type: INTEGER (long)</P>
+         */
+        public static final String EVENT_ID = "event_id";
+
+        /**
+         * The RFC2445 compliant time the event starts
+         * <P>Type: TEXT</P>
+         */
+        public static final String DTSTART_2445 = "dtstart2445";
+
+        /**
+         * The RFC2445 compliant time the event ends
+         * <P>Type: TEXT</P>
+         */
+        public static final String DTEND_2445 = "dtend2445";
+
+        /**
+         * The RFC2445 compliant original instance time of the recurring event for which this
+         * event is an exception.
+         * <P>Type: TEXT</P>
+         */
+        public static final String ORIGINAL_INSTANCE_TIME_2445 = "originalInstanceTime2445";
+
+        /**
+         * The RFC2445 compliant last date this event repeats on, or NULL if it never ends
+         * <P>Type: TEXT</P>
+         */
+        public static final String LAST_DATE_2445 = "lastDate2445";
+    }
+
+    public static final class EventsRawTimes implements BaseColumns, EventsRawTimesColumns {
+    }
 }
diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java
index f0ee838..48d5345 100644
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -30,7 +30,6 @@
 import android.content.res.Resources;
 import android.database.Cursor;
 import android.database.DatabaseUtils;
-import android.database.sqlite.SQLiteException;
 import android.graphics.Rect;
 import android.net.Uri;
 import android.os.RemoteException;
@@ -130,6 +129,17 @@
     public static final String REQUESTING_PACKAGE_PARAM_KEY = "requesting_package";
 
     /**
+     * Query parameter that should be used by the client to access a specific
+     * {@link Directory}. The parameter value should be the _ID of the corresponding
+     * directory, e.g.
+     * {@code content://com.android.contacts/data/emails/filter/acme?directory=3}
+     *
+     * @hide
+     */
+    public static final String DIRECTORY_PARAM_KEY = "directory";
+
+
+    /**
      * @hide
      */
     public static final class Preferences {
@@ -181,6 +191,336 @@
     }
 
     /**
+     * A Directory represents a contacts corpus, e.g. Local contacts,
+     * Google Apps Global Address List or Corporate Global Address List.
+     * <p>
+     * A Directory is implemented as a content provider with its unique authority and
+     * the same API as the main Contacts Provider.  However, there is no expectation that
+     * every directory provider will implement this Contract in its entirety.  If a
+     * directory provider does not have an implementation for a specific request, it
+     * should throw an UnsupportedOperationException.
+     * </p>
+     * <p>
+     * The most important use case for Directories is search.  A Directory provider is
+     * expected to support at least {@link Contacts#CONTENT_FILTER_URI
+     * Contacts#CONTENT_FILTER_URI}.  If a Directory provider wants to participate
+     * in email and phone lookup functionalities, it should also implement
+     * {@link CommonDataKinds.Email#CONTENT_FILTER_URI CommonDataKinds.Email.CONTENT_FILTER_URI}
+     * and
+     * {@link CommonDataKinds.Phone#CONTENT_FILTER_URI CommonDataKinds.Phone.CONTENT_FILTER_URI}.
+     * </p>
+     * <p>
+     * A directory provider should return NULL for every projection field it does not
+     * recognize, rather than throwing an exception.  This way it will not be broken
+     * if ContactsContract is extended with new fields in the future.
+     * </p>
+     * <p>
+     * The client interacts with a directory via Contacts Provider by supplying an
+     * optional {@code directory=} query parameter.
+     * <p>
+     * <p>
+     * When the Contacts Provider receives the request, it transforms the URI and forwards
+     * the request to the corresponding directory content provider.
+     * The URI is transformed in the following fashion:
+     * <ul>
+     * <li>The URI authority is replaced with the corresponding {@link #DIRECTORY_AUTHORITY}.</li>
+     * <li>The {@code accountName=} and {@code accountType=} parameters are added or
+     * replaced using the corresponding {@link #ACCOUNT_TYPE} and {@link #ACCOUNT_NAME} values.</li>
+     * <li>If the URI is missing a {@link ContactsContract#REQUESTING_PACKAGE_PARAM_KEY}
+     * parameter, this parameter is added.</li>
+     * </ul>
+     * </p>
+     * <p>
+     * Clients should send directory requests to Contacts Provider and let it
+     * forward them to the respective providers rather than constructing
+     * directory provider URIs by themselves. This level of indirection allows
+     * Contacts Provider to implement additional system-level features and
+     * optimizations. Access to Contacts Provider is protected by the
+     * READ_CONTACTS permission, but access to the directory provider is not.
+     * Therefore directory providers must reject requests coming from clients
+     * other than the Contacts Provider itself. An easy way to prevent such
+     * unauthorized access is to check the name of the calling package:
+     * <pre>
+     * private boolean isCallerAllowed() {
+     *   PackageManager pm = getContext().getPackageManager();
+     *   for (String packageName: pm.getPackagesForUid(Binder.getCallingUid())) {
+     *     if (packageName.equals("com.android.providers.contacts")) {
+     *       return true;
+     *     }
+     *   }
+     *   return false;
+     * }
+     * </pre>
+     * </p>
+     * <p>
+     * The Directory table is read-only and is maintained by the Contacts Provider
+     * automatically.
+     * </p>
+     * <p>It always has at least these two rows:
+     * <ul>
+     * <li>
+     * The local directory. It has {@link Directory#_ID Directory._ID} =
+     * {@link Directory#DEFAULT Directory.DEFAULT}. This directory can be used to access locally
+     * stored contacts. The same can be achieved by omitting the {@code directory=}
+     * parameter altogether.
+     * </li>
+     * <li>
+     * The local invisible contacts. The corresponding directory ID is
+     * {@link Directory#LOCAL_INVISIBLE Directory.LOCAL_INVISIBLE}.
+     * </li>
+     * </ul>
+     * </p>
+     * <p>Custom Directories are discovered by the Contacts Provider following this procedure:
+     * <ul>
+     * <li>It finds all installed content providers with meta data identifying them
+     * as directory providers in AndroidManifest.xml:
+     * <code>
+     * &lt;meta-data android:name="android.content.ContactDirectory"
+     *               android:value="true" /&gt;
+     * </code>
+     * <p>
+     * This tag should be placed inside the corresponding content provider declaration.
+     * </p>
+     * </li>
+     * <li>
+     * Then Contacts Provider sends a {@link Directory#CONTENT_URI Directory.CONTENT_URI}
+     * query to each of the directory authorities.  A directory provider must implement
+     * this query and return a list of directories.  Each directory returned by
+     * the provider must have a unique combination for the {@link #ACCOUNT_NAME} and
+     * {@link #ACCOUNT_TYPE} columns (nulls are allowed).  Since directory IDs are assigned
+     * automatically, the _ID field will not be part of the query projection.
+     * </li>
+     * <li>Contacts Provider compiles directory lists received from all directory
+     * providers into one, assigns each individual directory a globally unique ID and
+     * stores all directory records in the Directory table.
+     * </li>
+     * </ul>
+     * </p>
+     * <p>Contacts Provider automatically interrogates newly installed or replaced packages.
+     * Thus simply installing a package containing a directory provider is sufficient
+     * to have that provider registered.  A package supplying a directory provider does
+     * not have to contain launchable activities.
+     * </p>
+     * <p>
+     * Every row in the Directory table is automatically associated with the corresponding package
+     * (apk).  If the package is later uninstalled, all corresponding directory rows
+     * are automatically removed from the Contacts Provider.
+     * </p>
+     * <p>
+     * When the list of directories handled by a directory provider changes
+     * (for instance when the user adds a new Directory account), the directory provider
+     * should call {@link #notifyDirectoryChange} to notify the Contacts Provider of the change.
+     * In response, the Contacts Provider will requery the directory provider to obtain the
+     * new list of directories.
+     * </p>
+     * <p>
+     * A directory row can be optionally associated with an existing account
+     * (see {@link android.accounts.AccountManager}). If the account is later removed,
+     * the corresponding directory rows are
+     * automatically removed from the Contacts Provider.
+     * </p>
+     *
+     * @hide
+     */
+    public static final class Directory implements BaseColumns {
+
+        /**
+         * Not instantiable.
+         */
+        private Directory() {
+        }
+
+        /**
+         * The content:// style URI for this table.  Requests to this URI can be
+         * performed on the UI thread because they are always unblocking.
+         *
+         * @hide
+         */
+        public static final Uri CONTENT_URI =
+                Uri.withAppendedPath(AUTHORITY_URI, "directories");
+
+        /**
+         * The MIME-type of {@link #CONTENT_URI} providing a directory of
+         * contact directories.
+         *
+         * @hide
+         */
+        public static final String CONTENT_TYPE =
+                "vnd.android.cursor.dir/contact_directories";
+
+        /**
+         * The MIME type of a {@link #CONTENT_URI} item.
+         */
+        public static final String CONTENT_ITEM_TYPE =
+                "vnd.android.cursor.item/contact_directory";
+
+        /**
+         * _ID of the default directory, which represents locally stored contacts.
+         *
+         * @hide
+         */
+        public static final long DEFAULT = 0;
+
+        /**
+         * _ID of the directory that represents locally stored invisible contacts.
+         *
+         * @hide
+         */
+        public static final long LOCAL_INVISIBLE = 1;
+
+        /**
+         * The name of the package that owns this directory. Contacts Provider
+         * fill it in with the name of the package containing the directory provider.
+         * If the package is later uninstalled, the directories it owns are
+         * automatically removed from this table.
+         *
+         * <p>TYPE: TEXT</p>
+         *
+         * @hide
+         */
+        public static final String PACKAGE_NAME = "packageName";
+
+        /**
+         * The type of directory captured as a resource ID in the context of the
+         * package {@link #PACKAGE_NAME}, e.g. "Corporate Directory"
+         *
+         * <p>TYPE: INTEGER</p>
+         *
+         * @hide
+         */
+        public static final String TYPE_RESOURCE_ID = "typeResourceId";
+
+        /**
+         * An optional name that can be used in the UI to represent this directory,
+         * e.g. "Acme Corp"
+         * <p>TYPE: text</p>
+         *
+         * @hide
+         */
+        public static final String DISPLAY_NAME = "displayName";
+
+        /**
+         * <p>
+         * The authority of the Directory Provider. Contacts Provider will
+         * use this authority to forward requests to the directory provider.
+         * A directory provider can leave this column empty - Contacts Provider will fill it in.
+         * </p>
+         * <p>
+         * Clients of this API should not send requests directly to this authority.
+         * All directory requests must be routed through Contacts Provider.
+         * </p>
+         *
+         * <p>TYPE: text</p>
+         *
+         * @hide
+         */
+        public static final String DIRECTORY_AUTHORITY = "authority";
+
+        /**
+         * The account type which this directory is associated.
+         *
+         * <p>TYPE: text</p>
+         *
+         * @hide
+         */
+        public static final String ACCOUNT_TYPE = "accountType";
+
+        /**
+         * The account with which this directory is associated. If the account is later
+         * removed, the directories it owns are automatically removed from this table.
+         *
+         * <p>TYPE: text</p>
+         *
+         * @hide
+         */
+        public static final String ACCOUNT_NAME = "accountName";
+
+        /**
+         * One of {@link #EXPORT_SUPPORT_NONE}, {@link #EXPORT_SUPPORT_ANY_ACCOUNT},
+         * {@link #EXPORT_SUPPORT_SAME_ACCOUNT_ONLY}. This is the expectation the
+         * directory has for data exported from it.  Clients must obey this setting.
+         *
+         * @hide
+         */
+        public static final String EXPORT_SUPPORT = "exportSupport";
+
+        /**
+         * An {@link #EXPORT_SUPPORT} setting that indicates that the directory
+         * does not allow any data to be copied out of it.
+         *
+         * @hide
+         */
+        public static final int EXPORT_SUPPORT_NONE = 0;
+
+        /**
+         * An {@link #EXPORT_SUPPORT} setting that indicates that the directory
+         * allow its data copied only to the account specified by
+         * {@link #ACCOUNT_TYPE}/{@link #ACCOUNT_NAME}.
+         *
+         * @hide
+         */
+        public static final int EXPORT_SUPPORT_SAME_ACCOUNT_ONLY = 1;
+
+        /**
+         * An {@link #EXPORT_SUPPORT} setting that indicates that the directory
+         * allow its data copied to any contacts account.
+         *
+         * @hide
+         */
+        public static final int EXPORT_SUPPORT_ANY_ACCOUNT = 2;
+
+        /**
+         * One of {@link #SHORTCUT_SUPPORT_NONE}, {@link #SHORTCUT_SUPPORT_DATA_ITEMS_ONLY},
+         * {@link #SHORTCUT_SUPPORT_FULL}, This is the expectation the directory
+         * has for shortcuts created for its elements. Clients must obey this setting.
+         *
+         * @hide
+         */
+        public static final String SHORTCUT_SUPPORT = "shortcutSupport";
+
+        /**
+         * An {@link #SHORTCUT_SUPPORT} setting that indicates that the directory
+         * does not allow any shortcuts created for its contacts.
+         *
+         * @hide
+         */
+        public static final int SHORTCUT_SUPPORT_NONE = 0;
+
+        /**
+         * An {@link #SHORTCUT_SUPPORT} setting that indicates that the directory
+         * allow creation of shortcuts for data items like email, phone or postal address,
+         * but not the entire contact.
+         *
+         * @hide
+         */
+        public static final int SHORTCUT_SUPPORT_DATA_ITEMS_ONLY = 1;
+
+        /**
+         * An {@link #SHORTCUT_SUPPORT} setting that indicates that the directory
+         * allow creation of shortcuts for contact as well as their constituent elements.
+         *
+         * @hide
+         */
+        public static final int SHORTCUT_SUPPORT_FULL = 2;
+
+        /**
+         * Notifies the system of a change in the list of directories handled by
+         * a particular directory provider. The Contacts provider will turn around
+         * and send a query to the directory provider for the full list of directories,
+         * which will replace the previous list.
+         *
+         * @hide
+         */
+        public static void notifyDirectoryChange(ContentResolver resolver) {
+            // This is done to trigger a query by Contacts Provider back to the directory provider.
+            // No data needs to be sent back, because the provider can infer the calling
+            // package from binder.
+            ContentValues contentValues = new ContentValues();
+            resolver.update(Directory.CONTENT_URI, contentValues, null, null);
+        }
+    }
+
+    /**
      * @hide should be removed when users are updated to refer to SyncState
      * @deprecated use SyncState instead
      */
@@ -1056,6 +1396,59 @@
 
         /**
          * <p>
+         * A sub-directory of a contact that contains all of its
+         * {@link ContactsContract.RawContacts} as well as
+         * {@link ContactsContract.Data} rows. To access this directory append
+         * {@link #CONTENT_DIRECTORY} to the contact URI.
+         * </p>
+         * <p>
+         * Entity has three ID fields: {@link #CONTACT_ID} for the contact,
+         * {@link #RAW_CONTACT_ID} for the raw contact and {@link #DATA_ID} for
+         * the data rows. Entity always contains at least one row per
+         * constituent raw contact, even if there are no actual data rows. In
+         * this case the {@link #DATA_ID} field will be null.
+         * </p>
+         * <p>
+         * Entity reads all data for the entire contact in one transaction, to
+         * guarantee consistency.  There is significant data duplication
+         * in the Entity (each row repeats all Contact columns and all RawContact
+         * columns), so the benefits of transactional consistency should be weighed
+         * against the cost of transferring large amounts of denormalized data
+         * from the Provider.
+         * </p>
+         *
+         * @hide
+         */
+        public static final class Entity implements BaseColumns, ContactsColumns,
+                ContactNameColumns, RawContactsColumns, BaseSyncColumns, SyncColumns, DataColumns,
+                StatusColumns, ContactOptionsColumns, ContactStatusColumns {
+            /**
+             * no public constructor since this is a utility class
+             */
+            private Entity() {
+            }
+
+            /**
+             * The directory twig for this sub-table
+             */
+            public static final String CONTENT_DIRECTORY = "entities";
+
+            /**
+             * The ID of the raw contact row.
+             * <P>Type: INTEGER</P>
+             */
+            public static final String RAW_CONTACT_ID = "raw_contact_id";
+
+            /**
+             * The ID of the data row. The value will be null if this raw contact has no
+             * data rows.
+             * <P>Type: INTEGER</P>
+             */
+            public static final String DATA_ID = "data_id";
+        }
+
+        /**
+         * <p>
          * A <i>read-only</i> sub-directory of a single contact aggregate that
          * contains all aggregation suggestions (other contacts). The
          * aggregation suggestions are computed based on approximate data
@@ -1244,6 +1637,14 @@
          * @hide
          */
         public static final String NAME_VERIFIED = "name_verified";
+
+        /**
+         * The "read-only" flag: "0" by default, "1" if the row cannot be modified or
+         * deleted except by a sync adapter.  See {@link ContactsContract#CALLER_IS_SYNCADAPTER}.
+         * <P>Type: INTEGER</P>
+         * @hide
+         */
+        public static final String RAW_CONTACT_IS_READ_ONLY = "raw_contact_is_read_only";
     }
 
     /**
@@ -1751,7 +2152,7 @@
             public static final String CONTENT_DIRECTORY = "entity";
 
             /**
-             * The ID of the data column. The value will be null if this raw contact has no
+             * The ID of the data row. The value will be null if this raw contact has no
              * data rows.
              * <P>Type: INTEGER</P>
              */
@@ -1839,28 +2240,21 @@
                             Data.DATA_VERSION);
                     for (String key : DATA_KEYS) {
                         final int columnIndex = cursor.getColumnIndexOrThrow(key);
-                        if (cursor.isNull(columnIndex)) {
-                            // don't put anything
-                        } else {
-                            try {
+                        switch (cursor.getType(columnIndex)) {
+                            case Cursor.FIELD_TYPE_NULL:
+                                // don't put anything
+                                break;
+                            case Cursor.FIELD_TYPE_INTEGER:
+                            case Cursor.FIELD_TYPE_FLOAT:
+                            case Cursor.FIELD_TYPE_STRING:
                                 cv.put(key, cursor.getString(columnIndex));
-                            } catch (SQLiteException e) {
+                                break;
+                            case Cursor.FIELD_TYPE_BLOB:
                                 cv.put(key, cursor.getBlob(columnIndex));
-                            }
+                                break;
+                            default:
+                                throw new IllegalStateException("Invalid or unhandled data type");
                         }
-                        // TODO: go back to this version of the code when bug
-                        // http://b/issue?id=2306370 is fixed.
-//                        if (cursor.isNull(columnIndex)) {
-//                            // don't put anything
-//                        } else if (cursor.isLong(columnIndex)) {
-//                            values.put(key, cursor.getLong(columnIndex));
-//                        } else if (cursor.isFloat(columnIndex)) {
-//                            values.put(key, cursor.getFloat(columnIndex));
-//                        } else if (cursor.isString(columnIndex)) {
-//                            values.put(key, cursor.getString(columnIndex));
-//                        } else if (cursor.isBlob(columnIndex)) {
-//                            values.put(key, cursor.getBlob(columnIndex));
-//                        }
                     }
                     contact.addSubValue(ContactsContract.Data.CONTENT_URI, cv);
                 } while (cursor.moveToNext());
@@ -2023,6 +2417,14 @@
         public static final String IS_SUPER_PRIMARY = "is_super_primary";
 
         /**
+         * The "read-only" flag: "0" by default, "1" if the row cannot be modified or
+         * deleted except by a sync adapter.  See {@link ContactsContract#CALLER_IS_SYNCADAPTER}.
+         * <P>Type: INTEGER</P>
+         * @hide
+         */
+        public static final String IS_READ_ONLY = "is_read_only";
+
+        /**
          * The version of this data record. This is a read-only value. The data column is
          * guaranteed to not change without the version going up. This value is monotonically
          * increasing.
@@ -4951,6 +5353,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";
     }
 
     /**
@@ -5091,6 +5510,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);
             }
@@ -5607,6 +6028,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.
@@ -5627,6 +6070,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/provider/MediaStore.java b/core/java/android/provider/MediaStore.java
index 40ed980..227d94d 100644
--- a/core/java/android/provider/MediaStore.java
+++ b/core/java/android/provider/MediaStore.java
@@ -237,9 +237,82 @@
          * <P>Type: TEXT</P>
          */
         public static final String MIME_TYPE = "mime_type";
+
+        /**
+         * The row ID in the MTP object table corresponding to this media file.
+         * <P>Type: INTEGER</P>
+         * @hide
+         */
+        public static final String MTP_OBJECT_ID = "object_id";
+
+        /**
+         * The MTP object handle of a newly transfered file.
+         * Used to pass the new file's object handle through the media scanner
+         * from MTP to the media provider
+         * For internal use only by MTP, media scanner and media provider.
+         * <P>Type: INTEGER</P>
+         * @hide
+         */
+        public static final String MEDIA_SCANNER_NEW_OBJECT_ID = "media_scanner_new_object_id";
      }
 
     /**
+     * Media provider interface used by MTP implementation.
+     * @hide
+     */
+    public static final class MtpObjects {
+
+        public static Uri getContentUri(String volumeName) {
+            return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName +
+                    "/object");
+        }
+
+        public static final Uri getContentUri(String volumeName,
+                long objectId) {
+            return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName
+                    + "/object/" + objectId);
+        }
+
+        // used for MTP GetObjectReferences and SetObjectReferences
+        public static final Uri getReferencesUri(String volumeName,
+                long objectId) {
+            return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName
+                    + "/object/" + objectId + "/references");
+        }
+
+        /**
+         * Fields for master table for all media files.
+         * Table also contains MediaColumns._ID, DATA, SIZE and DATE_MODIFIED.
+         */
+        public interface ObjectColumns extends MediaColumns {
+            /**
+             * The MTP format code of the file
+             * <P>Type: INTEGER</P>
+             */
+            public static final String FORMAT = "format";
+
+            /**
+             * The index of the parent directory of the file
+             * <P>Type: INTEGER</P>
+             */
+            public static final String PARENT = "parent";
+
+            /**
+             * Identifier for the media table containing the object.
+             * Used internally by MediaProvider
+             * <P>Type: INTEGER</P>
+             */
+            public static final String MEDIA_TABLE = "media_table";
+
+            /**
+             * The ID of the object in its media table.
+             * <P>Type: INTEGER</P>
+             */
+            public static final String MEDIA_ID = "media_id";
+        }
+    }
+
+    /**
      * This class is used internally by Images.Thumbnails and Video.Thumbnails, it's not intended
      * to be accessed elsewhere.
      */
@@ -317,22 +390,23 @@
             // Log.v(TAG, "getThumbnail: origId="+origId+", kind="+kind+", isVideo="+isVideo);
             // If the magic is non-zero, we simply return thumbnail if it does exist.
             // querying MediaProvider and simply return thumbnail.
-            MiniThumbFile thumbFile = MiniThumbFile.instance(baseUri);
-            long magic = thumbFile.getMagic(origId);
-            if (magic != 0) {
-                if (kind == MICRO_KIND) {
-                    byte[] data = new byte[MiniThumbFile.BYTES_PER_MINTHUMB];
-                    if (thumbFile.getMiniThumbFromFile(origId, data) != null) {
-                        bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
-                        if (bitmap == null) {
-                            Log.w(TAG, "couldn't decode byte array.");
+            MiniThumbFile thumbFile = new MiniThumbFile(isVideo ? Video.Media.EXTERNAL_CONTENT_URI
+                    : Images.Media.EXTERNAL_CONTENT_URI);
+            Cursor c = null;
+            try {
+                long magic = thumbFile.getMagic(origId);
+                if (magic != 0) {
+                    if (kind == MICRO_KIND) {
+                        byte[] data = new byte[MiniThumbFile.BYTES_PER_MINTHUMB];
+                        if (thumbFile.getMiniThumbFromFile(origId, data) != null) {
+                            bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
+                            if (bitmap == null) {
+                                Log.w(TAG, "couldn't decode byte array.");
+                            }
                         }
-                    }
-                    return bitmap;
-                } else if (kind == MINI_KIND) {
-                    String column = isVideo ? "video_id=" : "image_id=";
-                    Cursor c = null;
-                    try {
+                        return bitmap;
+                    } else if (kind == MINI_KIND) {
+                        String column = isVideo ? "video_id=" : "image_id=";
                         c = cr.query(baseUri, PROJECTION, column + origId, null, null);
                         if (c != null && c.moveToFirst()) {
                             bitmap = getMiniThumbFromFile(c, baseUri, cr, options);
@@ -340,17 +414,13 @@
                                 return bitmap;
                             }
                         }
-                    } finally {
-                        if (c != null) c.close();
                     }
                 }
-            }
 
-            Cursor c = null;
-            try {
                 Uri blockingUri = baseUri.buildUpon().appendQueryParameter("blocking", "1")
                         .appendQueryParameter("orig_id", String.valueOf(origId))
                         .appendQueryParameter("group_id", String.valueOf(groupId)).build();
+                if (c != null) c.close();
                 c = cr.query(blockingUri, PROJECTION, null, null, null);
                 // This happens when original image/video doesn't exist.
                 if (c == null) return null;
@@ -397,6 +467,9 @@
                 Log.w(TAG, ex);
             } finally {
                 if (c != null) c.close();
+                // To avoid file descriptor leak in application process.
+                thumbFile.deactivate();
+                thumbFile = null;
             }
             return bitmap;
         }
diff --git a/core/java/android/provider/Mtp.java b/core/java/android/provider/Mtp.java
new file mode 100644
index 0000000..15f8666
--- /dev/null
+++ b/core/java/android/provider/Mtp.java
@@ -0,0 +1,335 @@
+/*
+ * 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.provider;
+
+import android.content.ContentUris;
+import android.net.Uri;
+import android.util.Log;
+
+
+/**
+ * The MTP provider supports accessing content on MTP and PTP devices.
+ * @hide
+ */
+public final class Mtp
+{
+    private final static String TAG = "Mtp";
+
+    public static final String AUTHORITY = "mtp";
+
+    private static final String CONTENT_AUTHORITY_SLASH = "content://" + AUTHORITY + "/";
+    private static final String CONTENT_AUTHORITY_DEVICE_SLASH = "content://" + AUTHORITY + "/device/";
+
+    /**
+     * Contains list of all MTP/PTP devices
+     */
+    public static final class Device implements BaseColumns {
+
+        public static final Uri CONTENT_URI = Uri.parse(CONTENT_AUTHORITY_SLASH + "device");
+
+        public static Uri getContentUri(int deviceID) {
+            return Uri.parse(CONTENT_AUTHORITY_DEVICE_SLASH + deviceID);
+        }
+
+        /**
+         * The manufacturer of the device
+         * <P>Type: TEXT</P>
+         */
+        public static final String MANUFACTURER = "manufacturer";
+
+        /**
+         * The model name of the device
+         * <P>Type: TEXT</P>
+         */
+        public static final String MODEL = "model";
+    }
+
+    /**
+     * Contains list of storage units for an MTP/PTP device
+     */
+    public static final class Storage implements BaseColumns {
+
+        public static Uri getContentUri(int deviceID) {
+            return Uri.parse(CONTENT_AUTHORITY_DEVICE_SLASH + deviceID + "/storage");
+        }
+
+        public static Uri getContentUri(int deviceID, int storageID) {
+            return Uri.parse(CONTENT_AUTHORITY_DEVICE_SLASH + deviceID + "/storage/" + storageID);
+        }
+
+        /**
+         * Storage unit identifier
+         * <P>Type: TEXT</P>
+         */
+        public static final String IDENTIFIER = "identifier";
+
+        /**
+         * Storage unit description
+         * <P>Type: TEXT</P>
+         */
+        public static final String DESCRIPTION = "description";
+    }
+
+    /**
+     * Contains list of objects on an MTP/PTP device
+     */
+    public static final class Object implements BaseColumns {
+
+        public static Uri getContentUri(int deviceID, int objectID) {
+            return Uri.parse(CONTENT_AUTHORITY_DEVICE_SLASH + deviceID
+                    + "/object/" + objectID);
+        }
+
+        public static Uri getContentUriForObjectChildren(int deviceID, int objectID) {
+            return Uri.parse(CONTENT_AUTHORITY_DEVICE_SLASH + deviceID
+                    + "/object/" + objectID + "/child");
+        }
+
+        public static Uri getContentUriForStorageChildren(int deviceID, int storageID) {
+            return Uri.parse(CONTENT_AUTHORITY_DEVICE_SLASH + deviceID
+                    + "/storage/" + storageID + "/child");
+        }
+
+        /**
+         * The following columns correspond to the fields in the ObjectInfo dataset
+         * as described in the MTP specification.
+         */
+
+        /**
+         * The ID of the storage unit containing the object.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String STORAGE_ID = "storage_id";
+
+        /**
+         * The object's format.  Can be one of the FORMAT_* symbols below,
+         * or any of the valid MTP object formats as defined in the MTP specification.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String FORMAT = "format";
+
+        /**
+         * The protection status of the object.  See the PROTECTION_STATUS_*symbols below.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String PROTECTION_STATUS = "protection_status";
+
+        /**
+         * The size of the object in bytes.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String SIZE = "size";
+
+        /**
+         * The object's thumbnail format.  Can be one of the FORMAT_* symbols below,
+         * or any of the valid MTP object formats as defined in the MTP specification.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String THUMB_FORMAT = "format";
+
+        /**
+         * The size of the object's thumbnail in bytes.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String THUMB_SIZE = "thumb_size";
+
+        /**
+         * The width of the object's thumbnail in pixels.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String THUMB_WIDTH = "thumb_width";
+
+        /**
+         * The height of the object's thumbnail in pixels.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String THUMB_HEIGHT = "thumb_height";
+
+        /**
+         * The object's thumbnail.
+         * <P>Type: BLOB</P>
+         */
+        public static final String THUMB = "thumb";
+
+        /**
+         * The width of the object in pixels.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String IMAGE_WIDTH = "image_width";
+
+        /**
+         * The height of the object in pixels.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String IMAGE_HEIGHT = "image_height";
+
+        /**
+         * The depth of the object in bits per pixel.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String IMAGE_DEPTH = "image_depth";
+
+        /**
+         * The ID of the object's parent, or zero if the object
+         * is in the root of its storage unit.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String PARENT = "parent";
+
+        /**
+         * The association type for a container object.
+         * For folders this is typically {@link #ASSOCIATION_TYPE_GENERIC_FOLDER}
+         * <P>Type: INTEGER</P>
+         */
+        public static final String ASSOCIATION_TYPE = "association_type";
+
+        /**
+         * Contains additional information about container objects.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String ASSOCIATION_DESC = "association_desc";
+
+        /**
+         * The sequence number of the object, typically used for an association
+         * containing images taken in sequence.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String SEQUENCE_NUMBER = "sequence_number";
+
+        /**
+         * The name of the object.
+         * <P>Type: TEXT</P>
+         */
+        public static final String NAME = "name";
+
+        /**
+         * The date the object was created, in seconds since January 1, 1970.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String DATE_CREATED = "date_created";
+
+        /**
+         * The date the object was last modified, in seconds since January 1, 1970.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String DATE_MODIFIED = "date_modified";
+
+        /**
+         * A list of keywords associated with an object, separated by spaces.
+         * <P>Type: TEXT</P>
+         */
+        public static final String KEYWORDS = "keywords";
+
+        /**
+         * Contants for {@link #FORMAT} and {@link #THUMB_FORMAT}
+         */
+        public static final int FORMAT_UNDEFINED = 0x3000;
+        public static final int FORMAT_ASSOCIATION = 0x3001;
+        public static final int FORMAT_SCRIPT = 0x3002;
+        public static final int FORMAT_EXECUTABLE = 0x3003;
+        public static final int FORMAT_TEXT = 0x3004;
+        public static final int FORMAT_HTML = 0x3005;
+        public static final int FORMAT_DPOF = 0x3006;
+        public static final int FORMAT_AIFF = 0x3007;
+        public static final int FORMAT_WAV = 0x3008;
+        public static final int FORMAT_MP3 = 0x3009;
+        public static final int FORMAT_AVI = 0x300A;
+        public static final int FORMAT_MPEG = 0x300B;
+        public static final int FORMAT_ASF = 0x300C;
+        public static final int FORMAT_DEFINED = 0x3800;
+        public static final int FORMAT_EXIF_JPEG = 0x3801;
+        public static final int FORMAT_TIFF_EP = 0x3802;
+        public static final int FORMAT_FLASHPIX = 0x3803;
+        public static final int FORMAT_BMP = 0x3804;
+        public static final int FORMAT_CIFF = 0x3805;
+        public static final int FORMAT_GIF = 0x3807;
+        public static final int FORMAT_JFIF = 0x3808;
+        public static final int FORMAT_CD = 0x3809;
+        public static final int FORMAT_PICT = 0x380A;
+        public static final int FORMAT_PNG = 0x380B;
+        public static final int FORMAT_TIFF = 0x380D;
+        public static final int FORMAT_TIFF_IT = 0x380E;
+        public static final int FORMAT_JP2 = 0x380F;
+        public static final int FORMAT_JPX = 0x3810;
+        public static final int FORMAT_UNDEFINED_FIRMWARE = 0xB802;
+        public static final int FORMAT_WINDOWS_IMAGE_FORMAT = 0xB881;
+        public static final int FORMAT_UNDEFINED_AUDIO = 0xB900;
+        public static final int FORMAT_WMA = 0xB901;
+        public static final int FORMAT_OGG = 0xB902;
+        public static final int FORMAT_AAC = 0xB903;
+        public static final int FORMAT_AUDIBLE = 0xB904;
+        public static final int FORMAT_FLAC = 0xB906;
+        public static final int FORMAT_UNDEFINED_VIDEO = 0xB980;
+        public static final int FORMAT_WMV = 0xB981;
+        public static final int FORMAT_MP4_CONTAINER = 0xB982;
+        public static final int FORMAT_MP2 = 0xB983;
+        public static final int FORMAT_3GP_CONTAINER = 0xB984;
+        public static final int FORMAT_UNDEFINED_COLLECTION = 0xBA00;
+        public static final int FORMAT_ABSTRACT_MULTIMEDIA_ALBUM = 0xBA01;
+        public static final int FORMAT_ABSTRACT_IMAGE_ALBUM = 0xBA02;
+        public static final int FORMAT_ABSTRACT_AUDIO_ALBUM = 0xBA03;
+        public static final int FORMAT_ABSTRACT_VIDEO_ALBUM = 0xBA04;
+        public static final int FORMAT_ABSTRACT_AV_PLAYLIST = 0xBA05;
+        public static final int FORMAT_ABSTRACT_CONTACT_GROUP = 0xBA06;
+        public static final int FORMAT_ABSTRACT_MESSAGE_FOLDER = 0xBA07;
+        public static final int FORMAT_ABSTRACT_CHAPTERED_PRODUCTION = 0xBA08;
+        public static final int FORMAT_ABSTRACT_AUDIO_PLAYLIST = 0xBA09;
+        public static final int FORMAT_ABSTRACT_VIDEO_PLAYLIST = 0xBA0A;
+        public static final int FORMAT_ABSTRACT_MEDIACAST = 0xBA0B;
+        public static final int FORMAT_WPL_PLAYLIST = 0xBA10;
+        public static final int FORMAT_M3U_PLAYLIST = 0xBA11;
+        public static final int FORMAT_MPL_PLAYLIST = 0xBA12;
+        public static final int FORMAT_ASX_PLAYLIST = 0xBA13;
+        public static final int FORMAT_PLS_PLAYLIST = 0xBA14;
+        public static final int FORMAT_UNDEFINED_DOCUMENT = 0xBA80;
+        public static final int FORMAT_ABSTRACT_DOCUMENT = 0xBA81;
+        public static final int FORMAT_XML_DOCUMENT = 0xBA82;
+        public static final int FORMAT_MS_WORD_DOCUMENT = 0xBA83;
+        public static final int FORMAT_MHT_COMPILED_HTML_DOCUMENT = 0xBA84;
+        public static final int FORMAT_MS_EXCEL_SPREADSHEET = 0xBA85;
+        public static final int FORMAT_MS_POWERPOINT_PRESENTATION = 0xBA86;
+        public static final int FORMAT_UNDEFINED_MESSAGE = 0xBB00;
+        public static final int FORMAT_ABSTRACT_MESSSAGE = 0xBB01;
+        public static final int FORMAT_UNDEFINED_CONTACT = 0xBB80;
+        public static final int FORMAT_ABSTRACT_CONTACT = 0xBB81;
+        public static final int FORMAT_VCARD_2 = 0xBB82;
+
+        /**
+         * Object is not protected. It may be modified and deleted, and its properties
+         * may be modified.
+         */
+        public static final int PROTECTION_STATUS_NONE = 0;
+
+        /**
+         * Object can not be modified or deleted and its properties can not be modified.
+         */
+        public static final int PROTECTION_STATUS_READ_ONLY = 0x8001;
+
+        /**
+         * Object can not be modified or deleted but its properties are modifiable.
+         */
+        public static final int PROTECTION_STATUS_READ_ONLY_DATA = 0x8002;
+
+        /**
+         * Object's contents can not be transfered from the device, but the object
+         * may be moved or deleted and its properties may be modified.
+         */
+        public static final int PROTECTION_STATUS_NON_TRANSFERABLE_DATA = 0x8003;
+
+        public static final int ASSOCIATION_TYPE_GENERIC_FOLDER = 0x0001;
+    }
+}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index ebc85d3..6d3ca276 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -16,14 +16,11 @@
 
 package android.provider;
 
-import com.google.android.collect.Maps;
 
-import org.apache.commons.codec.binary.Base64;
 
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
 import android.content.ComponentName;
-import android.content.ContentQueryMap;
 import android.content.ContentResolver;
 import android.content.ContentValues;
 import android.content.Context;
@@ -38,19 +35,14 @@
 import android.database.SQLException;
 import android.net.Uri;
 import android.os.*;
-import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.util.AndroidException;
 import android.util.Config;
 import android.util.Log;
 
 import java.net.URISyntaxException;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
-import java.util.Map;
 
 
 /**
@@ -1009,7 +1001,7 @@
         public static boolean hasInterestingConfigurationChanges(int changes) {
             return (changes&ActivityInfo.CONFIG_FONT_SCALE) != 0;
         }
-        
+
         public static boolean getShowGTalkServiceStatus(ContentResolver cr) {
             return getInt(cr, SHOW_GTALK_SERVICE_STATUS, 0) != 0;
         }
@@ -1216,7 +1208,7 @@
         public static final String LOCK_PATTERN_VISIBLE = "lock_pattern_visible_pattern";
 
         /**
-         * @deprecated Use 
+         * @deprecated Use
          * {@link android.provider.Settings.Secure#LOCK_PATTERN_TACTILE_FEEDBACK_ENABLED}
          * instead
          */
@@ -2290,6 +2282,14 @@
         }
 
         /**
+         * Get the key that retrieves a bluetooth Input Device's priority.
+         * @hide
+         */
+        public static final String getBluetoothInputDevicePriorityKey(String address) {
+            return ("bluetooth_input_device_priority_" + address.toUpperCase());
+        }
+
+        /**
          * Whether or not data roaming is enabled. (0 = false, 1 = true)
          */
         public static final String DATA_ROAMING = "data_roaming";
@@ -2321,11 +2321,26 @@
         public static final String DISABLED_SYSTEM_INPUT_METHODS = "disabled_system_input_methods";
 
         /**
-         * Host name and port for a user-selected proxy.
+         * Host name and port for global proxy.
          */
         public static final String HTTP_PROXY = "http_proxy";
 
         /**
+         * Exclusion list for global proxy. This string contains a list of comma-separated
+         * domains where the global proxy does not apply. Domains should be listed in a comma-
+         * separated list. Example of acceptable formats: ".domain1.com,my.domain2.com"
+         * @hide
+         */
+        public static final String HTTP_PROXY_EXCLUSION_LIST = "http_proxy_exclusion_list";
+
+        /**
+         * Enables the UI setting to allow the user to specify the global HTTP proxy
+         * and associated exclusion list.
+         * @hide
+         */
+        public static final String SET_GLOBAL_HTTP_PROXY = "set_global_http_proxy";
+
+        /**
          * Whether the package installer should allow installation of apps downloaded from
          * sources other than the Android Market (vending machine).
          *
@@ -2416,6 +2431,14 @@
         public static final String PARENTAL_CONTROL_REDIRECT_URL = "parental_control_redirect_url";
 
         /**
+         * A positive value indicates the frequency of SamplingProfiler
+         * taking snapshots in hertz. Zero value means SamplingProfiler is disabled.
+         *
+         * @hide
+         */
+        public static final String SAMPLING_PROFILER_HZ = "sampling_profiler_hz";
+
+        /**
          * Settings classname to launch when Settings is clicked from All
          * Applications.  Needed because of user testing between the old
          * and new Settings apps.
@@ -3581,20 +3604,8 @@
             // If a shortcut is supplied, and it is already defined for
             // another bookmark, then remove the old definition.
             if (shortcut != 0) {
-                Cursor c = cr.query(CONTENT_URI,
-                        sShortcutProjection, sShortcutSelection,
-                        new String[] { String.valueOf((int) shortcut) }, null);
-                try {
-                    if (c.moveToFirst()) {
-                        while (c.getCount() > 0) {
-                            if (!c.deleteRow()) {
-                                Log.w(TAG, "Could not delete existing shortcut row");
-                            }
-                        }
-                    }
-                } finally {
-                    if (c != null) c.close();
-                }
+                cr.delete(CONTENT_URI, sShortcutSelection,
+                        new String[] { String.valueOf((int) shortcut) });
             }
 
             ContentValues values = new ContentValues();
diff --git a/core/java/android/server/BluetoothEventLoop.java b/core/java/android/server/BluetoothEventLoop.java
index 35a582d..eb9b62b 100644
--- a/core/java/android/server/BluetoothEventLoop.java
+++ b/core/java/android/server/BluetoothEventLoop.java
@@ -20,6 +20,7 @@
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothInputDevice;
 import android.bluetooth.BluetoothUuid;
 import android.content.Context;
 import android.content.Intent;
@@ -427,6 +428,20 @@
         }
     }
 
+    private void onInputDevicePropertyChanged(String path, String[] propValues) {
+        String address = mBluetoothService.getAddressFromObjectPath(path);
+        if (address == null) {
+            Log.e(TAG, "onInputDevicePropertyChanged: Address of the remote device in null");
+            return;
+        }
+        log(" Input Device : Name of Property is:" + propValues[0]);
+        boolean state = false;
+        if (propValues[1].equals("true")) {
+            state = true;
+        }
+        mBluetoothService.handleInputDevicePropertyChange(address, state);
+    }
+
     private String checkPairingRequestAndGetAddress(String objectPath, int nativeData) {
         String address = mBluetoothService.getAddressFromObjectPath(objectPath);
         if (address == null) {
@@ -573,6 +588,8 @@
     }
 
     private boolean onAgentAuthorize(String objectPath, String deviceUuid) {
+        if (!mBluetoothService.isEnabled()) return false;
+
         String address = mBluetoothService.getAddressFromObjectPath(objectPath);
         if (address == null) {
             Log.e(TAG, "Unable to get device address in onAuthAgentAuthorize");
@@ -581,15 +598,15 @@
 
         boolean authorized = false;
         ParcelUuid uuid = ParcelUuid.fromString(deviceUuid);
-        BluetoothA2dp a2dp = new BluetoothA2dp(mContext);
+        BluetoothDevice device = mAdapter.getRemoteDevice(address);
 
         // Bluez sends the UUID of the local service being accessed, _not_ the
         // remote service
-        if (mBluetoothService.isEnabled() &&
-                (BluetoothUuid.isAudioSource(uuid) || BluetoothUuid.isAvrcpTarget(uuid)
-                        || BluetoothUuid.isAdvAudioDist(uuid)) &&
-                        !isOtherSinkInNonDisconnectingState(address)) {
-            BluetoothDevice device = mAdapter.getRemoteDevice(address);
+        if ((BluetoothUuid.isAudioSource(uuid) || BluetoothUuid.isAvrcpTarget(uuid)
+              || BluetoothUuid.isAdvAudioDist(uuid)) &&
+              !isOtherSinkInNonDisconnectingState(address)) {
+            BluetoothA2dp a2dp = new BluetoothA2dp(mContext);
+
             authorized = a2dp.getSinkPriority(device) > BluetoothA2dp.PRIORITY_OFF;
             if (authorized) {
                 Log.i(TAG, "Allowing incoming A2DP / AVRCP connection from " + address);
@@ -597,6 +614,15 @@
             } else {
                 Log.i(TAG, "Rejecting incoming A2DP / AVRCP connection from " + address);
             }
+        } else if (BluetoothUuid.isInputDevice(uuid) && !isOtherInputDeviceConnected(address)) {
+            BluetoothInputDevice inputDevice = new BluetoothInputDevice(mContext);
+            authorized = inputDevice.getInputDevicePriority(device) >
+                         BluetoothInputDevice.PRIORITY_OFF;
+             if (authorized) {
+                 Log.i(TAG, "Allowing incoming HID connection from " + address);
+             } else {
+                 Log.i(TAG, "Rejecting incoming HID connection from " + address);
+             }
         } else {
             Log.i(TAG, "Rejecting incoming " + deviceUuid + " connection from " + address);
         }
@@ -604,7 +630,19 @@
         return authorized;
     }
 
-    boolean isOtherSinkInNonDisconnectingState(String address) {
+    private boolean isOtherInputDeviceConnected(String address) {
+        Set<BluetoothDevice> devices =
+            mBluetoothService.lookupInputDevicesMatchingStates(new int[] {
+                                                BluetoothInputDevice.STATE_CONNECTING,
+                                                BluetoothInputDevice.STATE_CONNECTED});
+
+        for (BluetoothDevice device : devices) {
+            if (!device.getAddress().equals(address)) return true;
+        }
+        return false;
+    }
+
+    private boolean isOtherSinkInNonDisconnectingState(String address) {
         BluetoothA2dp a2dp = new BluetoothA2dp(mContext);
         Set<BluetoothDevice> devices = a2dp.getNonDisconnectedSinks();
         if (devices.size() == 0) return false;
@@ -655,6 +693,26 @@
         }
     }
 
+    private void onInputDeviceConnectionResult(String path, boolean result) {
+        // Success case gets handled by Property Change signal
+        if (!result) {
+            String address = mBluetoothService.getAddressFromObjectPath(path);
+            if (address == null) return;
+
+            boolean connected = false;
+            BluetoothDevice device = mAdapter.getRemoteDevice(address);
+            int state = mBluetoothService.getInputDeviceState(device);
+            if (state == BluetoothInputDevice.STATE_CONNECTING) {
+                connected = false;
+            } else if (state == BluetoothInputDevice.STATE_DISCONNECTING) {
+                connected = true;
+            } else {
+                Log.e(TAG, "Error onInputDeviceConnectionResult. State is:" + state);
+            }
+            mBluetoothService.handleInputDevicePropertyChange(address, connected);
+        }
+    }
+
     private void onRestartRequired() {
         if (mBluetoothService.isEnabled()) {
             Log.e(TAG, "*** A serious error occured (did bluetoothd crash?) - " +
diff --git a/core/java/android/server/BluetoothService.java b/core/java/android/server/BluetoothService.java
index f00389b..acfc0d7 100644
--- a/core/java/android/server/BluetoothService.java
+++ b/core/java/android/server/BluetoothService.java
@@ -24,16 +24,19 @@
 
 package android.server;
 
+import android.bluetooth.BluetoothA2dp;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothHeadset;
 import android.bluetooth.BluetoothDeviceProfileState;
 import android.bluetooth.BluetoothProfileState;
+import android.bluetooth.BluetoothInputDevice;
 import android.bluetooth.BluetoothSocket;
 import android.bluetooth.BluetoothUuid;
 import android.bluetooth.IBluetooth;
 import android.bluetooth.IBluetoothCallback;
+import android.bluetooth.IBluetoothHeadset;
 import android.content.BroadcastReceiver;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -70,8 +73,10 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Iterator;
 import java.util.Map;
+import java.util.Set;
 
 public class BluetoothService extends IBluetooth.Stub {
     private static final String TAG = "BluetoothService";
@@ -127,8 +132,11 @@
     private final HashMap<String, BluetoothDeviceProfileState> mDeviceProfileState;
     private final BluetoothProfileState mA2dpProfileState;
     private final BluetoothProfileState mHfpProfileState;
+    private final BluetoothProfileState mHidProfileState;
 
     private BluetoothA2dpService mA2dpService;
+    private final HashMap<BluetoothDevice, Integer> mInputDevices;
+
     private static String mDockAddress;
     private String mDockPin;
 
@@ -189,15 +197,18 @@
         mDeviceProfileState = new HashMap<String, BluetoothDeviceProfileState>();
         mA2dpProfileState = new BluetoothProfileState(mContext, BluetoothProfileState.A2DP);
         mHfpProfileState = new BluetoothProfileState(mContext, BluetoothProfileState.HFP);
+        mHidProfileState = new BluetoothProfileState(mContext, BluetoothProfileState.HID);
 
         mHfpProfileState.start();
         mA2dpProfileState.start();
+        mHidProfileState.start();
 
         IntentFilter filter = new IntentFilter();
         registerForAirplaneMode(filter);
 
         filter.addAction(Intent.ACTION_DOCK_EVENT);
         mContext.registerReceiver(mReceiver, filter);
+        mInputDevices = new HashMap<BluetoothDevice, Integer>();
     }
 
     public static synchronized String readDockBluetoothAddress() {
@@ -668,6 +679,10 @@
                 removeProfileState(address);
             }
 
+            // HID is handled by BluetoothService, other profiles
+            // will be handled by their respective services.
+            setInitialInputDevicePriority(mAdapter.getRemoteDevice(address), state);
+
             if (DBG) log(address + " bond state " + oldState + " -> " + state + " (" +
                          reason + ")");
             Intent intent = new Intent(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
@@ -1220,6 +1235,167 @@
         return sp.contains(SHARED_PREFERENCE_DOCK_ADDRESS + address);
     }
 
+    public synchronized boolean connectInputDevice(BluetoothDevice device) {
+        mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
+                                                "Need BLUETOOTH_ADMIN permission");
+
+        String objectPath = getObjectPathFromAddress(device.getAddress());
+        if (objectPath == null ||
+            getInputDeviceState(device) != BluetoothInputDevice.STATE_DISCONNECTED ||
+            getInputDevicePriority(device) == BluetoothInputDevice.PRIORITY_OFF) {
+            return false;
+        }
+        BluetoothDeviceProfileState state = mDeviceProfileState.get(device.getAddress());
+        if (state != null) {
+            Message msg = new Message();
+            msg.arg1 = BluetoothDeviceProfileState.CONNECT_HID_OUTGOING;
+            msg.obj = state;
+            mHidProfileState.sendMessage(msg);
+            return true;
+        }
+        return false;
+    }
+
+    public synchronized boolean connectInputDeviceInternal(BluetoothDevice device) {
+        String objectPath = getObjectPathFromAddress(device.getAddress());
+        handleInputDeviceStateChange(device, BluetoothInputDevice.STATE_CONNECTING);
+        if (!connectInputDeviceNative(objectPath)) {
+            handleInputDeviceStateChange(device, BluetoothInputDevice.STATE_DISCONNECTED);
+            return false;
+        }
+        return true;
+    }
+
+    public synchronized boolean disconnectInputDevice(BluetoothDevice device) {
+        mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
+                                                "Need BLUETOOTH_ADMIN permission");
+
+        String objectPath = getObjectPathFromAddress(device.getAddress());
+        if (objectPath == null || getConnectedInputDevices().length == 0) {
+            return false;
+        }
+        BluetoothDeviceProfileState state = mDeviceProfileState.get(device.getAddress());
+        if (state != null) {
+            Message msg = new Message();
+            msg.arg1 = BluetoothDeviceProfileState.DISCONNECT_HID_OUTGOING;
+            msg.obj = state;
+            mHidProfileState.sendMessage(msg);
+            return true;
+        }
+        return false;
+    }
+
+    public synchronized boolean disconnectInputDeviceInternal(BluetoothDevice device) {
+        String objectPath = getObjectPathFromAddress(device.getAddress());
+        handleInputDeviceStateChange(device, BluetoothInputDevice.STATE_DISCONNECTING);
+        if (!disconnectInputDeviceNative(objectPath)) {
+            handleInputDeviceStateChange(device, BluetoothInputDevice.STATE_CONNECTED);
+            return false;
+        }
+        return true;
+    }
+
+    public synchronized int getInputDeviceState(BluetoothDevice device) {
+        mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+
+        if (mInputDevices.get(device) == null) {
+            return BluetoothInputDevice.STATE_DISCONNECTED;
+        }
+        return mInputDevices.get(device);
+    }
+
+    public synchronized BluetoothDevice[] getConnectedInputDevices() {
+        mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        Set<BluetoothDevice> devices = lookupInputDevicesMatchingStates(
+            new int[] {BluetoothInputDevice.STATE_CONNECTED});
+        return devices.toArray(new BluetoothDevice[devices.size()]);
+    }
+
+    public synchronized int getInputDevicePriority(BluetoothDevice device) {
+        mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        return Settings.Secure.getInt(mContext.getContentResolver(),
+                Settings.Secure.getBluetoothInputDevicePriorityKey(device.getAddress()),
+                BluetoothInputDevice.PRIORITY_UNDEFINED);
+    }
+
+    public synchronized boolean setInputDevicePriority(BluetoothDevice device, int priority) {
+        mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
+                                                "Need BLUETOOTH_ADMIN permission");
+        if (!BluetoothAdapter.checkBluetoothAddress(device.getAddress())) {
+            return false;
+        }
+        return Settings.Secure.putInt(mContext.getContentResolver(),
+                Settings.Secure.getBluetoothInputDevicePriorityKey(device.getAddress()),
+                priority);
+    }
+
+    /*package*/synchronized Set<BluetoothDevice> lookupInputDevicesMatchingStates(int[] states) {
+        Set<BluetoothDevice> inputDevices = new HashSet<BluetoothDevice>();
+        if (mInputDevices.isEmpty()) {
+            return inputDevices;
+        }
+        for (BluetoothDevice device: mInputDevices.keySet()) {
+            int inputDeviceState = getInputDeviceState(device);
+            for (int state : states) {
+                if (state == inputDeviceState) {
+                    inputDevices.add(device);
+                    break;
+                }
+            }
+        }
+        return inputDevices;
+    }
+
+    private synchronized void handleInputDeviceStateChange(BluetoothDevice device, int state) {
+        int prevState;
+        if (mInputDevices.get(device) == null) {
+            prevState = BluetoothInputDevice.STATE_DISCONNECTED;
+        } else {
+            prevState = mInputDevices.get(device);
+        }
+        if (prevState == state) return;
+
+        mInputDevices.put(device, state);
+
+        if (getInputDevicePriority(device) >
+              BluetoothInputDevice.PRIORITY_OFF &&
+            state == BluetoothInputDevice.STATE_CONNECTING ||
+            state == BluetoothInputDevice.STATE_CONNECTED) {
+            // We have connected or attempting to connect.
+            // Bump priority
+            setInputDevicePriority(device, BluetoothInputDevice.PRIORITY_AUTO_CONNECT);
+        }
+
+        Intent intent = new Intent(BluetoothInputDevice.ACTION_INPUT_DEVICE_STATE_CHANGED);
+        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
+        intent.putExtra(BluetoothInputDevice.EXTRA_PREVIOUS_INPUT_DEVICE_STATE, prevState);
+        intent.putExtra(BluetoothInputDevice.EXTRA_INPUT_DEVICE_STATE, state);
+        mContext.sendBroadcast(intent, BLUETOOTH_PERM);
+
+        if (DBG) log("InputDevice state : device: " + device + " State:" + prevState + "->" + state);
+
+    }
+
+    /*package*/ void handleInputDevicePropertyChange(String address, boolean connected) {
+        int state = connected ? BluetoothInputDevice.STATE_CONNECTED :
+            BluetoothInputDevice.STATE_DISCONNECTED;
+        BluetoothDevice device = mAdapter.getRemoteDevice(address);
+        handleInputDeviceStateChange(device, state);
+    }
+
+    private void setInitialInputDevicePriority(BluetoothDevice device, int state) {
+        switch (state) {
+            case BluetoothDevice.BOND_BONDED:
+                if (getInputDevicePriority(device) == BluetoothInputDevice.PRIORITY_UNDEFINED) {
+                    setInputDevicePriority(device, BluetoothInputDevice.PRIORITY_ON);
+                }
+                break;
+            case BluetoothDevice.BOND_NONE:
+                setInputDevicePriority(device, BluetoothInputDevice.PRIORITY_UNDEFINED);
+                break;
+        }
+    }
+
     /*package*/ boolean isRemoteDeviceInCache(String address) {
         return (mDeviceProperties.get(address) != null);
     }
@@ -2103,4 +2279,6 @@
             short channel);
     private native boolean removeServiceRecordNative(int handle);
     private native boolean setLinkTimeoutNative(String path, int num_slots);
+    private native boolean connectInputDeviceNative(String path);
+    private native boolean disconnectInputDeviceNative(String path);
 }
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/ClipboardManager.java b/core/java/android/text/ClipboardManager.java
index 52039af..0b239cf 100644
--- a/core/java/android/text/ClipboardManager.java
+++ b/core/java/android/text/ClipboardManager.java
@@ -16,73 +16,26 @@
 
 package android.text;
 
-import android.content.Context;
-import android.os.RemoteException;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.ServiceManager;
-import android.util.Log;
-
 /**
- * Interface to the clipboard service, for placing and retrieving text in
- * the global clipboard.
- *
- * <p>
- * You do not instantiate this class directly; instead, retrieve it through
- * {@link android.content.Context#getSystemService}.
- *
- * @see android.content.Context#getSystemService
+ * @deprecated Old text-only interace to the clipboard.  See
+ * {@link android.content.ClipboardManager} for the modern API.
  */
-public class ClipboardManager {
-    private static IClipboard sService;
-
-    private Context mContext;
-
-    static private IClipboard getService() {
-        if (sService != null) {
-            return sService;
-        }
-        IBinder b = ServiceManager.getService("clipboard");
-        sService = IClipboard.Stub.asInterface(b);
-        return sService;
-    }
-
-    /** {@hide} */
-    public ClipboardManager(Context context, Handler handler) {
-        mContext = context;
-    }
-
+@Deprecated
+public abstract class ClipboardManager {
     /**
      * Returns the text on the clipboard.  It will eventually be possible
      * to store types other than text too, in which case this will return
      * null if the type cannot be coerced to text.
      */
-    public CharSequence getText() {
-        try {
-            return getService().getClipboardText();
-        } catch (RemoteException e) {
-            return null;
-        }
-    }
+    public abstract CharSequence getText();
 
     /**
      * Sets the contents of the clipboard to the specified text.
      */
-    public void setText(CharSequence text) {
-        try {
-            getService().setClipboardText(text);
-        } catch (RemoteException e) {
-        }
-    }
+    public abstract void setText(CharSequence text);
 
     /**
      * Returns true if the clipboard contains text; false otherwise.
      */
-    public boolean hasText() {
-        try {
-            return getService().hasClipboardText();
-        } catch (RemoteException e) {
-            return false;
-        }
-    }
+    public abstract boolean hasText();
 }
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/GraphicsOperations.java b/core/java/android/text/GraphicsOperations.java
index c3bd0ae..d426d124 100644
--- a/core/java/android/text/GraphicsOperations.java
+++ b/core/java/android/text/GraphicsOperations.java
@@ -34,13 +34,33 @@
                          float x, float y, Paint p);
 
     /**
+     * Just like {@link Canvas#drawTextRun}.
+     * {@hide}
+     */
+    void drawTextRun(Canvas c, int start, int end, int contextStart, int contextEnd,
+            float x, float y, int flags, Paint p);
+
+   /**
      * Just like {@link Paint#measureText}.
      */
     float measureText(int start, int end, Paint p);
 
-
     /**
      * Just like {@link Paint#getTextWidths}.
      */
     public int getTextWidths(int start, int end, float[] widths, Paint p);
+
+    /**
+     * Just like {@link Paint#getTextRunAdvances}.
+     * @hide
+     */
+    float getTextRunAdvances(int start, int end, int contextStart, int contextEnd,
+            int flags, float[] advances, int advancesIndex, Paint paint);
+
+    /**
+     * Just like {@link Paint#getTextRunCursor}.
+     * @hide
+     */
+    int getTextRunCursor(int contextStart, int contextEnd, int flags, int offset,
+            int cursorOpt, Paint p);
 }
diff --git a/core/java/android/text/IClipboard.aidl b/core/java/android/text/IClipboard.aidl
deleted file mode 100644
index 4deb5c8..0000000
--- a/core/java/android/text/IClipboard.aidl
+++ /dev/null
@@ -1,42 +0,0 @@
-/**
- * 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.
- */
-
-package android.text;
-
-/**
- * Programming interface to the clipboard, which allows copying and pasting
- * between applications.
- * {@hide}
- */
-interface IClipboard {
-    /**
-     * Returns the text on the clipboard.  It will eventually be possible
-     * to store types other than text too, in which case this will return
-     * null if the type cannot be coerced to text.
-     */
-    CharSequence getClipboardText();
-
-    /**
-     * Sets the contents of the clipboard to the specified text.
-     */
-    void setClipboardText(CharSequence text);
-
-    /**
-     * Returns true if the clipboard contains text; false otherwise.
-     */
-    boolean hasClipboardText();
-}
-
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index 38ac9b7..f533944 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -16,29 +16,33 @@
 
 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.text.style.LeadingMarginSpan.LeadingMarginSpan2;
 import android.view.KeyEvent;
 
+import java.util.Arrays;
+
 /**
- * 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 {
-    private static final boolean DEBUG = false;
     private static final ParagraphStyle[] NO_PARA_SPANS =
         ArrayUtils.emptyArray(ParagraphStyle.class);
 
@@ -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,7 @@
                 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);
 
             if (w > need)
                 need = w;
@@ -116,6 +117,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();
@@ -175,7 +185,6 @@
             dbottom = sTempRect.bottom;
         }
 
-
         int top = 0;
         int bottom = getLineTop(getLineCount());
 
@@ -185,26 +194,28 @@
         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;
         boolean spannedText = mSpannedText;
 
         ParagraphStyle[] spans = NO_PARA_SPANS;
-        int spanend = 0;
+        int spanEnd = 0;
         int textLength = 0;
 
         // First, draw LineBackgroundSpans.
-        // LineBackgroundSpans know nothing about the alignment or direction of
-        // the layout or line.  XXX: Should they?
+        // LineBackgroundSpans know nothing about the alignment, margins, or
+        // direction of the layout or line.  XXX: Should they?
+        // They are evaluated at each line.
         if (spannedText) {
+            Spanned sp = (Spanned) buf;
             textLength = buf.length();
             for (int i = first; i <= last; i++) {
                 int start = previousLineEnd;
@@ -216,12 +227,14 @@
                 previousLineBottom = lbottom;
                 int lbaseline = lbottom - getLineDescent(i);
 
-                if (start >= spanend) {
-                   Spanned sp = (Spanned) buf;
-                   spanend = sp.nextSpanTransition(start, textLength,
-                                                   LineBackgroundSpan.class);
-                   spans = sp.getSpans(start, spanend,
-                                       LineBackgroundSpan.class);
+                if (start >= spanEnd) {
+                    // These should be infrequent, so we'll use this so that
+                    // we don't have to check as often.
+                    spanEnd = sp.nextSpanTransition(start, textLength,
+                            LineBackgroundSpan.class);
+                    // All LineBackgroundSpans on a line contribute to its
+                    // background.
+                   spans = sp.getSpans(start, end, LineBackgroundSpan.class);
                 }
 
                 for (int n = 0; n < spans.length; n++) {
@@ -234,11 +247,11 @@
                 }
             }
             // reset to their original values
-            spanend = 0;
+            spanEnd = 0;
             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 +268,11 @@
         }
 
         Alignment align = mAlignment;
-        
+        TabStops tabStops = null;
+        boolean tabStopsIsInitialized = false;
+
+        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.
@@ -270,19 +287,30 @@
             previousLineBottom = lbottom;
             int lbaseline = lbottom - getLineDescent(i);
 
-            boolean isFirstParaLine = false;
-            if (spannedText) { 
-                if (start == 0 || buf.charAt(start - 1) == '\n') {
-                    isFirstParaLine = true;
-                }
-                // New batch of paragraph styles, compute the alignment.
-                // Last alignment style wins.
-                if (start >= spanend) {
-                    Spanned sp = (Spanned) buf;
-                    spanend = sp.nextSpanTransition(start, textLength,
+            int dir = getParagraphDirection(i);
+            int left = 0;
+            int right = mWidth;
+
+            if (spannedText) {
+                Spanned sp = (Spanned) buf;
+                boolean isFirstParaLine = (start == 0 ||
+                        buf.charAt(start - 1) == '\n');
+
+                // New batch of paragraph styles, collect into spans array.
+                // Compute the alignment, last alignment style wins.
+                // Reset tabStops, we'll rebuild if we encounter a line with
+                // tabs.
+                // We expect paragraph spans to be relatively infrequent, use
+                // spanEnd so that we can check less frequently.  Since
+                // paragraph styles ought to apply to entire paragraphs, we can
+                // just collect the ones present at the start of the paragraph.
+                // If spanEnd is before the end of the paragraph, that's not
+                // our problem.
+                if (start >= spanEnd && (i == first || isFirstParaLine)) {
+                    spanEnd = sp.nextSpanTransition(start, textLength,
                                                     ParagraphStyle.class);
-                    spans = sp.getSpans(start, spanend, 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) {
@@ -290,45 +318,49 @@
                             break;
                         }
                     }
-                }
-            }
-            
-            int dir = getParagraphDirection(i);
-            int left = 0;
-            int right = mWidth;
 
-            // Draw all leading margin spans.  Adjust left or right according
-            // to the paragraph direction of the line.
-            if (spannedText) {
+                    tabStopsIsInitialized = false;
+                }
+
+                // Draw all leading margin spans.  Adjust left or right according
+                // to the paragraph direction of the line.
                 final int length = spans.length;
                 for (int n = 0; n < length; n++) {
                     if (spans[n] instanceof LeadingMarginSpan) {
                         LeadingMarginSpan margin = (LeadingMarginSpan) spans[n];
+                        boolean useFirstLineMargin = isFirstParaLine;
+                        if (margin instanceof LeadingMarginSpan2) {
+                            int count = ((LeadingMarginSpan2) margin).getLeadingMarginLineCount();
+                            int startLine = getLineForOffset(sp.getSpanStart(margin));
+                            useFirstLineMargin = i < startLine + count;
+                        }
 
                         if (dir == DIR_RIGHT_TO_LEFT) {
                             margin.drawLeadingMargin(c, paint, right, dir, ltop,
                                                      lbaseline, lbottom, buf,
                                                      start, end, isFirstParaLine, this);
-                                
-                            right -= margin.getLeadingMargin(isFirstParaLine);
+                            right -= margin.getLeadingMargin(useFirstLineMargin);
                         } else {
                             margin.drawLeadingMargin(c, paint, left, dir, ltop,
                                                      lbaseline, lbottom, buf,
                                                      start, end, isFirstParaLine, this);
-
-                            boolean useMargin = isFirstParaLine;
-                            if (margin instanceof LeadingMarginSpan.LeadingMarginSpan2) {
-                                int count = ((LeadingMarginSpan.LeadingMarginSpan2)margin).getLeadingMarginLineCount();
-                                useMargin = count > i;
-                            }
-                            left += margin.getLeadingMargin(useMargin);
+                            left += margin.getLeadingMargin(useFirstLineMargin);
                         }
                     }
                 }
             }
 
-            // Adjust the point at which to start rendering depending on the
-            // alignment of the paragraph.
+            boolean hasTabOrEmoji = getLineContainsTab(i);
+            // Can't tell if we have tabs for sure, currently
+            if (hasTabOrEmoji && !tabStopsIsInitialized) {
+                if (tabStops == null) {
+                    tabStops = new TabStops(TAB_INCREMENT, spans);
+                } else {
+                    tabStops.reset(TAB_INCREMENT, spans);
+                }
+                tabStopsIsInitialized = true;
+            }
+
             int x;
             if (align == Alignment.ALIGN_NORMAL) {
                 if (dir == DIR_LEFT_TO_RIGHT) {
@@ -337,41 +369,80 @@
                     x = right;
                 }
             } else {
-                int max = (int)getLineMax(i, spans, false);
+                int max = (int)getLineExtent(i, tabStops, false);
                 if (align == Alignment.ALIGN_OPPOSITE) {
-                    if (dir == DIR_RIGHT_TO_LEFT) {
-                        x = left + max;
-                    } else {
+                    if (dir == DIR_LEFT_TO_RIGHT) {
                         x = right - max;
-                    }
-                } else {
-                    // Alignment.ALIGN_CENTER
-                    max = max & ~1;
-                    int half = (right - left - max) >> 1;
-                    if (dir == DIR_RIGHT_TO_LEFT) {
-                        x = right - half;
                     } else {
-                        x = left + half;
+                        x = left - max;
                     }
+                } else { // Alignment.ALIGN_CENTER
+                    max = max & ~1;
+                    x = (right + left - max) >> 1;
                 }
             }
 
             Directions directions = getLineDirections(i);
-            boolean hasTab = getLineContainsTab(i);
             if (directions == DIRS_ALL_LEFT_TO_RIGHT &&
-                    !spannedText && !hasTab) {
-                if (DEBUG) {
-                    Assert.assertTrue(dir == DIR_LEFT_TO_RIGHT);
-                    Assert.assertNotNull(c);
-                }
+                    !spannedText && !hasTabOrEmoji) {
                 // 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, hasTabOrEmoji, tabStops);
+                tl.draw(c, x, ltop, lbaseline, lbottom);
             }
         }
+
+        TextLine.recycle(tl);
+    }
+
+    /**
+     * Return the start position of the line, given the left and right bounds
+     * of the margins.
+     *
+     * @param line the line index
+     * @param left the left bounds (0, or leading margin if ltr para)
+     * @param right the right bounds (width, minus leading margin if rtl para)
+     * @return the start position of the line (to right of line if rtl para)
+     */
+    private int getLineStartPos(int line, int left, int right) {
+        // Adjust the point at which to start rendering depending on the
+        // alignment of the paragraph.
+        Alignment align = getParagraphAlignment(line);
+        int dir = getParagraphDirection(line);
+
+        int x;
+        if (align == Alignment.ALIGN_NORMAL) {
+            if (dir == DIR_LEFT_TO_RIGHT) {
+                x = left;
+            } else {
+                x = right;
+            }
+        } else {
+            TabStops tabStops = null;
+            if (mSpannedText && getLineContainsTab(line)) {
+                Spanned spanned = (Spanned) mText;
+                int start = getLineStart(line);
+                int spanEnd = spanned.nextSpanTransition(start, spanned.length(),
+                        TabStopSpan.class);
+                TabStopSpan[] tabSpans = spanned.getSpans(start, spanEnd, TabStopSpan.class);
+                if (tabSpans.length > 0) {
+                    tabStops = new TabStops(TAB_INCREMENT, tabSpans);
+                }
+            }
+            int max = (int)getLineExtent(line, tabStops, false);
+            if (align == Alignment.ALIGN_OPPOSITE) {
+                if (dir == DIR_LEFT_TO_RIGHT) {
+                    x = right - max;
+                } else {
+                    x = left - max;
+                }
+            } else { // Alignment.ALIGN_CENTER
+                max = max & ~1;
+                x = (left + right - max) >> 1;
+            }
+        }
+        return x;
     }
 
     /**
@@ -417,7 +488,7 @@
 
         mWidth = wid;
     }
-    
+
     /**
      * Return the total height of this layout.
      */
@@ -450,7 +521,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 +595,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,66 +692,42 @@
      * 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);
+        boolean hasTabOrEmoji = getLineContainsTab(line);
         Directions directions = getLineDirections(line);
 
-        TabStopSpan[] tabs = null;
-        if (tab && mText instanceof Spanned) {
-            tabs = ((Spanned) mText).getSpans(start, end, TabStopSpan.class);
+        TabStops tabStops = null;
+        if (hasTabOrEmoji && mText instanceof Spanned) {
+            // Just checking this line should be good enough, tabs should be
+            // consistent across all lines in a paragraph.
+            TabStopSpan[] tabs = ((Spanned) mText).getSpans(start, end, TabStopSpan.class);
+            if (tabs.length > 0) {
+                tabStops = new TabStops(TAB_INCREMENT, tabs); // XXX should reuse
+            }
         }
 
-        float wid = measureText(mPaint, mWorkPaint, mText, start, offset, end,
-                                dir, directions, trailing, alt, tab, tabs);
+        TextLine tl = TextLine.obtain();
+        tl.set(mPaint, mText, start, end, dir, directions, hasTabOrEmoji, tabStops);
+        float wid = tl.measure(offset - start, trailing, null);
+        TextLine.recycle(tl);
 
-        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);
-        }
-
-        Alignment align = getParagraphAlignment(line);
         int left = getParagraphLeft(line);
         int right = getParagraphRight(line);
 
-        if (align == Alignment.ALIGN_NORMAL) {
-            if (dir == DIR_RIGHT_TO_LEFT)
-                return right + wid;
-            else
-                return left + wid;
-        }
-
-        float max = getLineMax(line);
-
-        if (align == Alignment.ALIGN_OPPOSITE) {
-            if (dir == DIR_RIGHT_TO_LEFT)
-                return left + max + wid;
-            else
-                return right - max + wid;
-        } else { /* align == Alignment.ALIGN_CENTER */
-            int imax = ((int) max) & ~1;
-
-            if (dir == DIR_RIGHT_TO_LEFT)
-                return right - (((right - left) - imax) / 2) + wid;
-            else
-                return left + ((right - left) - imax) / 2 + wid;
-        }
+        return getLineStartPos(line, left, right) + wid;
     }
 
     /**
@@ -656,38 +785,76 @@
     }
 
     /**
-     * Gets the horizontal extent of the specified line, excluding
-     * trailing whitespace.
+     * Gets the unsigned horizontal extent of the specified line, including
+     * leading margin indent, but excluding trailing whitespace.
      */
     public float getLineMax(int line) {
-        return getLineMax(line, null, false);
+        float margin = getParagraphLeadingMargin(line);
+        float signedExtent = getLineExtent(line, false);
+        return margin + signedExtent >= 0 ? signedExtent : -signedExtent;
     }
 
     /**
-     * Gets the horizontal extent of the specified line, including
-     * trailing whitespace.
+     * Gets the unsigned horizontal extent of the specified line, including
+     * leading margin indent and trailing whitespace.
      */
     public float getLineWidth(int line) {
-        return getLineMax(line, null, true);
+        float margin = getParagraphLeadingMargin(line);
+        float signedExtent = getLineExtent(line, true);
+        return margin + signedExtent >= 0 ? signedExtent : -signedExtent;
     }
 
-    private float getLineMax(int line, Object[] tabs, boolean full) {
+    /**
+     * Like {@link #getLineExtent(int,TabStops,boolean)} but determines the
+     * tab stops instead of using the ones passed in.
+     * @param line the index of the line
+     * @param full whether to include trailing whitespace
+     * @return the extent of the line
+     */
+    private float getLineExtent(int line, boolean full) {
         int start = getLineStart(line);
-        int end;
+        int end = full ? getLineEnd(line) : getLineVisibleEnd(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);
+        boolean hasTabsOrEmoji = getLineContainsTab(line);
+        TabStops tabStops = null;
+        if (hasTabsOrEmoji && mText instanceof Spanned) {
+            // Just checking this line should be good enough, tabs should be
+            // consistent across all lines in a paragraph.
+            TabStopSpan[] tabs = ((Spanned) mText).getSpans(start, end, TabStopSpan.class);
+            if (tabs.length > 0) {
+                tabStops = new TabStops(TAB_INCREMENT, tabs); // XXX should reuse
+            }
         }
+        Directions directions = getLineDirections(line);
+        int dir = getParagraphDirection(line);
 
-        return measureText(mPaint, mWorkPaint,
-                           mText, start, end, null, tab, tabs);
+        TextLine tl = TextLine.obtain();
+        tl.set(mPaint, mText, start, end, dir, directions, hasTabsOrEmoji, tabStops);
+        float width = tl.metrics(null);
+        TextLine.recycle(tl);
+        return width;
+    }
+
+    /**
+     * Returns the signed horizontal extent of the specified line, excluding
+     * leading margin.  If full is false, excludes trailing whitespace.
+     * @param line the index of the line
+     * @param tabStops the tab stops, can be null if we know they're not used.
+     * @param full whether to include trailing whitespace
+     * @return the extent of the text on this line
+     */
+    private float getLineExtent(int line, TabStops tabStops, boolean full) {
+        int start = getLineStart(line);
+        int end = full ? getLineEnd(line) : getLineVisibleEnd(line);
+        boolean hasTabsOrEmoji = getLineContainsTab(line);
+        Directions directions = getLineDirections(line);
+        int dir = getParagraphDirection(line);
+
+        TextLine tl = TextLine.obtain();
+        tl.set(mPaint, mText, start, end, dir, directions, hasTabsOrEmoji, tabStops);
+        float width = tl.metrics(null);
+        TextLine.recycle(tl);
+        return width;
     }
 
     /**
@@ -738,7 +905,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 +919,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 +958,7 @@
 
                 if (dist < bestdist) {
                     bestdist = dist;
-                    best = low;   
+                    best = low;
                 }
             }
 
@@ -802,8 +968,6 @@
                 bestdist = dist;
                 best = here;
             }
-
-            here = there;
         }
 
         float dist = Math.abs(getPrimaryHorizontal(max) - horiz);
@@ -823,19 +987,15 @@
         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);
-        }
 
+    private int getLineVisibleEnd(int line, int start, int end) {
         CharSequence text = mText;
         char ch;
         if (line == getLineCount() - 1) {
@@ -882,207 +1042,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 +1130,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 +1142,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 +1220,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 +1232,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 +1272,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);
@@ -1310,422 +1325,173 @@
      * Get the left edge of the specified paragraph, inset by left margins.
      */
     public final int getParagraphLeft(int line) {
-        int dir = getParagraphDirection(line);
-
         int left = 0;
-
-        boolean par = false;
-        int off = getLineStart(line);
-        if (off == 0 || mText.charAt(off - 1) == '\n')
-            par = true;
-
-        if (dir == DIR_LEFT_TO_RIGHT) {
-            if (mSpannedText) {
-                Spanned sp = (Spanned) mText;
-                LeadingMarginSpan[] spans = sp.getSpans(getLineStart(line),
-                                                        getLineEnd(line),
-                                                        LeadingMarginSpan.class);
-
-                for (int i = 0; i < spans.length; i++) {
-                    boolean margin = par;
-                    LeadingMarginSpan span = spans[i];
-                    if (span instanceof LeadingMarginSpan.LeadingMarginSpan2) {
-                        int count = ((LeadingMarginSpan.LeadingMarginSpan2)span).getLeadingMarginLineCount();
-                        margin = count >= line;
-                    }
-                    left += span.getLeadingMargin(margin);
-                }
-            }
+        int dir = getParagraphDirection(line);
+        if (dir == DIR_RIGHT_TO_LEFT || !mSpannedText) {
+            return left; // leading margin has no impact, or no styles
         }
-
-        return left;
+        return getParagraphLeadingMargin(line);
     }
 
     /**
      * Get the right edge of the specified paragraph, inset by right margins.
      */
     public final int getParagraphRight(int line) {
-        int dir = getParagraphDirection(line);
-
         int right = mWidth;
-
-        boolean par = false;
-        int off = getLineStart(line);
-        if (off == 0 || mText.charAt(off - 1) == '\n')
-            par = true;
-
-
-        if (dir == DIR_RIGHT_TO_LEFT) {
-            if (mSpannedText) {
-                Spanned sp = (Spanned) mText;
-                LeadingMarginSpan[] spans = sp.getSpans(getLineStart(line),
-                                                        getLineEnd(line),
-                                                        LeadingMarginSpan.class);
-
-                for (int i = 0; i < spans.length; i++) {
-                    right -= spans[i].getLeadingMargin(par);
-                }
-            }
+        int dir = getParagraphDirection(line);
+        if (dir == DIR_LEFT_TO_RIGHT || !mSpannedText) {
+            return right; // leading margin has no impact, or no styles
         }
-
-        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;
-            }
-            buf = null;
-        } else {
-            buf = TextUtils.obtain(end - start);
-            TextUtils.getChars(text, start, end, buf, 0);
-        }
-
-        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;
+        return right - getParagraphLeadingMargin(line);
     }
 
     /**
-     * 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
+     * Returns the effective leading margin (unsigned) for this line,
+     * taking into account LeadingMarginSpan and LeadingMarginSpan2.
+     * @param line the line index
+     * @return the leading margin of this line
      */
-    /* 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);
+    private int getParagraphLeadingMargin(int line) {
+        if (!mSpannedText) {
+            return 0;
+        }
+        Spanned spanned = (Spanned) mText;
+
+        int lineStart = getLineStart(line);
+        int lineEnd = getLineEnd(line);
+        int spanEnd = spanned.nextSpanTransition(lineStart, lineEnd,
+                LeadingMarginSpan.class);
+        LeadingMarginSpan[] spans = spanned.getSpans(lineStart, spanEnd,
+                                                LeadingMarginSpan.class);
+        if (spans.length == 0) {
+            return 0; // no leading margin span;
         }
 
-        int len = end - start;
+        int margin = 0;
 
-        int lastPos = 0;
-        float width = 0;
-        int ascent = 0, descent = 0, top = 0, bottom = 0;
+        boolean isFirstParaLine = lineStart == 0 ||
+            spanned.charAt(lineStart - 1) == '\n';
 
-        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];
+        for (int i = 0; i < spans.length; i++) {
+            LeadingMarginSpan span = spans[i];
+            boolean useFirstLineMargin = isFirstParaLine;
+            if (span instanceof LeadingMarginSpan2) {
+                int spStart = spanned.getSpanStart(span);
+                int spanLine = getLineForOffset(spStart);
+                int count = ((LeadingMarginSpan2)span).getLeadingMarginLineCount();
+                useFirstLineMargin = line < spanLine + count;
             }
+            margin += span.getLeadingMargin(useFirstLineMargin);
+        }
 
-            if (codept >= 0xD800 && codept <= 0xDFFF && pos < len) {
-                codept = Character.codePointAt(buf, pos);
+        return margin;
+    }
 
-                if (codept >= MIN_EMOJI && codept <= MAX_EMOJI) {
-                    bm = EMOJI_FACTORY.getBitmapFromAndroidPua(codept);
+    /* package */
+    static float measurePara(TextPaint paint, TextPaint workPaint,
+            CharSequence text, int start, int end) {
+
+        MeasuredText mt = MeasuredText.obtain();
+        TextLine tl = TextLine.obtain();
+        try {
+            mt.setPara(text, start, end, DIR_REQUEST_LTR);
+            Directions directions;
+            int dir;
+            if (mt.mEasy) {
+                directions = DIRS_ALL_LEFT_TO_RIGHT;
+                dir = Layout.DIR_LEFT_TO_RIGHT;
+            } else {
+                directions = AndroidBidi.directions(mt.mDir, mt.mLevels,
+                    0, mt.mChars, 0, mt.mLen);
+                dir = mt.mDir;
+            }
+            char[] chars = mt.mChars;
+            int len = mt.mLen;
+            boolean hasTabs = false;
+            TabStops tabStops = null;
+            for (int i = 0; i < len; ++i) {
+                if (chars[i] == '\t') {
+                    hasTabs = true;
+                    if (text instanceof Spanned) {
+                        Spanned spanned = (Spanned) text;
+                        int spanEnd = spanned.nextSpanTransition(start, end,
+                                TabStopSpan.class);
+                        TabStopSpan[] spans = spanned.getSpans(start, spanEnd,
+                                TabStopSpan.class);
+                        if (spans.length > 0) {
+                            tabStops = new TabStops(TAB_INCREMENT, spans);
+                        }
+                    }
+                    break;
                 }
             }
+            tl.set(paint, text, start, end, dir, directions, hasTabs, tabStops);
+            return tl.metrics(null);
+        } finally {
+            TextLine.recycle(tl);
+            MeasuredText.recycle(mt);
+        }
+    }
 
-            if (pos == len || codept == '\t' || bm != null) {
-                workPaint.baselineShift = 0;
+    /**
+     * @hide
+     */
+    /* package */ static class TabStops {
+        private int[] mStops;
+        private int mNumStops;
+        private int mIncrement;
 
-                width += Styled.measureText(paint, workPaint, text,
-                                        start + lastPos, start + pos,
-                                        fm);
+        TabStops(int increment, Object[] spans) {
+            reset(increment, spans);
+        }
 
-                if (fm != null) {
-                    if (workPaint.baselineShift < 0) {
-                        fm.ascent += workPaint.baselineShift;
-                        fm.top += workPaint.baselineShift;
-                    } else {
-                        fm.descent += workPaint.baselineShift;
-                        fm.bottom += workPaint.baselineShift;
+        void reset(int increment, Object[] spans) {
+            this.mIncrement = increment;
+
+            int ns = 0;
+            if (spans != null) {
+                int[] stops = this.mStops;
+                for (Object o : spans) {
+                    if (o instanceof TabStopSpan) {
+                        if (stops == null) {
+                            stops = new int[10];
+                        } else if (ns == stops.length) {
+                            int[] nstops = new int[ns * 2];
+                            for (int i = 0; i < ns; ++i) {
+                                nstops[i] = stops[i];
+                            }
+                            stops = nstops;
+                        }
+                        stops[ns++] = ((TabStopSpan) o).getTabStop();
                     }
                 }
-
-                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 (ns > 1) {
+                    Arrays.sort(stops, 0, ns);
                 }
-
-                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.
+                if (stops != this.mStops) {
+                    this.mStops = stops;
                 }
-
-                lastPos = pos + 1;
             }
+            this.mNumStops = ns;
         }
 
-        if (fm != null) {
-            fm.ascent = ascent;
-            fm.descent = descent;
-            fm.top = top;
-            fm.bottom = bottom;
+        float nextTab(float h) {
+            int ns = this.mNumStops;
+            if (ns > 0) {
+                int[] stops = this.mStops;
+                for (int i = 0; i < ns; ++i) {
+                    int stop = stops[i];
+                    if (stop > h) {
+                        return stop;
+                    }
+                }
+            }
+            return nextDefaultStop(h, mIncrement);
         }
 
-        if (hasTabs)
-            TextUtils.recycle(buf);
-
-        return width;
+        public static float nextDefaultStop(float h, int inc) {
+            return ((int) ((h + inc) / inc)) * inc;
+        }
     }
 
     /**
@@ -1804,23 +1570,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 +1596,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 +1636,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 +1702,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 +1724,7 @@
     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..d5699f1
--- /dev/null
+++ b/core/java/android/text/MeasuredText.java
@@ -0,0 +1,229 @@
+/*
+ * 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.Canvas;
+import android.graphics.Paint;
+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 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)];
+        }
+        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 = Layout.DIR_LEFT_TO_RIGHT;
+            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;
+        }
+    }
+
+    float addStyleRun(TextPaint paint, int len, Paint.FontMetricsInt fm) {
+        if (fm != null) {
+            paint.getFontMetricsInt(fm);
+        }
+
+        int p = mPos;
+        mPos = p + len;
+
+        if (mEasy) {
+            int flags = mDir == Layout.DIR_LEFT_TO_RIGHT
+                ? Canvas.DIRECTION_LTR : Canvas.DIRECTION_RTL;
+            return paint.getTextRunAdvances(mChars, p, len, p, len, flags, mWidths, p);
+        }
+
+        float totalAdvance = 0;
+        int level = mLevels[p];
+        for (int q = p, i = p + 1, e = p + len;; ++i) {
+            if (i == e || mLevels[i] != level) {
+                int flags = (level & 0x1) == 0 ? Canvas.DIRECTION_LTR : Canvas.DIRECTION_RTL;
+                totalAdvance +=
+                        paint.getTextRunAdvances(mChars, q, i - q, q, i - q, flags, mWidths, q);
+                if (i == e) {
+                    break;
+                }
+                q = i;
+                level = mLevels[i];
+            }
+        }
+        return totalAdvance;
+    }
+
+    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/Selection.java b/core/java/android/text/Selection.java
index bb98bce..13cb5e6 100644
--- a/core/java/android/text/Selection.java
+++ b/core/java/android/text/Selection.java
@@ -417,8 +417,8 @@
         }
     }
 
-    private static final class START implements NoCopySpan { };
-    private static final class END implements NoCopySpan { };
+    private static final class START implements NoCopySpan { }
+    private static final class END implements NoCopySpan { }
     
     /*
      * Public constants
diff --git a/core/java/android/text/SpannableStringBuilder.java b/core/java/android/text/SpannableStringBuilder.java
index caaafa1..fc01ef2 100644
--- a/core/java/android/text/SpannableStringBuilder.java
+++ b/core/java/android/text/SpannableStringBuilder.java
@@ -17,8 +17,9 @@
 package android.text;
 
 import com.android.internal.util.ArrayUtils;
-import android.graphics.Paint;
+
 import android.graphics.Canvas;
+import android.graphics.Paint;
 
 import java.lang.reflect.Array;
 
@@ -312,12 +313,15 @@
 
         moveGapTo(end);
 
-        if (tbend - tbstart >= mGapLength + (end - start))
-            resizeFor(mText.length - mGapLength +
-                      tbend - tbstart - (end - start));
+        // Can be negative
+        final int nbNewChars = (tbend - tbstart) - (end - start);
 
-        mGapStart += tbend - tbstart - (end - start);
-        mGapLength -= tbend - tbstart - (end - start);
+        if (nbNewChars >= mGapLength) {
+            resizeFor(mText.length + nbNewChars - mGapLength);
+        }
+
+        mGapStart += nbNewChars;
+        mGapLength -= nbNewChars;
 
         if (mGapLength < 1)
             new Exception("mGapLength < 1").printStackTrace();
@@ -707,6 +711,7 @@
      * the specified range of the buffer.  The kind may be Object.class to get
      * a list of all the spans regardless of type.
      */
+    @SuppressWarnings("unchecked")
     public <T> T[] getSpans(int queryStart, int queryEnd, Class<T> kind) {
         int spanCount = mSpanCount;
         Object[] spans = mSpans;
@@ -717,8 +722,8 @@
         int gaplen = mGapLength;
 
         int count = 0;
-        Object[] ret = null;
-        Object ret1 = null;
+        T[] ret = null;
+        T ret1 = null;
 
         for (int i = 0; i < spanCount; i++) {
             int spanStart = starts[i];
@@ -750,11 +755,13 @@
             }
 
             if (count == 0) {
-                ret1 = spans[i];
+                // Safe conversion thanks to the isInstance test above
+                ret1 = (T) spans[i];
                 count++;
             } else {
                 if (count == 1) {
-                    ret = (Object[]) Array.newInstance(kind, spanCount - i + 1);
+                    // Safe conversion, but requires a suppressWarning
+                    ret = (T[]) Array.newInstance(kind, spanCount - i + 1);
                     ret[0] = ret1;
                 }
 
@@ -771,29 +778,33 @@
                     }
 
                     System.arraycopy(ret, j, ret, j + 1, count - j);
-                    ret[j] = spans[i];
+                    // Safe conversion thanks to the isInstance test above
+                    ret[j] = (T) spans[i];
                     count++;
                 } else {
-                    ret[count++] = spans[i];
+                    // Safe conversion thanks to the isInstance test above
+                    ret[count++] = (T) spans[i];
                 }
             }
         }
 
         if (count == 0) {
-            return (T[]) ArrayUtils.emptyArray(kind);
+            return ArrayUtils.emptyArray(kind);
         }
         if (count == 1) {
-            ret = (Object[]) Array.newInstance(kind, 1);
+            // Safe conversion, but requires a suppressWarning
+            ret = (T[]) Array.newInstance(kind, 1);
             ret[0] = ret1;
-            return (T[]) ret;
+            return ret;
         }
         if (count == ret.length) {
-            return (T[]) ret;
+            return ret;
         }
 
-        Object[] nret = (Object[]) Array.newInstance(kind, count);
+        // Safe conversion, but requires a suppressWarning
+        T[] nret = (T[]) Array.newInstance(kind, count);
         System.arraycopy(ret, 0, nret, 0, count);
-        return (T[]) nret;
+        return nret;
     }
 
     /**
@@ -862,6 +873,7 @@
     /**
      * Return a String containing a copy of the chars in this buffer.
      */
+    @Override
     public String toString() {
         int len = length();
         char[] buf = new char[len];
@@ -952,6 +964,7 @@
         }
     }
 
+/*
     private boolean isprint(char c) { // XXX
         if (c >= ' ' && c <= '~')
             return true;
@@ -959,7 +972,6 @@
             return false;
     }
 
-/*
     private static final int startFlag(int flag) {
         return (flag >> 4) & 0x0F;
     }
@@ -1054,7 +1066,32 @@
         }
     }
 
+
     /**
+     * Don't call this yourself -- exists for Canvas to use internally.
+     * {@hide}
+     */
+    public void drawTextRun(Canvas c, int start, int end,
+            int contextStart, int contextEnd,
+            float x, float y, int flags, Paint p) {
+        checkRange("drawTextRun", start, end);
+
+        int contextLen = contextEnd - contextStart;
+        int len = end - start;
+        if (contextEnd <= mGapStart) {
+            c.drawTextRun(mText, start, len, contextStart, contextLen, x, y, flags, p);
+        } else if (contextStart >= mGapStart) {
+            c.drawTextRun(mText, start + mGapLength, len, contextStart + mGapLength,
+                    contextLen, x, y, flags, p);
+        } else {
+            char[] buf = TextUtils.obtain(contextLen);
+            getChars(contextStart, contextEnd, buf, 0);
+            c.drawTextRun(buf, start - contextStart, len, 0, contextLen, x, y, flags, p);
+            TextUtils.recycle(buf);
+        }
+    }
+
+   /**
      * Don't call this yourself -- exists for Paint to use internally.
      * {@hide}
      */
@@ -1103,6 +1140,58 @@
         return ret;
     }
 
+    /**
+     * Don't call this yourself -- exists for Paint to use internally.
+     * {@hide}
+     */
+    public float getTextRunAdvances(int start, int end, int contextStart, int contextEnd, int flags,
+            float[] advances, int advancesPos, Paint p) {
+
+        float ret;
+
+        int contextLen = contextEnd - contextStart;
+        int len = end - start;
+
+        if (end <= mGapStart) {
+            ret = p.getTextRunAdvances(mText, start, len, contextStart, contextLen,
+                    flags, advances, advancesPos);
+        } else if (start >= mGapStart) {
+            ret = p.getTextRunAdvances(mText, start + mGapLength, len,
+                    contextStart + mGapLength, contextLen, flags, advances, advancesPos);
+        } else {
+            char[] buf = TextUtils.obtain(contextLen);
+            getChars(contextStart, contextEnd, buf, 0);
+            ret = p.getTextRunAdvances(buf, start - contextStart, len,
+                    0, contextLen, flags, advances, advancesPos);
+            TextUtils.recycle(buf);
+        }
+
+        return ret;
+    }
+
+    public int getTextRunCursor(int contextStart, int contextEnd, int flags, int offset,
+            int cursorOpt, Paint p) {
+
+        int ret;
+
+        int contextLen = contextEnd - contextStart;
+        if (contextEnd <= mGapStart) {
+            ret = p.getTextRunCursor(mText, contextStart, contextLen,
+                    flags, offset, cursorOpt);
+        } else if (contextStart >= mGapStart) {
+            ret = p.getTextRunCursor(mText, contextStart + mGapLength, contextLen,
+                    flags, offset + mGapLength, cursorOpt) - mGapLength;
+        } else {
+            char[] buf = TextUtils.obtain(contextLen);
+            getChars(contextStart, contextEnd, buf, 0);
+            ret = p.getTextRunCursor(buf, 0, contextLen,
+                    flags, offset - contextStart, cursorOpt) + contextStart;
+            TextUtils.recycle(buf);
+        }
+
+        return ret;
+    }
+
     // Documentation from interface
     public void setFilters(InputFilter[] filters) {
         if (filters == null) {
diff --git a/core/java/android/text/Spanned.java b/core/java/android/text/Spanned.java
index 154497d..d14fcbc 100644
--- a/core/java/android/text/Spanned.java
+++ b/core/java/android/text/Spanned.java
@@ -91,7 +91,7 @@
     public static final int SPAN_EXCLUSIVE_EXCLUSIVE = SPAN_POINT_MARK;
 
     /**
-     * Non-0-length spans of type SPAN_INCLUSIVE_EXCLUSIVE expand
+     * Non-0-length spans of type SPAN_EXCLUSIVE_INCLUSIVE expand
      * to include text inserted at their ending point but not at their
      * starting point.  When 0-length, they behave like points.
      */
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index f02ad2a..44157de 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -16,14 +16,15 @@
 
 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;
+import android.text.style.TabStopSpan;
+import android.text.style.LeadingMarginSpan.LeadingMarginSpan2;
 
 /**
  * StaticLayout is a Layout for text that will not be edited after it
@@ -31,8 +32,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 +64,7 @@
                         boolean includepad,
                         TextUtils.TruncateAt ellipsize, int ellipsizedWidth) {
         super((ellipsize == null)
-                ? source 
+                ? source
                 : (source instanceof Spanned)
                     ? new SpannedEllipsizer(source)
                     : new Ellipsizer(source),
@@ -72,7 +74,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 +96,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 +113,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,59 +131,50 @@
         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);
+                paraEnd++;
+            int paraLen = paraEnd - paraStart;
 
-            if (end < 0)
-                end = bufend;
-            else
-                end++;
-
-            int firstWidthLineCount = 1;
+            int firstWidthLineLimit = mLineCount + 1;
             int firstwidth = outerwidth;
             int restwidth = outerwidth;
 
             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();
+                    
+                    // LeadingMarginSpan2 is odd.  The count affects all
+                    // leading margin spans, not just this particular one,
+                    // and start from the top of the span, not the top of the
+                    // paragraph.
+                    if (lms instanceof LeadingMarginSpan2) {
+                        LeadingMarginSpan2 lms2 = (LeadingMarginSpan2) lms;
+                        int lmsFirstLine = getLineForOffset(spanned.getSpanStart(lms2));
+                        firstWidthLineLimit = lmsFirstLine + 
+                            lms2.getLeadingMarginLineCount();
                     }
                 }
 
-                chooseht = spanned.getSpans(start, end, LineHeightSpan.class);
+                chooseht = spanned.getSpans(paraStart, paraEnd, LineHeightSpan.class);
 
                 if (chooseht.length != 0) {
                     if (choosehtv == null ||
@@ -192,11 +186,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,162 +200,87 @@
                 }
             }
 
-            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;
+            boolean hasTabOrEmoji = false;
+            boolean hasTab = false;
+            TabStops tabStops = null;
 
-            int next;
-            for (int i = start; i < end; i = next) {
-                if (spanned == null)
-                    next = end;
-                else
-                    next = spanned.nextSpanTransition(i, end,
-                                                      MetricAffectingSpan.
-                                                      class);
+            for (int spanStart = paraStart, spanEnd = spanStart, nextSpanStart;
+                    spanStart < paraEnd; spanStart = nextSpanStart) {
 
-                if (spanned == null) {
-                    paint.getTextWidths(sub, i, next, widths);
-                    System.arraycopy(widths, 0, widths,
-                                     end - start + (i - start), next - i);
-                                     
-                    paint.getFontMetricsInt(fm);
-                } else {
-                    mWorkPaint.baselineShift = 0;
+                if (spanStart == spanEnd) {
+                    if (spanned == null)
+                        spanEnd = paraEnd;
+                    else
+                        spanEnd = spanned.nextSpanTransition(spanStart, paraEnd,
+                                MetricAffectingSpan.class);
 
-                    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;
+                    int spanLen = spanEnd - spanStart;
+                    if (spanned == null) {
+                        measured.addStyleRun(paint, spanLen, fm);
                     } else {
-                        fm.descent += mWorkPaint.baselineShift;
-                        fm.bottom += mWorkPaint.baselineShift;
+                        MetricAffectingSpan[] spans =
+                            spanned.getSpans(spanStart, spanEnd, MetricAffectingSpan.class);
+                        measured.addStyleRun(paint, spans, spanLen, fm);
                     }
                 }
 
+                nextSpanStart = spanEnd;
+                int startInPara = spanStart - paraStart;
+                int endInPara = spanEnd - paraStart;
+
                 int fmtop = fm.top;
                 int fmbottom = fm.bottom;
                 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);
-                        tab = true;
-                    } else if (c >= 0xD800 && c <= 0xDFFF && j + 1 < next) {
-                        int emoji = Character.codePointAt(chs, j - start);
+                        if (hasTab == false) {
+                            hasTab = true;
+                            hasTabOrEmoji = true;
+                            if (spanned != null) {
+                                // First tab this para, check for tabstops
+                                TabStopSpan[] spans = spanned.getSpans(paraStart,
+                                        paraEnd, TabStopSpan.class);
+                                if (spans.length > 0) {
+                                    tabStops = new TabStops(TAB_INCREMENT, spans);
+                                }
+                            }
+                        }
+                        if (tabStops != null) {
+                            w = tabStops.nextTab(w);
+                        } else {
+                            w = TabStops.nextDefaultStop(w, TAB_INCREMENT);
+                        }
+                    } 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,21 +295,21 @@
                                     whichPaint = mWorkPaint;
                                 }
 
-                                float wid = (float) bm.getWidth() *
+                                float wid = bm.getWidth() *
                                             -whichPaint.ascent() /
                                             bm.getHeight();
 
                                 w += wid;
-                                tab = true;
+                                hasTabOrEmoji = 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 +330,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 +345,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 +367,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++;
                             }
 
@@ -457,10 +376,10 @@
                                     okascent, okdescent, oktop, okbottom,
                                     v,
                                     spacingmult, spacingadd, chooseht,
-                                    choosehtv, fm, tab,
-                                    needMultiply, start, chdirs, dir, easy,
+                                    choosehtv, fm, hasTabOrEmoji,
+                                    needMultiply, paraStart, chdirs, dir, easy,
                                     ok == bufend, includepad, trackpad,
-                                    widths, start, end - start,
+                                    chs, widths, here - paraStart,
                                     where, ellipsizedWidth, okwidth,
                                     paint);
 
@@ -484,7 +403,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++;
                             }
 
@@ -493,10 +412,10 @@
                                     okascent, okdescent, oktop, okbottom,
                                     v,
                                     spacingmult, spacingadd, chooseht,
-                                    choosehtv, fm, tab,
-                                    needMultiply, start, chdirs, dir, easy,
+                                    choosehtv, fm, hasTabOrEmoji,
+                                    needMultiply, paraStart, chdirs, dir, easy,
                                     ok == bufend, includepad, trackpad,
-                                    widths, start, end - start,
+                                    chs, widths, here - paraStart,
                                     where, ellipsizedWidth, okwidth,
                                     paint);
 
@@ -509,19 +428,20 @@
                                     fittop, fitbottom,
                                     v,
                                     spacingmult, spacingadd, chooseht,
-                                    choosehtv, fm, tab,
-                                    needMultiply, start, chdirs, dir, easy,
+                                    choosehtv, fm, hasTabOrEmoji,
+                                    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,
@@ -529,19 +449,22 @@
                                     fm.top, fm.bottom,
                                     v,
                                     spacingmult, spacingadd, chooseht,
-                                    choosehtv, fm, tab,
-                                    needMultiply, start, chdirs, dir, easy,
+                                    choosehtv, fm, hasTabOrEmoji,
+                                    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) {
+                            // didn't output all the text for this span
+                            // we've measured the raw widths, though, so
+                            // just reset the start point
+                            j = nextSpanStart = here;
                         } else {
                             j = here - 1;    // continue looping
                         }
@@ -551,14 +474,14 @@
                         fitascent = fitdescent = fittop = fitbottom = 0;
                         okascent = okdescent = oktop = okbottom = 0;
 
-                        if (--firstWidthLineCount <= 0) {
+                        if (--firstWidthLineLimit <= 0) {
                             width = restwidth;
                         }
                     }
                 }
             }
 
-            if (end != here) {
+            if (paraEnd != here) {
                 if ((fittop | fitbottom | fitdescent | fitascent) == 0) {
                     paint.getFontMetricsInt(fm);
 
@@ -571,20 +494,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,
+                        choosehtv, fm, hasTabOrEmoji,
+                        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 +522,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,37 +634,15 @@
     }
 */
 
-    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,
                       LineHeightSpan[] chooseht, int[] choosehtv,
-                      Paint.FontMetricsInt fm, boolean tab,
+                      Paint.FontMetricsInt fm, boolean hasTabOrEmoji,
                       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 +650,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];
@@ -1059,59 +725,23 @@
         lines[off + mColumns + START] = end;
         lines[off + mColumns + TOP] = v;
 
-        if (tab)
+        if (hasTabOrEmoji)
             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 +752,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 +772,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 +788,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 +805,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 +816,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 +833,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 +862,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) {
@@ -1309,13 +939,11 @@
     private static final int DIR_SHIFT  = 30;
     private static final int TAB_MASK   = 0x20000000;
 
-    private static final char FIRST_RIGHT_TO_LEFT = '\u0590';
+    private static final int TAB_INCREMENT = 20; // same as Layout, but that's private
 
     /*
-     * 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..0e3522e
--- /dev/null
+++ b/core/java/android/text/TextLine.java
@@ -0,0 +1,940 @@
+/*
+ * 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.Paint.FontMetricsInt;
+import android.graphics.RectF;
+import android.text.Layout.Directions;
+import android.text.Layout.TabStops;
+import android.text.style.CharacterStyle;
+import android.text.style.MetricAffectingSpan;
+import android.text.style.ReplacementSpan;
+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 TabStops mTabs;
+    private char[] mChars;
+    private boolean mCharsValid;
+    private Spanned mSpanned;
+    private final TextPaint mWorkPaint = new TextPaint();
+
+    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 tabStops the tabStops. Can be null.
+     */
+    void set(TextPaint paint, CharSequence text, int start, int limit, int dir,
+            Directions directions, boolean hasTabs, TabStops tabStops) {
+        mPaint = paint;
+        mText = text;
+        mStart = start;
+        mLen = limit - start;
+        mDir = dir;
+        mDirections = directions;
+        mHasTabs = hasTabs;
+        mSpanned = null;
+
+        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 (hasReplacement) {
+                // Handle these all at once so we don't have to do it as we go.
+                // Replace the first character of each replacement run with the
+                // object-replacement character and the remainder with zero width
+                // non-break space aka BOM.  Cursor movement code skips these
+                // zero-width characters.
+                char[] chars = mChars;
+                for (int i = start, inext; i < limit; i = inext) {
+                    inext = mSpanned.nextSpanTransition(i, limit,
+                            ReplacementSpan.class);
+                    if (mSpanned.getSpans(i, inext, ReplacementSpan.class)
+                            .length > 0) { // transition into a span
+                        chars[i - start] = '\ufffc';
+                        for (int j = i - start + 1, e = inext - start; j < e; ++j) {
+                            chars[j] = '\ufeff'; // used as ZWNBS, marks positions to skip
+                        }
+                    }
+                }
+            }
+        }
+        mTabs = tabStops;
+    }
+
+    /**
+     * 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, offset, mLen, false, fmi);
+            }
+            if (mDirections == Layout.DIRS_ALL_RIGHT_TO_LEFT) {
+                return measureRun(0, 0, offset, 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);
+            return w;
+        }
+
+        return handleRun(runIndex, start, limit, limit, runIsRtl, c, x, top,
+                y, bottom, null, needWidth);
+    }
+
+    /**
+     * 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);
+    }
+
+    /**
+     * 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.
+     *
+     * @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.
+                  newCaret = getOffsetBeforeAfter(runIndex, runStart, runLimit,
+                          runIsRtl, 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) {
+                newCaret = getOffsetBeforeAfter(otherRunIndex, otherRunStart,
+                        otherRunLimit, otherRunIsRtl,
+                        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 = advance ? mLen + 1 : -1;
+              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 line, since the returned values might not be valid
+     * on neighboring lines.  If the returned offset is less than zero or
+     * greater than the line length, the offset should be recomputed on the
+     * preceding or following line, respectively.
+     *
+     * @param runIndex the run index
+     * @param runStart the start of the run
+     * @param runLimit the limit of the run
+     * @param runIsRtl true if the run is right-to-left
+     * @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 runStart, int runLimit,
+            boolean runIsRtl, int offset, boolean after) {
+
+        if (runIndex < 0 || offset == (after ? mLen : 0)) {
+            // Walking off end of line.  Since we don't know
+            // what cursor positions are available on other lines, we can't
+            // return accurate values.  These are a guess.
+            if (after) {
+                return TextUtils.getOffsetAfter(mText, offset + mStart) - mStart;
+            }
+            return TextUtils.getOffsetBefore(mText, offset + mStart) - mStart;
+        }
+
+        TextPaint wp = mWorkPaint;
+        wp.set(mPaint);
+
+        int spanStart = runStart;
+        int spanLimit;
+        if (mSpanned == null) {
+            spanLimit = runLimit;
+        } else {
+            int target = after ? offset + 1 : offset;
+            int limit = mStart + runLimit;
+            while (true) {
+                spanLimit = mSpanned.nextSpanTransition(mStart + spanStart, limit,
+                        MetricAffectingSpan.class) - mStart;
+                if (spanLimit >= target) {
+                    break;
+                }
+                spanStart = spanLimit;
+            }
+
+            MetricAffectingSpan[] spans = mSpanned.getSpans(mStart + spanStart,
+                    mStart + spanLimit, 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.updateMeasureState(wp);
+                    }
+                }
+
+                if (replacement != null) {
+                    // If we have a replacement span, we're moving either to
+                    // the start or end of this span.
+                    return after ? spanLimit : spanStart;
+                }
+            }
+        }
+
+        int flags = runIsRtl ? Paint.DIRECTION_RTL : Paint.DIRECTION_LTR;
+        int cursorOpt = after ? Paint.CURSOR_AFTER : Paint.CURSOR_BEFORE;
+        if (mCharsValid) {
+            return wp.getTextRunCursor(mChars, spanStart, spanLimit - spanStart,
+                    flags, offset, cursorOpt);
+        } else {
+            return wp.getTextRunCursor(mText, mStart + spanStart,
+                    mStart + spanLimit, flags, mStart + offset, cursorOpt) - 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 end the end 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 end,
+            int contextStart, int contextEnd, boolean runIsRtl,
+            Canvas c, float x, int top, int y, int bottom,
+            FontMetricsInt fmi, boolean needWidth) {
+
+        float ret = 0;
+
+        int runLen = end - start;
+        int contextLen = contextEnd - contextStart;
+        if (needWidth || (c != null && (wp.bgColor != 0 || runIsRtl))) {
+            int flags = runIsRtl ? Paint.DIRECTION_RTL : Paint.DIRECTION_LTR;
+            if (mCharsValid) {
+                ret = wp.getTextRunAdvances(mChars, start, runLen,
+                        contextStart, contextLen, flags, null, 0);
+            } else {
+                int delta = mStart;
+                ret = wp.getTextRunAdvances(mText, delta + start,
+                        delta + end, delta + contextStart, delta + contextEnd,
+                        flags, null, 0);
+            }
+        }
+
+        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, end, contextStart, contextEnd, 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
+     * @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) {
+
+        float ret = 0;
+
+        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 runIndex the run index
+     * @param start the line-relative start of the run
+     * @param measureLimit 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
+     * @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 measureLimit,
+            int limit, boolean runIsRtl, Canvas c, float x, int top, int y,
+            int bottom, FontMetricsInt fmi, boolean needWidth) {
+
+        // 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 to get metric bounds,
+        // then within each metric run iterate through character style runs
+        // for the run bounds.
+        float ox = x;
+        for (int i = start, inext; i < measureLimit; i = inext) {
+            TextPaint wp = mWorkPaint;
+            wp.set(mPaint);
+
+            int mlimit;
+            if (mSpanned == null) {
+                inext = limit;
+                mlimit = measureLimit;
+            } else {
+                inext = mSpanned.nextSpanTransition(mStart + i, mStart + limit,
+                        MetricAffectingSpan.class) - mStart;
+
+                mlimit = inext < measureLimit ? inext : measureLimit;
+                MetricAffectingSpan[] spans = mSpanned.getSpans(mStart + i,
+                        mStart + mlimit, 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 {
+                            // We might have a replacement that uses the draw
+                            // state, otherwise measure state would suffice.
+                            span.updateDrawState(wp);
+                        }
+                    }
+
+                    if (replacement != null) {
+                        x += handleReplacement(replacement, wp, runIndex, i,
+                                mlimit, runIsRtl, c, x, top, y, bottom, fmi,
+                                needWidth || mlimit < measureLimit);
+                        continue;
+                    }
+                }
+            }
+
+            if (mSpanned == null || c == null) {
+                x += handleText(wp, i, mlimit, i, inext, runIsRtl, c, x, top,
+                        y, bottom, fmi, needWidth || mlimit < measureLimit);
+            } else {
+                for (int j = i, jnext; j < mlimit; j = jnext) {
+                    jnext = mSpanned.nextSpanTransition(mStart + j,
+                            mStart + mlimit, 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, i, inext, runIsRtl, c, x,
+                            top, y, bottom, fmi, needWidth || jnext < measureLimit);
+                }
+            }
+        }
+
+        return x - ox;
+    }
+
+    /**
+     * 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 start of the run
+     * @param end the end of the run
+     * @param contextStart the start of context for the run
+     * @param contextEnd the end of the context for the run
+     * @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 end,
+            int contextStart, int contextEnd, boolean runIsRtl, float x, int y) {
+
+        int flags = runIsRtl ? Canvas.DIRECTION_RTL : Canvas.DIRECTION_LTR;
+        if (mCharsValid) {
+            int count = end - start;
+            int contextCount = contextEnd - contextStart;
+            c.drawTextRun(mChars, start, count, contextStart, contextCount,
+                    x, y, flags, wp);
+        } else {
+            int delta = mStart;
+            c.drawTextRun(mText, delta + start, delta + end,
+                    delta + contextStart, delta + contextEnd, x, y, flags, wp);
+        }
+    }
+
+    /**
+     * 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) {
+        if (mTabs != null) {
+            return mTabs.nextTab(h);
+        }
+        return TabStops.nextDefaultStop(h, 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/text/method/ArrowKeyMovementMethod.java b/core/java/android/text/method/ArrowKeyMovementMethod.java
index 9af42cc..79a0c37 100644
--- a/core/java/android/text/method/ArrowKeyMovementMethod.java
+++ b/core/java/android/text/method/ArrowKeyMovementMethod.java
@@ -16,30 +16,38 @@
 
 package android.text.method;
 
-import android.util.Log;
+import android.text.Layout;
+import android.text.Selection;
+import android.text.Spannable;
 import android.view.KeyEvent;
-import android.graphics.Rect;
-import android.text.*;
-import android.widget.TextView;
-import android.view.View;
-import android.view.ViewConfiguration;
 import android.view.MotionEvent;
+import android.view.View;
+import android.widget.TextView;
+import android.widget.TextView.CursorController;
 
 // XXX this doesn't extend MetaKeyKeyListener because the signatures
 // don't match.  Need to figure that out.  Meanwhile the meta keys
 // won't work in fields that don't take input.
 
-public class
-ArrowKeyMovementMethod
-implements MovementMethod
-{
+public class ArrowKeyMovementMethod implements MovementMethod {
+    /**
+     * An optional controller for the cursor.
+     * Use {@link #setCursorController(CursorController)} to set this field.
+     */
+    protected CursorController mCursorController;
+
+    private boolean isCap(Spannable buffer) {
+        return ((MetaKeyKeyListener.getMetaState(buffer, KeyEvent.META_SHIFT_ON) == 1) ||
+                (MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0));
+    }
+
+    private boolean isAlt(Spannable buffer) {
+        return MetaKeyKeyListener.getMetaState(buffer, KeyEvent.META_ALT_ON) == 1;
+    }
+
     private boolean up(TextView widget, Spannable buffer) {
-        boolean cap = (MetaKeyKeyListener.getMetaState(buffer,
-                        KeyEvent.META_SHIFT_ON) == 1) ||
-                      (MetaKeyKeyListener.getMetaState(buffer,
-                        MetaKeyKeyListener.META_SELECTING) != 0);
-        boolean alt = MetaKeyKeyListener.getMetaState(buffer,
-                        KeyEvent.META_ALT_ON) == 1;
+        boolean cap = isCap(buffer);
+        boolean alt = isAlt(buffer);
         Layout layout = widget.getLayout();
 
         if (cap) {
@@ -60,12 +68,8 @@
     }
 
     private boolean down(TextView widget, Spannable buffer) {
-        boolean cap = (MetaKeyKeyListener.getMetaState(buffer,
-                        KeyEvent.META_SHIFT_ON) == 1) ||
-                      (MetaKeyKeyListener.getMetaState(buffer,
-                        MetaKeyKeyListener.META_SELECTING) != 0);
-        boolean alt = MetaKeyKeyListener.getMetaState(buffer,
-                        KeyEvent.META_ALT_ON) == 1;
+        boolean cap = isCap(buffer);
+        boolean alt = isAlt(buffer);
         Layout layout = widget.getLayout();
 
         if (cap) {
@@ -86,12 +90,8 @@
     }
 
     private boolean left(TextView widget, Spannable buffer) {
-        boolean cap = (MetaKeyKeyListener.getMetaState(buffer,
-                        KeyEvent.META_SHIFT_ON) == 1) ||
-                      (MetaKeyKeyListener.getMetaState(buffer,
-                        MetaKeyKeyListener.META_SELECTING) != 0);
-        boolean alt = MetaKeyKeyListener.getMetaState(buffer,
-                        KeyEvent.META_ALT_ON) == 1;
+        boolean cap = isCap(buffer);
+        boolean alt = isAlt(buffer);
         Layout layout = widget.getLayout();
 
         if (cap) {
@@ -110,12 +110,8 @@
     }
 
     private boolean right(TextView widget, Spannable buffer) {
-        boolean cap = (MetaKeyKeyListener.getMetaState(buffer,
-                        KeyEvent.META_SHIFT_ON) == 1) ||
-                      (MetaKeyKeyListener.getMetaState(buffer,
-                        MetaKeyKeyListener.META_SELECTING) != 0);
-        boolean alt = MetaKeyKeyListener.getMetaState(buffer,
-                        KeyEvent.META_ALT_ON) == 1;
+        boolean cap = isCap(buffer);
+        boolean alt = isAlt(buffer);
         Layout layout = widget.getLayout();
 
         if (cap) {
@@ -133,35 +129,6 @@
         }
     }
 
-    private int getOffset(int x, int y, TextView widget){
-      // Converts the absolute X,Y coordinates to the character offset for the
-      // character whose position is closest to the specified
-      // horizontal position.
-      x -= widget.getTotalPaddingLeft();
-      y -= widget.getTotalPaddingTop();
-
-      // Clamp the position to inside of the view.
-      if (x < 0) {
-          x = 0;
-      } else if (x >= (widget.getWidth()-widget.getTotalPaddingRight())) {
-          x = widget.getWidth()-widget.getTotalPaddingRight() - 1;
-      }
-      if (y < 0) {
-          y = 0;
-      } else if (y >= (widget.getHeight()-widget.getTotalPaddingBottom())) {
-          y = widget.getHeight()-widget.getTotalPaddingBottom() - 1;
-      }
-
-      x += widget.getScrollX();
-      y += widget.getScrollY();
-
-      Layout layout = widget.getLayout();
-      int line = layout.getLineForVertical(y);
-
-      int offset = layout.getOffsetForHorizontal(line, x);
-      return offset;
-    }
-
     public boolean onKeyDown(TextView widget, Spannable buffer, int keyCode, KeyEvent event) {
         if (executeDown(widget, buffer, keyCode)) {
             MetaKeyKeyListener.adjustMetaAfterKeypress(buffer);
@@ -193,10 +160,9 @@
             break;
 
         case KeyEvent.KEYCODE_DPAD_CENTER:
-            if (MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0) {
-                if (widget.showContextMenu()) {
+            if ((MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0) &&
+                (widget.showContextMenu())) {
                     handled = true;
-                }
             }
         }
 
@@ -214,8 +180,7 @@
 
     public boolean onKeyOther(TextView view, Spannable text, KeyEvent event) {
         int code = event.getKeyCode();
-        if (code != KeyEvent.KEYCODE_UNKNOWN
-                && event.getAction() == KeyEvent.ACTION_MULTIPLE) {
+        if (code != KeyEvent.KEYCODE_UNKNOWN && event.getAction() == KeyEvent.ACTION_MULTIPLE) {
             int repeat = event.getRepeatCount();
             boolean handled = false;
             while ((--repeat) > 0) {
@@ -226,13 +191,22 @@
         return false;
     }
 
-    public boolean onTrackballEvent(TextView widget, Spannable text,
-            MotionEvent event) {
+    public boolean onTrackballEvent(TextView widget, Spannable text, MotionEvent event) {
+        if (mCursorController != null) {
+            mCursorController.hide();
+        }
         return false;
     }
 
-    public boolean onTouchEvent(TextView widget, Spannable buffer,
-                                MotionEvent event) {
+    public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) {
+        if (mCursorController != null) {
+            return onTouchEventCursor(widget, buffer, event);
+        } else {
+            return onTouchEventStandard(widget, buffer, event);
+        }
+    }
+
+    private boolean onTouchEventStandard(TextView widget, Spannable buffer, MotionEvent event) {
         int initialScrollX = -1, initialScrollY = -1;
         if (event.getAction() == MotionEvent.ACTION_UP) {
             initialScrollX = Touch.getInitialScrollX(widget, buffer);
@@ -243,53 +217,20 @@
 
         if (widget.isFocused() && !widget.didTouchFocusSelect()) {
             if (event.getAction() == MotionEvent.ACTION_DOWN) {
-              boolean cap = (MetaKeyKeyListener.getMetaState(buffer,
-                              KeyEvent.META_SHIFT_ON) == 1) ||
-                            (MetaKeyKeyListener.getMetaState(buffer,
-                              MetaKeyKeyListener.META_SELECTING) != 0);
-              int x = (int) event.getX();
-              int y = (int) event.getY();
-              int offset = getOffset(x, y, widget);
-
+              boolean cap = isCap(buffer);
               if (cap) {
-                  buffer.setSpan(LAST_TAP_DOWN, offset, offset,
-                                 Spannable.SPAN_POINT_POINT);
+                  int offset = widget.getOffset((int) event.getX(), (int) event.getY());
+                  
+                  buffer.setSpan(LAST_TAP_DOWN, offset, offset, Spannable.SPAN_POINT_POINT);
 
                   // Disallow intercepting of the touch events, so that
                   // users can scroll and select at the same time.
                   // without this, users would get booted out of select
                   // mode once the view detected it needed to scroll.
                   widget.getParent().requestDisallowInterceptTouchEvent(true);
-              } else {
-                  OnePointFiveTapState[] tap = buffer.getSpans(0, buffer.length(),
-                      OnePointFiveTapState.class);
-
-                  if (tap.length > 0) {
-                      if (event.getEventTime() - tap[0].mWhen <=
-                          ViewConfiguration.getDoubleTapTimeout() &&
-                          sameWord(buffer, offset, Selection.getSelectionEnd(buffer))) {
-
-                          tap[0].active = true;
-                          MetaKeyKeyListener.startSelecting(widget, buffer);
-                          widget.getParent().requestDisallowInterceptTouchEvent(true);
-                          buffer.setSpan(LAST_TAP_DOWN, offset, offset,
-                              Spannable.SPAN_POINT_POINT);
-                      }
-
-                      tap[0].mWhen = event.getEventTime();
-                  } else {
-                      OnePointFiveTapState newtap = new OnePointFiveTapState();
-                      newtap.mWhen = event.getEventTime();
-                      newtap.active = false;
-                      buffer.setSpan(newtap, 0, buffer.length(),
-                          Spannable.SPAN_INCLUSIVE_INCLUSIVE);
-                  }
               }
             } else if (event.getAction() == MotionEvent.ACTION_MOVE) {
-                boolean cap = (MetaKeyKeyListener.getMetaState(buffer,
-                                KeyEvent.META_SHIFT_ON) == 1) ||
-                              (MetaKeyKeyListener.getMetaState(buffer,
-                                MetaKeyKeyListener.META_SELECTING) != 0);
+                boolean cap = isCap(buffer);
 
                 if (cap && handled) {
                     // Before selecting, make sure we've moved out of the "slop".
@@ -297,45 +238,15 @@
                     // OUT of the slop
 
                     // Turn long press off while we're selecting. User needs to
-                    // re-tap on the selection to enable longpress
+                    // re-tap on the selection to enable long press
                     widget.cancelLongPress();
 
                     // Update selection as we're moving the selection area.
 
                     // Get the current touch position
-                    int x = (int) event.getX();
-                    int y = (int) event.getY();
-                    int offset = getOffset(x, y, widget);
+                    int offset = widget.getOffset((int) event.getX(), (int) event.getY());
 
-                    final OnePointFiveTapState[] tap = buffer.getSpans(0, buffer.length(),
-                            OnePointFiveTapState.class);
-
-                    if (tap.length > 0 && tap[0].active) {
-                        // Get the last down touch position (the position at which the
-                        // user started the selection)
-                        int lastDownOffset = buffer.getSpanStart(LAST_TAP_DOWN);
-
-                        // Compute the selection boundaries
-                        int spanstart;
-                        int spanend;
-                        if (offset >= lastDownOffset) {
-                            // Expand from word start of the original tap to new word
-                            // end, since we are selecting "forwards"
-                            spanstart = findWordStart(buffer, lastDownOffset);
-                            spanend = findWordEnd(buffer, offset);
-                        } else {
-                            // Expand to from new word start to word end of the original
-                            // tap since we are selecting "backwards".
-                            // The spanend will always need to be associated with the touch
-                            // up position, so that refining the selection with the
-                            // trackball will work as expected.
-                            spanstart = findWordEnd(buffer, lastDownOffset);
-                            spanend = findWordStart(buffer, offset);
-                        }
-                        Selection.setSelection(buffer, spanstart, spanend);
-                    } else {
-                        Selection.extendSelection(buffer, offset);
-                    }
+                    Selection.extendSelection(buffer, offset);
                     return true;
                 }
             } else if (event.getAction() == MotionEvent.ACTION_UP) {
@@ -344,70 +255,17 @@
                 // the current scroll offset to avoid the scroll jumping later
                 // to show it.
                 if ((initialScrollY >= 0 && initialScrollY != widget.getScrollY()) ||
-                        (initialScrollX >= 0 && initialScrollX != widget.getScrollX())) {
+                    (initialScrollX >= 0 && initialScrollX != widget.getScrollX())) {
                     widget.moveCursorToVisibleOffset();
                     return true;
                 }
 
-                int x = (int) event.getX();
-                int y = (int) event.getY();
-                int off = getOffset(x, y, widget);
-
-                // XXX should do the same adjust for x as we do for the line.
-
-                OnePointFiveTapState[] onepointfivetap = buffer.getSpans(0, buffer.length(),
-                    OnePointFiveTapState.class);
-                if (onepointfivetap.length > 0 && onepointfivetap[0].active &&
-                    Selection.getSelectionStart(buffer) == Selection.getSelectionEnd(buffer)) {
-                    // If we've set select mode, because there was a onepointfivetap,
-                    // but there was no ensuing swipe gesture, undo the select mode
-                    // and remove reference to the last onepointfivetap.
-                    MetaKeyKeyListener.stopSelecting(widget, buffer);
-                    for (int i=0; i < onepointfivetap.length; i++) {
-                        buffer.removeSpan(onepointfivetap[i]);
-                    }
+                int offset = widget.getOffset((int) event.getX(), (int) event.getY());
+                if (isCap(buffer)) {
                     buffer.removeSpan(LAST_TAP_DOWN);
-                }
-                boolean cap = (MetaKeyKeyListener.getMetaState(buffer,
-                                KeyEvent.META_SHIFT_ON) == 1) ||
-                              (MetaKeyKeyListener.getMetaState(buffer,
-                                MetaKeyKeyListener.META_SELECTING) != 0);
-
-                DoubleTapState[] tap = buffer.getSpans(0, buffer.length(),
-                                                       DoubleTapState.class);
-                boolean doubletap = false;
-
-                if (tap.length > 0) {
-                    if (event.getEventTime() - tap[0].mWhen <=
-                        ViewConfiguration.getDoubleTapTimeout() &&
-                        sameWord(buffer, off, Selection.getSelectionEnd(buffer))) {
-
-                        doubletap = true;
-                    }
-
-                    tap[0].mWhen = event.getEventTime();
+                    Selection.extendSelection(buffer, offset);
                 } else {
-                    DoubleTapState newtap = new DoubleTapState();
-                    newtap.mWhen = event.getEventTime();
-                    buffer.setSpan(newtap, 0, buffer.length(),
-                                   Spannable.SPAN_INCLUSIVE_INCLUSIVE);
-                }
-
-                if (cap) {
-                    buffer.removeSpan(LAST_TAP_DOWN);
-                    if (onepointfivetap.length > 0 && onepointfivetap[0].active) {
-                        // If we selecting something with the onepointfivetap-and
-                        // swipe gesture, stop it on finger up.
-                        MetaKeyKeyListener.stopSelecting(widget, buffer);
-                    } else {
-                        Selection.extendSelection(buffer, off);
-                    }
-                } else if (doubletap) {
-                    Selection.setSelection(buffer,
-                                           findWordStart(buffer, off),
-                                           findWordEnd(buffer, off));
-                } else {
-                    Selection.setSelection(buffer, off);
+                    Selection.setSelection(buffer, offset);
                 }
 
                 MetaKeyKeyListener.adjustMetaAfterKeypress(buffer);
@@ -420,73 +278,36 @@
         return handled;
     }
 
-    private static class DoubleTapState implements NoCopySpan {
-        long mWhen;
-    }
+    private boolean onTouchEventCursor(TextView widget, Spannable buffer, MotionEvent event) {
+        if (widget.isFocused() && !widget.didTouchFocusSelect()) {
+            switch (event.getActionMasked()) {
+                case MotionEvent.ACTION_MOVE:
+                    widget.cancelLongPress();
 
-    /* We check for a onepointfive tap. This is similar to
-    *  doubletap gesture (where a finger goes down, up, down, up, in a short
-    *  time period), except in the onepointfive tap, a users finger only needs
-    *  to go down, up, down in a short time period. We detect this type of tap
-    *  to implement the onepointfivetap-and-swipe selection gesture.
-    *  This gesture allows users to select a segment of text without going
-    *  through the "select text" option in the context menu.
-    */
-    private static class OnePointFiveTapState implements NoCopySpan {
-        long mWhen;
-        boolean active;
-    }
+                    // Offset the current touch position (from controller to cursor)
+                    final float x = event.getX() + mCursorController.getOffsetX();
+                    final float y = event.getY() + mCursorController.getOffsetY();
+                    int offset = widget.getOffset((int) x, (int) y);
+                    mCursorController.updatePosition(offset);
+                    return true;
 
-    private static boolean sameWord(CharSequence text, int one, int two) {
-        int start = findWordStart(text, one);
-        int end = findWordEnd(text, one);
-
-        if (end == start) {
-            return false;
-        }
-
-        return start == findWordStart(text, two) &&
-               end == findWordEnd(text, two);
-    }
-
-    // TODO: Unify with TextView.getWordForDictionary()
-    private static int findWordStart(CharSequence text, int start) {
-        for (; start > 0; start--) {
-            char c = text.charAt(start - 1);
-            int type = Character.getType(c);
-
-            if (c != '\'' &&
-                type != Character.UPPERCASE_LETTER &&
-                type != Character.LOWERCASE_LETTER &&
-                type != Character.TITLECASE_LETTER &&
-                type != Character.MODIFIER_LETTER &&
-                type != Character.DECIMAL_DIGIT_NUMBER) {
-                break;
+                case MotionEvent.ACTION_UP:
+                case MotionEvent.ACTION_CANCEL:
+                    mCursorController = null;
+                    return true;
             }
         }
-
-        return start;
+        return false;
     }
 
-    // TODO: Unify with TextView.getWordForDictionary()
-    private static int findWordEnd(CharSequence text, int end) {
-        int len = text.length();
-
-        for (; end < len; end++) {
-            char c = text.charAt(end);
-            int type = Character.getType(c);
-
-            if (c != '\'' &&
-                type != Character.UPPERCASE_LETTER &&
-                type != Character.LOWERCASE_LETTER &&
-                type != Character.TITLECASE_LETTER &&
-                type != Character.MODIFIER_LETTER &&
-                type != Character.DECIMAL_DIGIT_NUMBER) {
-                break;
-            }
-        }
-
-        return end;
+    /**
+     * Defines the cursor controller.
+     *
+     * When set, this object can be used to handle events, that can be translated in cursor updates.
+     * @param cursorController A cursor controller implementation
+     */
+    public void setCursorController(CursorController cursorController) {
+        mCursorController = cursorController;
     }
 
     public boolean canSelectArbitrarily() {
@@ -525,8 +346,9 @@
     }
 
     public static MovementMethod getInstance() {
-        if (sInstance == null)
+        if (sInstance == null) {
             sInstance = new ArrowKeyMovementMethod();
+        }
 
         return sInstance;
     }
diff --git a/core/java/android/text/method/Touch.java b/core/java/android/text/method/Touch.java
index 42ad10e..3b98fc3 100644
--- a/core/java/android/text/method/Touch.java
+++ b/core/java/android/text/method/Touch.java
@@ -17,14 +17,13 @@
 package android.text.method;
 
 import android.text.Layout;
-import android.text.NoCopySpan;
 import android.text.Layout.Alignment;
+import android.text.NoCopySpan;
 import android.text.Spannable;
-import android.util.Log;
+import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.ViewConfiguration;
 import android.widget.TextView;
-import android.view.KeyEvent;
 
 public class Touch {
     private Touch() { }
@@ -45,6 +44,7 @@
         int left = Integer.MAX_VALUE;
         int right = 0;
         Alignment a = null;
+        boolean ltr = true;
 
         for (int i = top; i <= bottom; i++) {
             left = (int) Math.min(left, layout.getLineLeft(i));
@@ -52,6 +52,7 @@
 
             if (a == null) {
                 a = layout.getParagraphAlignment(i);
+                ltr = layout.getParagraphDirection(i) > 0;
             }
         }
 
@@ -59,10 +60,12 @@
         int width = widget.getWidth();
         int diff = 0;
 
+        // align_opposite does NOT mean align_right, we need the paragraph
+        // direction to resolve it to left or right
         if (right - left < width - padding) {
             if (a == Alignment.ALIGN_CENTER) {
                 diff = (width - padding - (right - left)) / 2;
-            } else if (a == Alignment.ALIGN_OPPOSITE) {
+            } else if (ltr == (a == Alignment.ALIGN_OPPOSITE)) {
                 diff = width - padding - (right - left);
             }
         }
@@ -99,7 +102,7 @@
                                        MotionEvent event) {
         DragState[] ds;
 
-        switch (event.getAction()) {
+        switch (event.getActionMasked()) {
         case MotionEvent.ACTION_DOWN:
             ds = buffer.getSpans(0, buffer.length(), DragState.class);
 
diff --git a/core/java/android/util/CharsetUtils.java b/core/java/android/util/CharsetUtils.java
index 9d91aca..a763a69 100644
--- a/core/java/android/util/CharsetUtils.java
+++ b/core/java/android/util/CharsetUtils.java
@@ -17,36 +17,58 @@
 package android.util;
 
 import android.os.Build;
+import android.text.TextUtils;
 
 import java.nio.charset.Charset;
 import java.nio.charset.IllegalCharsetNameException;
 import java.nio.charset.UnsupportedCharsetException;
+import java.util.HashMap;
+import java.util.Map;
 
 /**
+ * <p>
  * A class containing utility methods related to character sets. This
  * class is primarily useful for code that wishes to be vendor-aware
- * in its interpretation of Japanese encoding names.
- * 
- * <p>As of this writing, the only vendor that is recognized by this
- * class is Docomo (identified case-insensitively as {@code "docomo"}).</p>
- * 
- * <b>Note:</b> This class is hidden in Cupcake, with a plan to
- * un-hide in Donut. This was done because the first deployment to use
- * this code is based on Cupcake, but the API had to be introduced
- * after the public API freeze for that release. The upshot is that
- * only system applications can safely use this class until Donut is
- * available.
- * 
+ * in its interpretation of Japanese charset names (used in DoCoMo,
+ * KDDI, and SoftBank).
+ * </p>
+ *
+ * <p>
+ * <b>Note:</b> Developers will need to add an appropriate mapping for
+ * each vendor-specific charset. You may need to modify the C libraries
+ * like icu4c in order to let Android support an additional charset.
+ * </p>
+ *
  * @hide
  */
 public final class CharsetUtils {
     /**
-     * name of the vendor "Docomo". <b>Note:</b> This isn't a public
+     * name of the vendor "DoCoMo". <b>Note:</b> This isn't a public
      * constant, in order to keep this class from becoming a de facto
      * reference list of vendor names.
      */
     private static final String VENDOR_DOCOMO = "docomo";
-    
+    /**
+     * Name of the vendor "KDDI".
+     */
+    private static final String VENDOR_KDDI = "kddi";
+    /**
+     * Name of the vendor "SoftBank".
+     */
+    private static final String VENDOR_SOFTBANK = "softbank";
+
+    /**
+     * Represents one-to-one mapping from a vendor name to a charset specific to the vendor.
+     */
+    private static final Map<String, String> sVendorShiftJisMap = new HashMap<String, String>();
+
+    static {
+        // These variants of Shift_JIS come from icu's mapping data (convrtrs.txt)
+        sVendorShiftJisMap.put(VENDOR_DOCOMO, "docomo-shift_jis-2007");
+        sVendorShiftJisMap.put(VENDOR_KDDI, "kddi-shift_jis-2007");
+        sVendorShiftJisMap.put(VENDOR_SOFTBANK, "softbank-shift_jis-2007");
+    }
+
     /**
      * This class is uninstantiable.
      */
@@ -58,20 +80,22 @@
      * Returns the name of the vendor-specific character set
      * corresponding to the given original character set name and
      * vendor. If there is no vendor-specific character set for the
-     * given name/vendor pair, this returns the original character set
-     * name. The vendor name is matched case-insensitively.
-     * 
+     * given name/vendor pair, this returns the original character set name.
+     *
      * @param charsetName the base character set name
-     * @param vendor the vendor to specialize for
+     * @param vendor the vendor to specialize for. All characters should be lower-cased.
      * @return the specialized character set name, or {@code charsetName} if
      * there is no specialized name
      */
     public static String nameForVendor(String charsetName, String vendor) {
-        // TODO: Eventually, this may want to be table-driven.
-
-        if (vendor.equalsIgnoreCase(VENDOR_DOCOMO)
-                && isShiftJis(charsetName)) {
-            return "docomo-shift_jis-2007";
+        if (!TextUtils.isEmpty(charsetName) && !TextUtils.isEmpty(vendor)) {
+            // You can add your own mapping here.
+            if (isShiftJis(charsetName)) {
+                final String vendorShiftJis = sVendorShiftJisMap.get(vendor);
+                if (vendorShiftJis != null) {
+                    return vendorShiftJis;
+                }
+            }
         }
 
         return charsetName;
diff --git a/core/java/android/util/Patterns.java b/core/java/android/util/Patterns.java
index 5cbfd29..3bcd266 100644
--- a/core/java/android/util/Patterns.java
+++ b/core/java/android/util/Patterns.java
@@ -25,7 +25,7 @@
 public class Patterns {
     /**
      *  Regular expression to match all IANA top-level domains.
-     *  List accurate as of 2010/02/05.  List taken from:
+     *  List accurate as of 2010/05/06.  List taken from:
      *  http://data.iana.org/TLD/tlds-alpha-by-domain.txt
      *  This pattern is auto-generated by frameworks/base/common/tools/make-iana-tld-pattern.py
      */
@@ -53,8 +53,8 @@
         + "|u[agksyz]"
         + "|v[aceginu]"
         + "|w[fs]"
-        + "|(xn\\-\\-0zwm56d|xn\\-\\-11b5bs3a9aj6g|xn\\-\\-80akhbyknj4f|xn\\-\\-9t4b11yi5a|xn\\-\\-deba0ad|xn\\-\\-g6w251d|xn\\-\\-hgbk6aj7f53bba|xn\\-\\-hlcj6aya9esc7a|xn\\-\\-jxalpdlp|xn\\-\\-kgbechtv|xn\\-\\-zckzah)"
-        + "|y[etu]"
+        + "|(xn\\-\\-0zwm56d|xn\\-\\-11b5bs3a9aj6g|xn\\-\\-80akhbyknj4f|xn\\-\\-9t4b11yi5a|xn\\-\\-deba0ad|xn\\-\\-g6w251d|xn\\-\\-hgbk6aj7f53bba|xn\\-\\-hlcj6aya9esc7a|xn\\-\\-jxalpdlp|xn\\-\\-kgbechtv|xn\\-\\-mgbaam7a8h|xn\\-\\-mgberp4a5d4ar|xn\\-\\-wgbh1c|xn\\-\\-zckzah)"
+        + "|y[et]"
         + "|z[amw])";
 
     /**
@@ -65,7 +65,7 @@
 
     /**
      *  Regular expression to match all IANA top-level domains for WEB_URL.
-     *  List accurate as of 2010/02/05.  List taken from:
+     *  List accurate as of 2010/05/06.  List taken from:
      *  http://data.iana.org/TLD/tlds-alpha-by-domain.txt
      *  This pattern is auto-generated by frameworks/base/common/tools/make-iana-tld-pattern.py
      */
@@ -94,8 +94,8 @@
         + "|u[agksyz]"
         + "|v[aceginu]"
         + "|w[fs]"
-        + "|(?:xn\\-\\-0zwm56d|xn\\-\\-11b5bs3a9aj6g|xn\\-\\-80akhbyknj4f|xn\\-\\-9t4b11yi5a|xn\\-\\-deba0ad|xn\\-\\-g6w251d|xn\\-\\-hgbk6aj7f53bba|xn\\-\\-hlcj6aya9esc7a|xn\\-\\-jxalpdlp|xn\\-\\-kgbechtv|xn\\-\\-zckzah)"
-        + "|y[etu]"
+        + "|(?:xn\\-\\-0zwm56d|xn\\-\\-11b5bs3a9aj6g|xn\\-\\-80akhbyknj4f|xn\\-\\-9t4b11yi5a|xn\\-\\-deba0ad|xn\\-\\-g6w251d|xn\\-\\-hgbk6aj7f53bba|xn\\-\\-hlcj6aya9esc7a|xn\\-\\-jxalpdlp|xn\\-\\-kgbechtv|xn\\-\\-mgbaam7a8h|xn\\-\\-mgberp4a5d4ar|xn\\-\\-wgbh1c|xn\\-\\-zckzah)"
+        + "|y[et]"
         + "|z[amw]))";
 
     /**
diff --git a/core/java/android/util/SparseArray.java b/core/java/android/util/SparseArray.java
index 1c8b330..7fc43b9 100644
--- a/core/java/android/util/SparseArray.java
+++ b/core/java/android/util/SparseArray.java
@@ -90,6 +90,16 @@
         delete(key);
     }
 
+    /**
+     * Removes the mapping at the specified index.
+     */
+    public void removeAt(int index) {
+        if (mValues[index] != DELETED) {
+            mValues[index] = DELETED;
+            mGarbage = true;
+        }
+    }
+    
     private void gc() {
         // Log.e("SparseArray", "gc start with " + mSize);
 
diff --git a/core/java/android/view/AbsSavedState.java b/core/java/android/view/AbsSavedState.java
index 840d7c1..6ad33dd 100644
--- a/core/java/android/view/AbsSavedState.java
+++ b/core/java/android/view/AbsSavedState.java
@@ -54,7 +54,7 @@
      */
     protected AbsSavedState(Parcel source) {
         // FIXME need class loader
-        Parcelable superState = (Parcelable) source.readParcelable(null);
+        Parcelable superState = source.readParcelable(null);
          
         mSuperState = superState != null ? superState : EMPTY_STATE;
     }
@@ -75,7 +75,7 @@
         = new Parcelable.Creator<AbsSavedState>() {
         
         public AbsSavedState createFromParcel(Parcel in) {
-            Parcelable superState = (Parcelable) in.readParcelable(null);
+            Parcelable superState = in.readParcelable(null);
             if (superState != null) {
                 throw new IllegalStateException("superState must be null");
             }
diff --git a/core/java/android/view/ActionMode.java b/core/java/android/view/ActionMode.java
new file mode 100644
index 0000000..bfafa98
--- /dev/null
+++ b/core/java/android/view/ActionMode.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+
+/**
+ * Represents a contextual mode of the user interface. Action modes can be used for
+ * modal interactions with content and replace parts of the normal UI until finished.
+ * Examples of good action modes include selection modes, search, content editing, etc.
+ */
+public abstract class ActionMode {
+    /**
+     * Set the title of the action mode. This method will have no visible effect if
+     * a custom view has been set.
+     *
+     * @param title Title string to set
+     *
+     * @see #setTitle(int)
+     * @see #setCustomView(View)
+     */
+    public abstract void setTitle(CharSequence title);
+
+    /**
+     * Set the title of the action mode. This method will have no visible effect if
+     * a custom view has been set.
+     *
+     * @param resId Resource ID of a string to set as the title
+     *
+     * @see #setTitle(CharSequence)
+     * @see #setCustomView(View)
+     */
+    public abstract void setTitle(int resId);
+
+    /**
+     * Set the subtitle of the action mode. This method will have no visible effect if
+     * a custom view has been set.
+     *
+     * @param subtitle Subtitle string to set
+     *
+     * @see #setSubtitle(int)
+     * @see #setCustomView(View)
+     */
+    public abstract void setSubtitle(CharSequence subtitle);
+
+    /**
+     * Set the subtitle of the action mode. This method will have no visible effect if
+     * a custom view has been set.
+     *
+     * @param resId Resource ID of a string to set as the subtitle
+     *
+     * @see #setSubtitle(CharSequence)
+     * @see #setCustomView(View)
+     */
+    public abstract void setSubtitle(int resId);
+
+    /**
+     * Set a custom view for this action mode. The custom view will take the place of
+     * the title and subtitle. Useful for things like search boxes.
+     *
+     * @param view Custom view to use in place of the title/subtitle.
+     *
+     * @see #setTitle(CharSequence)
+     * @see #setSubtitle(CharSequence)
+     */
+    public abstract void setCustomView(View view);
+
+    /**
+     * Invalidate the action mode and refresh menu content. The mode's
+     * {@link ActionMode.Callback} will have its
+     * {@link Callback#onPrepareActionMode(ActionMode, Menu)} method called.
+     * If it returns true the menu will be scanned for updated content and any relevant changes
+     * will be reflected to the user.
+     */
+    public abstract void invalidate();
+
+    /**
+     * Finish and close this action mode. The action mode's {@link ActionMode.Callback} will
+     * have its {@link Callback#onDestroyActionMode(ActionMode)} method called.
+     */
+    public abstract void finish();
+
+    /**
+     * Returns the menu of actions that this action mode presents.
+     * @return The action mode's menu.
+     */
+    public abstract Menu getMenu();
+
+    /**
+     * Returns the current title of this action mode.
+     * @return Title text
+     */
+    public abstract CharSequence getTitle();
+
+    /**
+     * Returns the current subtitle of this action mode.
+     * @return Subtitle text
+     */
+    public abstract CharSequence getSubtitle();
+
+    /**
+     * Returns the current custom view for this action mode.
+     * @return The current custom view
+     */
+    public abstract View getCustomView();
+
+    /**
+     * Returns a {@link MenuInflater} with the ActionMode's context.
+     */
+    public abstract MenuInflater getMenuInflater();
+
+    /**
+     * Callback interface for action modes. Supplied to
+     * {@link View#startActionMode(Callback)}, a Callback
+     * configures and handles events raised by a user's interaction with an action mode.
+     *
+     * <p>An action mode's lifecycle is as follows:
+     * <ul>
+     * <li>{@link Callback#onCreateActionMode(ActionMode, Menu)} once on initial
+     * creation</li>
+     * <li>{@link Callback#onPrepareActionMode(ActionMode, Menu)} after creation
+     * and any time the {@link ActionMode} is invalidated</li>
+     * <li>{@link Callback#onActionItemClicked(ActionMode, MenuItem)} any time a
+     * contextual action button is clicked</li>
+     * <li>{@link Callback#onDestroyActionMode(ActionMode)} when the action mode
+     * is closed</li>
+     * </ul>
+     */
+    public interface Callback {
+        /**
+         * Called when action mode is first created. The menu supplied will be used to
+         * generate action buttons for the action mode.
+         *
+         * @param mode ActionMode being created
+         * @param menu Menu used to populate action buttons
+         * @return true if the action mode should be created, false if entering this
+         *              mode should be aborted.
+         */
+        public boolean onCreateActionMode(ActionMode mode, Menu menu);
+
+        /**
+         * Called to refresh an action mode's action menu whenever it is invalidated.
+         *
+         * @param mode ActionMode being prepared
+         * @param menu Menu used to populate action buttons
+         * @return true if the menu or action mode was updated, false otherwise.
+         */
+        public boolean onPrepareActionMode(ActionMode mode, Menu menu);
+
+        /**
+         * Called to report a user click on an action button.
+         *
+         * @param mode The current ActionMode
+         * @param item The item that was clicked
+         * @return true if this callback handled the event, false if the standard MenuItem
+         *          invocation should continue.
+         */
+        public boolean onActionItemClicked(ActionMode mode, MenuItem item);
+
+        /**
+         * Called when an action mode is about to be exited and destroyed.
+         *
+         * @param mode The current ActionMode being destroyed
+         */
+        public void onDestroyActionMode(ActionMode mode);
+    }
+}
\ No newline at end of file
diff --git a/core/java/android/view/GLES20Canvas.java b/core/java/android/view/GLES20Canvas.java
new file mode 100644
index 0000000..63a0c4c
--- /dev/null
+++ b/core/java/android/view/GLES20Canvas.java
@@ -0,0 +1,712 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.DrawFilter;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.Picture;
+import android.graphics.PorterDuff;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.Region;
+import android.graphics.Shader;
+import android.graphics.TemporaryBuffer;
+import android.text.GraphicsOperations;
+import android.text.SpannableString;
+import android.text.SpannedString;
+import android.text.TextUtils;
+
+import javax.microedition.khronos.opengles.GL;
+
+/**
+ * An implementation of Canvas on top of OpenGL ES 2.0.
+ */
+@SuppressWarnings({"deprecation"})
+class GLES20Canvas extends Canvas {
+    @SuppressWarnings({"FieldCanBeLocal", "UnusedDeclaration"})
+    private final GL mGl;
+    private final boolean mOpaque;
+    private final int mRenderer;
+    
+    private int mWidth;
+    private int mHeight;
+    
+    private final float[] mPoint = new float[2];
+    private final float[] mLine = new float[4];
+    
+    private final Rect mClipBounds = new Rect();
+
+    private DrawFilter mFilter;
+
+    ///////////////////////////////////////////////////////////////////////////
+    // Constructors
+    ///////////////////////////////////////////////////////////////////////////
+    
+    GLES20Canvas(GL gl, boolean translucent) {
+        mGl = gl;
+        mOpaque = !translucent;
+
+        mRenderer = nCreateRenderer();
+    }
+
+    private native int nCreateRenderer();
+
+    @Override
+    protected void finalize() throws Throwable {
+        try {
+            super.finalize();
+        } finally {
+            nDestroyRenderer(mRenderer);
+        }
+    }
+
+    private native void nDestroyRenderer(int renderer);
+
+    ///////////////////////////////////////////////////////////////////////////
+    // Canvas management
+    ///////////////////////////////////////////////////////////////////////////
+    
+    @Override
+    public boolean isHardwareAccelerated() {
+        return true;
+    }
+
+    @Override
+    public GL getGL() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void setBitmap(Bitmap bitmap) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean isOpaque() {
+        return mOpaque;
+    }
+
+    @Override
+    public int getWidth() {
+        return mWidth;
+    }
+
+    @Override
+    public int getHeight() {
+        return mHeight;
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // Setup
+    ///////////////////////////////////////////////////////////////////////////
+
+    @Override
+    public void setViewport(int width, int height) {
+        mWidth = width;
+        mHeight = height;
+
+        nSetViewport(mRenderer, width, height);
+    }
+    
+    private native void nSetViewport(int renderer, int width, int height);
+
+    void onPreDraw() {
+        nPrepare(mRenderer);
+    }
+    
+    private native void nPrepare(int renderer);
+
+    ///////////////////////////////////////////////////////////////////////////
+    // Clipping
+    ///////////////////////////////////////////////////////////////////////////
+
+    @Override
+    public boolean clipPath(Path path) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean clipPath(Path path, Region.Op op) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean clipRect(float left, float top, float right, float bottom) {
+        return nClipRect(mRenderer, left, top, right, bottom, Region.Op.INTERSECT.nativeInt);
+    }
+    
+    private native boolean nClipRect(int renderer, float left, float top,
+            float right, float bottom, int op);
+
+    @Override
+    public boolean clipRect(float left, float top, float right, float bottom, Region.Op op) {
+        return nClipRect(mRenderer, left, top, right, bottom, op.nativeInt);
+    }
+
+    @Override
+    public boolean clipRect(int left, int top, int right, int bottom) {
+        return nClipRect(mRenderer, left, top, right, bottom, Region.Op.INTERSECT.nativeInt);        
+    }
+    
+    private native boolean nClipRect(int renderer, int left, int top, int right, int bottom, int op);
+
+    @Override
+    public boolean clipRect(Rect rect) {
+        return nClipRect(mRenderer, rect.left, rect.top, rect.right, rect.bottom,
+                Region.Op.INTERSECT.nativeInt);        
+    }
+
+    @Override
+    public boolean clipRect(Rect rect, Region.Op op) {
+        return nClipRect(mRenderer, rect.left, rect.top, rect.right, rect.bottom, op.nativeInt);
+    }
+
+    @Override
+    public boolean clipRect(RectF rect) {
+        return nClipRect(mRenderer, rect.left, rect.top, rect.right, rect.bottom,
+                Region.Op.INTERSECT.nativeInt);
+    }
+
+    @Override
+    public boolean clipRect(RectF rect, Region.Op op) {
+        return nClipRect(mRenderer, rect.left, rect.top, rect.right, rect.bottom, op.nativeInt);
+    }
+
+    @Override
+    public boolean clipRegion(Region region) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean clipRegion(Region region, Region.Op op) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean getClipBounds(Rect bounds) {
+        return nGetClipBounds(mRenderer, bounds);
+    }
+
+    private native boolean nGetClipBounds(int renderer, Rect bounds);
+
+    @Override
+    public boolean quickReject(float left, float top, float right, float bottom, EdgeType type) {
+        return nQuickReject(mRenderer, left, top, right, bottom, type.nativeInt);
+    }
+    
+    private native boolean nQuickReject(int renderer, float left, float top,
+            float right, float bottom, int edge);
+
+    @Override
+    public boolean quickReject(Path path, EdgeType type) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean quickReject(RectF rect, EdgeType type) {
+        return quickReject(rect.left, rect.top, rect.right, rect.bottom, type);
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // Transformations
+    ///////////////////////////////////////////////////////////////////////////
+
+    @Override
+    public void translate(float dx, float dy) {
+        nTranslate(mRenderer, dx, dy);
+    }
+    
+    private native void nTranslate(int renderer, float dx, float dy);
+
+    @Override
+    public void skew(float sx, float sy) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void rotate(float degrees) {
+        nRotate(mRenderer, degrees);
+    }
+    
+    private native void nRotate(int renderer, float degrees);
+
+    @Override
+    public void scale(float sx, float sy) {
+        nScale(mRenderer, sx, sy);
+    }
+    
+    private native void nScale(int renderer, float sx, float sy);
+
+    @Override
+    public void setMatrix(Matrix matrix) {
+        nSetMatrix(mRenderer, matrix.native_instance);
+    }
+    
+    private native void nSetMatrix(int renderer, int matrix);
+
+    @Override
+    public void getMatrix(Matrix matrix) {
+        nGetMatrix(mRenderer, matrix.native_instance);
+    }
+    
+    private native void nGetMatrix(int renderer, int matrix);
+
+    @Override
+    public void concat(Matrix matrix) {
+        nConcatMatrix(mRenderer, matrix.native_instance);
+    }
+    
+    private native void nConcatMatrix(int renderer, int matrix);
+    
+    ///////////////////////////////////////////////////////////////////////////
+    // State management
+    ///////////////////////////////////////////////////////////////////////////
+
+    @Override
+    public int save() {
+        return nSave(mRenderer, 0);
+    }
+    
+    @Override
+    public int save(int saveFlags) {
+        return nSave(mRenderer, saveFlags);
+    }
+
+    private native int nSave(int renderer, int flags);
+    
+    @Override
+    public int saveLayer(RectF bounds, Paint paint, int saveFlags) {
+        return saveLayer(bounds.left, bounds.top, bounds.right, bounds.bottom, paint, saveFlags);
+    }
+
+    @Override
+    public int saveLayer(float left, float top, float right, float bottom, Paint paint,
+            int saveFlags) {
+        int nativePaint = paint == null ? 0 : paint.mNativePaint;
+        return nSaveLayer(mRenderer, left, top, right, bottom, nativePaint, saveFlags);
+    }
+
+    private native int nSaveLayer(int renderer, float left, float top, float right, float bottom,
+            int paint, int saveFlags);
+
+    @Override
+    public int saveLayerAlpha(RectF bounds, int alpha, int saveFlags) {
+        return saveLayerAlpha(bounds.left, bounds.top, bounds.right, bounds.bottom,
+                alpha, saveFlags);
+    }
+
+    @Override
+    public int saveLayerAlpha(float left, float top, float right, float bottom, int alpha,
+            int saveFlags) {
+        return nSaveLayerAlpha(mRenderer, left, top, right, bottom, alpha, saveFlags);
+    }
+
+    private native int nSaveLayerAlpha(int renderer, float left, float top, float right,
+            float bottom, int alpha, int saveFlags);
+    
+    @Override
+    public void restore() {
+        nRestore(mRenderer);
+    }
+    
+    private native void nRestore(int renderer);
+
+    @Override
+    public void restoreToCount(int saveCount) {
+        nRestoreToCount(mRenderer, saveCount);
+    }
+
+    private native void nRestoreToCount(int renderer, int saveCount);
+    
+    @Override
+    public int getSaveCount() {
+        return nGetSaveCount(mRenderer);
+    }
+    
+    private native int nGetSaveCount(int renderer);
+
+    ///////////////////////////////////////////////////////////////////////////
+    // Filtering
+    ///////////////////////////////////////////////////////////////////////////
+
+    @Override
+    public void setDrawFilter(DrawFilter filter) {
+        // TODO: Implement PaintDrawFilter
+        mFilter = filter;
+    }
+
+    @Override
+    public DrawFilter getDrawFilter() {
+        return mFilter;
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // Drawing
+    ///////////////////////////////////////////////////////////////////////////
+
+    @Override
+    public void drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter,
+            Paint paint) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void drawARGB(int a, int r, int g, int b) {
+        drawColor((a & 0xFF) << 24 | (r & 0xFF) << 16 | (g & 0xFF) << 8 | (b & 0xFF));
+    }
+
+    @Override
+    public void drawPatch(Bitmap bitmap, byte[] chunks, RectF dst, Paint paint) {
+        // Shaders are ignored when drawing patches
+        boolean hasColorFilter = paint != null && setupColorFilter(paint);
+        final int nativePaint = paint == null ? 0 : paint.mNativePaint;
+        nDrawPatch(mRenderer, bitmap.mNativeBitmap, chunks, dst.left, dst.top,
+                dst.right, dst.bottom, nativePaint);
+        if (hasColorFilter) nResetModifiers(mRenderer);
+    }
+
+    private native void nDrawPatch(int renderer, int bitmap, byte[] chunks, float left, float top,
+            float right, float bottom, int paint);
+
+    @Override
+    public void drawBitmap(Bitmap bitmap, float left, float top, Paint paint) {
+        // Shaders are ignored when drawing bitmaps
+        boolean hasColorFilter = paint != null && setupColorFilter(paint);
+        final int nativePaint = paint == null ? 0 : paint.mNativePaint;
+        nDrawBitmap(mRenderer, bitmap.mNativeBitmap, left, top, nativePaint);
+        if (hasColorFilter) nResetModifiers(mRenderer);
+    }
+
+    private native void nDrawBitmap(int renderer, int bitmap, float left, float top, int paint);
+
+    @Override
+    public void drawBitmap(Bitmap bitmap, Matrix matrix, Paint paint) {
+        // Shaders are ignored when drawing bitmaps
+        boolean hasColorFilter = paint != null && setupColorFilter(paint);
+        final int nativePaint = paint == null ? 0 : paint.mNativePaint;
+        nDrawBitmap(mRenderer, bitmap.mNativeBitmap, matrix.native_instance, nativePaint);
+        if (hasColorFilter) nResetModifiers(mRenderer);
+    }
+
+    private native void nDrawBitmap(int renderer, int bitmap, int matrix, int paint);
+
+    @Override
+    public void drawBitmap(Bitmap bitmap, Rect src, Rect dst, Paint paint) {
+        // Shaders are ignored when drawing bitmaps
+        boolean hasColorFilter = paint != null && setupColorFilter(paint);
+        final int nativePaint = paint == null ? 0 : paint.mNativePaint;
+
+        int left, top, right, bottom;
+        if (src == null) {
+            left = top = 0;
+            right = bitmap.getWidth();
+            bottom = bitmap.getHeight();
+        } else {
+            left = src.left;
+            right = src.right;
+            top = src.top;
+            bottom = src.bottom;
+        }
+
+        nDrawBitmap(mRenderer, bitmap.mNativeBitmap, left, top, right, bottom,
+                dst.left, dst.top, dst.right, dst.bottom, nativePaint);
+        if (hasColorFilter) nResetModifiers(mRenderer);
+    }
+
+    @Override
+    public void drawBitmap(Bitmap bitmap, Rect src, RectF dst, Paint paint) {
+        // Shaders are ignored when drawing bitmaps
+        boolean hasColorFilter = paint != null && setupColorFilter(paint);
+        final int nativePaint = paint == null ? 0 : paint.mNativePaint;
+        nDrawBitmap(mRenderer, bitmap.mNativeBitmap, src.left, src.top, src.right, src.bottom,
+                dst.left, dst.top, dst.right, dst.bottom, nativePaint);
+        if (hasColorFilter) nResetModifiers(mRenderer);
+    }
+
+    private native void nDrawBitmap(int renderer, int bitmap,
+            float srcLeft, float srcTop, float srcRight, float srcBottom,
+            float left, float top, float right, float bottom, int paint);
+
+    @Override
+    public void drawBitmap(int[] colors, int offset, int stride, float x, float y,
+            int width, int height, boolean hasAlpha, Paint paint) {
+        // Shaders are ignored when drawing bitmaps
+        boolean hasColorFilter = paint != null && setupColorFilter(paint);
+        final Bitmap.Config config = hasAlpha ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565;
+        final Bitmap b = Bitmap.createBitmap(colors, offset, stride, width, height, config);
+        final int nativePaint = paint == null ? 0 : paint.mNativePaint;
+        nDrawBitmap(mRenderer, b.mNativeBitmap, x, y, nativePaint);
+        b.recycle();
+        if (hasColorFilter) nResetModifiers(mRenderer);
+    }
+
+    @Override
+    public void drawBitmap(int[] colors, int offset, int stride, int x, int y,
+            int width, int height, boolean hasAlpha, Paint paint) {
+        // Shaders are ignored when drawing bitmaps
+        drawBitmap(colors, offset, stride, (float) x, (float) y, width, height, hasAlpha, paint);
+    }
+
+    @Override
+    public void drawBitmapMesh(Bitmap bitmap, int meshWidth, int meshHeight, float[] verts,
+            int vertOffset, int[] colors, int colorOffset, Paint paint) {
+        // TODO: Implement
+    }
+
+    @Override
+    public void drawCircle(float cx, float cy, float radius, Paint paint) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void drawColor(int color) {
+        drawColor(color, PorterDuff.Mode.SRC_OVER);
+    }
+
+    @Override
+    public void drawColor(int color, PorterDuff.Mode mode) {
+        nDrawColor(mRenderer, color, mode.nativeInt);
+    }
+    
+    private native void nDrawColor(int renderer, int color, int mode);
+
+    @Override
+    public void drawLine(float startX, float startY, float stopX, float stopY, Paint paint) {
+        mLine[0] = startX;
+        mLine[1] = startY;
+        mLine[2] = stopX;
+        mLine[3] = stopY;
+        drawLines(mLine, 0, 1, paint);
+    }
+
+    @Override
+    public void drawLines(float[] pts, int offset, int count, Paint paint) {
+        // TODO: Implement
+    }
+
+    @Override
+    public void drawLines(float[] pts, Paint paint) {
+        drawLines(pts, 0, pts.length / 4, paint);
+    }
+
+    @Override
+    public void drawOval(RectF oval, Paint paint) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void drawPaint(Paint paint) {
+        final Rect r = mClipBounds;
+        nGetClipBounds(mRenderer, r);
+        drawRect(r.left, r.top, r.right, r.bottom, paint);
+    }
+
+    @Override
+    public void drawPath(Path path, Paint paint) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void drawPicture(Picture picture) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void drawPicture(Picture picture, Rect dst) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void drawPicture(Picture picture, RectF dst) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void drawPoint(float x, float y, Paint paint) {
+        mPoint[0] = x;
+        mPoint[1] = y;
+        drawPoints(mPoint, 0, 1, paint);
+    }
+
+    @Override
+    public void drawPoints(float[] pts, int offset, int count, Paint paint) {
+        // TODO: Implement
+    }
+
+    @Override
+    public void drawPoints(float[] pts, Paint paint) {
+        drawPoints(pts, 0, pts.length / 2, paint);
+    }
+
+    @Override
+    public void drawPosText(char[] text, int index, int count, float[] pos, Paint paint) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void drawPosText(String text, float[] pos, Paint paint) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void drawRect(float left, float top, float right, float bottom, Paint paint) {
+        boolean hasModifier = setupModifiers(paint);
+        nDrawRect(mRenderer, left, top, right, bottom, paint.mNativePaint);
+        if (hasModifier) nResetModifiers(mRenderer);
+    }
+
+    private native void nDrawRect(int renderer, float left, float top, float right, float bottom,
+            int paint);
+
+    @Override
+    public void drawRect(Rect r, Paint paint) {
+        drawRect(r.left, r.top, r.right, r.bottom, paint);
+    }
+
+    @Override
+    public void drawRect(RectF r, Paint paint) {
+        drawRect(r.left, r.top, r.right, r.bottom, paint);
+    }
+
+    @Override
+    public void drawRGB(int r, int g, int b) {
+        drawColor(0xFF000000 | (r & 0xFF) << 16 | (g & 0xFF) << 8 | (b & 0xFF));
+    }
+
+    @Override
+    public void drawRoundRect(RectF rect, float rx, float ry, Paint paint) {
+        // TODO: Implement
+    }
+
+    @Override
+    public void drawText(char[] text, int index, int count, float x, float y, Paint paint) {
+        if ((index | count | (index + count) | (text.length - index - count)) < 0) {
+            throw new IndexOutOfBoundsException();
+        }
+        boolean hasModifier = setupModifiers(paint);
+        nDrawText(mRenderer, text, index, count, x, y, paint.mBidiFlags, paint.mNativePaint);
+        if (hasModifier) nResetModifiers(mRenderer);
+    }
+    
+    private native void nDrawText(int renderer, char[] text, int index, int count, float x, float y,
+            int bidiFlags, int paint);
+
+    @Override
+    public void drawText(CharSequence text, int start, int end, float x, float y, Paint paint) {
+        boolean hasModifier = setupModifiers(paint);
+        if (text instanceof String || text instanceof SpannedString ||
+                text instanceof SpannableString) {
+            nDrawText(mRenderer, text.toString(), start, end, x, y, paint.mBidiFlags,
+                    paint.mNativePaint);
+        } else if (text instanceof GraphicsOperations) {
+            ((GraphicsOperations) text).drawText(this, start, end, x, y,
+                                                     paint);
+        } else {
+            char[] buf = TemporaryBuffer.obtain(end - start);
+            TextUtils.getChars(text, start, end, buf, 0);
+            nDrawText(mRenderer, buf, 0, end - start, x, y, paint.mBidiFlags, paint.mNativePaint);
+            TemporaryBuffer.recycle(buf);
+        }
+        if (hasModifier) nResetModifiers(mRenderer);
+    }
+
+    @Override
+    public void drawText(String text, int start, int end, float x, float y, Paint paint) {
+        if ((start | end | (end - start) | (text.length() - end)) < 0) {
+            throw new IndexOutOfBoundsException();
+        }
+        boolean hasModifier = setupModifiers(paint);
+        nDrawText(mRenderer, text, start, end, x, y, paint.mBidiFlags, paint.mNativePaint);
+        if (hasModifier) nResetModifiers(mRenderer);
+    }
+
+    private native void nDrawText(int renderer, String text, int start, int end, float x, float y,
+            int bidiFlags, int paint);
+
+    @Override
+    public void drawText(String text, float x, float y, Paint paint) {
+        boolean hasModifier = setupModifiers(paint);
+        nDrawText(mRenderer, text, 0, text.length(), x, y, paint.mBidiFlags, paint.mNativePaint);
+        if (hasModifier) nResetModifiers(mRenderer);
+    }
+
+    @Override
+    public void drawTextOnPath(char[] text, int index, int count, Path path, float hOffset,
+            float vOffset, Paint paint) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void drawTextOnPath(String text, Path path, float hOffset, float vOffset, Paint paint) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void drawTextRun(char[] text, int index, int count, int contextIndex, int contextCount,
+            float x, float y, int dir, Paint paint) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void drawTextRun(CharSequence text, int start, int end, int contextStart, int contextEnd,
+            float x, float y, int dir, Paint paint) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void drawVertices(VertexMode mode, int vertexCount, float[] verts, int vertOffset,
+            float[] texs, int texOffset, int[] colors, int colorOffset, short[] indices,
+            int indexOffset, int indexCount, Paint paint) {
+        // TODO: Implement
+    }
+
+    private boolean setupModifiers(Paint paint) {
+        boolean hasModifier = false;
+
+        final Shader shader = paint.getShader();
+        if (shader != null) {
+            nSetupShader(mRenderer, shader.native_shader);
+            hasModifier = true;
+        }
+
+        final ColorFilter filter = paint.getColorFilter();
+        if (filter != null) {
+            nSetupColorFilter(mRenderer, filter.nativeColorFilter);
+            hasModifier = true;
+        }
+
+        return hasModifier;
+    }
+    
+    private boolean setupColorFilter(Paint paint) {
+        final ColorFilter filter = paint.getColorFilter();
+        if (filter != null) {
+            nSetupColorFilter(mRenderer, filter.nativeColorFilter);
+            return true;
+        }
+        return false;        
+    }
+    
+    private native void nSetupShader(int renderer, int shader);
+    private native void nSetupColorFilter(int renderer, int colorFilter);
+    private native void nResetModifiers(int renderer);
+}
diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java
new file mode 100644
index 0000000..090a743
--- /dev/null
+++ b/core/java/android/view/HardwareRenderer.java
@@ -0,0 +1,562 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.view;
+
+import android.graphics.Canvas;
+import android.os.SystemClock;
+import android.util.Log;
+
+import javax.microedition.khronos.egl.EGL10;
+import javax.microedition.khronos.egl.EGL11;
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.egl.EGLContext;
+import javax.microedition.khronos.egl.EGLDisplay;
+import javax.microedition.khronos.egl.EGLSurface;
+import javax.microedition.khronos.opengles.GL;
+import javax.microedition.khronos.opengles.GL11;
+
+import static javax.microedition.khronos.opengles.GL10.GL_COLOR_BUFFER_BIT;
+import static javax.microedition.khronos.opengles.GL10.GL_SCISSOR_TEST;
+
+/**
+ * Interface for rendering a ViewRoot using hardware acceleration.
+ * 
+ * @hide
+ */
+abstract class HardwareRenderer {
+    private boolean mEnabled;
+    private boolean mRequested = true;
+    private static final String LOG_TAG = "HardwareRenderer";
+
+    /**
+     * Destroys the hardware rendering context.
+     */
+    abstract void destroy();
+
+    /**
+     * Initializes the hardware renderer for the specified surface.
+     * 
+     * @param holder The holder for the surface to hardware accelerate.
+     * 
+     * @return True if the initialization was successful, false otherwise.
+     */
+    abstract boolean initialize(SurfaceHolder holder);
+
+    /**
+     * Setup the hardware renderer for drawing. This is called for every
+     * frame to draw.
+     * 
+     * @param width Width of the drawing surface.
+     * @param height Height of the drawing surface.
+     * @param attachInfo The AttachInfo used to render the ViewRoot. 
+     */
+    abstract void setup(int width, int height, View.AttachInfo attachInfo);
+
+    /**
+     * Draws the specified view.
+     * 
+     * @param view The view to draw.
+     * @param attachInfo AttachInfo tied to the specified view.
+     */
+    abstract void draw(View view, View.AttachInfo attachInfo, int yOffset);
+
+    /**
+     * Initializes the hardware renderer for the specified surface and setup the
+     * renderer for drawing, if needed. This is invoked when the ViewRoot has
+     * potentially lost the hardware renderer. The hardware renderer should be
+     * reinitialized and setup when the render {@link #isRequested()} and
+     * {@link #isEnabled()}.
+     * 
+     * @param width The width of the drawing surface.
+     * @param height The height of the drawing surface.
+     * @param attachInfo The 
+     * @param holder
+     */
+    void initializeIfNeeded(int width, int height, View.AttachInfo attachInfo,
+            SurfaceHolder holder) {
+
+        if (isRequested()) {
+            // We lost the gl context, so recreate it.
+            if (!isEnabled()) {
+                if (initialize(holder)) {
+                    setup(width, height, attachInfo);
+                }
+            }
+        }        
+    }
+
+    /**
+     * Creates a hardware renderer using OpenGL.
+     * 
+     * @param glVersion The version of OpenGL to use (1 for OpenGL 1, 11 for OpenGL 1.1, etc.)
+     * @param translucent True if the surface is translucent, false otherwise
+     * 
+     * @return A hardware renderer backed by OpenGL.
+     */
+    static HardwareRenderer createGlRenderer(int glVersion, boolean translucent) {
+        switch (glVersion) {
+            case 1:
+                return new Gl10Renderer(translucent);
+            case 2:
+                return new Gl20Renderer(translucent);
+        }
+        throw new IllegalArgumentException("Unknown GL version: " + glVersion);
+    }
+
+    /**
+     * Indicates whether hardware acceleration is currently enabled.
+     * 
+     * @return True if hardware acceleration is in use, false otherwise.
+     */
+    boolean isEnabled() {
+        return mEnabled;
+    }
+
+    /**
+     * Indicates whether hardware acceleration is currently enabled.
+     * 
+     * @param enabled True if the hardware renderer is in use, false otherwise.
+     */
+    void setEnabled(boolean enabled) {
+        mEnabled = enabled;
+    }
+
+    /**
+     * Indicates whether hardware acceleration is currently request but not
+     * necessarily enabled yet.
+     * 
+     * @return True if requested, false otherwise.
+     */
+    boolean isRequested() {
+        return mRequested;
+    }
+
+    /**
+     * Indicates whether hardware acceleration is currently request but not
+     * necessarily enabled yet.
+     * 
+     * @return True to request hardware acceleration, false otherwise.
+     */
+    void setRequested(boolean requested) {
+        mRequested = requested;
+    }
+
+    @SuppressWarnings({"deprecation"})
+    static abstract class GlRenderer extends HardwareRenderer {
+        private static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
+
+        EGL10 mEgl;
+        EGLDisplay mEglDisplay;
+        EGLContext mEglContext;
+        EGLSurface mEglSurface;
+        EGLConfig mEglConfig;
+
+        GL mGl;
+        Canvas mCanvas;
+
+        final int mGlVersion;
+        final boolean mTranslucent;
+
+        GlRenderer(int glVersion, boolean translucent) {
+            mGlVersion = glVersion;
+            mTranslucent = translucent;
+        }
+
+        /**
+         * Checks for OpenGL errors. If an error has occured, {@link #destroy()}
+         * is invoked and the requested flag is turned off. The error code is
+         * also logged as a warning.
+         */
+        void checkErrors() {
+            if (isEnabled()) {
+                int error = mEgl.eglGetError();
+                if (error != EGL10.EGL_SUCCESS) {
+                    // something bad has happened revert to
+                    // normal rendering.
+                    destroy();
+                    if (error != EGL11.EGL_CONTEXT_LOST) {
+                        // we'll try again if it was context lost
+                        setRequested(false);
+                    }
+                    Log.w(LOG_TAG, "OpenGL error: " + error);
+                }
+            }
+        }
+        
+        @Override
+        boolean initialize(SurfaceHolder holder) {
+            if (isRequested() && !isEnabled()) {
+                initializeEgl();
+                mGl = createEglSurface(holder);
+
+                if (mGl != null) {
+                    int err = mEgl.eglGetError();
+                    if (err != EGL10.EGL_SUCCESS) {
+                        destroy();
+                        setRequested(false);
+                    } else {
+                        mCanvas = createCanvas();
+                        if (mCanvas != null) {
+                            setEnabled(true);
+                        } else {
+                            Log.w(LOG_TAG, "Hardware accelerated Canvas could not be created");
+                        }
+                    }
+
+                    return mCanvas != null;
+                }
+            }
+            return false;
+        }
+
+        abstract Canvas createCanvas();
+
+        void initializeEgl() {
+            mEgl = (EGL10) EGLContext.getEGL();
+            
+            // Get to the default display.
+            mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
+            
+            if (mEglDisplay == EGL10.EGL_NO_DISPLAY) {
+                throw new RuntimeException("eglGetDisplay failed");
+            }
+            
+            // We can now initialize EGL for that display
+            int[] version = new int[2];
+            if (!mEgl.eglInitialize(mEglDisplay, version)) {
+                throw new RuntimeException("eglInitialize failed");
+            }
+            mEglConfig = getConfigChooser(mGlVersion).chooseConfig(mEgl, mEglDisplay);
+            
+            /*
+            * Create an EGL context. We want to do this as rarely as we can, because an
+            * EGL context is a somewhat heavy object.
+            */
+            mEglContext = createContext(mEgl, mEglDisplay, mEglConfig);
+        }
+
+        GL createEglSurface(SurfaceHolder holder) {
+            // Check preconditions.
+            if (mEgl == null) {
+                throw new RuntimeException("egl not initialized");
+            }
+            if (mEglDisplay == null) {
+                throw new RuntimeException("eglDisplay not initialized");
+            }
+            if (mEglConfig == null) {
+                throw new RuntimeException("mEglConfig not initialized");
+            }
+
+            /*
+             *  The window size has changed, so we need to create a new
+             *  surface.
+             */
+            if (mEglSurface != null && mEglSurface != EGL10.EGL_NO_SURFACE) {
+
+                /*
+                 * Unbind and destroy the old EGL surface, if
+                 * there is one.
+                 */
+                mEgl.eglMakeCurrent(mEglDisplay, EGL10.EGL_NO_SURFACE,
+                        EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT);
+                mEgl.eglDestroySurface(mEglDisplay, mEglSurface);
+            }
+
+            // Create an EGL surface we can render into.
+            mEglSurface = mEgl.eglCreateWindowSurface(mEglDisplay, mEglConfig, holder, null);
+
+            if (mEglSurface == null || mEglSurface == EGL10.EGL_NO_SURFACE) {
+                int error = mEgl.eglGetError();
+                if (error == EGL10.EGL_BAD_NATIVE_WINDOW) {
+                    Log.e("EglHelper", "createWindowSurface returned EGL_BAD_NATIVE_WINDOW.");
+                    return null;
+                }
+                throw new RuntimeException("createWindowSurface failed");
+            }
+
+            /*
+             * Before we can issue GL commands, we need to make sure
+             * the context is current and bound to a surface.
+             */
+            if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
+                throw new RuntimeException("eglMakeCurrent failed");
+                
+            }
+
+            return mEglContext.getGL();
+        }
+
+        EGLContext createContext(EGL10 egl, EGLDisplay eglDisplay, EGLConfig eglConfig) {
+            int[] attrib_list = { EGL_CONTEXT_CLIENT_VERSION, mGlVersion, EGL10.EGL_NONE };
+
+            return egl.eglCreateContext(eglDisplay, eglConfig, EGL10.EGL_NO_CONTEXT,
+                    mGlVersion != 0 ? attrib_list : null);            
+        }
+
+        @Override
+        void initializeIfNeeded(int width, int height, View.AttachInfo attachInfo,
+                SurfaceHolder holder) {
+
+            if (isRequested()) {
+                checkErrors();
+                super.initializeIfNeeded(width, height, attachInfo, holder);
+            }
+        }
+        
+        @Override
+        void destroy() {
+            if (!isEnabled()) return;
+
+            mEgl.eglMakeCurrent(mEglDisplay, EGL10.EGL_NO_SURFACE,
+                    EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT);
+            mEgl.eglDestroyContext(mEglDisplay, mEglContext);
+            mEgl.eglDestroySurface(mEglDisplay, mEglSurface);
+            mEgl.eglTerminate(mEglDisplay);
+
+            mEglContext = null;
+            mEglSurface = null;
+            mEglDisplay = null;
+            mEgl = null;
+            mGl = null;
+            mCanvas = null;
+
+            setEnabled(false);
+        }        
+        
+        @Override
+        void setup(int width, int height, View.AttachInfo attachInfo) {
+            final float scale = attachInfo.mApplicationScale;
+            mCanvas.setViewport((int) (width * scale + 0.5f), (int) (height * scale + 0.5f));
+        }
+        
+        boolean canDraw() {
+            return mGl != null && mCanvas != null;
+        }        
+        
+        void onPreDraw() {
+        }
+
+        /**
+         * Defines the EGL configuration for this renderer. The default configuration
+         * is RGBX, no depth, no stencil.
+         * 
+         * @return An {@link android.view.HardwareRenderer.GlRenderer.EglConfigChooser}.
+         * @param glVersion
+         */
+        EglConfigChooser getConfigChooser(int glVersion) {
+            return new ComponentSizeChooser(glVersion, 8, 8, 8, mTranslucent ? 8 : 0, 0, 0);
+        }
+
+        @Override
+        void draw(View view, View.AttachInfo attachInfo, int yOffset) {
+            if (canDraw()) {
+                attachInfo.mDrawingTime = SystemClock.uptimeMillis();
+                attachInfo.mIgnoreDirtyState = true;
+                view.mPrivateFlags |= View.DRAWN;
+
+                onPreDraw();
+
+                Canvas canvas = mCanvas;
+                int saveCount = canvas.save(Canvas.MATRIX_SAVE_FLAG);
+                canvas.translate(0, -yOffset);
+
+                try {
+                    view.draw(canvas);
+                } finally {
+                    canvas.restoreToCount(saveCount);
+                }
+
+                attachInfo.mIgnoreDirtyState = false;
+
+                mEgl.eglSwapBuffers(mEglDisplay, mEglSurface);
+                checkErrors();
+            }
+        }
+
+        static abstract class EglConfigChooser {
+            final int[] mConfigSpec;
+            private final int mGlVersion;
+
+            EglConfigChooser(int glVersion, int[] configSpec) {
+                mGlVersion = glVersion;
+                mConfigSpec = filterConfigSpec(configSpec);
+            }
+
+            EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) {
+                int[] index = new int[1];
+                if (!egl.eglChooseConfig(display, mConfigSpec, null, 0, index)) {
+                    throw new IllegalArgumentException("eglChooseConfig failed");
+                }
+
+                int numConfigs = index[0];
+                if (numConfigs <= 0) {
+                    throw new IllegalArgumentException("No configs match configSpec");
+                }
+
+                EGLConfig[] configs = new EGLConfig[numConfigs];
+                if (!egl.eglChooseConfig(display, mConfigSpec, configs, numConfigs, index)) {
+                    throw new IllegalArgumentException("eglChooseConfig failed");
+                }
+
+                EGLConfig config = chooseConfig(egl, display, configs);
+                if (config == null) {
+                    throw new IllegalArgumentException("No config chosen");
+                }
+
+                return config;
+            }
+
+            abstract EGLConfig chooseConfig(EGL10 egl, EGLDisplay display, EGLConfig[] configs);
+
+            private int[] filterConfigSpec(int[] configSpec) {
+                if (mGlVersion != 2) {
+                    return configSpec;
+                }
+                /* We know none of the subclasses define EGL_RENDERABLE_TYPE.
+                 * And we know the configSpec is well formed.
+                 */
+                int len = configSpec.length;
+                int[] newConfigSpec = new int[len + 2];
+                System.arraycopy(configSpec, 0, newConfigSpec, 0, len - 1);
+                newConfigSpec[len - 1] = EGL10.EGL_RENDERABLE_TYPE;
+                newConfigSpec[len] = 4; /* EGL_OPENGL_ES2_BIT */
+                newConfigSpec[len + 1] = EGL10.EGL_NONE;
+                return newConfigSpec;
+            }
+        }
+
+        /**
+         * Choose a configuration with exactly the specified r,g,b,a sizes,
+         * and at least the specified depth and stencil sizes.
+         */
+        static class ComponentSizeChooser extends EglConfigChooser {
+            private int[] mValue;
+
+            private int mRedSize;
+            private int mGreenSize;
+            private int mBlueSize;
+            private int mAlphaSize;
+            private int mDepthSize;
+            private int mStencilSize;
+
+            ComponentSizeChooser(int glVersion, int redSize, int greenSize, int blueSize,
+                    int alphaSize, int depthSize, int stencilSize) {
+                super(glVersion, new int[] {
+                        EGL10.EGL_RED_SIZE, redSize,
+                        EGL10.EGL_GREEN_SIZE, greenSize,
+                        EGL10.EGL_BLUE_SIZE, blueSize,
+                        EGL10.EGL_ALPHA_SIZE, alphaSize,
+                        EGL10.EGL_DEPTH_SIZE, depthSize,
+                        EGL10.EGL_STENCIL_SIZE, stencilSize,
+                        EGL10.EGL_NONE });
+                mValue = new int[1];
+                mRedSize = redSize;
+                mGreenSize = greenSize;
+                mBlueSize = blueSize;
+                mAlphaSize = alphaSize;
+                mDepthSize = depthSize;
+                mStencilSize = stencilSize;
+           }
+
+            @Override
+            EGLConfig chooseConfig(EGL10 egl, EGLDisplay display, EGLConfig[] configs) {
+                for (EGLConfig config : configs) {
+                    int d = findConfigAttrib(egl, display, config, EGL10.EGL_DEPTH_SIZE, 0);
+                    int s = findConfigAttrib(egl, display, config, EGL10.EGL_STENCIL_SIZE, 0);
+                    if (d >= mDepthSize && s >= mStencilSize) {
+                        int r = findConfigAttrib(egl, display, config, EGL10.EGL_RED_SIZE, 0);
+                        int g = findConfigAttrib(egl, display, config, EGL10.EGL_GREEN_SIZE, 0);
+                        int b = findConfigAttrib(egl, display, config, EGL10.EGL_BLUE_SIZE, 0);
+                        int a = findConfigAttrib(egl, display, config, EGL10.EGL_ALPHA_SIZE, 0);
+                        if (r == mRedSize && g == mGreenSize && b == mBlueSize && a >= mAlphaSize) {
+                            return config;
+                        }
+                    }
+                }
+                return null;
+            }
+
+            private int findConfigAttrib(EGL10 egl, EGLDisplay display, EGLConfig config,
+                    int attribute, int defaultValue) {
+                if (egl.eglGetConfigAttrib(display, config, attribute, mValue)) {
+                    return mValue[0];
+                }
+
+                return defaultValue;
+            }
+        }
+    }
+    
+    /**
+     * Hardware renderer using OpenGL ES 2.0.
+     */
+    static class Gl20Renderer extends GlRenderer {
+        private GLES20Canvas mGlCanvas;
+
+        Gl20Renderer(boolean translucent) {
+            super(2, translucent);
+        }
+
+        @Override
+        Canvas createCanvas() {
+            return mGlCanvas = new GLES20Canvas(mGl, mTranslucent);
+        }
+
+        @Override
+        void onPreDraw() {
+            mGlCanvas.onPreDraw();
+        }        
+    }
+
+    /**
+     * Hardware renderer using OpenGL ES 1.0.
+     */
+    @SuppressWarnings({"deprecation"})
+    static class Gl10Renderer extends GlRenderer {
+        Gl10Renderer(boolean translucent) {
+            super(1, translucent);
+        }
+
+        @Override
+        Canvas createCanvas() {
+            return new Canvas(mGl);
+        }
+
+        @Override
+        void destroy() {
+            if (isEnabled()) {
+                nativeAbandonGlCaches();
+            }
+
+            super.destroy();
+        }
+
+        @Override
+        void onPreDraw() {
+            GL11 gl = (GL11) mGl;
+            gl.glDisable(GL_SCISSOR_TEST);
+            gl.glClearColor(0, 0, 0, 0);
+            gl.glClear(GL_COLOR_BUFFER_BIT);
+            gl.glEnable(GL_SCISSOR_TEST);
+        }
+    }
+
+    // Inform Skia to just abandon its texture cache IDs doesn't call glDeleteTextures
+    // Used only by the native Skia OpenGL ES 1.x implementation
+    private static native void nativeAbandonGlCaches();
+}
diff --git a/core/java/android/view/LayoutInflater.java b/core/java/android/view/LayoutInflater.java
index e5985c1..479e757 100644
--- a/core/java/android/view/LayoutInflater.java
+++ b/core/java/android/view/LayoutInflater.java
@@ -16,15 +16,15 @@
 
 package android.view;
 
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.content.res.XmlResourceParser;
 import android.util.AttributeSet;
 import android.util.Xml;
 
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
 import java.io.IOException;
 import java.lang.reflect.Constructor;
 import java.util.HashMap;
@@ -71,11 +71,11 @@
 
     private final Object[] mConstructorArgs = new Object[2];
 
-    private static final Class[] mConstructorSignature = new Class[] {
+    private static final Class<?>[] mConstructorSignature = new Class[] {
             Context.class, AttributeSet.class};
 
-    private static final HashMap<String, Constructor> sConstructorMap =
-            new HashMap<String, Constructor>();
+    private static final HashMap<String, Constructor<? extends View>> sConstructorMap =
+            new HashMap<String, Constructor<? extends View>>();
     
     private HashMap<String, Boolean> mFilterMap;
 
@@ -97,6 +97,7 @@
          * 
          * @return True if this class is allowed to be inflated, or false otherwise
          */
+        @SuppressWarnings("unchecked")
         boolean onLoadClass(Class clazz);
     }
     
@@ -379,7 +380,7 @@
                                 + "ViewGroup root and attachToRoot=true");
                     }
 
-                    rInflate(parser, root, attrs);
+                    rInflate(parser, root, attrs, false);
                 } else {
                     // Temp is the root view that was found in the xml
                     View temp = createViewFromTag(name, attrs);
@@ -404,7 +405,7 @@
                         System.out.println("-----> start inflating children");
                     }
                     // Inflate all children under temp
-                    rInflate(parser, temp, attrs);
+                    rInflate(parser, temp, attrs, true);
                     if (DEBUG) {
                         System.out.println("-----> done inflating children");
                     }
@@ -453,18 +454,18 @@
      * @param name The full name of the class to be instantiated.
      * @param attrs The XML attributes supplied for this instance.
      * 
-     * @return View The newly instantied view, or null.
+     * @return View The newly instantiated view, or null.
      */
     public final View createView(String name, String prefix, AttributeSet attrs)
             throws ClassNotFoundException, InflateException {
-        Constructor constructor = sConstructorMap.get(name);
-        Class clazz = null;
+        Constructor<? extends View> constructor = sConstructorMap.get(name);
+        Class<? extends View> clazz = null;
 
         try {
             if (constructor == null) {
                 // Class not found in the cache, see if it's real, and try to add it
                 clazz = mContext.getClassLoader().loadClass(
-                        prefix != null ? (prefix + name) : name);
+                        prefix != null ? (prefix + name) : name).asSubclass(View.class);
                 
                 if (mFilter != null && clazz != null) {
                     boolean allowed = mFilter.onLoadClass(clazz);
@@ -482,7 +483,7 @@
                     if (allowedState == null) {
                         // New class -- remember whether it is allowed
                         clazz = mContext.getClassLoader().loadClass(
-                                prefix != null ? (prefix + name) : name);
+                                prefix != null ? (prefix + name) : name).asSubclass(View.class);
                         
                         boolean allowed = clazz != null && mFilter.onLoadClass(clazz);
                         mFilterMap.put(name, allowed);
@@ -497,7 +498,7 @@
 
             Object[] args = mConstructorArgs;
             args[1] = attrs;
-            return (View) constructor.newInstance(args);
+            return constructor.newInstance(args);
 
         } catch (NoSuchMethodException e) {
             InflateException ie = new InflateException(attrs.getPositionDescription()
@@ -506,6 +507,13 @@
             ie.initCause(e);
             throw ie;
 
+        } catch (ClassCastException e) {
+            // If loaded class is not a View subclass
+            InflateException ie = new InflateException(attrs.getPositionDescription()
+                    + ": Class is not a View "
+                    + (prefix != null ? (prefix + name) : name));
+            ie.initCause(e);
+            throw ie;
         } catch (ClassNotFoundException e) {
             // If loadClass fails, we should propagate the exception.
             throw e;
@@ -519,7 +527,7 @@
     }
 
     /**
-     * Throw an excpetion because the specified class is not allowed to be inflated.
+     * Throw an exception because the specified class is not allowed to be inflated.
      */
     private void failNotAllowed(String name, String prefix, AttributeSet attrs) {
         InflateException ie = new InflateException(attrs.getPositionDescription()
@@ -590,8 +598,8 @@
      * Recursive method used to descend down the xml hierarchy and instantiate
      * views, instantiate their children, and then call onFinishInflate().
      */
-    private void rInflate(XmlPullParser parser, View parent, final AttributeSet attrs)
-            throws XmlPullParserException, IOException {
+    private void rInflate(XmlPullParser parser, View parent, final AttributeSet attrs,
+            boolean finishInflate) throws XmlPullParserException, IOException {
 
         final int depth = parser.getDepth();
         int type;
@@ -618,12 +626,12 @@
                 final View view = createViewFromTag(name, attrs);
                 final ViewGroup viewGroup = (ViewGroup) parent;
                 final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
-                rInflate(parser, view, attrs);
+                rInflate(parser, view, attrs, true);
                 viewGroup.addView(view, params);
             }
         }
 
-        parent.onFinishInflate();
+        if (finishInflate) parent.onFinishInflate();
     }
 
     private void parseRequestFocus(XmlPullParser parser, View parent)
@@ -674,7 +682,7 @@
 
                     if (TAG_MERGE.equals(childName)) {
                         // Inflate all children.
-                        rInflate(childParser, parent, childAttrs);
+                        rInflate(childParser, parent, childAttrs, false);
                     } else {
                         final View view = createViewFromTag(childName, childAttrs);
                         final ViewGroup group = (ViewGroup) parent;
@@ -699,7 +707,7 @@
                         }
 
                         // Inflate all children.
-                        rInflate(childParser, view, childAttrs);
+                        rInflate(childParser, view, childAttrs, true);
 
                         // Attempt to override the included layout's android:id with the
                         // one set on the <include /> tag itself.
diff --git a/core/java/android/view/MenuInflater.java b/core/java/android/view/MenuInflater.java
index 46c805c..a959e0d 100644
--- a/core/java/android/view/MenuInflater.java
+++ b/core/java/android/view/MenuInflater.java
@@ -16,9 +16,8 @@
 
 package android.view;
 
-import com.android.internal.view.menu.MenuItemImpl;
-
 import java.io.IOException;
+import java.lang.reflect.Method;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -30,6 +29,8 @@
 import android.util.AttributeSet;
 import android.util.Xml;
 
+import com.android.internal.view.menu.MenuItemImpl;
+
 /**
  * This class is used to instantiate menu XML files into Menu objects.
  * <p>
@@ -166,6 +167,41 @@
         }
     }
     
+    private static class InflatedOnMenuItemClickListener
+            implements MenuItem.OnMenuItemClickListener {
+        private static final Class[] PARAM_TYPES = new Class[] { MenuItem.class };
+        
+        private Context mContext;
+        private Method mMethod;
+        
+        public InflatedOnMenuItemClickListener(Context context, String methodName) {
+            mContext = context;
+            Class c = context.getClass();
+            try {
+                mMethod = c.getMethod(methodName, PARAM_TYPES);
+            } catch (Exception e) {
+                InflateException ex = new InflateException(
+                        "Couldn't resolve menu item onClick handler " + methodName +
+                        " in class " + c.getName());
+                ex.initCause(e);
+                throw ex;
+            }
+        }
+        
+        public boolean onMenuItemClick(MenuItem item) {
+            try {
+                if (mMethod.getReturnType() == Boolean.TYPE) {
+                    return (Boolean) mMethod.invoke(mContext, item);
+                } else {
+                    mMethod.invoke(mContext, item);
+                    return true;
+                }
+            } catch (Exception e) {
+                throw new RuntimeException(e);
+            }
+        }
+    }
+    
     /**
      * State for the current menu.
      * <p>
@@ -205,6 +241,16 @@
         private boolean itemVisible;
         private boolean itemEnabled;
         
+        /**
+         * Sync to attrs.xml enum, values in MenuItem:
+         * - 0: never
+         * - 1: ifRoom
+         * - 2: always
+         */
+        private int itemShowAsAction = MenuItem.SHOW_AS_ACTION_NEVER;
+        
+        private String itemListenerMethodName;
+        
         private static final int defaultGroupId = NO_ID;
         private static final int defaultItemId = NO_ID;
         private static final int defaultItemCategory = 0;
@@ -276,6 +322,8 @@
             itemChecked = a.getBoolean(com.android.internal.R.styleable.MenuItem_checked, defaultItemChecked);
             itemVisible = a.getBoolean(com.android.internal.R.styleable.MenuItem_visible, groupVisible);
             itemEnabled = a.getBoolean(com.android.internal.R.styleable.MenuItem_enabled, groupEnabled);
+            itemShowAsAction = a.getInt(com.android.internal.R.styleable.MenuItem_showAsAction, 0);
+            itemListenerMethodName = a.getString(com.android.internal.R.styleable.MenuItem_onClick);
             
             a.recycle();
             
@@ -298,10 +346,23 @@
                 .setTitleCondensed(itemTitleCondensed)
                 .setIcon(itemIconResId)
                 .setAlphabeticShortcut(itemAlphabeticShortcut)
-                .setNumericShortcut(itemNumericShortcut);
+                .setNumericShortcut(itemNumericShortcut)
+                .setShowAsAction(itemShowAsAction);
+            
+            if (itemListenerMethodName != null) {
+                if (mContext.isRestricted()) {
+                    throw new IllegalStateException("The android:onClick attribute cannot "
+                            + "be used within a restricted context");
+                }
+                item.setOnMenuItemClickListener(
+                        new InflatedOnMenuItemClickListener(mContext, itemListenerMethodName));
+            }
 
-            if (itemCheckable >= 2) {
-                ((MenuItemImpl) item).setExclusiveCheckable(true);
+            if (item instanceof MenuItemImpl) {
+                MenuItemImpl impl = (MenuItemImpl) item;
+                if (itemCheckable >= 2) {
+                    impl.setExclusiveCheckable(true);
+                }
             }
         }
         
diff --git a/core/java/android/view/MenuItem.java b/core/java/android/view/MenuItem.java
index fcebec5..2604cf9 100644
--- a/core/java/android/view/MenuItem.java
+++ b/core/java/android/view/MenuItem.java
@@ -31,6 +31,21 @@
  * For a feature set of specific menu types, see {@link Menu}.
  */
 public interface MenuItem {
+    /*
+     * These should be kept in sync with attrs.xml enum constants for showAsAction
+     */
+    /** Never show this item as a button in an Action Bar. */
+    public static final int SHOW_AS_ACTION_NEVER = 0;
+    /** Show this item as a button in an Action Bar if the system decides there is room for it. */
+    public static final int SHOW_AS_ACTION_IF_ROOM = 1;
+    /**
+     * Always show this item as a button in an Action Bar.
+     * Use sparingly! If too many items are set to always show in the Action Bar it can
+     * crowd the Action Bar and degrade the user experience on devices with smaller screens.
+     * A good rule of thumb is to have no more than 2 items set to always show at a time.
+     */
+    public static final int SHOW_AS_ACTION_ALWAYS = 2;
+    
     /**
      * Interface definition for a callback to be invoked when a menu item is
      * clicked.
@@ -381,4 +396,15 @@
      *         menu item to the menu. This can be null.
      */
     public ContextMenuInfo getMenuInfo();
+    
+    /**
+     * Sets how this item should display in the presence of an Action Bar.
+     *
+     * @param actionEnum How the item should display. One of
+     * {@link #SHOW_AS_ACTION_ALWAYS}, {@link #SHOW_AS_ACTION_IF_ROOM}, or
+     * {@link #SHOW_AS_ACTION_NEVER}. SHOW_AS_ACTION_NEVER is the default.
+     * 
+     * @see android.app.ActionBar
+     */
+    public void setShowAsAction(int actionEnum);
 }
\ No newline at end of file
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index 47061c1..1328525 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -772,7 +772,7 @@
      * 
      * @param pointerId The identifier of the pointer to be found.
      * @return Returns either the index of the pointer (for use with
-     * {@link #getX(int) et al.), or -1 if there is no data available for
+     * {@link #getX(int)} et al.), or -1 if there is no data available for
      * that pointer identifier.
      */
     public final int findPointerIndex(int pointerId) {
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index c3f81a2..7c644e4 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -34,6 +34,7 @@
 import android.graphics.PorterDuff;
 import android.graphics.PorterDuffXfermode;
 import android.graphics.Rect;
+import android.graphics.RectF;
 import android.graphics.Region;
 import android.graphics.Shader;
 import android.graphics.drawable.ColorDrawable;
@@ -56,6 +57,7 @@
 import android.util.Pools;
 import android.util.SparseArray;
 import android.view.ContextMenu.ContextMenuInfo;
+import android.view.View.MeasureSpec;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityEventSource;
 import android.view.accessibility.AccessibilityManager;
@@ -887,6 +889,20 @@
     public static final int HAPTIC_FEEDBACK_ENABLED = 0x10000000;
 
     /**
+     * <p>Indicates that the view hierarchy should stop saving state when
+     * it reaches this view.  If state saving is initiated immediately at
+     * the view, it will be allowed.
+     * {@hide}
+     */
+    static final int PARENT_SAVE_DISABLED = 0x20000000;
+
+    /**
+     * <p>Mask for use with setFlags indicating bits used for PARENT_SAVE_DISABLED.</p>
+     * {@hide}
+     */
+    static final int PARENT_SAVE_DISABLED_MASK = 0x20000000;
+
+    /**
      * View flag indicating whether {@link #addFocusables(ArrayList, int, int)}
      * should add all focusable Views regardless if they are focusable in touch mode.
      */
@@ -1571,6 +1587,99 @@
     int mViewFlags;
 
     /**
+     * The transform matrix for the View. This transform is calculated internally
+     * based on the rotation, scaleX, and scaleY properties. The identity matrix
+     * is used by default. Do *not* use this variable directly; instead call
+     * getMatrix(), which will automatically recalculate the matrix if necessary
+     * to get the correct matrix based on the latest rotation and scale properties.
+     */
+    private final Matrix mMatrix = new Matrix();
+
+    /**
+     * The transform matrix for the View. This transform is calculated internally
+     * based on the rotation, scaleX, and scaleY properties. The identity matrix
+     * is used by default. Do *not* use this variable directly; instead call
+     * getMatrix(), which will automatically recalculate the matrix if necessary
+     * to get the correct matrix based on the latest rotation and scale properties.
+     */
+    private Matrix mInverseMatrix;
+
+    /**
+     * An internal variable that tracks whether we need to recalculate the
+     * transform matrix, based on whether the rotation or scaleX/Y properties
+     * have changed since the matrix was last calculated.
+     */
+    private boolean mMatrixDirty = false;
+
+    /**
+     * An internal variable that tracks whether we need to recalculate the
+     * transform matrix, based on whether the rotation or scaleX/Y properties
+     * have changed since the matrix was last calculated.
+     */
+    private boolean mInverseMatrixDirty = true;
+
+    /**
+     * A variable that tracks whether we need to recalculate the
+     * transform matrix, based on whether the rotation or scaleX/Y properties
+     * have changed since the matrix was last calculated. This variable
+     * is only valid after a call to getMatrix().
+     */
+    private boolean mMatrixIsIdentity = true;
+
+    /**
+     * The degrees rotation around the pivot point.
+     */
+    @ViewDebug.ExportedProperty
+    private float mRotation = 0f;
+
+    /**
+     * The amount of translation of the object away from its left property (post-layout).
+     */
+    @ViewDebug.ExportedProperty
+    private float mTranslationX = 0f;
+
+    /**
+     * The amount of translation of the object away from its top property (post-layout).
+     */
+    @ViewDebug.ExportedProperty
+    private float mTranslationY = 0f;
+
+    /**
+     * The amount of scale in the x direction around the pivot point. A
+     * value of 1 means no scaling is applied.
+     */
+    @ViewDebug.ExportedProperty
+    private float mScaleX = 1f;
+
+    /**
+     * The amount of scale in the y direction around the pivot point. A
+     * value of 1 means no scaling is applied.
+     */
+    @ViewDebug.ExportedProperty
+    private float mScaleY = 1f;
+
+    /**
+     * The amount of scale in the x direction around the pivot point. A
+     * value of 1 means no scaling is applied.
+     */
+    @ViewDebug.ExportedProperty
+    private float mPivotX = 0f;
+
+    /**
+     * The amount of scale in the y direction around the pivot point. A
+     * value of 1 means no scaling is applied.
+     */
+    @ViewDebug.ExportedProperty
+    private float mPivotY = 0f;
+
+    /**
+     * The opacity of the View. This is a value from 0 to 1, where 0 means
+     * completely transparent and 1 means completely opaque.
+     */
+    @ViewDebug.ExportedProperty
+    private float mAlpha = 1f;
+
+    /**
      * The distance in pixels from the left edge of this view's parent
      * to the left edge of this view.
      * {@hide}
@@ -2413,11 +2522,10 @@
     }
 
     /**
-     * Call this view's OnLongClickListener, if it is defined. Invokes the context menu
-     * if the OnLongClickListener did not consume the event.
+     * Call this view's OnLongClickListener, if it is defined. Invokes the context menu if the
+     * OnLongClickListener did not consume the event.
      *
-     * @return True there was an assigned OnLongClickListener that was called, false
-     *         otherwise is returned.
+     * @return True if one of the above receivers consumed the event, false otherwise.
      */
     public boolean performLongClick() {
         sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
@@ -2445,6 +2553,18 @@
     }
 
     /**
+     * Start an action mode.
+     *
+     * @param callback Callback that will control the lifecycle of the action mode
+     * @return The new action mode if it is started, null otherwise
+     *
+     * @see ActionMode
+     */
+    public ActionMode startActionMode(ActionMode.Callback callback) {
+        return getParent().startActionModeForChild(this, callback);
+    }
+
+    /**
      * Register a callback to be invoked when a key is pressed in this view.
      * @param l the key listener to attach to this view
      */
@@ -3352,6 +3472,38 @@
 
 
     /**
+     * Indicates whether the entire hierarchy under this view will save its
+     * state when a state saving traversal occurs from its parent.  The default
+     * is true; if false, these views will not be saved unless
+     * {@link #saveHierarchyState(SparseArray)} is called directly on this view.
+     *
+     * @return Returns true if the view state saving from parent is enabled, else false.
+     *
+     * @see #setSaveFromParentEnabled(boolean)
+     */
+    public boolean isSaveFromParentEnabled() {
+        return (mViewFlags & PARENT_SAVE_DISABLED_MASK) != PARENT_SAVE_DISABLED;
+    }
+
+    /**
+     * Controls whether the entire hierarchy under this view will save its
+     * state when a state saving traversal occurs from its parent.  The default
+     * is true; if false, these views will not be saved unless
+     * {@link #saveHierarchyState(SparseArray)} is called directly on this view.
+     *
+     * @param enabled Set to false to <em>disable</em> state saving, or true
+     * (the default) to allow it.
+     *
+     * @see #isSaveFromParentEnabled()
+     * @see #setId(int)
+     * @see #onSaveInstanceState()
+     */
+    public void setSaveFromParentEnabled(boolean enabled) {
+        setFlags(enabled ? 0 : PARENT_SAVE_DISABLED, PARENT_SAVE_DISABLED_MASK);
+    }
+
+
+    /**
      * Returns whether this View is able to take focus.
      *
      * @return True if this view can take focus, or false otherwise.
@@ -4218,6 +4370,10 @@
      * Show the context menu for this view. It is not safe to hold on to the
      * menu after returning from this method.
      *
+     * You should normally not overload this method. Overload
+     * {@link #onCreateContextMenu(ContextMenu)} or define an
+     * {@link OnCreateContextMenuListener} to add items to the context menu.
+     *
      * @param menu The context menu to populate
      */
     public void createContextMenu(ContextMenu menu) {
@@ -4370,9 +4526,7 @@
                     final int y = (int) event.getY();
 
                     // Be lenient about moving outside of buttons
-                    int slop = mTouchSlop;
-                    if ((x < 0 - slop) || (x >= getWidth() + slop) ||
-                            (y < 0 - slop) || (y >= getHeight() + slop)) {
+                    if (!pointInView(x, y, mTouchSlop)) {
                         // Outside button
                         removeTapCallback();
                         if ((mPrivateFlags & PRESSED) != 0) {
@@ -4718,6 +4872,245 @@
     }
 
     /**
+     * The transform matrix of this view, which is calculated based on the current
+     * roation, scale, and pivot properties.
+     *
+     * @see #getRotation()
+     * @see #getScaleX()
+     * @see #getScaleY()
+     * @see #getPivotX()
+     * @see #getPivotY()
+     * @return The current transform matrix for the view
+     */
+    public Matrix getMatrix() {
+        hasIdentityMatrix();
+        return mMatrix;
+    }
+
+    /**
+     * Recomputes the transform matrix if necessary.
+     * 
+     * @return True if the transform matrix is the identity matrix, false otherwise.
+     */
+    boolean hasIdentityMatrix() {
+        if (mMatrixDirty) {
+            // transform-related properties have changed since the last time someone
+            // asked for the matrix; recalculate it with the current values
+            mMatrix.reset();
+            mMatrix.setTranslate(mTranslationX, mTranslationY);
+            mMatrix.preRotate(mRotation, mPivotX, mPivotY);
+            mMatrix.preScale(mScaleX, mScaleY, mPivotX, mPivotY);
+            mMatrixDirty = false;
+            mMatrixIsIdentity = mMatrix.isIdentity();
+            mInverseMatrixDirty = true;
+        }
+        return mMatrixIsIdentity;
+    }
+
+    /**
+     * Utility method to retrieve the inverse of the current mMatrix property.
+     * We cache the matrix to avoid recalculating it when transform properties
+     * have not changed.
+     *
+     * @return The inverse of the current matrix of this view.
+     */
+    Matrix getInverseMatrix() {
+        if (mInverseMatrixDirty) {
+            if (mInverseMatrix == null) {
+                mInverseMatrix = new Matrix();
+            }
+            mMatrix.invert(mInverseMatrix);
+            mInverseMatrixDirty = false;
+        }
+        return mInverseMatrix;
+    }
+
+    /**
+     * The degrees that the view is rotated around the pivot point.
+     *
+     * @see #getPivotX()
+     * @see #getPivotY()
+     * @return The degrees of rotation.
+     */
+    public float getRotation() {
+        return mRotation;
+    }
+
+    /**
+     * Sets the degrees that the view is rotated around the pivot point.
+     *
+     * @param rotation The degrees of rotation.
+     * @see #getPivotX()
+     * @see #getPivotY()
+     */
+    public void setRotation(float rotation) {
+        if (mRotation != rotation) {
+            // Double-invalidation is necessary to capture view's old and new areas
+            invalidate();
+            mRotation = rotation;
+            mMatrixDirty = true;
+            mPrivateFlags |= DRAWN; // force another invalidation with the new orientation
+            invalidate();
+        }
+    }
+
+    /**
+     * The amount that the view is scaled in x around the pivot point, as a proportion of
+     * the view's unscaled width. A value of 1, the default, means that no scaling is applied.
+     *
+     * @default 1.0f
+     * @see #getPivotX()
+     * @see #getPivotY()
+     * @return The scaling factor.
+     */
+    public float getScaleX() {
+        return mScaleX;
+    }
+
+    /**
+     * Sets the amount that the view is scaled in x around the pivot point, as a proportion of
+     * the view's unscaled width. A value of 1 means that no scaling is applied.
+     *
+     * @param scaleX The scaling factor.
+     * @see #getPivotX()
+     * @see #getPivotY()
+     */
+    public void setScaleX(float scaleX) {
+        if (mScaleX != scaleX) {
+            // Double-invalidation is necessary to capture view's old and new areas
+            invalidate();
+            mScaleX = scaleX;
+            mMatrixDirty = true;
+            mPrivateFlags |= DRAWN; // force another invalidation with the new orientation
+            invalidate();
+        }
+    }
+
+    /**
+     * The amount that the view is scaled in y around the pivot point, as a proportion of
+     * the view's unscaled height. A value of 1, the default, means that no scaling is applied.
+     *
+     * @default 1.0f
+     * @see #getPivotX()
+     * @see #getPivotY()
+     * @return The scaling factor.
+     */
+    public float getScaleY() {
+        return mScaleY;
+    }
+
+    /**
+     * Sets the amount that the view is scaled in Y around the pivot point, as a proportion of
+     * the view's unscaled width. A value of 1 means that no scaling is applied.
+     *
+     * @param scaleY The scaling factor.
+     * @see #getPivotX()
+     * @see #getPivotY()
+     */
+    public void setScaleY(float scaleY) {
+        if (mScaleY != scaleY) {
+            // Double-invalidation is necessary to capture view's old and new areas
+            invalidate();
+            mScaleY = scaleY;
+            mMatrixDirty = true;
+            mPrivateFlags |= DRAWN; // force another invalidation with the new orientation
+            invalidate();
+        }
+    }
+
+    /**
+     * The x location of the point around which the view is {@link #setRotation(float) rotated}
+     * and {@link #setScaleX(float) scaled}.
+     *
+     * @see #getRotation()
+     * @see #getScaleX()
+     * @see #getScaleY()
+     * @see #getPivotY()
+     * @return The x location of the pivot point.
+     */
+    public float getPivotX() {
+        return mPivotX;
+    }
+
+    /**
+     * Sets the x location of the point around which the view is
+     * {@link #setRotation(float) rotated} and {@link #setScaleX(float) scaled}.
+     *
+     * @param pivotX The x location of the pivot point.
+     * @see #getRotation()
+     * @see #getScaleX()
+     * @see #getScaleY()
+     * @see #getPivotY()
+     */
+    public void setPivotX(float pivotX) {
+        if (mPivotX != pivotX) {
+            // Double-invalidation is necessary to capture view's old and new areas
+            invalidate();
+            mPivotX = pivotX;
+            mMatrixDirty = true;
+            mPrivateFlags |= DRAWN; // force another invalidation with the new orientation
+            invalidate();
+        }
+    }
+
+    /**
+     * The y location of the point around which the view is {@link #setRotation(float) rotated}
+     * and {@link #setScaleY(float) scaled}.
+     *
+     * @see #getRotation()
+     * @see #getScaleX()
+     * @see #getScaleY()
+     * @see #getPivotY()
+     * @return The y location of the pivot point.
+     */
+    public float getPivotY() {
+        return mPivotY;
+    }
+
+    /**
+     * Sets the y location of the point around which the view is {@link #setRotation(float) rotated}
+     * and {@link #setScaleY(float) scaled}.
+     *
+     * @param pivotY The y location of the pivot point.
+     * @see #getRotation()
+     * @see #getScaleX()
+     * @see #getScaleY()
+     * @see #getPivotY()
+     */
+    public void setPivotY(float pivotY) {
+        if (mPivotY != pivotY) {
+            // Double-invalidation is necessary to capture view's old and new areas
+            invalidate();
+            mPivotY = pivotY;
+            mMatrixDirty = true;
+            mPrivateFlags |= DRAWN; // force another invalidation with the new orientation
+            invalidate();
+        }
+    }
+
+    /**
+     * The opacity of the view. This is a value from 0 to 1, where 0 means the view is
+     * completely transparent and 1 means the view is completely opaque.
+     *
+     * @default 1.0f
+     * @return The opacity of the view.
+     */
+    public float getAlpha() {
+        return mAlpha;
+    }
+
+    /**
+     * Sets the opacity of the view. This is a value from 0 to 1, where 0 means the view is
+     * completely transparent and 1 means the view is completely opaque.
+     *
+     * @param alpha The opacity of the view.
+     */
+    public void setAlpha(float alpha) {
+        mAlpha = alpha;
+        invalidate();
+    }
+
+    /**
      * Top position of this view relative to its parent.
      *
      * @return The top of this view, in pixels.
@@ -4758,12 +5151,178 @@
     }
 
     /**
+     * The visual x position of this view, in pixels. This is equivalent to the
+     * {@link #setTranslationX(float) translationX} property plus the current
+     * {@link #getLeft() left} property. 
+     *
+     * @return The visual x position of this view, in pixels.
+     */
+    public float getX() {
+        return mLeft + mTranslationX;
+    }
+
+    /**
+     * Sets the visual x position of this view, in pixels. This is equivalent to setting the
+     * {@link #setTranslationX(float) translationX} property to be the difference between
+     * the x value passed in and the current {@link #getLeft() left} property.
+     *
+     * @param x The visual x position of this view, in pixels.
+     */
+    public void setX(float x) {
+        setTranslationX(x - mLeft);
+    }
+
+    /**
+     * The visual y position of this view, in pixels. This is equivalent to the
+     * {@link #setTranslationY(float) translationY} property plus the current
+     * {@link #getTop() top} property.
+     *
+     * @return The visual y position of this view, in pixels.
+     */
+    public float getY() {
+        return mTop + mTranslationY;
+    }
+
+    /**
+     * Sets the visual y position of this view, in pixels. This is equivalent to setting the
+     * {@link #setTranslationY(float) translationY} property to be the difference between
+     * the y value passed in and the current {@link #getTop() top} property.
+     *
+     * @param y The visual y position of this view, in pixels.
+     */
+    public void setY(float y) {
+        setTranslationY(y - mTop);
+    }
+
+
+    /**
+     * The horizontal location of this view relative to its {@link #getLeft() left} position.
+     * This position is post-layout, in addition to wherever the object's
+     * layout placed it.
+     *
+     * @return The horizontal position of this view relative to its left position, in pixels.
+     */
+    public float getTranslationX() {
+        return mTranslationX;
+    }
+
+    /**
+     * Sets the horizontal location of this view relative to its {@link #getLeft() left} position.
+     * This effectively positions the object post-layout, in addition to wherever the object's
+     * layout placed it.
+     *
+     * @param translationX The horizontal position of this view relative to its left position,
+     * in pixels.
+     */
+    public void setTranslationX(float translationX) {
+        if (mTranslationX != translationX) {
+            // Double-invalidation is necessary to capture view's old and new areas
+            invalidate();
+            mTranslationX = translationX;
+            mMatrixDirty = true;
+            mPrivateFlags |= DRAWN; // force another invalidation with the new orientation
+            invalidate();
+        }
+    }
+
+    /**
+     * The horizontal location of this view relative to its {@link #getTop() top} position.
+     * This position is post-layout, in addition to wherever the object's
+     * layout placed it.
+     *
+     * @return The vertical position of this view relative to its top position,
+     * in pixels.
+     */
+    public float getTranslationY() {
+        return mTranslationY;
+    }
+
+    /**
+     * Sets the vertical location of this view relative to its {@link #getTop() top} position.
+     * This effectively positions the object post-layout, in addition to wherever the object's
+     * layout placed it.
+     *
+     * @param translationY The vertical position of this view relative to its top position,
+     * in pixels.
+     */
+    public void setTranslationY(float translationY) {
+        if (mTranslationY != translationY) {
+            // Double-invalidation is necessary to capture view's old and new areas
+            invalidate();
+            mTranslationY = translationY;
+            mMatrixDirty = true;
+            mPrivateFlags |= DRAWN; // force another invalidation with the new orientation
+            invalidate();
+        }
+    }
+
+    /**
      * Hit rectangle in parent's coordinates
      *
      * @param outRect The hit rectangle of the view.
      */
     public void getHitRect(Rect outRect) {
-        outRect.set(mLeft, mTop, mRight, mBottom);
+        if (hasIdentityMatrix() || mAttachInfo == null) {
+            outRect.set(mLeft, mTop, mRight, mBottom);
+        } else {
+            Matrix m = getMatrix();
+            final RectF tmpRect = mAttachInfo.mTmpTransformRect;
+            tmpRect.set(-mPivotX, -mPivotY, getWidth() - mPivotX, getHeight() - mPivotY);
+            m.mapRect(tmpRect);
+            outRect.set((int) tmpRect.left + mLeft, (int) tmpRect.top + mTop,
+                    (int) tmpRect.right + mLeft, (int) tmpRect.bottom + mTop);
+        }
+    }
+
+    /**
+     * This method detects whether the given event is inside the view and, if so,
+     * handles it via the dispatchEvent(MotionEvent) method.
+     *
+     * @param ev The event that is being dispatched.
+     * @param parentX The x location of the event in the parent's coordinates.
+     * @param parentY The y location of the event in the parent's coordinates.
+     * @return true if the event was inside this view, false otherwise.
+     */
+    boolean dispatchTouchEvent(MotionEvent ev, float parentX, float parentY) {
+        float localX = parentX - mLeft;
+        float localY = parentY - mTop;
+        if (!hasIdentityMatrix() && mAttachInfo != null) {
+            // non-identity matrix: transform the point into the view's coordinates
+            final float[] localXY = mAttachInfo.mTmpTransformLocation;
+            localXY[0] = localX;
+            localXY[1] = localY;
+            getInverseMatrix().mapPoints(localXY);
+            localX = localXY[0];
+            localY = localXY[1];
+        }
+        if (localX >= 0 && localY >= 0 && localX < (mRight - mLeft) && localY < (mBottom - mTop)) {
+            // It would be safer to clone the event here but we don't for performance.
+            // There are many subtle interactions in touch event dispatch; change at your own risk.
+            mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
+            ev.setLocation(localX, localY);
+            return dispatchTouchEvent(ev);
+        }
+        return false;
+    }
+
+    /**
+     * Utility method to determine whether the given point, in local coordinates,
+     * is inside the view, where the area of the view is expanded by the slop factor.
+     * This method is called while processing touch-move events to determine if the event
+     * is still within the view.
+     */
+    private boolean pointInView(float localX, float localY, float slop) {
+        if (!hasIdentityMatrix() && mAttachInfo != null) {
+            // non-identity matrix: transform the point into the view's coordinates
+            final float[] localXY = mAttachInfo.mTmpTransformLocation;
+            localXY[0] = localX;
+            localXY[1] = localY;
+            getInverseMatrix().mapPoints(localXY);
+            localX = localXY[0];
+            localY = localXY[1];
+        }
+        return localX > -slop && localY > -slop && localX < ((mRight - mLeft) + slop) &&
+                localY < ((mBottom - mTop) + slop);
     }
 
     /**
@@ -4826,8 +5385,38 @@
      * @param offset the number of pixels to offset the view by
      */
     public void offsetTopAndBottom(int offset) {
-        mTop += offset;
-        mBottom += offset;
+        if (offset != 0) {
+            if (hasIdentityMatrix()) {
+                final ViewParent p = mParent;
+                if (p != null && mAttachInfo != null) {
+                    final Rect r = mAttachInfo.mTmpInvalRect;
+                    int minTop;
+                    int maxBottom;
+                    int yLoc;
+                    if (offset < 0) {
+                        minTop = mTop + offset;
+                        maxBottom = mBottom;
+                        yLoc = offset;
+                    } else {
+                        minTop = mTop;
+                        maxBottom = mBottom + offset;
+                        yLoc = 0;
+                    }
+                    r.set(0, yLoc, mRight - mLeft, maxBottom - minTop);
+                    p.invalidateChild(this, r);
+                }
+            } else {
+                invalidate();
+            }
+
+            mTop += offset;
+            mBottom += offset;
+
+            if (!mMatrixIsIdentity) {
+                mPrivateFlags |= DRAWN; // force another invalidation with the new orientation
+                invalidate();
+            }
+        }
     }
 
     /**
@@ -4836,8 +5425,38 @@
      * @param offset the numer of pixels to offset the view by
      */
     public void offsetLeftAndRight(int offset) {
-        mLeft += offset;
-        mRight += offset;
+        if (offset != 0) {
+            if (hasIdentityMatrix()) {
+                final ViewParent p = mParent;
+                if (p != null && mAttachInfo != null) {
+                    final Rect r = mAttachInfo.mTmpInvalRect;
+                    int minLeft;
+                    int maxRight;
+                    int xLoc;
+                    if (offset < 0) {
+                        minLeft = mLeft + offset;
+                        maxRight = mRight;
+                        xLoc = offset;
+                    } else {
+                        minLeft = mLeft;
+                        maxRight = mRight + offset;
+                        xLoc = 0;
+                    }
+                    r.set(0, 0, maxRight - minLeft, mBottom - mTop);
+                    p.invalidateChild(this, r);
+                }
+            } else {
+                invalidate();
+            }
+
+            mLeft += offset;
+            mRight += offset;
+
+            if (!mMatrixIsIdentity) {
+                mPrivateFlags |= DRAWN; // force another invalidation with the new orientation
+                invalidate();
+            }
+        }
     }
 
     /**
@@ -6812,16 +7431,16 @@
 
         if (verticalEdges) {
             topFadeStrength = Math.max(0.0f, Math.min(1.0f, getTopFadingEdgeStrength()));
-            drawTop = topFadeStrength >= 0.0f;
+            drawTop = topFadeStrength > 0.0f;
             bottomFadeStrength = Math.max(0.0f, Math.min(1.0f, getBottomFadingEdgeStrength()));
-            drawBottom = bottomFadeStrength >= 0.0f;
+            drawBottom = bottomFadeStrength > 0.0f;
         }
 
         if (horizontalEdges) {
             leftFadeStrength = Math.max(0.0f, Math.min(1.0f, getLeftFadingEdgeStrength()));
-            drawLeft = leftFadeStrength >= 0.0f;
+            drawLeft = leftFadeStrength > 0.0f;
             rightFadeStrength = Math.max(0.0f, Math.min(1.0f, getRightFadingEdgeStrength()));
-            drawRight = rightFadeStrength >= 0.0f;
+            drawRight = rightFadeStrength > 0.0f;
         }
 
         saveCount = canvas.getSaveCount();
@@ -8560,13 +9179,12 @@
         if (mAttachInfo == null) {
             return false;
         }
-        if ((flags&HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING) == 0
+        if ((flags & HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING) == 0
                 && !isHapticFeedbackEnabled()) {
             return false;
         }
-        return mAttachInfo.mRootCallbacks.performHapticFeedback(
-                feedbackConstant,
-                (flags&HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING) != 0);
+        return mAttachInfo.mRootCallbacks.performHapticFeedback(feedbackConstant,
+                (flags & HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING) != 0);
     }
 
     /**
@@ -8637,8 +9255,7 @@
                 ViewConfiguration.getLongPressTimeout() - delayOffset);
     }
 
-    private static int[] stateSetUnion(final int[] stateSet1,
-                                       final int[] stateSet2) {
+    private static int[] stateSetUnion(final int[] stateSet1, final int[] stateSet2) {
         final int stateSet1Length = stateSet1.length;
         final int stateSet2Length = stateSet2.length;
         final int[] newSet = new int[stateSet1Length + stateSet2Length];
@@ -9159,6 +9776,13 @@
          */
         final int[] mInvalidateChildLocation = new int[2];
 
+
+        /**
+         * Global to the view hierarchy used as a temporary for dealing with
+         * x/y location when view is transformed.
+         */
+        final float[] mTmpTransformLocation = new float[2];
+
         /**
          * The view tree observer used to dispatch global events like
          * layout, pre-draw, touch mode change, etc.
@@ -9195,6 +9819,16 @@
         final Rect mTmpInvalRect = new Rect();
 
         /**
+         * Temporary for use in computing hit areas with transformed views
+         */
+        final RectF mTmpTransformRect = new RectF();
+
+        /**
+         * Temporary for use in computing invalidation areas with transformed views
+         */
+        final float[] mTmpTransformBounds = new float[8];
+
+        /**
          * Temporary list for use in collecting focusable descendents of a view.
          */
         final ArrayList<View> mFocusablesTempList = new ArrayList<View>(24);
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index e7b6c50..9da5637 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -23,6 +23,7 @@
 import android.content.res.TypedArray;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
+import android.graphics.Matrix;
 import android.graphics.Paint;
 import android.graphics.Rect;
 import android.graphics.RectF;
@@ -34,6 +35,7 @@
 import android.util.EventLog;
 import android.util.Log;
 import android.util.SparseArray;
+import android.view.ViewGroup.LayoutParams;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.animation.Animation;
 import android.view.animation.AnimationUtils;
@@ -86,13 +88,25 @@
     // The view contained within this ViewGroup that has or contains focus.
     private View mFocused;
 
-    // The current transformation to apply on the child being drawn
-    private Transformation mChildTransformation;
+    /**
+     * A Transformation used when drawing children, to
+     * apply on the child being drawn.
+     */
+    private final Transformation mChildTransformation = new Transformation();
+
+    /**
+     * Used to track the current invalidation region.
+     */
     private RectF mInvalidateRegion;
 
+    /**
+     * A Transformation used to calculate a correct
+     * invalidation area when the application is autoscaled.
+     */
+    private Transformation mInvalidationTransformation;
+
     // Target of Motion events
     private View mMotionTarget;
-    private final Rect mTempRect = new Rect();
 
     // Layout animation
     private LayoutAnimationController mLayoutAnimationController;
@@ -461,6 +475,13 @@
     }
 
     /**
+     * {@inheritDoc}
+     */
+    public ActionMode startActionModeForChild(View originalView, ActionMode.Callback callback) {
+        return mParent != null ? mParent.startActionModeForChild(originalView, callback) : null;
+    }
+
+    /**
      * Find the nearest view in the specified direction that wants to take
      * focus.
      *
@@ -827,7 +848,6 @@
         final float yf = ev.getY();
         final float scrolledXFloat = xf + mScrollX;
         final float scrolledYFloat = yf + mScrollY;
-        final Rect frame = mTempRect;
 
         boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
 
@@ -846,29 +866,15 @@
                 ev.setAction(MotionEvent.ACTION_DOWN);
                 // We know we want to dispatch the event down, find a child
                 // who can handle it, start with the front-most child.
-                final int scrolledXInt = (int) scrolledXFloat;
-                final int scrolledYInt = (int) scrolledYFloat;
                 final View[] children = mChildren;
                 final int count = mChildrenCount;
                 for (int i = count - 1; i >= 0; i--) {
                     final View child = children[i];
                     if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE
                             || child.getAnimation() != null) {
-                        child.getHitRect(frame);
-                        if (frame.contains(scrolledXInt, scrolledYInt)) {
-                            // offset the event to the view's coordinate system
-                            final float xc = scrolledXFloat - child.mLeft;
-                            final float yc = scrolledYFloat - child.mTop;
-                            ev.setLocation(xc, yc);
-                            child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
-                            if (child.dispatchTouchEvent(ev))  {
-                                // Event handled, we have a target now.
-                                mMotionTarget = child;
-                                return true;
-                            }
-                            // The event didn't get handled, try the next view.
-                            // Don't reset the event's location, it's not
-                            // necessary here.
+                        if (child.dispatchTouchEvent(ev, scrolledXFloat, scrolledYFloat)) {
+                            mMotionTarget = child;
+                            return true;
                         }
                     }
                 }
@@ -898,11 +904,25 @@
             return super.dispatchTouchEvent(ev);
         }
 
+        // Calculate the offset point into the target's local coordinates
+        float xc;
+        float yc;
+        if (target.hasIdentityMatrix() || mAttachInfo == null) {
+            xc = scrolledXFloat - (float) target.mLeft;
+            yc = scrolledYFloat - (float) target.mTop;
+        } else {
+            // non-identity matrix: transform the point into the view's coordinates
+            final float[] localXY = mAttachInfo.mTmpTransformLocation;
+            localXY[0] = scrolledXFloat;
+            localXY[1] = scrolledYFloat;
+            target.getInverseMatrix().mapPoints(localXY);
+            xc = localXY[0] - (float) target.mLeft;
+            yc = localXY[1] - (float) target.mTop;
+        }
+
         // if have a target, see if we're allowed to and want to intercept its
         // events
         if (!disallowIntercept && onInterceptTouchEvent(ev)) {
-            final float xc = scrolledXFloat - (float) target.mLeft;
-            final float yc = scrolledYFloat - (float) target.mTop;
             mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
             ev.setAction(MotionEvent.ACTION_CANCEL);
             ev.setLocation(xc, yc);
@@ -924,8 +944,6 @@
 
         // finally offset the event to the target's coordinate system and
         // dispatch the event.
-        final float xc = scrolledXFloat - (float) target.mLeft;
-        final float yc = scrolledYFloat - (float) target.mTop;
         ev.setLocation(xc, yc);
 
         if ((target.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
@@ -1182,7 +1200,10 @@
         final int count = mChildrenCount;
         final View[] children = mChildren;
         for (int i = 0; i < count; i++) {
-            children[i].dispatchSaveInstanceState(container);
+            View c = children[i];
+            if ((c.mViewFlags & PARENT_SAVE_DISABLED_MASK) != PARENT_SAVE_DISABLED) {
+                c.dispatchSaveInstanceState(container);
+            }
         }
     }
 
@@ -1207,7 +1228,10 @@
         final int count = mChildrenCount;
         final View[] children = mChildren;
         for (int i = 0; i < count; i++) {
-            children[i].dispatchRestoreInstanceState(container);
+            View c = children[i];
+            if ((c.mViewFlags & PARENT_SAVE_DISABLED_MASK) != PARENT_SAVE_DISABLED) {
+                c.dispatchRestoreInstanceState(container);
+            }
         }
     }
 
@@ -1477,22 +1501,25 @@
         final int flags = mGroupFlags;
 
         if ((flags & FLAG_CLEAR_TRANSFORMATION) == FLAG_CLEAR_TRANSFORMATION) {
-            if (mChildTransformation != null) {
-                mChildTransformation.clear();
-            }
+            mChildTransformation.clear();
             mGroupFlags &= ~FLAG_CLEAR_TRANSFORMATION;
         }
 
         Transformation transformToApply = null;
+        Transformation invalidationTransform;
         final Animation a = child.getAnimation();
         boolean concatMatrix = false;
 
-        if (a != null) {
-            if (mInvalidateRegion == null) {
-                mInvalidateRegion = new RectF();
-            }
-            final RectF region = mInvalidateRegion;
+        boolean scalingRequired = false;
+        boolean caching = false;
+        if (!canvas.isHardwareAccelerated() &&
+                (flags & FLAG_CHILDREN_DRAWN_WITH_CACHE) == FLAG_CHILDREN_DRAWN_WITH_CACHE ||
+                (flags & FLAG_ALWAYS_DRAWN_WITH_CACHE) == FLAG_ALWAYS_DRAWN_WITH_CACHE) {
+            caching = true;
+            if (mAttachInfo != null) scalingRequired = mAttachInfo.mScalingRequired;
+        }
 
+        if (a != null) {
             final boolean initialized = a.isInitialized();
             if (!initialized) {
                 a.initialize(cr - cl, cb - ct, getWidth(), getHeight());
@@ -1500,10 +1527,17 @@
                 child.onAnimationStart();
             }
 
-            if (mChildTransformation == null) {
-                mChildTransformation = new Transformation();
+            more = a.getTransformation(drawingTime, mChildTransformation,
+                    scalingRequired ? mAttachInfo.mApplicationScale : 1f);
+            if (scalingRequired && mAttachInfo.mApplicationScale != 1f) {
+                if (mInvalidationTransformation == null) {
+                    mInvalidationTransformation = new Transformation();
+                }
+                invalidationTransform = mInvalidationTransformation;
+                a.getTransformation(drawingTime, invalidationTransform, 1f);
+            } else {
+                invalidationTransform = mChildTransformation;
             }
-            more = a.getTransformation(drawingTime, mChildTransformation);
             transformToApply = mChildTransformation;
 
             concatMatrix = a.willChangeTransformationMatrix();
@@ -1520,7 +1554,11 @@
                         invalidate(cl, ct, cr, cb);
                     }
                 } else {
-                    a.getInvalidateRegion(0, 0, cr - cl, cb - ct, region, transformToApply);
+                    if (mInvalidateRegion == null) {
+                        mInvalidateRegion = new RectF();
+                    }
+                    final RectF region = mInvalidateRegion;
+                    a.getInvalidateRegion(0, 0, cr - cl, cb - ct, region, invalidationTransform);
 
                     // The child need to draw an animation, potentially offscreen, so
                     // make sure we do not cancel invalidate requests
@@ -1533,9 +1571,6 @@
             }
         } else if ((flags & FLAG_SUPPORT_STATIC_TRANSFORMATIONS) ==
                 FLAG_SUPPORT_STATIC_TRANSFORMATIONS) {
-            if (mChildTransformation == null) {
-                mChildTransformation = new Transformation();
-            }
             final boolean hasTransform = getChildStaticTransformation(child, mChildTransformation);
             if (hasTransform) {
                 final int transformType = mChildTransformation.getTransformationType();
@@ -1545,6 +1580,8 @@
             }
         }
 
+        concatMatrix |= !child.hasIdentityMatrix();
+
         // Sets the flag as early as possible to allow draw() implementations
         // to call invalidate() successfully when doing animations
         child.mPrivateFlags |= DRAWN;
@@ -1559,12 +1596,9 @@
         final int sx = child.mScrollX;
         final int sy = child.mScrollY;
 
-        boolean scalingRequired = false;
         Bitmap cache = null;
-        if ((flags & FLAG_CHILDREN_DRAWN_WITH_CACHE) == FLAG_CHILDREN_DRAWN_WITH_CACHE ||
-                (flags & FLAG_ALWAYS_DRAWN_WITH_CACHE) == FLAG_ALWAYS_DRAWN_WITH_CACHE) {
+        if (caching) {
             cache = child.getDrawingCache(true);
-            if (mAttachInfo != null) scalingRequired = mAttachInfo.mScalingRequired;
         }
 
         final boolean hasNoCache = cache == null;
@@ -1581,36 +1615,51 @@
             }
         }
 
-        float alpha = 1.0f;
+        float alpha = child.getAlpha();
 
-        if (transformToApply != null) {
-            if (concatMatrix) {
-                int transX = 0;
-                int transY = 0;
-                if (hasNoCache) {
-                    transX = -sx;
-                    transY = -sy;
-                }
-                // Undo the scroll translation, apply the transformation matrix,
-                // then redo the scroll translate to get the correct result.
-                canvas.translate(-transX, -transY);
-                canvas.concat(transformToApply.getMatrix());
-                canvas.translate(transX, transY);
-                mGroupFlags |= FLAG_CLEAR_TRANSFORMATION;
+        if (transformToApply != null || alpha < 1.0f || !child.hasIdentityMatrix()) {
+            int transX = 0;
+            int transY = 0;
+
+            if (hasNoCache) {
+                transX = -sx;
+                transY = -sy;
             }
 
-            alpha = transformToApply.getAlpha();
+            if (transformToApply != null) {
+                if (concatMatrix) {
+                    // Undo the scroll translation, apply the transformation matrix,
+                    // then redo the scroll translate to get the correct result.
+                    canvas.translate(-transX, -transY);
+                    canvas.concat(transformToApply.getMatrix());
+                    canvas.translate(transX, transY);
+                    mGroupFlags |= FLAG_CLEAR_TRANSFORMATION;
+                }
+
+                float transformAlpha = transformToApply.getAlpha();
+                if (transformAlpha < 1.0f) {
+                    alpha *= transformToApply.getAlpha();
+                    mGroupFlags |= FLAG_CLEAR_TRANSFORMATION;
+                }
+            }
+
+            if (!child.hasIdentityMatrix()) {
+                canvas.translate(-transX, -transY);
+                canvas.concat(child.getMatrix());
+                canvas.translate(transX, transY);
+            }
+
             if (alpha < 1.0f) {
                 mGroupFlags |= FLAG_CLEAR_TRANSFORMATION;
-            }
-
-            if (alpha < 1.0f && hasNoCache) {
-                final int multipliedAlpha = (int) (255 * alpha);
-                if (!child.onSetAlpha(multipliedAlpha)) {
-                    canvas.saveLayerAlpha(sx, sy, sx + cr - cl, sy + cb - ct, multipliedAlpha,
-                            Canvas.HAS_ALPHA_LAYER_SAVE_FLAG | Canvas.CLIP_TO_LAYER_SAVE_FLAG);
-                } else {
-                    child.mPrivateFlags |= ALPHA_SET;
+            
+                if (hasNoCache) {
+                    final int multipliedAlpha = (int) (255 * alpha);
+                    if (!child.onSetAlpha(multipliedAlpha)) {
+                        canvas.saveLayerAlpha(sx, sy, sx + cr - cl, sy + cb - ct, multipliedAlpha,
+                                Canvas.HAS_ALPHA_LAYER_SAVE_FLAG | Canvas.CLIP_TO_LAYER_SAVE_FLAG);
+                    } else {
+                        child.mPrivateFlags |= ALPHA_SET;
+                    }
                 }
             }
         } else if ((child.mPrivateFlags & ALPHA_SET) == ALPHA_SET) {
@@ -1697,6 +1746,7 @@
         final View[] children = mChildren;
         final int count = mChildrenCount;
         for (int i = 0; i < count; i++) {
+            
             children[i].setSelected(selected);
         }
     }
@@ -2470,6 +2520,15 @@
             final int[] location = attachInfo.mInvalidateChildLocation;
             location[CHILD_LEFT_INDEX] = child.mLeft;
             location[CHILD_TOP_INDEX] = child.mTop;
+            Matrix childMatrix = child.getMatrix();
+            if (!childMatrix.isIdentity()) {
+                RectF boundingRect = attachInfo.mTmpTransformRect;
+                boundingRect.set(dirty);
+                childMatrix.mapRect(boundingRect);
+                dirty.set((int) boundingRect.left, (int) boundingRect.top,
+                        (int) (boundingRect.right + 0.5f),
+                        (int) (boundingRect.bottom + 0.5f));
+            }
 
             // If the child is drawing an animation, we want to copy this flag onto
             // ourselves and the parent to make sure the invalidate request goes
@@ -2504,6 +2563,18 @@
                 }
 
                 parent = parent.invalidateChildInParent(location, dirty);
+                if (view != null) {
+                    // Account for transform on current parent
+                    Matrix m = view.getMatrix();
+                    if (!m.isIdentity()) {
+                        RectF boundingRect = attachInfo.mTmpTransformRect;
+                        boundingRect.set(dirty);
+                        m.mapRect(boundingRect);
+                        dirty.set((int) boundingRect.left, (int) boundingRect.top,
+                                (int) (boundingRect.right + 0.5f),
+                                (int) (boundingRect.bottom + 0.5f));
+                    }
+                }
             } while (parent != null);
         }
     }
@@ -2870,7 +2941,7 @@
      */
     @ViewDebug.ExportedProperty(mapping = {
         @ViewDebug.IntToString(from = PERSISTENT_NO_CACHE,        to = "NONE"),
-        @ViewDebug.IntToString(from = PERSISTENT_ALL_CACHES,      to = "ANIMATION"),
+        @ViewDebug.IntToString(from = PERSISTENT_ANIMATION_CACHE, to = "ANIMATION"),
         @ViewDebug.IntToString(from = PERSISTENT_SCROLLING_CACHE, to = "SCROLLING"),
         @ViewDebug.IntToString(from = PERSISTENT_ALL_CACHES,      to = "ALL")
     })
diff --git a/core/java/android/view/ViewParent.java b/core/java/android/view/ViewParent.java
index b456c5d..d7d4c3f 100644
--- a/core/java/android/view/ViewParent.java
+++ b/core/java/android/view/ViewParent.java
@@ -162,6 +162,20 @@
     public void createContextMenu(ContextMenu menu);
 
     /**
+     * Start an action mode for the specified view.
+     * <p>
+     * In most cases, a subclass does not need to override this. However, if the
+     * subclass is added directly to the window manager (for example,
+     * {@link ViewManager#addView(View, android.view.ViewGroup.LayoutParams)})
+     * then it should override this and start the action mode.
+     *
+     * @param originalView The source view where the action mode was first invoked
+     * @param callback The callback that will handle lifecycle events for the action mode
+     * @return The new action mode if it was started, null otherwise
+     */
+    public ActionMode startActionModeForChild(View originalView, ActionMode.Callback callback);
+
+    /**
      * This method is called on the parent when a child's drawable state
      * has changed.
      *
diff --git a/core/java/android/view/ViewRoot.java b/core/java/android/view/ViewRoot.java
index 7ce04cf..8abbf58 100644
--- a/core/java/android/view/ViewRoot.java
+++ b/core/java/android/view/ViewRoot.java
@@ -33,7 +33,6 @@
 import android.util.DisplayMetrics;
 import android.util.Log;
 import android.util.EventLog;
-import android.util.Slog;
 import android.util.SparseArray;
 import android.view.View.MeasureSpec;
 import android.view.accessibility.AccessibilityEvent;
@@ -52,15 +51,10 @@
 import android.media.AudioManager;
 
 import java.lang.ref.WeakReference;
-import java.io.FileDescriptor;
 import java.io.IOException;
 import java.io.OutputStream;
 import java.util.ArrayList;
 
-import javax.microedition.khronos.egl.*;
-import javax.microedition.khronos.opengles.*;
-import static javax.microedition.khronos.opengles.GL10.*;
-
 /**
  * The top of a view hierarchy, implementing the needed protocol between View
  * and the WindowManager.  This is for the most part an internal implementation
@@ -68,14 +62,12 @@
  *
  * {@hide}
  */
-@SuppressWarnings({"EmptyCatchBlock"})
-public final class ViewRoot extends Handler implements ViewParent,
-        View.AttachInfo.Callbacks {
+@SuppressWarnings({"EmptyCatchBlock", "PointlessBooleanExpression"})
+public final class ViewRoot extends Handler implements ViewParent, View.AttachInfo.Callbacks {
     private static final String TAG = "ViewRoot";
     private static final boolean DBG = false;
     private static final boolean SHOW_FPS = false;
-    @SuppressWarnings({"ConstantConditionalExpression"})
-    private static final boolean LOCAL_LOGV = false ? Config.LOGD : Config.LOGV;
+    private static final boolean LOCAL_LOGV = false;
     /** @noinspection PointlessBooleanExpression*/
     private static final boolean DEBUG_DRAW = false || LOCAL_LOGV;
     private static final boolean DEBUG_LAYOUT = false || LOCAL_LOGV;
@@ -205,14 +197,7 @@
     int mCurScrollY;
     Scroller mScroller;
 
-    EGL10 mEgl;
-    EGLDisplay mEglDisplay;
-    EGLContext mEglContext;
-    EGLSurface mEglSurface;
-    GL11 mGL;
-    Canvas mGlCanvas;
-    boolean mUseGL;
-    boolean mGlWanted;
+    HardwareRenderer mHwRenderer;
 
     final ViewConfiguration mViewConfiguration;
 
@@ -242,8 +227,10 @@
     public ViewRoot(Context context) {
         super();
 
-        if (MEASURE_LATENCY && lt == null) {
-            lt = new LatencyTimer(100, 1000);
+        if (MEASURE_LATENCY) {
+            if (lt == null) {
+                lt = new LatencyTimer(100, 1000);
+            }
         }
 
         // For debug only
@@ -331,122 +318,18 @@
         return false;
     }
 
-    private void initializeGL() {
-        initializeGLInner();
-        int err = mEgl.eglGetError();
-        if (err != EGL10.EGL_SUCCESS) {
-            // give-up on using GL
-            destroyGL();
-            mGlWanted = false;
-        }
-    }
-
-    private void initializeGLInner() {
-        final EGL10 egl = (EGL10) EGLContext.getEGL();
-        mEgl = egl;
-
-        /*
-         * Get to the default display.
-         */
-        final EGLDisplay eglDisplay = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
-        mEglDisplay = eglDisplay;
-
-        /*
-         * We can now initialize EGL for that display
-         */
-        int[] version = new int[2];
-        egl.eglInitialize(eglDisplay, version);
-
-        /*
-         * Specify a configuration for our opengl session
-         * and grab the first configuration that matches is
-         */
-        final int[] configSpec = {
-                EGL10.EGL_RED_SIZE,      5,
-                EGL10.EGL_GREEN_SIZE,    6,
-                EGL10.EGL_BLUE_SIZE,     5,
-                EGL10.EGL_DEPTH_SIZE,    0,
-                EGL10.EGL_NONE
-        };
-        final EGLConfig[] configs = new EGLConfig[1];
-        final int[] num_config = new int[1];
-        egl.eglChooseConfig(eglDisplay, configSpec, configs, 1, num_config);
-        final EGLConfig config = configs[0];
-
-        /*
-         * Create an OpenGL ES context. This must be done only once, an
-         * OpenGL context is a somewhat heavy object.
-         */
-        final EGLContext context = egl.eglCreateContext(eglDisplay, config,
-                EGL10.EGL_NO_CONTEXT, null);
-        mEglContext = context;
-
-        /*
-         * Create an EGL surface we can render into.
-         */
-        final EGLSurface surface = egl.eglCreateWindowSurface(eglDisplay, config, mHolder, null);
-        mEglSurface = surface;
-
-        /*
-         * Before we can issue GL commands, we need to make sure
-         * the context is current and bound to a surface.
-         */
-        egl.eglMakeCurrent(eglDisplay, surface, surface, context);
-
-        /*
-         * Get to the appropriate GL interface.
-         * This is simply done by casting the GL context to either
-         * GL10 or GL11.
-         */
-        final GL11 gl = (GL11) context.getGL();
-        mGL = gl;
-        mGlCanvas = new Canvas(gl);
-        mUseGL = true;
-    }
-
-    private void destroyGL() {
-        // inform skia that the context is gone
-        nativeAbandonGlCaches();
-
-        mEgl.eglMakeCurrent(mEglDisplay, EGL10.EGL_NO_SURFACE,
-                EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT);
-        mEgl.eglDestroyContext(mEglDisplay, mEglContext);
-        mEgl.eglDestroySurface(mEglDisplay, mEglSurface);
-        mEgl.eglTerminate(mEglDisplay);
-        mEglContext = null;
-        mEglSurface = null;
-        mEglDisplay = null;
-        mEgl = null;
-        mGlCanvas = null;
-        mGL = null;
-        mUseGL = false;
-    }
-
-    private void checkEglErrors() {
-        if (mUseGL) {
-            int err = mEgl.eglGetError();
-            if (err != EGL10.EGL_SUCCESS) {
-                // something bad has happened revert to
-                // normal rendering.
-                destroyGL();
-                if (err != EGL11.EGL_CONTEXT_LOST) {
-                    // we'll try again if it was context lost
-                    mGlWanted = false;
-                }
-            }
-        }
-    }
-
     /**
      * We have one child
      */
-    public void setView(View view, WindowManager.LayoutParams attrs,
-            View panelParentView) {
+    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
         synchronized (this) {
             if (mView == null) {
                 mView = view;
                 mWindowAttributes.copyFrom(attrs);
                 attrs = mWindowAttributes;
+                
+                enableHardwareAcceleration(attrs);
+
                 if (view instanceof RootViewSurfaceTaker) {
                     mSurfaceHolderCallback =
                             ((RootViewSurfaceTaker)view).willYouTakeTheSurface();
@@ -576,6 +459,20 @@
         }
     }
 
+    private void enableHardwareAcceleration(WindowManager.LayoutParams attrs) {
+        // Only enable hardware acceleration if we are not in the system process
+        // The window manager creates ViewRoots to display animated preview windows
+        // of launching apps and we don't want those to be hardware accelerated
+        if (Process.myUid() != Process.SYSTEM_UID) {
+            // Try to enable hardware acceleration if requested
+            if (attrs != null &&
+                    (attrs.flags & WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED) != 0) {
+                final boolean translucent = attrs.format != PixelFormat.OPAQUE;
+                mHwRenderer = HardwareRenderer.createGlRenderer(2, translucent);
+            }
+        }
+    }
+
     public View getView() {
         return mView;
     }
@@ -732,8 +629,6 @@
         boolean viewVisibilityChanged = mViewVisibility != viewVisibility
                 || mNewSurfaceNeeded;
 
-        float appScale = mAttachInfo.mApplicationScale;
-
         WindowManager.LayoutParams params = null;
         if (mWindowAttributesChanged) {
             mWindowAttributesChanged = false;
@@ -781,8 +676,8 @@
             attachInfo.mWindowVisibility = viewVisibility;
             host.dispatchWindowVisibilityChanged(viewVisibility);
             if (viewVisibility != View.VISIBLE || mNewSurfaceNeeded) {
-                if (mUseGL) {
-                    destroyGL();
+                if (mHwRenderer != null) {
+                    mHwRenderer.destroy();
                 }
             }
             if (viewVisibility == View.GONE) {
@@ -898,10 +793,12 @@
 
         final boolean computesInternalInsets =
                 attachInfo.mTreeObserver.hasComputeInternalInsetsListeners();
+
         boolean insetsPending = false;
         int relayoutResult = 0;
-        if (mFirst || windowShouldResize || insetsChanged
-                || viewVisibilityChanged || params != null) {
+
+        if (mFirst || windowShouldResize || insetsChanged ||
+                viewVisibilityChanged || params != null) {
 
             if (viewVisibility == View.VISIBLE) {
                 // If this window is giving internal insets to the window
@@ -913,26 +810,19 @@
                 // window, waiting until we can finish laying out this window
                 // and get back to the window manager with the ultimately
                 // computed insets.
-                insetsPending = computesInternalInsets
-                        && (mFirst || viewVisibilityChanged);
-
-                if (mWindowAttributes.memoryType == WindowManager.LayoutParams.MEMORY_TYPE_GPU) {
-                    if (params == null) {
-                        params = mWindowAttributes;
-                    }
-                    mGlWanted = true;
-                }
+                insetsPending = computesInternalInsets && (mFirst || viewVisibilityChanged);
             }
 
             if (mSurfaceHolder != null) {
                 mSurfaceHolder.mSurfaceLock.lock();
                 mDrawingAllowed = true;
             }
-            
-            boolean initialized = false;
+
+            boolean hwIntialized = false;
             boolean contentInsetsChanged = false;
             boolean visibleInsetsChanged;
             boolean hadSurface = mSurface.isValid();
+
             try {
                 int fl = 0;
                 if (params != null) {
@@ -992,9 +882,8 @@
                         fullRedrawNeeded = true;
                         mPreviousTransparentRegion.setEmpty();
 
-                        if (mGlWanted && !mUseGL) {
-                            initializeGL();
-                            initialized = mGlCanvas != null;
+                        if (mHwRenderer != null) {
+                            hwIntialized = mHwRenderer.initialize(mHolder);
                         }
                     }
                 } else if (!mSurface.isValid()) {
@@ -1072,9 +961,8 @@
                 }
             }
             
-            if (initialized) {
-                mGlCanvas.setViewport((int) (mWidth * appScale + 0.5f),
-                        (int) (mHeight * appScale + 0.5f));
+            if (hwIntialized) {
+                mHwRenderer.setup(mWidth, mHeight, mAttachInfo);
             }
 
             boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
@@ -1347,7 +1235,8 @@
         if (!sFirstDrawComplete) {
             synchronized (sFirstDrawHandlers) {
                 sFirstDrawComplete = true;
-                for (int i=0; i<sFirstDrawHandlers.size(); i++) {
+                final int count = sFirstDrawHandlers.size();
+                for (int i = 0; i< count; i++) {
                     post(sFirstDrawHandlers.get(i));
                 }
             }
@@ -1381,53 +1270,16 @@
             return;
         }
         
-        if (mUseGL) {
+        if (mHwRenderer != null && mHwRenderer.isEnabled()) {
             if (!dirty.isEmpty()) {
-                Canvas canvas = mGlCanvas;
-                if (mGL != null && canvas != null) {
-                    mGL.glDisable(GL_SCISSOR_TEST);
-                    mGL.glClearColor(0, 0, 0, 0);
-                    mGL.glClear(GL_COLOR_BUFFER_BIT);
-                    mGL.glEnable(GL_SCISSOR_TEST);
-
-                    mAttachInfo.mDrawingTime = SystemClock.uptimeMillis();
-                    mAttachInfo.mIgnoreDirtyState = true;
-                    mView.mPrivateFlags |= View.DRAWN;
-
-                    int saveCount = canvas.save(Canvas.MATRIX_SAVE_FLAG);
-                    try {
-                        canvas.translate(0, -yoff);
-                        if (mTranslator != null) {
-                            mTranslator.translateCanvas(canvas);
-                        }
-                        canvas.setScreenDensity(scalingRequired
-                                ? DisplayMetrics.DENSITY_DEVICE : 0);
-                        mView.draw(canvas);
-                        if (Config.DEBUG && ViewDebug.consistencyCheckEnabled) {
-                            mView.dispatchConsistencyCheck(ViewDebug.CONSISTENCY_DRAWING);
-                        }
-                    } finally {
-                        canvas.restoreToCount(saveCount);
-                    }
-
-                    mAttachInfo.mIgnoreDirtyState = false;
-
-                    mEgl.eglSwapBuffers(mEglDisplay, mEglSurface);
-                    checkEglErrors();
-
-                    if (SHOW_FPS || Config.DEBUG && ViewDebug.showFps) {
-                        int now = (int)SystemClock.elapsedRealtime();
-                        if (sDrawTime != 0) {
-                            nativeShowFPS(canvas, now - sDrawTime);
-                        }
-                        sDrawTime = now;
-                    }
-                }
+                mHwRenderer.draw(mView, mAttachInfo, yoff);
             }
+
             if (scrolling) {
                 mFullRedrawNeeded = true;
                 scheduleTraversals();
             }
+
             return;
         }
 
@@ -1739,8 +1591,6 @@
     }
 
     void dispatchDetachedFromWindow() {
-        if (Config.LOGV) Log.v(TAG, "Detaching in " + this + " of " + mSurface);
-
         if (mView != null) {
             mView.dispatchDetachedFromWindow();
         }
@@ -1749,8 +1599,8 @@
         mAttachInfo.mRootView = null;
         mAttachInfo.mSurface = null;
 
-        if (mUseGL) {
-            destroyGL();
+        if (mHwRenderer != null) {
+            mHwRenderer.destroy();
         }
         mSurface.release();
 
@@ -1934,18 +1784,8 @@
                     boolean inTouchMode = msg.arg2 != 0;
                     ensureTouchModeLocally(inTouchMode);
 
-                    if (mGlWanted) {
-                        checkEglErrors();
-                        // we lost the gl context, so recreate it.
-                        if (mGlWanted && !mUseGL) {
-                            initializeGL();
-                            if (mGlCanvas != null) {
-                                float appScale = mAttachInfo.mApplicationScale;
-                                mGlCanvas.setViewport(
-                                        (int) (mWidth * appScale + 0.5f),
-                                        (int) (mHeight * appScale + 0.5f));
-                            }
-                        }
+                    if (mHwRenderer != null) {
+                        mHwRenderer.initializeIfNeeded(mWidth, mHeight, mAttachInfo, mHolder);
                     }
                 }
 
@@ -1995,8 +1835,7 @@
             if ((event.getFlags()&KeyEvent.FLAG_FROM_SYSTEM) != 0) {
                 // The IME is trying to say this event is from the
                 // system!  Bad bad bad!
-                event = KeyEvent.changeFlags(event,
-                        event.getFlags()&~KeyEvent.FLAG_FROM_SYSTEM);
+                event = KeyEvent.changeFlags(event, event.getFlags() & ~KeyEvent.FLAG_FROM_SYSTEM);
             }
             deliverKeyEventToViewHierarchy((KeyEvent)msg.obj, false);
         } break;
@@ -2479,8 +2318,7 @@
     private void deliverKeyEvent(KeyEvent event, boolean sendDone) {
         // If mView is null, we just consume the key event because it doesn't
         // make sense to do anything else with it.
-        boolean handled = mView != null
-                ? mView.dispatchKeyEventPreIme(event) : true;
+        boolean handled = mView == null || mView.dispatchKeyEventPreIme(event);
         if (handled) {
             if (sendDone) {
                 if (LOCAL_LOGV) Log.v(
@@ -2514,7 +2352,6 @@
             final boolean sendDone = seq >= 0;
             if (!handled) {
                 deliverKeyEventToViewHierarchy(event, sendDone);
-                return;
             } else if (sendDone) {
                 if (LOCAL_LOGV) Log.v(
                         TAG, "Telling window manager key is finished");
@@ -2715,7 +2552,7 @@
 
     void doDie() {
         checkThread();
-        if (Config.LOGV) Log.v(TAG, "DIE in " + this + " of " + mSurface);
+        if (LOCAL_LOGV) Log.v(TAG, "DIE in " + this + " of " + mSurface);
         synchronized (this) {
             if (mAdded && !mFirst) {
                 int viewVisibility = mView.getVisibility();
@@ -2792,13 +2629,12 @@
         if (event.getAction() == KeyEvent.ACTION_DOWN) {
             //noinspection ConstantConditions
             if (false && event.getKeyCode() == KeyEvent.KEYCODE_CAMERA) {
-                if (Config.LOGD) Log.d("keydisp",
-                        "===================================================");
-                if (Config.LOGD) Log.d("keydisp", "Focused view Hierarchy is:");
+                if (DBG) Log.d("keydisp", "===================================================");
+                if (DBG) Log.d("keydisp", "Focused view Hierarchy is:");
+
                 debug();
 
-                if (Config.LOGD) Log.d("keydisp",
-                        "===================================================");
+                if (DBG) Log.d("keydisp", "===================================================");
             }
         }
 
@@ -2880,6 +2716,10 @@
         return false;
     }
 
+    public ActionMode startActionModeForChild(View originalView, ActionMode.Callback callback) {
+        return null;
+    }
+
     public void createContextMenu(ContextMenu menu) {
     }
 
@@ -3380,8 +3220,4 @@
     }
 
     private static native void nativeShowFPS(Canvas canvas, int durationMillis);
-
-    // inform skia to just abandon its texture cache IDs
-    // doesn't call glDeleteTextures
-    private static native void nativeAbandonGlCaches();
 }
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index 11c09c1..f32ff77 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -56,11 +56,22 @@
     public static final int FEATURE_CONTEXT_MENU = 6;
     /** Flag for custom title. You cannot combine this feature with other title features. */
     public static final int FEATURE_CUSTOM_TITLE = 7;
-    /** Flag for asking for an OpenGL enabled window.
-        All 2D graphics will be handled by OpenGL ES.
-        @hide
-    */
-    public static final int FEATURE_OPENGL = 8;
+    /**
+     * Flag for enabling the Action Bar.
+     * This is enabled by default for some devices. The Action Bar
+     * replaces the title bar and provides an alternate location
+     * for an on-screen menu button on some devices.
+     */
+    public static final int FEATURE_ACTION_BAR = 8;
+    /**
+     * Flag for specifying the behavior of action modes when an Action Bar is not present.
+     * If overlay is enabled, the action mode UI will be allowed to cover existing window content.
+     */
+    public static final int FEATURE_ACTION_MODE_OVERLAY = 9;
+    /**
+     * Flag for requesting this window to be hardware accelerated, if possible. 
+     */
+    public static final int FEATURE_HARDWARE_ACCELERATED = 10;
     /** Flag for setting the progress bar's visibility to VISIBLE */
     public static final int PROGRESS_VISIBILITY_ON = -1;
     /** Flag for setting the progress bar's visibility to GONE */
@@ -292,6 +303,14 @@
          * @see android.app.Activity#onSearchRequested() 
          */
         public boolean onSearchRequested();
+
+        /**
+         * Called when an action mode is being started.
+         *
+         * @param callback Callback to control the lifecycle of this action mode
+         * @return The ActionMode that was started, or null if it was canceled
+         */
+        public ActionMode onStartActionMode(ActionMode.Callback callback);
     }
 
     public Window(Context context) {
@@ -360,21 +379,35 @@
      *
      * @param wm The ViewManager for adding new windows.
      */
-    public void setWindowManager(WindowManager wm,
-            IBinder appToken, String appName) {
+    public void setWindowManager(WindowManager wm, IBinder appToken, String appName) {
+        setWindowManager(wm, appToken, appName, false);
+    }
+
+    /**
+     * Set the window manager for use by this Window to, for example,
+     * display panels.  This is <em>not</em> used for displaying the
+     * Window itself -- that must be done by the client.
+     *
+     * @param wm The ViewManager for adding new windows.
+     */
+    public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
+            boolean hardwareAccelerated) {
         mAppToken = appToken;
         mAppName = appName;
         if (wm == null) {
             wm = WindowManagerImpl.getDefault();
         }
-        mWindowManager = new LocalWindowManager(wm);
+        mWindowManager = new LocalWindowManager(wm, hardwareAccelerated);
     }
 
     private class LocalWindowManager implements WindowManager {
-        LocalWindowManager(WindowManager wm) {
+        private boolean mHardwareAccelerated;
+
+        LocalWindowManager(WindowManager wm, boolean hardwareAccelerated) {
             mWindowManager = wm;
             mDefaultDisplay = mContext.getResources().getDefaultDisplay(
                     mWindowManager.getDefaultDisplay());
+            mHardwareAccelerated = hardwareAccelerated;
         }
 
         public final void addView(View view, ViewGroup.LayoutParams params) {
@@ -421,6 +454,9 @@
             if (wp.packageName == null) {
                 wp.packageName = mContext.getPackageName();
             }
+            if (mHardwareAccelerated) {
+                wp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
+            }
             mWindowManager.addView(view, params);
         }
 
@@ -817,6 +853,8 @@
 
     public abstract void togglePanel(int featureId, KeyEvent event);
 
+    public abstract void invalidatePanelMenu(int featureId);
+    
     public abstract boolean performPanelShortcut(int featureId,
                                                  int keyCode,
                                                  KeyEvent event,
@@ -996,6 +1034,16 @@
     {
         return mFeatures;
     }
+    
+    /**
+     * Query for the availability of a certain feature.
+     * 
+     * @param feature The feature ID to check
+     * @return true if the feature is enabled, false otherwise.
+     */
+    public boolean hasFeature(int feature) {
+        return (getFeatures() & (1 << feature)) != 0;
+    }
 
     /**
      * Return the feature bits that are being implemented by this Window.
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index eebbc93..c147b74 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -72,6 +72,7 @@
          * When using {@link Gravity#LEFT} or {@link Gravity#RIGHT} it provides
          * an offset from the given edge.
          */
+        @ViewDebug.ExportedProperty
         public int x;
         
         /**
@@ -79,6 +80,7 @@
          * When using {@link Gravity#TOP} or {@link Gravity#BOTTOM} it provides
          * an offset from the given edge.
          */
+        @ViewDebug.ExportedProperty
         public int y;
 
         /**
@@ -87,6 +89,7 @@
          * should not be stretched. Otherwise the extra pixels will be pro-rated
          * among all views whose weight is greater than 0.
          */
+        @ViewDebug.ExportedProperty
         public float horizontalWeight;
 
         /**
@@ -95,8 +98,9 @@
          * should not be stretched. Otherwise the extra pixels will be pro-rated
          * among all views whose weight is greater than 0.
          */
+        @ViewDebug.ExportedProperty
         public float verticalWeight;
-        
+
         /**
          * The general type of window.  There are three main classes of
          * window types:
@@ -389,6 +393,7 @@
          * @see #FLAG_FULLSCREEN
          * @see #FLAG_FORCE_NOT_FULLSCREEN
          * @see #FLAG_IGNORE_CHEEK_PRESSES
+         * @see #FLAG_HARDWARE_ACCELERATED
          */
         @ViewDebug.ExportedProperty(flagMapping = {
             @ViewDebug.FlagToString(mask = FLAG_BLUR_BEHIND, equals = FLAG_BLUR_BEHIND,
@@ -420,7 +425,9 @@
             @ViewDebug.FlagToString(mask = FLAG_FORCE_NOT_FULLSCREEN,
                     equals = FLAG_FORCE_NOT_FULLSCREEN, name = "FLAG_FORCE_NOT_FULLSCREEN"),
             @ViewDebug.FlagToString(mask = FLAG_IGNORE_CHEEK_PRESSES,
-                    equals = FLAG_IGNORE_CHEEK_PRESSES, name = "FLAG_IGNORE_CHEEK_PRESSES")
+                    equals = FLAG_IGNORE_CHEEK_PRESSES, name = "FLAG_IGNORE_CHEEK_PRESSES"),
+            @ViewDebug.FlagToString(mask = FLAG_HARDWARE_ACCELERATED,
+                    equals = FLAG_HARDWARE_ACCELERATED, name = "FLAG_HARDWARE_ACCELERATED")
         })
         public int flags;
         
@@ -603,6 +610,12 @@
          * it is created.
          * {@hide} */
         public static final int FLAG_SYSTEM_ERROR = 0x40000000;
+        
+        /**
+         * Indicates whether this window should be hardware accelerated.
+         * Requesting hardware acceleration does not guarantee it will happen.
+         */
+        public static final int FLAG_HARDWARE_ACCELERATED = 0x80000000;        
 
         /**
          * Given a particular set of window manager flags, determine whether
@@ -1075,6 +1088,7 @@
                 screenOrientation = o.screenOrientation;
                 changes |= SCREEN_ORIENTATION_CHANGED;
             }
+
             return changes;
         }
     
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/view/animation/Animation.java b/core/java/android/view/animation/Animation.java
index 349b7e5..f3392d9 100644
--- a/core/java/android/view/animation/Animation.java
+++ b/core/java/android/view/animation/Animation.java
@@ -174,7 +174,13 @@
      * Desired Z order mode during animation.
      */
     private int mZAdjustment;
-    
+
+    /**
+     * scalefactor to apply to pivot points, etc. during animation. Subclasses retrieve the
+     * value via getScaleFactor().
+     */
+    private float mScaleFactor = 1f;
+
     /**
      * Don't animate the wallpaper.
      */
@@ -553,6 +559,19 @@
     }
     
     /**
+     * The scale factor is set by the call to <code>getTransformation</code>. Overrides of 
+     * {@link #getTransformation(long, Transformation, float)} will get this value
+     * directly. Overrides of {@link #applyTransformation(float, Transformation)} can
+     * call this method to get the value.
+     * 
+     * @return float The scale factor that should be applied to pre-scaled values in
+     * an Animation such as the pivot points in {@link ScaleAnimation} and {@link RotateAnimation}.
+     */
+    protected float getScaleFactor() {
+        return mScaleFactor;
+    }
+
+    /**
      * If detachWallpaper is true, and this is a window animation of a window
      * that has a wallpaper background, then the window will be detached from
      * the wallpaper while it runs.  That is, the animation will only be applied
@@ -735,6 +754,7 @@
      * @return True if the animation is still running
      */
     public boolean getTransformation(long currentTime, Transformation outTransformation) {
+
         if (mStartTime == -1) {
             mStartTime = currentTime;
         }
@@ -806,6 +826,24 @@
 
         return mMore;
     }
+    
+    /**
+     * Gets the transformation to apply at a specified point in time. Implementations of this
+     * method should always replace the specified Transformation or document they are doing
+     * otherwise.
+     *
+     * @param currentTime Where we are in the animation. This is wall clock time.
+     * @param outTransformation A tranformation object that is provided by the
+     *        caller and will be filled in by the animation.
+     * @param scale Scaling factor to apply to any inputs to the transform operation, such
+     *        pivot points being rotated or scaled around.
+     * @return True if the animation is still running
+     */
+    public boolean getTransformation(long currentTime, Transformation outTransformation,
+            float scale) {
+        mScaleFactor = scale;
+        return getTransformation(currentTime, outTransformation);
+    }
 
     /**
      * <p>Indicates whether this animation has started or not.</p>
diff --git a/core/java/android/view/animation/AnimationSet.java b/core/java/android/view/animation/AnimationSet.java
index 1546dcd..873ce53 100644
--- a/core/java/android/view/animation/AnimationSet.java
+++ b/core/java/android/view/animation/AnimationSet.java
@@ -312,7 +312,7 @@
             final Animation a = animations.get(i);
 
             temp.clear();
-            more = a.getTransformation(currentTime, temp) || more;
+            more = a.getTransformation(currentTime, temp, getScaleFactor()) || more;
             t.compose(temp);
 
             started = started || a.hasStarted();
diff --git a/core/java/android/view/animation/AnimationUtils.java b/core/java/android/view/animation/AnimationUtils.java
index 3088382..8e4cf67 100644
--- a/core/java/android/view/animation/AnimationUtils.java
+++ b/core/java/android/view/animation/AnimationUtils.java
@@ -16,6 +16,12 @@
 
 package android.view.animation;
 
+import android.animation.Animatable;
+import android.animation.Animator;
+import android.animation.PropertyAnimator;
+import android.animation.Sequencer;
+import android.content.res.TypedArray;
+import android.util.TypedValue;
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
@@ -27,12 +33,21 @@
 import android.os.SystemClock;
 
 import java.io.IOException;
+import java.util.ArrayList;
 
 /**
  * Defines common utilities for working with animations.
  *
  */
 public class AnimationUtils {
+
+    /**
+     * These flags are used when parsing Sequencer objects
+     */
+    private static final int TOGETHER = 0;
+    private static final int SEQUENTIALLY = 1;
+
+
     /**
      * Returns the current animation time in milliseconds. This time should be used when invoking
      * {@link Animation#setStartTime(long)}. Refer to {@link android.os.SystemClock} for more
@@ -49,7 +64,7 @@
 
     /**
      * Loads an {@link Animation} object from a resource
-     * 
+     *
      * @param context Application context used to access resources
      * @param id The resource id of the animation to load
      * @return The animation object reference by the specified id
@@ -77,17 +92,53 @@
         }
     }
 
+    /**
+     * Loads an {@link Animation} object from a resource
+     *
+     * @param context Application context used to access resources
+     * @param id The resource id of the animation to load
+     * @return The animation object reference by the specified id
+     * @throws NotFoundException when the animation cannot be loaded
+     */
+    public static Animatable loadAnimator(Context context, int id)
+            throws NotFoundException {
+
+        XmlResourceParser parser = null;
+        try {
+            parser = context.getResources().getAnimation(id);
+            return createAnimatableFromXml(context, parser);
+        } catch (XmlPullParserException ex) {
+            NotFoundException rnf = new NotFoundException("Can't load animation resource ID #0x" +
+                    Integer.toHexString(id));
+            rnf.initCause(ex);
+            throw rnf;
+        } catch (IOException ex) {
+            NotFoundException rnf = new NotFoundException("Can't load animation resource ID #0x" +
+                    Integer.toHexString(id));
+            rnf.initCause(ex);
+            throw rnf;
+        } finally {
+            if (parser != null) parser.close();
+        }
+    }
+
+    private static Animatable createAnimatableFromXml(Context c, XmlPullParser parser)
+            throws XmlPullParserException, IOException {
+
+        return createAnimatableFromXml(c, parser, Xml.asAttributeSet(parser), null, 0);
+    }
+
     private static Animation createAnimationFromXml(Context c, XmlPullParser parser)
             throws XmlPullParserException, IOException {
 
         return createAnimationFromXml(c, parser, null, Xml.asAttributeSet(parser));
     }
-    
+
     private static Animation createAnimationFromXml(Context c, XmlPullParser parser,
             AnimationSet parent, AttributeSet attrs) throws XmlPullParserException, IOException {
-        
+
         Animation anim = null;
- 
+
         // Make sure we are on a start tag.
         int type;
         int depth = parser.getDepth();
@@ -100,7 +151,7 @@
             }
 
             String  name = parser.getName();
-    
+
             if (name.equals("set")) {
                 anim = new AnimationSet(c, attrs);
                 createAnimationFromXml(c, parser, (AnimationSet)anim, attrs);
@@ -120,7 +171,67 @@
                 parent.addAnimation(anim);
             }
         }
-    
+
+        return anim;
+
+    }
+
+    private static Animatable createAnimatableFromXml(Context c, XmlPullParser parser,
+            AttributeSet attrs, Sequencer parent, int sequenceOrdering)
+            throws XmlPullParserException, IOException {
+
+        Animatable anim = null;
+        ArrayList<Animatable> childAnims = 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 (name.equals("property")) {
+                anim = new PropertyAnimator(c, attrs);
+            } else if (name.equals("animator")) {
+                anim = new Animator(c, attrs);
+            } else if (name.equals("sequencer")) {
+                anim = new Sequencer();
+                TypedArray a = c.obtainStyledAttributes(attrs,
+                        com.android.internal.R.styleable.Sequencer);
+                int ordering = a.getInt(com.android.internal.R.styleable.Sequencer_ordering,
+                        TOGETHER);
+                createAnimatableFromXml(c, parser, attrs, (Sequencer) anim,  ordering);
+                a.recycle();
+            } else {
+                throw new RuntimeException("Unknown animator name: " + parser.getName());
+            }
+
+            if (parent != null) {
+                if (childAnims == null) {
+                    childAnims = new ArrayList<Animatable>();
+                }
+                childAnims.add(anim);
+            }
+        }
+        if (parent != null && childAnims != null) {
+            Animatable[] animsArray = new Animatable[childAnims.size()];
+            int index = 0;
+            for (Animatable a : childAnims) {
+                animsArray[index++] = a;
+            }
+            if (sequenceOrdering == TOGETHER) {
+                parent.playTogether(animsArray);
+            } else {
+                parent.playSequentially(animsArray);
+            }
+        }
+
         return anim;
 
     }
diff --git a/core/java/android/view/animation/RotateAnimation.java b/core/java/android/view/animation/RotateAnimation.java
index 284ccce..58bf084 100644
--- a/core/java/android/view/animation/RotateAnimation.java
+++ b/core/java/android/view/animation/RotateAnimation.java
@@ -148,11 +148,12 @@
     @Override
     protected void applyTransformation(float interpolatedTime, Transformation t) {
         float degrees = mFromDegrees + ((mToDegrees - mFromDegrees) * interpolatedTime);
-
+        float scale = getScaleFactor();
+        
         if (mPivotX == 0.0f && mPivotY == 0.0f) {
             t.getMatrix().setRotate(degrees);
         } else {
-            t.getMatrix().setRotate(degrees, mPivotX, mPivotY);
+            t.getMatrix().setRotate(degrees, mPivotX * scale, mPivotY * scale);
         }
     }
 
diff --git a/core/java/android/view/animation/ScaleAnimation.java b/core/java/android/view/animation/ScaleAnimation.java
index 1a56c8b..8537d42 100644
--- a/core/java/android/view/animation/ScaleAnimation.java
+++ b/core/java/android/view/animation/ScaleAnimation.java
@@ -161,6 +161,7 @@
     protected void applyTransformation(float interpolatedTime, Transformation t) {
         float sx = 1.0f;
         float sy = 1.0f;
+        float scale = getScaleFactor();
 
         if (mFromX != 1.0f || mToX != 1.0f) {
             sx = mFromX + ((mToX - mFromX) * interpolatedTime);
@@ -172,7 +173,7 @@
         if (mPivotX == 0 && mPivotY == 0) {
             t.getMatrix().setScale(sx, sy);
         } else {
-            t.getMatrix().setScale(sx, sy, mPivotX, mPivotY);
+            t.getMatrix().setScale(sx, sy, scale * mPivotX, scale * mPivotY);
         }
     }
 
diff --git a/core/java/android/webkit/AccessibilityInjector.java b/core/java/android/webkit/AccessibilityInjector.java
new file mode 100644
index 0000000..49ddc19
--- /dev/null
+++ b/core/java/android/webkit/AccessibilityInjector.java
@@ -0,0 +1,99 @@
+/*
+ * 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.webkit;
+
+import android.view.KeyEvent;
+import android.view.accessibility.AccessibilityEvent;
+import android.webkit.WebViewCore.EventHub;
+
+/**
+ * This class injects accessibility into WebViews with disabled JavaScript or
+ * WebViews with enabled JavaScript but for which we have no accessibility
+ * script to inject.
+ */
+class AccessibilityInjector {
+
+    // Handle to the WebView this injector is associated with.
+    private final WebView mWebView;
+
+    /**
+     * Creates a new injector associated with a given VwebView.
+     *
+     * @param webView The associated WebView.
+     */
+    public AccessibilityInjector(WebView webView) {
+        mWebView = webView;
+    }
+
+    /**
+     * Processes a key down <code>event</code>.
+     *
+     * @return True if the event was processed.
+     */
+    public boolean onKeyEvent(KeyEvent event) {
+
+        // as a proof of concept let us do the simplest example
+
+        if (event.getAction() != KeyEvent.ACTION_UP) {
+            return false;
+        }
+
+        int keyCode = event.getKeyCode();
+
+        switch (keyCode) {
+            case KeyEvent.KEYCODE_N:
+                modifySelection("extend", "forward", "sentence");
+                break;
+            case KeyEvent.KEYCODE_P:
+                modifySelection("extend", "backward", "sentence");
+                break;
+        }
+
+        return false;
+    }
+
+    /**
+     * Called when the <code>selectionString</code> has changed.
+     */
+    public void onSelectionStringChange(String selectionString) {
+        // put the selection string in an AccessibilityEvent and send it
+        AccessibilityEvent event = AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_FOCUSED);
+        event.getText().add(selectionString);
+        mWebView.sendAccessibilityEventUnchecked(event);
+    }
+
+    /**
+     * Modifies the current selection.
+     *
+     * @param alter Specifies how to alter the selection.
+     * @param direction The direction in which to alter the selection.
+     * @param granularity The granularity of the selection modification.
+     */
+    private void modifySelection(String alter, String direction, String granularity) {
+        WebViewCore webViewCore = mWebView.getWebViewCore();
+
+        if (webViewCore == null) {
+            return;
+        }
+
+        WebViewCore.ModifySelectionData data = new WebViewCore.ModifySelectionData();
+        data.mAlter = alter;
+        data.mDirection = direction;
+        data.mGranularity = granularity;
+        webViewCore.sendMessage(EventHub.MODIFY_SELECTION, data);
+    }
+}
diff --git a/core/java/android/webkit/BrowserFrame.java b/core/java/android/webkit/BrowserFrame.java
index 219a469..b021ded 100644
--- a/core/java/android/webkit/BrowserFrame.java
+++ b/core/java/android/webkit/BrowserFrame.java
@@ -72,6 +72,8 @@
     // queue has been cleared,they are ignored.
     private boolean mBlockMessages = false;
 
+    private static String sDataDirectory = "";
+
     // Is this frame the main frame?
     private boolean mIsMainFrame;
 
@@ -224,6 +226,11 @@
         AssetManager am = context.getAssets();
         nativeCreateFrame(w, am, proxy.getBackForwardList());
 
+        if (sDataDirectory.length() == 0) {
+            String dir = appContext.getFilesDir().getAbsolutePath();
+            sDataDirectory =  dir.substring(0, dir.lastIndexOf('/'));
+        }
+
         if (DebugFlags.BROWSER_FRAME) {
             Log.v(LOGTAG, "BrowserFrame constructor: this=" + this);
         }
@@ -294,6 +301,18 @@
     }
 
     /**
+     * Saves the contents of the frame as a web archive.
+     *
+     * @param basename The filename where the archive should be placed.
+     * @param autoname If false, takes filename to be a file. If true, filename
+     *                 is assumed to be a directory in which a filename will be
+     *                 chosen according to the url of the current page.
+     */
+    /* package */ String saveWebArchive(String basename, boolean autoname) {
+        return nativeSaveWebArchive(basename, autoname);
+    }
+
+    /**
      * Go back or forward the number of steps given.
      * @param steps A negative or positive number indicating the direction
      *              and number of steps to move.
@@ -510,12 +529,21 @@
     private native String externalRepresentation();
 
     /**
-     * Retrieves the visual text of the current frame, puts it as the object for
+     * Retrieves the visual text of the frames, puts it as the object for
      * the message and sends the message.
      * @param callback the message to use to send the visual text
      */
     public void documentAsText(Message callback) {
-        callback.obj = documentAsText();;
+        StringBuilder text = new StringBuilder();
+        if (callback.arg1 != 0) {
+            // Dump top frame as text.
+            text.append(documentAsText());
+        }
+        if (callback.arg2 != 0) {
+            // Dump child frames as text.
+            text.append(childFramesAsText());
+        }
+        callback.obj = text.toString();
         callback.sendToTarget();
     }
 
@@ -524,6 +552,11 @@
      */
     private native String documentAsText();
 
+    /**
+     * Return the text drawn on the child frames as a string
+     */
+    private native String childFramesAsText();
+
     /*
      * This method is called by WebCore to inform the frame that
      * the Javascript window object has been cleared.
@@ -617,6 +650,14 @@
     }
 
     /**
+     * Called by JNI. Gets the applications data directory
+     * @return String The applications data directory
+     */
+    private static String getDataDirectory() {
+        return sDataDirectory;
+    }
+
+    /**
      * Start loading a resource.
      * @param loaderHandle The native ResourceLoader that is the target of the
      *                     data.
@@ -785,11 +826,7 @@
      * @return The BrowserFrame object stored in the new WebView.
      */
     private BrowserFrame createWindow(boolean dialog, boolean userGesture) {
-        WebView w = mCallbackProxy.createWindow(dialog, userGesture);
-        if (w != null) {
-            return w.getWebViewCore().getBrowserFrame();
-        }
-        return null;
+        return mCallbackProxy.createWindow(dialog, userGesture);
     }
 
     /**
@@ -846,6 +883,7 @@
     private static final int FILE_UPLOAD_LABEL = 4;
     private static final int RESET_LABEL = 5;
     private static final int SUBMIT_LABEL = 6;
+    private static final int FILE_UPLOAD_NO_FILE_CHOSEN = 7;
 
     String getRawResFilename(int id) {
         int resid;
@@ -875,6 +913,10 @@
                 return mContext.getResources().getString(
                         com.android.internal.R.string.submit);
 
+            case FILE_UPLOAD_NO_FILE_CHOSEN:
+                return mContext.getResources().getString(
+                        com.android.internal.R.string.no_file_chosen);
+
             default:
                 Log.e(LOGTAG, "getRawResFilename got incompatible resource ID");
                 return "";
@@ -1010,5 +1052,7 @@
      */
     private native HashMap getFormTextData();
 
+    private native String nativeSaveWebArchive(String basename, boolean autoname);
+
     private native void nativeOrientationChanged(int orientation);
 }
diff --git a/core/java/android/webkit/CallbackProxy.java b/core/java/android/webkit/CallbackProxy.java
index 0e0e032..1b5651b 100644
--- a/core/java/android/webkit/CallbackProxy.java
+++ b/core/java/android/webkit/CallbackProxy.java
@@ -16,6 +16,7 @@
 
 package android.webkit;
 
+import android.app.Activity;
 import android.app.AlertDialog;
 import android.content.ActivityNotFoundException;
 import android.content.Context;
@@ -41,6 +42,7 @@
 import java.net.MalformedURLException;
 import java.net.URL;
 import java.util.HashMap;
+import java.util.Map;
 
 /**
  * This class is a proxy class for handling WebCore -> UI thread messaging. All
@@ -112,6 +114,7 @@
     private static final int ADD_HISTORY_ITEM                    = 135;
     private static final int HISTORY_INDEX_CHANGED               = 136;
     private static final int AUTH_CREDENTIALS                    = 137;
+    private static final int SET_INSTALLABLE_WEBAPP              = 138;
 
     // Message triggered by the client to resume execution
     private static final int NOTIFY                              = 200;
@@ -500,18 +503,32 @@
                     String url = msg.getData().getString("url");
                     if (!mWebChromeClient.onJsAlert(mWebView, url, message,
                             res)) {
+                        // only display the alert dialog if the mContext is
+                        // Activity and its window has the focus.
+                        if (!(mContext instanceof Activity)
+                                || !((Activity) mContext).hasWindowFocus()) {
+                            res.cancel();
+                            res.setReady();
+                            break;
+                        }
                         new AlertDialog.Builder(mContext)
                                 .setTitle(getJsDialogTitle(url))
                                 .setMessage(message)
                                 .setPositiveButton(R.string.ok,
-                                        new AlertDialog.OnClickListener() {
+                                        new DialogInterface.OnClickListener() {
                                             public void onClick(
                                                     DialogInterface dialog,
                                                     int which) {
                                                 res.confirm();
                                             }
                                         })
-                                .setCancelable(false)
+                                .setOnCancelListener(
+                                        new DialogInterface.OnCancelListener() {
+                                            public void onCancel(
+                                                    DialogInterface dialog) {
+                                                res.cancel();
+                                            }
+                                        })
                                 .show();
                     }
                     res.setReady();
@@ -525,6 +542,14 @@
                     String url = msg.getData().getString("url");
                     if (!mWebChromeClient.onJsConfirm(mWebView, url, message,
                             res)) {
+                        // only display the alert dialog if the mContext is
+                        // Activity and its window has the focus.
+                        if (!(mContext instanceof Activity)
+                                || !((Activity) mContext).hasWindowFocus()) {
+                            res.cancel();
+                            res.setReady();
+                            break;
+                        }
                         new AlertDialog.Builder(mContext)
                                 .setTitle(getJsDialogTitle(url))
                                 .setMessage(message)
@@ -565,6 +590,14 @@
                     String url = msg.getData().getString("url");
                     if (!mWebChromeClient.onJsPrompt(mWebView, url, message,
                                 defaultVal, res)) {
+                        // only display the alert dialog if the mContext is
+                        // Activity and its window has the focus.
+                        if (!(mContext instanceof Activity)
+                                || !((Activity) mContext).hasWindowFocus()) {
+                            res.cancel();
+                            res.setReady();
+                            break;
+                        }
                         final LayoutInflater factory = LayoutInflater
                                 .from(mContext);
                         final View view = factory.inflate(R.layout.js_prompt,
@@ -616,6 +649,14 @@
                     String url = msg.getData().getString("url");
                     if (!mWebChromeClient.onJsBeforeUnload(mWebView, url,
                             message, res)) {
+                        // only display the alert dialog if the mContext is
+                        // Activity and its window has the focus.
+                        if (!(mContext instanceof Activity)
+                                || !((Activity) mContext).hasWindowFocus()) {
+                            res.cancel();
+                            res.setReady();
+                            break;
+                        }
                         final String m = mContext.getString(
                                 R.string.js_dialog_before_unload, message);
                         new AlertDialog.Builder(mContext)
@@ -725,7 +766,8 @@
 
             case OPEN_FILE_CHOOSER:
                 if (mWebChromeClient != null) {
-                    mWebChromeClient.openFileChooser((UploadFile) msg.obj);
+                    UploadFileMessageData data = (UploadFileMessageData)msg.obj;
+                    mWebChromeClient.openFileChooser(data.getUploadFile(), data.getAcceptType());
                 }
                 break;
 
@@ -750,6 +792,9 @@
                 mWebView.setHttpAuthUsernamePassword(
                         host, realm, username, password);
                 break;
+            case SET_INSTALLABLE_WEBAPP:
+                mWebChromeClient.setInstallableWebApp();
+                break;
         }
     }
 
@@ -1087,10 +1132,15 @@
     public void onProgressChanged(int newProgress) {
         // Synchronize so that mLatestProgress is up-to-date.
         synchronized (this) {
-            if (mWebChromeClient == null || mLatestProgress == newProgress) {
+            // update mLatestProgress even mWebChromeClient is null as
+            // WebView.getProgress() needs it
+            if (mLatestProgress == newProgress) {
                 return;
             }
             mLatestProgress = newProgress;
+            if (mWebChromeClient == null) {
+                return;
+            }
             if (!mProgressUpdatePending) {
                 sendEmptyMessage(PROGRESS);
                 mProgressUpdatePending = true;
@@ -1098,7 +1148,7 @@
         }
     }
 
-    public WebView createWindow(boolean dialog, boolean userGesture) {
+    public BrowserFrame createWindow(boolean dialog, boolean userGesture) {
         // Do an unsynchronized quick check to avoid posting if no callback has
         // been set.
         if (mWebChromeClient == null) {
@@ -1122,9 +1172,15 @@
 
         WebView w = transport.getWebView();
         if (w != null) {
-            w.getWebViewCore().initializeSubwindow();
+            WebViewCore core = w.getWebViewCore();
+            // If WebView.destroy() has been called, core may be null.  Skip
+            // initialization in that case and return null.
+            if (core != null) {
+                core.initializeSubwindow();
+                return core.getBrowserFrame();
+            }
         }
-        return w;
+        return null;
     }
 
     public void onRequestFocus() {
@@ -1166,9 +1222,7 @@
         // for null.
         WebHistoryItem i = mBackForwardList.getCurrentItem();
         if (i != null) {
-            if (precomposed || i.getTouchIconUrl() == null) {
-                i.setTouchIconUrl(url);
-            }
+            i.setTouchIconUrl(url, precomposed);
         }
         // Do an unsynchronized quick check to avoid posting if no callback has
         // been set.
@@ -1426,6 +1480,24 @@
         sendMessage(msg);
     }
 
+    private static class UploadFileMessageData {
+        private UploadFile mCallback;
+        private String mAcceptType;
+
+        public UploadFileMessageData(UploadFile uploadFile, String acceptType) {
+            mCallback = uploadFile;
+            mAcceptType = acceptType;
+        }
+
+        public UploadFile getUploadFile() {
+            return mCallback;
+        }
+
+        public String getAcceptType() {
+            return mAcceptType;
+        }
+    }
+
     private class UploadFile implements ValueCallback<Uri> {
         private Uri mValue;
         public void onReceiveValue(Uri value) {
@@ -1442,13 +1514,14 @@
     /**
      * Called by WebViewCore to open a file chooser.
      */
-    /* package */ Uri openFileChooser() {
+    /* package */ Uri openFileChooser(String acceptType) {
         if (mWebChromeClient == null) {
             return null;
         }
         Message myMessage = obtainMessage(OPEN_FILE_CHOOSER);
         UploadFile uploadFile = new UploadFile();
-        myMessage.obj = uploadFile;
+        UploadFileMessageData data = new UploadFileMessageData(uploadFile, acceptType);
+        myMessage.obj = data;
         synchronized (this) {
             sendMessage(myMessage);
             try {
@@ -1477,4 +1550,11 @@
         Message msg = obtainMessage(HISTORY_INDEX_CHANGED, index, 0, item);
         sendMessage(msg);
     }
+
+    void setInstallableWebApp() {
+        if (mWebChromeClient == null) {
+            return;
+        }
+        sendMessage(obtainMessage(SET_INSTALLABLE_WEBAPP));
+    }
 }
diff --git a/core/java/android/webkit/GeolocationService.java b/core/java/android/webkit/GeolocationService.java
index 24306f4..91de1d8 100755
--- a/core/java/android/webkit/GeolocationService.java
+++ b/core/java/android/webkit/GeolocationService.java
@@ -45,14 +45,13 @@
 
     /**
      * Constructor
+     * @param context The context from which we obtain the system service.
      * @param nativeObject The native object to which this object will report position updates and
      *     errors.
      */
-    public GeolocationService(long nativeObject) {
+    public GeolocationService(Context context, long nativeObject) {
         mNativeObject = nativeObject;
         // Register newLocationAvailable with platform service.
-        ActivityThread thread = ActivityThread.systemMain();
-        Context context = thread.getApplication();
         mLocationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
         if (mLocationManager == null) {
             Log.e(TAG, "Could not get location manager.");
@@ -62,9 +61,10 @@
     /**
      * Start listening for location updates.
      */
-    public void start() {
+    public boolean start() {
         registerForLocationUpdates();
         mIsRunning = true;
+        return mIsNetworkProviderAvailable || mIsGpsProviderAvailable;
     }
 
     /**
@@ -87,6 +87,8 @@
                 // only unregister from all, then reregister with all but the GPS.
                 unregisterFromLocationUpdates();
                 registerForLocationUpdates();
+                // Check that the providers are still available after we re-register.
+                maybeReportError("The last location provider is no longer available");
             }
         }
     }
@@ -156,11 +158,16 @@
      */
     private void registerForLocationUpdates() {
         try {
-            mLocationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0, this);
-            mIsNetworkProviderAvailable = true;
+            // Registration may fail if providers are not present on the device.
+            try {
+                mLocationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0, this);
+                mIsNetworkProviderAvailable = true;
+            } catch(IllegalArgumentException e) { }
             if (mIsGpsEnabled) {
-                mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, this);
-                mIsGpsProviderAvailable = true;
+                try {
+                    mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, this);
+                    mIsGpsProviderAvailable = true;
+                } catch(IllegalArgumentException e) { }
             }
         } catch(SecurityException e) {
             Log.e(TAG, "Caught security exception registering for location updates from system. " +
@@ -173,6 +180,8 @@
      */
     private void unregisterFromLocationUpdates() {
         mLocationManager.removeUpdates(this);
+        mIsNetworkProviderAvailable = false;
+        mIsGpsProviderAvailable = false;
     }
 
     /**
diff --git a/core/java/android/webkit/HTML5Audio.java b/core/java/android/webkit/HTML5Audio.java
new file mode 100644
index 0000000..d292881
--- /dev/null
+++ b/core/java/android/webkit/HTML5Audio.java
@@ -0,0 +1,224 @@
+/*
+ * 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.webkit;
+
+import android.media.MediaPlayer;
+import android.media.MediaPlayer.OnBufferingUpdateListener;
+import android.media.MediaPlayer.OnCompletionListener;
+import android.media.MediaPlayer.OnErrorListener;
+import android.media.MediaPlayer.OnPreparedListener;
+import android.media.MediaPlayer.OnSeekCompleteListener;
+import android.os.Handler;
+import android.os.Message;
+import android.util.Log;
+
+import java.io.IOException;
+import java.util.Timer;
+import java.util.TimerTask;
+
+/**
+ * <p>HTML5 support class for Audio.
+ */
+class HTML5Audio extends Handler
+                 implements MediaPlayer.OnBufferingUpdateListener,
+                            MediaPlayer.OnCompletionListener,
+                            MediaPlayer.OnErrorListener,
+                            MediaPlayer.OnPreparedListener,
+                            MediaPlayer.OnSeekCompleteListener {
+    // Logging tag.
+    private static final String LOGTAG = "HTML5Audio";
+
+    private MediaPlayer mMediaPlayer;
+
+    // The C++ MediaPlayerPrivateAndroid object.
+    private int mNativePointer;
+
+    private static int IDLE        =  0;
+    private static int INITIALIZED =  1;
+    private static int PREPARED    =  2;
+    private static int STARTED     =  4;
+    private static int COMPLETE    =  5;
+    private static int PAUSED      =  6;
+    private static int STOPPED     = -2;
+    private static int ERROR       = -1;
+
+    private int mState = IDLE;
+
+    private String mUrl;
+    private boolean mAskToPlay = false;
+
+    // Timer thread -> UI thread
+    private static final int TIMEUPDATE = 100;
+
+    // The spec says the timer should fire every 250 ms or less.
+    private static final int TIMEUPDATE_PERIOD = 250;  // ms
+    // The timer for timeupate events.
+    // See http://www.whatwg.org/specs/web-apps/current-work/#event-media-timeupdate
+    private Timer mTimer;
+    private final class TimeupdateTask extends TimerTask {
+        public void run() {
+            HTML5Audio.this.obtainMessage(TIMEUPDATE).sendToTarget();
+        }
+    }
+
+    @Override
+    public void handleMessage(Message msg) {
+        switch (msg.what) {
+            case TIMEUPDATE: {
+                try {
+                    if (mState != ERROR && mMediaPlayer.isPlaying()) {
+                        int position = mMediaPlayer.getCurrentPosition();
+                        nativeOnTimeupdate(position, mNativePointer);
+                    }
+                } catch (IllegalStateException e) {
+                    mState = ERROR;
+                }
+            }
+        }
+    }
+
+    // event listeners for MediaPlayer
+    // Those are called from the same thread we created the MediaPlayer
+    // (i.e. the webviewcore thread here)
+
+    // MediaPlayer.OnBufferingUpdateListener
+    public void onBufferingUpdate(MediaPlayer mp, int percent) {
+        nativeOnBuffering(percent, mNativePointer);
+    }
+
+    // MediaPlayer.OnCompletionListener;
+    public void onCompletion(MediaPlayer mp) {
+        resetMediaPlayer();
+        mState = IDLE;
+        nativeOnEnded(mNativePointer);
+    }
+
+    // MediaPlayer.OnErrorListener
+    public boolean onError(MediaPlayer mp, int what, int extra) {
+        mState = ERROR;
+        resetMediaPlayer();
+        mState = IDLE;
+        return false;
+    }
+
+    // MediaPlayer.OnPreparedListener
+    public void onPrepared(MediaPlayer mp) {
+        mState = PREPARED;
+        if (mTimer != null) {
+            mTimer.schedule(new TimeupdateTask(),
+                            TIMEUPDATE_PERIOD, TIMEUPDATE_PERIOD);
+        }
+        nativeOnPrepared(mp.getDuration(), 0, 0, mNativePointer);
+        if (mAskToPlay) {
+            mAskToPlay = false;
+            play();
+        }
+    }
+
+    // MediaPlayer.OnSeekCompleteListener
+    public void onSeekComplete(MediaPlayer mp) {
+        nativeOnTimeupdate(mp.getCurrentPosition(), mNativePointer);
+    }
+
+
+    /**
+     * @param nativePtr is the C++ pointer to the MediaPlayerPrivate object.
+     */
+    public HTML5Audio(int nativePtr) {
+        // Save the native ptr
+        mNativePointer = nativePtr;
+        resetMediaPlayer();
+    }
+
+    private void resetMediaPlayer() {
+        if (mMediaPlayer == null) {
+            mMediaPlayer = new MediaPlayer();
+        } else {
+            mMediaPlayer.reset();
+        }
+        mMediaPlayer.setOnBufferingUpdateListener(this);
+        mMediaPlayer.setOnCompletionListener(this);
+        mMediaPlayer.setOnErrorListener(this);
+        mMediaPlayer.setOnPreparedListener(this);
+        mMediaPlayer.setOnSeekCompleteListener(this);
+
+        if (mTimer != null) {
+            mTimer.cancel();
+        }
+        mTimer = new Timer();
+        mState = IDLE;
+    }
+
+    private void setDataSource(String url) {
+        mUrl = url;
+        try {
+            if (mState != IDLE) {
+                resetMediaPlayer();
+            }
+            mMediaPlayer.setDataSource(url);
+            mState = INITIALIZED;
+            mMediaPlayer.prepareAsync();
+        } catch (IOException e) {
+            Log.e(LOGTAG, "couldn't load the resource: " + url + " exc: " + e);
+            resetMediaPlayer();
+        }
+    }
+
+    private void play() {
+        if ((mState == ERROR || mState == IDLE) && mUrl != null) {
+            resetMediaPlayer();
+            setDataSource(mUrl);
+            mAskToPlay = true;
+        }
+
+        if (mState >= PREPARED) {
+            mMediaPlayer.start();
+            mState = STARTED;
+        }
+    }
+
+    private void pause() {
+        if (mState == STARTED) {
+            if (mTimer != null) {
+                mTimer.purge();
+            }
+            mMediaPlayer.pause();
+            mState = PAUSED;
+        }
+    }
+
+    private void seek(int msec) {
+        if (mState >= PREPARED) {
+            mMediaPlayer.seekTo(msec);
+        }
+    }
+
+    private void teardown() {
+        mMediaPlayer.release();
+        mState = ERROR;
+        mNativePointer = 0;
+    }
+
+    private float getMaxTimeSeekable() {
+        return mMediaPlayer.getDuration() / 1000.0f;
+    }
+
+    private native void nativeOnBuffering(int percent, int nativePointer);
+    private native void nativeOnEnded(int nativePointer);
+    private native void nativeOnPrepared(int duration, int width, int height, int nativePointer);
+    private native void nativeOnTimeupdate(int position, int nativePointer);
+}
diff --git a/core/java/android/webkit/JWebCoreJavaBridge.java b/core/java/android/webkit/JWebCoreJavaBridge.java
index e766693..ecad261 100644
--- a/core/java/android/webkit/JWebCoreJavaBridge.java
+++ b/core/java/android/webkit/JWebCoreJavaBridge.java
@@ -16,10 +16,12 @@
 
 package android.webkit;
 
+import android.net.Uri;
 import android.os.Handler;
 import android.os.Message;
 import android.util.Log;
 
+import java.util.HashMap;
 import java.util.Set;
 
 final class JWebCoreJavaBridge extends Handler {
@@ -49,12 +51,15 @@
     /* package */
     static final int REFRESH_PLUGINS = 100;
 
+    private HashMap<String, String> mContentUriToFilePathMap;
+
     /**
      * Construct a new JWebCoreJavaBridge to interface with
      * WebCore timers and cookies.
      */
     public JWebCoreJavaBridge() {
         nativeConstructor();
+
     }
 
     @Override
@@ -267,6 +272,28 @@
         }
     }
 
+    // Called on the WebCore thread through JNI.
+    private String resolveFilePathForContentUri(String uri) {
+        if (mContentUriToFilePathMap != null) {
+            String fileName = mContentUriToFilePathMap.get(uri);
+            if (fileName != null) {
+                return fileName;
+            }
+        }
+
+        // Failsafe fallback to just use the last path segment.
+        // (See OpenableColumns documentation in the SDK)
+        Uri jUri = Uri.parse(uri);
+        return jUri.getLastPathSegment();
+    }
+
+    public void storeFilePathForContentUri(String path, String contentUri) {
+        if (mContentUriToFilePathMap == null) {
+            mContentUriToFilePathMap = new HashMap<String, String>();
+        }
+        mContentUriToFilePathMap.put(contentUri, path);
+    }
+
     private native void nativeConstructor();
     private native void nativeFinalize();
     private native void sharedTimerFired();
diff --git a/core/java/android/webkit/LoadListener.java b/core/java/android/webkit/LoadListener.java
index e6fa405..5b256f2 100644
--- a/core/java/android/webkit/LoadListener.java
+++ b/core/java/android/webkit/LoadListener.java
@@ -110,6 +110,7 @@
     private RequestHandle mRequestHandle;
     private RequestHandle mSslErrorRequestHandle;
     private long     mPostIdentifier;
+    private boolean  mSetNativeResponse;
 
     // Request data. It is only valid when we are doing a load from the
     // cache. It is needed if the cache returns a redirect
@@ -181,6 +182,7 @@
     private void clearNativeLoader() {
         sNativeLoaderCount -= 1;
         mNativeLoader = 0;
+        mSetNativeResponse = false;
     }
 
     /*
@@ -1086,13 +1088,18 @@
         // request with some credentials then don't commit the headers
         // of this response; wait for the response to the request with the
         // credentials.
-        if (mAuthHeader != null)
+        if (mAuthHeader != null) {
             return;
+        }
 
-        // Commit the headers to WebCore
+        setNativeResponse();
+    }
+
+    private void setNativeResponse() {
         int nativeResponse = createNativeResponse();
         // The native code deletes the native response object.
         nativeReceivedResponse(nativeResponse);
+        mSetNativeResponse = true;
     }
 
     /**
@@ -1127,6 +1134,9 @@
      */
     private void commitLoad() {
         if (mCancelled) return;
+        if (!mSetNativeResponse) {
+            setNativeResponse();
+        }
 
         if (mIsMainPageLoader) {
             String type = sCertificateTypeMap.get(mMimeType);
@@ -1197,6 +1207,10 @@
         }
         if (mNativeLoader != 0) {
             PerfChecker checker = new PerfChecker();
+            if (!mSetNativeResponse) {
+                setNativeResponse();
+            }
+
             nativeFinished();
             checker.responseAlert("res nativeFinished");
             clearNativeLoader();
@@ -1312,6 +1326,9 @@
                 final String text = mContext
                         .getString(R.string.open_permission_deny)
                         + "\n" + redirectTo;
+                if (!mSetNativeResponse) {
+                    setNativeResponse();
+                }
                 nativeAddData(text.getBytes(), text.length());
                 nativeFinished();
                 clearNativeLoader();
diff --git a/core/java/android/webkit/MimeTypeMap.java b/core/java/android/webkit/MimeTypeMap.java
index c1ac180..f00005b 100644
--- a/core/java/android/webkit/MimeTypeMap.java
+++ b/core/java/android/webkit/MimeTypeMap.java
@@ -16,35 +16,22 @@
 
 package android.webkit;
 
+import android.text.TextUtils;
 import java.util.HashMap;
 import java.util.regex.Pattern;
+import libcore.net.MimeUtils;
 
 /**
  * Two-way map that maps MIME-types to file extensions and vice versa.
+ *
+ * <p>See also {@link java.net.URLConnection#guessContentTypeFromName}
+ * and {@link java.net.URLConnection#guessContentTypeFromStream}. This
+ * class and {@code URLConnection} share the same MIME-type database.
  */
 public class MimeTypeMap {
+    private static final MimeTypeMap sMimeTypeMap = new MimeTypeMap();
 
-    /**
-     * Singleton MIME-type map instance:
-     */
-    private static MimeTypeMap sMimeTypeMap;
-
-    /**
-     * MIME-type to file extension mapping:
-     */
-    private HashMap<String, String> mMimeTypeToExtensionMap;
-
-    /**
-     * File extension to MIME type mapping:
-     */
-    private HashMap<String, String> mExtensionToMimeTypeMap;
-
-    /**
-     * Creates a new MIME-type map.
-     */
     private MimeTypeMap() {
-        mMimeTypeToExtensionMap = new HashMap<String, String>();
-        mExtensionToMimeTypeMap = new HashMap<String, String>();
     }
 
     /**
@@ -55,7 +42,7 @@
      * @return The file extension of the given url.
      */
     public static String getFileExtensionFromUrl(String url) {
-        if (url != null && url.length() > 0) {
+        if (!TextUtils.isEmpty(url)) {
             int query = url.lastIndexOf('?');
             if (query > 0) {
                 url = url.substring(0, query);
@@ -66,7 +53,7 @@
 
             // if the filename contains special characters, we don't
             // consider it valid for our matching purposes:
-            if (filename.length() > 0 &&
+            if (!filename.isEmpty() &&
                 Pattern.matches("[a-zA-Z_0-9\\.\\-\\(\\)\\%]+", filename)) {
                 int dotPos = filename.lastIndexOf('.');
                 if (0 <= dotPos) {
@@ -79,36 +66,12 @@
     }
 
     /**
-     * Load an entry into the map. This does not check if the item already
-     * exists, it trusts the caller!
-     */
-    private void loadEntry(String mimeType, String extension) {
-        //
-        // if we have an existing x --> y mapping, we do not want to
-        // override it with another mapping x --> ?
-        // this is mostly because of the way the mime-type map below
-        // is constructed (if a mime type maps to several extensions
-        // the first extension is considered the most popular and is
-        // added first; we do not want to overwrite it later).
-        //
-        if (!mMimeTypeToExtensionMap.containsKey(mimeType)) {
-            mMimeTypeToExtensionMap.put(mimeType, extension);
-        }
-
-        mExtensionToMimeTypeMap.put(extension, mimeType);
-    }
-
-    /**
      * Return true if the given MIME type has an entry in the map.
      * @param mimeType A MIME type (i.e. text/plain)
      * @return True iff there is a mimeType entry in the map.
      */
     public boolean hasMimeType(String mimeType) {
-        if (mimeType != null && mimeType.length() > 0) {
-            return mMimeTypeToExtensionMap.containsKey(mimeType);
-        }
-
-        return false;
+        return MimeUtils.hasMimeType(mimeType);
     }
 
     /**
@@ -117,16 +80,12 @@
      * @return The MIME type for the given extension or null iff there is none.
      */
     public String getMimeTypeFromExtension(String extension) {
-        if (extension != null && extension.length() > 0) {
-            return mExtensionToMimeTypeMap.get(extension);
-        }
-
-        return null;
+        return MimeUtils.guessMimeTypeFromExtension(extension);
     }
 
     // Static method called by jni.
     private static String mimeTypeFromExtension(String extension) {
-        return getSingleton().getMimeTypeFromExtension(extension);
+        return MimeUtils.guessMimeTypeFromExtension(extension);
     }
 
     /**
@@ -135,10 +94,7 @@
      * @return True iff there is an extension entry in the map.
      */
     public boolean hasExtension(String extension) {
-        if (extension != null && extension.length() > 0) {
-            return mExtensionToMimeTypeMap.containsKey(extension);
-        }
-        return false;
+        return MimeUtils.hasExtension(extension);
     }
 
     /**
@@ -149,11 +105,7 @@
      * @return The extension for the given MIME type or null iff there is none.
      */
     public String getExtensionFromMimeType(String mimeType) {
-        if (mimeType != null && mimeType.length() > 0) {
-            return mMimeTypeToExtensionMap.get(mimeType);
-        }
-
-        return null;
+        return MimeUtils.guessExtensionFromMimeType(mimeType);
     }
 
     /**
@@ -161,363 +113,6 @@
      * @return The singleton instance of the MIME-type map.
      */
     public static MimeTypeMap getSingleton() {
-        if (sMimeTypeMap == null) {
-            sMimeTypeMap = new MimeTypeMap();
-
-            // The following table is based on /etc/mime.types data minus
-            // chemical/* MIME types and MIME types that don't map to any
-            // file extensions. We also exclude top-level domain names to
-            // deal with cases like:
-            //
-            // mail.google.com/a/google.com
-            //
-            // and "active" MIME types (due to potential security issues).
-
-            sMimeTypeMap.loadEntry("application/andrew-inset", "ez");
-            sMimeTypeMap.loadEntry("application/dsptype", "tsp");
-            sMimeTypeMap.loadEntry("application/futuresplash", "spl");
-            sMimeTypeMap.loadEntry("application/hta", "hta");
-            sMimeTypeMap.loadEntry("application/mac-binhex40", "hqx");
-            sMimeTypeMap.loadEntry("application/mac-compactpro", "cpt");
-            sMimeTypeMap.loadEntry("application/mathematica", "nb");
-            sMimeTypeMap.loadEntry("application/msaccess", "mdb");
-            sMimeTypeMap.loadEntry("application/oda", "oda");
-            sMimeTypeMap.loadEntry("application/ogg", "ogg");
-            sMimeTypeMap.loadEntry("application/pdf", "pdf");
-            sMimeTypeMap.loadEntry("application/pgp-keys", "key");
-            sMimeTypeMap.loadEntry("application/pgp-signature", "pgp");
-            sMimeTypeMap.loadEntry("application/pics-rules", "prf");
-            sMimeTypeMap.loadEntry("application/rar", "rar");
-            sMimeTypeMap.loadEntry("application/rdf+xml", "rdf");
-            sMimeTypeMap.loadEntry("application/rss+xml", "rss");
-            sMimeTypeMap.loadEntry("application/zip", "zip");
-            sMimeTypeMap.loadEntry("application/vnd.android.package-archive", 
-                    "apk");
-            sMimeTypeMap.loadEntry("application/vnd.cinderella", "cdy");
-            sMimeTypeMap.loadEntry("application/vnd.ms-pki.stl", "stl");
-            sMimeTypeMap.loadEntry(
-                    "application/vnd.oasis.opendocument.database", "odb");
-            sMimeTypeMap.loadEntry(
-                    "application/vnd.oasis.opendocument.formula", "odf");
-            sMimeTypeMap.loadEntry(
-                    "application/vnd.oasis.opendocument.graphics", "odg");
-            sMimeTypeMap.loadEntry(
-                    "application/vnd.oasis.opendocument.graphics-template",
-                    "otg");
-            sMimeTypeMap.loadEntry(
-                    "application/vnd.oasis.opendocument.image", "odi");
-            sMimeTypeMap.loadEntry(
-                    "application/vnd.oasis.opendocument.spreadsheet", "ods");
-            sMimeTypeMap.loadEntry(
-                    "application/vnd.oasis.opendocument.spreadsheet-template",
-                    "ots");
-            sMimeTypeMap.loadEntry(
-                    "application/vnd.oasis.opendocument.text", "odt");
-            sMimeTypeMap.loadEntry(
-                    "application/vnd.oasis.opendocument.text-master", "odm");
-            sMimeTypeMap.loadEntry(
-                    "application/vnd.oasis.opendocument.text-template", "ott");
-            sMimeTypeMap.loadEntry(
-                    "application/vnd.oasis.opendocument.text-web", "oth");
-            sMimeTypeMap.loadEntry("application/msword", "doc");
-            sMimeTypeMap.loadEntry("application/msword", "dot");
-            sMimeTypeMap.loadEntry(
-                    "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
-                    "docx");
-            sMimeTypeMap.loadEntry(
-                    "application/vnd.openxmlformats-officedocument.wordprocessingml.template",
-                    "dotx");
-            sMimeTypeMap.loadEntry("application/vnd.ms-excel", "xls");
-            sMimeTypeMap.loadEntry("application/vnd.ms-excel", "xlt");
-            sMimeTypeMap.loadEntry(
-                    "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
-                    "xlsx");
-            sMimeTypeMap.loadEntry(
-                    "application/vnd.openxmlformats-officedocument.spreadsheetml.template",
-                    "xltx");
-            sMimeTypeMap.loadEntry("application/vnd.ms-powerpoint", "ppt");
-            sMimeTypeMap.loadEntry("application/vnd.ms-powerpoint", "pot");
-            sMimeTypeMap.loadEntry("application/vnd.ms-powerpoint", "pps");
-            sMimeTypeMap.loadEntry(
-                    "application/vnd.openxmlformats-officedocument.presentationml.presentation",
-                    "pptx");
-            sMimeTypeMap.loadEntry(
-                    "application/vnd.openxmlformats-officedocument.presentationml.template",
-                    "potx");
-            sMimeTypeMap.loadEntry(
-                    "application/vnd.openxmlformats-officedocument.presentationml.slideshow",
-                    "ppsx");
-            sMimeTypeMap.loadEntry("application/vnd.rim.cod", "cod");
-            sMimeTypeMap.loadEntry("application/vnd.smaf", "mmf");
-            sMimeTypeMap.loadEntry("application/vnd.stardivision.calc", "sdc");
-            sMimeTypeMap.loadEntry("application/vnd.stardivision.draw", "sda");
-            sMimeTypeMap.loadEntry(
-                    "application/vnd.stardivision.impress", "sdd");
-            sMimeTypeMap.loadEntry(
-                    "application/vnd.stardivision.impress", "sdp");
-            sMimeTypeMap.loadEntry("application/vnd.stardivision.math", "smf");
-            sMimeTypeMap.loadEntry("application/vnd.stardivision.writer",
-                    "sdw");
-            sMimeTypeMap.loadEntry("application/vnd.stardivision.writer",
-                    "vor");
-            sMimeTypeMap.loadEntry(
-                    "application/vnd.stardivision.writer-global", "sgl");
-            sMimeTypeMap.loadEntry("application/vnd.sun.xml.calc", "sxc");
-            sMimeTypeMap.loadEntry(
-                    "application/vnd.sun.xml.calc.template", "stc");
-            sMimeTypeMap.loadEntry("application/vnd.sun.xml.draw", "sxd");
-            sMimeTypeMap.loadEntry(
-                    "application/vnd.sun.xml.draw.template", "std");
-            sMimeTypeMap.loadEntry("application/vnd.sun.xml.impress", "sxi");
-            sMimeTypeMap.loadEntry(
-                    "application/vnd.sun.xml.impress.template", "sti");
-            sMimeTypeMap.loadEntry("application/vnd.sun.xml.math", "sxm");
-            sMimeTypeMap.loadEntry("application/vnd.sun.xml.writer", "sxw");
-            sMimeTypeMap.loadEntry(
-                    "application/vnd.sun.xml.writer.global", "sxg");
-            sMimeTypeMap.loadEntry(
-                    "application/vnd.sun.xml.writer.template", "stw");
-            sMimeTypeMap.loadEntry("application/vnd.visio", "vsd");
-            sMimeTypeMap.loadEntry("application/x-abiword", "abw");
-            sMimeTypeMap.loadEntry("application/x-apple-diskimage", "dmg");
-            sMimeTypeMap.loadEntry("application/x-bcpio", "bcpio");
-            sMimeTypeMap.loadEntry("application/x-bittorrent", "torrent");
-            sMimeTypeMap.loadEntry("application/x-cdf", "cdf");
-            sMimeTypeMap.loadEntry("application/x-cdlink", "vcd");
-            sMimeTypeMap.loadEntry("application/x-chess-pgn", "pgn");
-            sMimeTypeMap.loadEntry("application/x-cpio", "cpio");
-            sMimeTypeMap.loadEntry("application/x-debian-package", "deb");
-            sMimeTypeMap.loadEntry("application/x-debian-package", "udeb");
-            sMimeTypeMap.loadEntry("application/x-director", "dcr");
-            sMimeTypeMap.loadEntry("application/x-director", "dir");
-            sMimeTypeMap.loadEntry("application/x-director", "dxr");
-            sMimeTypeMap.loadEntry("application/x-dms", "dms");
-            sMimeTypeMap.loadEntry("application/x-doom", "wad");
-            sMimeTypeMap.loadEntry("application/x-dvi", "dvi");
-            sMimeTypeMap.loadEntry("application/x-flac", "flac");
-            sMimeTypeMap.loadEntry("application/x-font", "pfa");
-            sMimeTypeMap.loadEntry("application/x-font", "pfb");
-            sMimeTypeMap.loadEntry("application/x-font", "gsf");
-            sMimeTypeMap.loadEntry("application/x-font", "pcf");
-            sMimeTypeMap.loadEntry("application/x-font", "pcf.Z");
-            sMimeTypeMap.loadEntry("application/x-freemind", "mm");
-            sMimeTypeMap.loadEntry("application/x-futuresplash", "spl");
-            sMimeTypeMap.loadEntry("application/x-gnumeric", "gnumeric");
-            sMimeTypeMap.loadEntry("application/x-go-sgf", "sgf");
-            sMimeTypeMap.loadEntry("application/x-graphing-calculator", "gcf");
-            sMimeTypeMap.loadEntry("application/x-gtar", "gtar");
-            sMimeTypeMap.loadEntry("application/x-gtar", "tgz");
-            sMimeTypeMap.loadEntry("application/x-gtar", "taz");
-            sMimeTypeMap.loadEntry("application/x-hdf", "hdf");
-            sMimeTypeMap.loadEntry("application/x-ica", "ica");
-            sMimeTypeMap.loadEntry("application/x-internet-signup", "ins");
-            sMimeTypeMap.loadEntry("application/x-internet-signup", "isp");
-            sMimeTypeMap.loadEntry("application/x-iphone", "iii");
-            sMimeTypeMap.loadEntry("application/x-iso9660-image", "iso");
-            sMimeTypeMap.loadEntry("application/x-jmol", "jmz");
-            sMimeTypeMap.loadEntry("application/x-kchart", "chrt");
-            sMimeTypeMap.loadEntry("application/x-killustrator", "kil");
-            sMimeTypeMap.loadEntry("application/x-koan", "skp");
-            sMimeTypeMap.loadEntry("application/x-koan", "skd");
-            sMimeTypeMap.loadEntry("application/x-koan", "skt");
-            sMimeTypeMap.loadEntry("application/x-koan", "skm");
-            sMimeTypeMap.loadEntry("application/x-kpresenter", "kpr");
-            sMimeTypeMap.loadEntry("application/x-kpresenter", "kpt");
-            sMimeTypeMap.loadEntry("application/x-kspread", "ksp");
-            sMimeTypeMap.loadEntry("application/x-kword", "kwd");
-            sMimeTypeMap.loadEntry("application/x-kword", "kwt");
-            sMimeTypeMap.loadEntry("application/x-latex", "latex");
-            sMimeTypeMap.loadEntry("application/x-lha", "lha");
-            sMimeTypeMap.loadEntry("application/x-lzh", "lzh");
-            sMimeTypeMap.loadEntry("application/x-lzx", "lzx");
-            sMimeTypeMap.loadEntry("application/x-maker", "frm");
-            sMimeTypeMap.loadEntry("application/x-maker", "maker");
-            sMimeTypeMap.loadEntry("application/x-maker", "frame");
-            sMimeTypeMap.loadEntry("application/x-maker", "fb");
-            sMimeTypeMap.loadEntry("application/x-maker", "book");
-            sMimeTypeMap.loadEntry("application/x-maker", "fbdoc");
-            sMimeTypeMap.loadEntry("application/x-mif", "mif");
-            sMimeTypeMap.loadEntry("application/x-ms-wmd", "wmd");
-            sMimeTypeMap.loadEntry("application/x-ms-wmz", "wmz");
-            sMimeTypeMap.loadEntry("application/x-msi", "msi");
-            sMimeTypeMap.loadEntry("application/x-ns-proxy-autoconfig", "pac");
-            sMimeTypeMap.loadEntry("application/x-nwc", "nwc");
-            sMimeTypeMap.loadEntry("application/x-object", "o");
-            sMimeTypeMap.loadEntry("application/x-oz-application", "oza");
-            sMimeTypeMap.loadEntry("application/x-pkcs12", "p12");
-            sMimeTypeMap.loadEntry("application/x-pkcs7-certreqresp", "p7r");
-            sMimeTypeMap.loadEntry("application/x-pkcs7-crl", "crl");
-            sMimeTypeMap.loadEntry("application/x-quicktimeplayer", "qtl");
-            sMimeTypeMap.loadEntry("application/x-shar", "shar");
-            sMimeTypeMap.loadEntry("application/x-shockwave-flash", "swf");
-            sMimeTypeMap.loadEntry("application/x-stuffit", "sit");
-            sMimeTypeMap.loadEntry("application/x-sv4cpio", "sv4cpio");
-            sMimeTypeMap.loadEntry("application/x-sv4crc", "sv4crc");
-            sMimeTypeMap.loadEntry("application/x-tar", "tar");
-            sMimeTypeMap.loadEntry("application/x-texinfo", "texinfo");
-            sMimeTypeMap.loadEntry("application/x-texinfo", "texi");
-            sMimeTypeMap.loadEntry("application/x-troff", "t");
-            sMimeTypeMap.loadEntry("application/x-troff", "roff");
-            sMimeTypeMap.loadEntry("application/x-troff-man", "man");
-            sMimeTypeMap.loadEntry("application/x-ustar", "ustar");
-            sMimeTypeMap.loadEntry("application/x-wais-source", "src");
-            sMimeTypeMap.loadEntry("application/x-wingz", "wz");
-            sMimeTypeMap.loadEntry("application/x-webarchive", "webarchive");
-            sMimeTypeMap.loadEntry("application/x-x509-ca-cert", "crt");
-            sMimeTypeMap.loadEntry("application/x-x509-user-cert", "crt");
-            sMimeTypeMap.loadEntry("application/x-xcf", "xcf");
-            sMimeTypeMap.loadEntry("application/x-xfig", "fig");
-            sMimeTypeMap.loadEntry("application/xhtml+xml", "xhtml");
-            sMimeTypeMap.loadEntry("audio/3gpp", "3gpp");
-            sMimeTypeMap.loadEntry("audio/amr", "amr");
-            sMimeTypeMap.loadEntry("audio/basic", "snd");
-            sMimeTypeMap.loadEntry("audio/midi", "mid");
-            sMimeTypeMap.loadEntry("audio/midi", "midi");
-            sMimeTypeMap.loadEntry("audio/midi", "kar");
-            sMimeTypeMap.loadEntry("audio/midi", "xmf");
-            sMimeTypeMap.loadEntry("audio/mobile-xmf", "mxmf");
-            sMimeTypeMap.loadEntry("audio/mpeg", "mpga");
-            sMimeTypeMap.loadEntry("audio/mpeg", "mpega");
-            sMimeTypeMap.loadEntry("audio/mpeg", "mp2");
-            sMimeTypeMap.loadEntry("audio/mpeg", "mp3");
-            sMimeTypeMap.loadEntry("audio/mpeg", "m4a");
-            sMimeTypeMap.loadEntry("audio/mpegurl", "m3u");
-            sMimeTypeMap.loadEntry("audio/prs.sid", "sid");
-            sMimeTypeMap.loadEntry("audio/x-aiff", "aif");
-            sMimeTypeMap.loadEntry("audio/x-aiff", "aiff");
-            sMimeTypeMap.loadEntry("audio/x-aiff", "aifc");
-            sMimeTypeMap.loadEntry("audio/x-gsm", "gsm");
-            sMimeTypeMap.loadEntry("audio/x-mpegurl", "m3u");
-            sMimeTypeMap.loadEntry("audio/x-ms-wma", "wma");
-            sMimeTypeMap.loadEntry("audio/x-ms-wax", "wax");
-            sMimeTypeMap.loadEntry("audio/x-pn-realaudio", "ra");
-            sMimeTypeMap.loadEntry("audio/x-pn-realaudio", "rm");
-            sMimeTypeMap.loadEntry("audio/x-pn-realaudio", "ram");
-            sMimeTypeMap.loadEntry("audio/x-realaudio", "ra");
-            sMimeTypeMap.loadEntry("audio/x-scpls", "pls");
-            sMimeTypeMap.loadEntry("audio/x-sd2", "sd2");
-            sMimeTypeMap.loadEntry("audio/x-wav", "wav");
-            sMimeTypeMap.loadEntry("image/bmp", "bmp");
-            sMimeTypeMap.loadEntry("image/gif", "gif");
-            sMimeTypeMap.loadEntry("image/ico", "cur");
-            sMimeTypeMap.loadEntry("image/ico", "ico");
-            sMimeTypeMap.loadEntry("image/ief", "ief");
-            sMimeTypeMap.loadEntry("image/jpeg", "jpeg");
-            sMimeTypeMap.loadEntry("image/jpeg", "jpg");
-            sMimeTypeMap.loadEntry("image/jpeg", "jpe");
-            sMimeTypeMap.loadEntry("image/pcx", "pcx");
-            sMimeTypeMap.loadEntry("image/png", "png");
-            sMimeTypeMap.loadEntry("image/svg+xml", "svg");
-            sMimeTypeMap.loadEntry("image/svg+xml", "svgz");
-            sMimeTypeMap.loadEntry("image/tiff", "tiff");
-            sMimeTypeMap.loadEntry("image/tiff", "tif");
-            sMimeTypeMap.loadEntry("image/vnd.djvu", "djvu");
-            sMimeTypeMap.loadEntry("image/vnd.djvu", "djv");
-            sMimeTypeMap.loadEntry("image/vnd.wap.wbmp", "wbmp");
-            sMimeTypeMap.loadEntry("image/x-cmu-raster", "ras");
-            sMimeTypeMap.loadEntry("image/x-coreldraw", "cdr");
-            sMimeTypeMap.loadEntry("image/x-coreldrawpattern", "pat");
-            sMimeTypeMap.loadEntry("image/x-coreldrawtemplate", "cdt");
-            sMimeTypeMap.loadEntry("image/x-corelphotopaint", "cpt");
-            sMimeTypeMap.loadEntry("image/x-icon", "ico");
-            sMimeTypeMap.loadEntry("image/x-jg", "art");
-            sMimeTypeMap.loadEntry("image/x-jng", "jng");
-            sMimeTypeMap.loadEntry("image/x-ms-bmp", "bmp");
-            sMimeTypeMap.loadEntry("image/x-photoshop", "psd");
-            sMimeTypeMap.loadEntry("image/x-portable-anymap", "pnm");
-            sMimeTypeMap.loadEntry("image/x-portable-bitmap", "pbm");
-            sMimeTypeMap.loadEntry("image/x-portable-graymap", "pgm");
-            sMimeTypeMap.loadEntry("image/x-portable-pixmap", "ppm");
-            sMimeTypeMap.loadEntry("image/x-rgb", "rgb");
-            sMimeTypeMap.loadEntry("image/x-xbitmap", "xbm");
-            sMimeTypeMap.loadEntry("image/x-xpixmap", "xpm");
-            sMimeTypeMap.loadEntry("image/x-xwindowdump", "xwd");
-            sMimeTypeMap.loadEntry("model/iges", "igs");
-            sMimeTypeMap.loadEntry("model/iges", "iges");
-            sMimeTypeMap.loadEntry("model/mesh", "msh");
-            sMimeTypeMap.loadEntry("model/mesh", "mesh");
-            sMimeTypeMap.loadEntry("model/mesh", "silo");
-            sMimeTypeMap.loadEntry("text/calendar", "ics");
-            sMimeTypeMap.loadEntry("text/calendar", "icz");
-            sMimeTypeMap.loadEntry("text/comma-separated-values", "csv");
-            sMimeTypeMap.loadEntry("text/css", "css");
-            sMimeTypeMap.loadEntry("text/html", "htm");
-            sMimeTypeMap.loadEntry("text/html", "html");
-            sMimeTypeMap.loadEntry("text/h323", "323");
-            sMimeTypeMap.loadEntry("text/iuls", "uls");
-            sMimeTypeMap.loadEntry("text/mathml", "mml");
-            // add it first so it will be the default for ExtensionFromMimeType
-            sMimeTypeMap.loadEntry("text/plain", "txt");
-            sMimeTypeMap.loadEntry("text/plain", "asc");
-            sMimeTypeMap.loadEntry("text/plain", "text");
-            sMimeTypeMap.loadEntry("text/plain", "diff");
-            sMimeTypeMap.loadEntry("text/plain", "po");     // reserve "pot" for vnd.ms-powerpoint
-            sMimeTypeMap.loadEntry("text/richtext", "rtx");
-            sMimeTypeMap.loadEntry("text/rtf", "rtf");
-            sMimeTypeMap.loadEntry("text/texmacs", "ts");
-            sMimeTypeMap.loadEntry("text/text", "phps");
-            sMimeTypeMap.loadEntry("text/tab-separated-values", "tsv");
-            sMimeTypeMap.loadEntry("text/xml", "xml");
-            sMimeTypeMap.loadEntry("text/x-bibtex", "bib");
-            sMimeTypeMap.loadEntry("text/x-boo", "boo");
-            sMimeTypeMap.loadEntry("text/x-c++hdr", "h++");
-            sMimeTypeMap.loadEntry("text/x-c++hdr", "hpp");
-            sMimeTypeMap.loadEntry("text/x-c++hdr", "hxx");
-            sMimeTypeMap.loadEntry("text/x-c++hdr", "hh");
-            sMimeTypeMap.loadEntry("text/x-c++src", "c++");
-            sMimeTypeMap.loadEntry("text/x-c++src", "cpp");
-            sMimeTypeMap.loadEntry("text/x-c++src", "cxx");
-            sMimeTypeMap.loadEntry("text/x-chdr", "h");
-            sMimeTypeMap.loadEntry("text/x-component", "htc");
-            sMimeTypeMap.loadEntry("text/x-csh", "csh");
-            sMimeTypeMap.loadEntry("text/x-csrc", "c");
-            sMimeTypeMap.loadEntry("text/x-dsrc", "d");
-            sMimeTypeMap.loadEntry("text/x-haskell", "hs");
-            sMimeTypeMap.loadEntry("text/x-java", "java");
-            sMimeTypeMap.loadEntry("text/x-literate-haskell", "lhs");
-            sMimeTypeMap.loadEntry("text/x-moc", "moc");
-            sMimeTypeMap.loadEntry("text/x-pascal", "p");
-            sMimeTypeMap.loadEntry("text/x-pascal", "pas");
-            sMimeTypeMap.loadEntry("text/x-pcs-gcd", "gcd");
-            sMimeTypeMap.loadEntry("text/x-setext", "etx");
-            sMimeTypeMap.loadEntry("text/x-tcl", "tcl");
-            sMimeTypeMap.loadEntry("text/x-tex", "tex");
-            sMimeTypeMap.loadEntry("text/x-tex", "ltx");
-            sMimeTypeMap.loadEntry("text/x-tex", "sty");
-            sMimeTypeMap.loadEntry("text/x-tex", "cls");
-            sMimeTypeMap.loadEntry("text/x-vcalendar", "vcs");
-            sMimeTypeMap.loadEntry("text/x-vcard", "vcf");
-            sMimeTypeMap.loadEntry("video/3gpp", "3gpp");
-            sMimeTypeMap.loadEntry("video/3gpp", "3gp");
-            sMimeTypeMap.loadEntry("video/3gpp", "3g2");
-            sMimeTypeMap.loadEntry("video/dl", "dl");
-            sMimeTypeMap.loadEntry("video/dv", "dif");
-            sMimeTypeMap.loadEntry("video/dv", "dv");
-            sMimeTypeMap.loadEntry("video/fli", "fli");
-            sMimeTypeMap.loadEntry("video/m4v", "m4v");
-            sMimeTypeMap.loadEntry("video/mpeg", "mpeg");
-            sMimeTypeMap.loadEntry("video/mpeg", "mpg");
-            sMimeTypeMap.loadEntry("video/mpeg", "mpe");
-            sMimeTypeMap.loadEntry("video/mp4", "mp4");
-            sMimeTypeMap.loadEntry("video/mpeg", "VOB");
-            sMimeTypeMap.loadEntry("video/quicktime", "qt");
-            sMimeTypeMap.loadEntry("video/quicktime", "mov");
-            sMimeTypeMap.loadEntry("video/vnd.mpegurl", "mxu");
-            sMimeTypeMap.loadEntry("video/x-la-asf", "lsf");
-            sMimeTypeMap.loadEntry("video/x-la-asf", "lsx");
-            sMimeTypeMap.loadEntry("video/x-mng", "mng");
-            sMimeTypeMap.loadEntry("video/x-ms-asf", "asf");
-            sMimeTypeMap.loadEntry("video/x-ms-asf", "asx");
-            sMimeTypeMap.loadEntry("video/x-ms-wm", "wm");
-            sMimeTypeMap.loadEntry("video/x-ms-wmv", "wmv");
-            sMimeTypeMap.loadEntry("video/x-ms-wmx", "wmx");
-            sMimeTypeMap.loadEntry("video/x-ms-wvx", "wvx");
-            sMimeTypeMap.loadEntry("video/x-msvideo", "avi");
-            sMimeTypeMap.loadEntry("video/x-sgi-movie", "movie");
-            sMimeTypeMap.loadEntry("x-conference/x-cooltalk", "ice");
-            sMimeTypeMap.loadEntry("x-epoc/x-sisx-app", "sisx");
-        }
-
         return sMimeTypeMap;
     }
 }
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/WebChromeClient.java b/core/java/android/webkit/WebChromeClient.java
index 1d5aac7..e83bef0 100644
--- a/core/java/android/webkit/WebChromeClient.java
+++ b/core/java/android/webkit/WebChromeClient.java
@@ -314,10 +314,37 @@
     /**
      * Tell the client to open a file chooser.
      * @param uploadFile A ValueCallback to set the URI of the file to upload.
-     *      onReceiveValue must be called to wake up the thread.
+     *      onReceiveValue must be called to wake up the thread.a
+     * @param acceptType The value of the 'accept' attribute of the input tag
+     *         associated with this file picker.
      * @hide
      */
-    public void openFileChooser(ValueCallback<Uri> uploadFile) {
+    public void openFileChooser(ValueCallback<Uri> uploadFile, String acceptType) {
         uploadFile.onReceiveValue(null);
     }
+
+    /**
+     * Tell the client that the selection has been initiated.
+     * @hide
+     */
+    public void onSelectionStart(WebView view) {
+        // By default we cancel the selection again, thus disabling
+        // text selection unless the chrome client supports it.
+        view.notifySelectDialogDismissed();
+    }
+
+    /**
+     * Tell the client that the selection has been copied or canceled.
+     * @hide
+     */
+    public void onSelectionDone(WebView view) {
+    }
+
+    /**
+     * Tell the client that the page being viewed is web app capable,
+     * i.e. has specified the fullscreen-web-app-capable meta tag.
+     * @hide
+     */
+    public void setInstallableWebApp() { }
+
 }
diff --git a/core/java/android/webkit/WebHistoryItem.java b/core/java/android/webkit/WebHistoryItem.java
index 428a59c..7c0e478 100644
--- a/core/java/android/webkit/WebHistoryItem.java
+++ b/core/java/android/webkit/WebHistoryItem.java
@@ -18,6 +18,9 @@
 
 import android.graphics.Bitmap;
 
+import java.net.MalformedURLException;
+import java.net.URL;
+
 /**
  * A convenience class for accessing fields in an entry in the back/forward list
  * of a WebView. Each WebHistoryItem is a snapshot of the requested history
@@ -39,8 +42,12 @@
     private Bitmap mFavicon;
     // The pre-flattened data used for saving the state.
     private byte[] mFlattenedData;
-    // The apple-touch-icon url for use when adding the site to the home screen
-    private String mTouchIconUrl;
+    // The apple-touch-icon url for use when adding the site to the home screen,
+    // as obtained from a <link> element in the page.
+    private String mTouchIconUrlFromLink;
+    // If no <link> is specified, this holds the default location of the
+    // apple-touch-icon.
+    private String mTouchIconUrlServerDefault;
     // Custom client data that is not flattened or read by native code.
     private Object mCustomData;
 
@@ -132,10 +139,28 @@
 
     /**
      * Return the touch icon url.
+     * If no touch icon <link> tag was specified, returns
+     * <host>/apple-touch-icon.png. The DownloadTouchIcon class that
+     * attempts to retrieve the touch icon will handle the case where
+     * that file does not exist. An icon set by a <link> tag is always
+     * used in preference to an icon saved on the server.
      * @hide
      */
     public String getTouchIconUrl() {
-        return mTouchIconUrl;
+        if (mTouchIconUrlFromLink != null) {
+            return mTouchIconUrlFromLink;
+        } else if (mTouchIconUrlServerDefault != null) {
+            return mTouchIconUrlServerDefault;
+        }
+
+        try {
+            URL url = new URL(mOriginalUrl);
+            mTouchIconUrlServerDefault = new URL(url.getProtocol(), url.getHost(), url.getPort(),
+                    "/apple-touch-icon.png").toString();
+        } catch (MalformedURLException e) {
+            return null;
+        }
+        return mTouchIconUrlServerDefault;
     }
 
     /**
@@ -171,11 +196,14 @@
     }
 
     /**
-     * Set the touch icon url.
+     * Set the touch icon url. Will not overwrite an icon that has been
+     * set already from a <link> tag, unless the new icon is precomposed.
      * @hide
      */
-    /*package*/ void setTouchIconUrl(String url) {
-        mTouchIconUrl = url;
+    /*package*/ void setTouchIconUrl(String url, boolean precomposed) {
+        if (precomposed || mTouchIconUrlFromLink == null) {
+            mTouchIconUrlFromLink = url;
+        }
     }
 
     /**
diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java
index b767f11..681ef70 100644
--- a/core/java/android/webkit/WebSettings.java
+++ b/core/java/android/webkit/WebSettings.java
@@ -23,7 +23,7 @@
 import android.os.Handler;
 import android.os.Message;
 import android.util.EventLog;
-import java.lang.SecurityException;
+
 import java.util.Locale;
 
 /**
@@ -107,7 +107,7 @@
      * Use with {@link #setCacheMode}.
      */
     public static final int LOAD_NO_CACHE = 2;
-    
+
     /**
      * Don't use the network, load from cache only.
      * Use with {@link #setCacheMode}.
@@ -178,12 +178,14 @@
     private boolean         mUseWideViewport = false;
     private boolean         mSupportMultipleWindows = false;
     private boolean         mShrinksStandaloneImagesToFit = false;
+    private long            mMaximumDecodedImageSize = 0; // 0 means default
     // HTML5 API flags
     private boolean         mAppCacheEnabled = false;
     private boolean         mDatabaseEnabled = false;
     private boolean         mDomStorageEnabled = false;
     private boolean         mWorkersEnabled = false;  // only affects V8.
     private boolean         mGeolocationEnabled = true;
+    private boolean         mXSSAuditorEnabled = false;
     // HTML5 configuration parameters
     private long            mAppCacheMaxSize = Long.MAX_VALUE;
     private String          mAppCachePath = "";
@@ -207,6 +209,7 @@
     private boolean         mBuiltInZoomControls = false;
     private boolean         mAllowFileAccess = true;
     private boolean         mLoadWithOverviewMode = false;
+    private boolean         mEnableSmoothTransition = false;
 
     // private WebSettings, not accessible by the host activity
     static private int      mDoubleTapToastCount = 3;
@@ -296,13 +299,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 +330,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 +341,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 +405,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("-");
@@ -406,11 +432,13 @@
             buffer.append(" Build/");
             buffer.append(id);
         }
+        String mobile = mContext.getResources().getText(
+            com.android.internal.R.string.web_user_agent_target_content).toString();
         final String base = mContext.getResources().getText(
                 com.android.internal.R.string.web_user_agent).toString();
-        return String.format(base, buffer);
+        return String.format(base, buffer, mobile);
     }
-    
+
     /**
      * Enables dumping the pages navigation cache to a text file.
      */
@@ -426,6 +454,21 @@
     }
 
     /**
+     * If WebView only supports touch, a different navigation model will be
+     * applied. Otherwise, the navigation to support both touch and keyboard
+     * will be used.
+     * @hide
+    public void setSupportTouchOnly(boolean touchOnly) {
+        mSupportTounchOnly = touchOnly;
+    }
+     */
+
+    boolean supportTouchOnly() {
+        // for debug only, use mLightTouchEnabled for mSupportTounchOnly
+        return mLightTouchEnabled;
+    }
+
+    /**
      * Set whether the WebView supports zoom
      */
     public void setSupportZoom(boolean support) {
@@ -447,14 +490,14 @@
         mBuiltInZoomControls = enabled;
         mWebView.updateMultiTouchSupport(mContext);
     }
-    
+
     /**
      * Returns true if the zoom mechanism built into WebView is being used.
      */
     public boolean getBuiltInZoomControls() {
         return mBuiltInZoomControls;
     }
-    
+
     /**
      * Enable or disable file access within WebView. File access is enabled by
      * default.
@@ -485,6 +528,25 @@
     }
 
     /**
+     * Set whether the WebView will enable smooth transition while panning or
+     * zooming. If it is true, WebView will choose a solution to maximize the
+     * performance. e.g. the WebView's content may not be updated during the
+     * transition. If it is false, WebView will keep its fidelity. The default
+     * value is false.
+     */
+    public void setEnableSmoothTransition(boolean enable) {
+        mEnableSmoothTransition = enable;
+    }
+
+    /**
+     * Returns true if the WebView enables smooth transition while panning or
+     * zooming.
+     */
+    public boolean enableSmoothTransition() {
+        return mEnableSmoothTransition;
+    }
+
+    /**
      * Store whether the WebView is saving form data.
      */
     public void setSaveFormData(boolean save) {
@@ -984,8 +1046,8 @@
 
     private void verifyNetworkAccess() {
         if (!mBlockNetworkLoads) {
-            if (mContext.checkPermission("android.permission.INTERNET", 
-                    android.os.Process.myPid(), android.os.Process.myUid()) != 
+            if (mContext.checkPermission("android.permission.INTERNET",
+                    android.os.Process.myPid(), android.os.Process.myUid()) !=
                         PackageManager.PERMISSION_GRANTED) {
                 throw new SecurityException
                         ("Permission denied - " +
@@ -1011,6 +1073,7 @@
      * @deprecated This method has been deprecated in favor of
      *             {@link #setPluginState}
      */
+    @Deprecated
     public synchronized void setPluginsEnabled(boolean flag) {
         setPluginState(PluginState.ON);
     }
@@ -1176,6 +1239,18 @@
     }
 
     /**
+     * Sets whether XSS Auditor is enabled.
+     * @param flag Whether XSS Auditor should be enabled.
+     * @hide Only used by LayoutTestController.
+     */
+    public synchronized void setXSSAuditorEnabled(boolean flag) {
+        if (mXSSAuditorEnabled != flag) {
+            mXSSAuditorEnabled = flag;
+            postSync();
+        }
+    }
+
+    /**
      * Return true if javascript is enabled. <b>Note: The default is false.</b>
      * @return True if javascript is enabled.
      */
@@ -1188,6 +1263,7 @@
      * @return True if plugins are enabled.
      * @deprecated This method has been replaced by {@link #getPluginState}
      */
+    @Deprecated
     public synchronized boolean getPluginsEnabled() {
         return mPluginState == PluginState.ON;
     }
@@ -1256,7 +1332,7 @@
     public synchronized void setUserAgentString(String ua) {
         if (ua == null || ua.length() == 0) {
             synchronized(sLockForLocaleSettings) {
-                Locale currentLocale = Locale.getDefault(); 
+                Locale currentLocale = Locale.getDefault();
                 if (!sLocale.equals(currentLocale)) {
                     sLocale = currentLocale;
                     mAcceptLanguage = getCurrentAcceptLanguage();
@@ -1311,11 +1387,11 @@
         }
         return mAcceptLanguage;
     }
-    
+
     /**
      * Tell the WebView whether it needs to set a node to have focus when
      * {@link WebView#requestFocus(int, android.graphics.Rect)} is called.
-     * 
+     *
      * @param flag
      */
     public void setNeedInitialFocus(boolean flag) {
@@ -1342,7 +1418,7 @@
                     EventHandler.PRIORITY));
         }
     }
-    
+
     /**
      * Override the way the cache is used. The way the cache is used is based
      * on the navigation option. For a normal page load, the cache is checked
@@ -1356,7 +1432,7 @@
             mOverrideCacheMode = mode;
         }
     }
-    
+
     /**
      * Return the current setting for overriding the cache mode. For a full
      * description, see the {@link #setCacheMode(int)} function.
@@ -1364,7 +1440,7 @@
     public int getCacheMode() {
         return mOverrideCacheMode;
     }
-    
+
     /**
      * If set, webkit alternately shrinks and expands images viewed outside
      * of an HTML page to fit the screen. This conflicts with attempts by
@@ -1379,6 +1455,19 @@
         }
      }
 
+    /**
+     * Specify the maximum decoded image size. The default is
+     * 2 megs for small memory devices and 8 megs for large memory devices.
+     * @param size The maximum decoded size, or zero to set to the default.
+     * @hide pending api council approval
+     */
+    public void setMaximumDecodedImageSize(long size) {
+        if (mMaximumDecodedImageSize != size) {
+            mMaximumDecodedImageSize = size;
+            postSync();
+        }
+    }
+
     int getDoubleTapToastCount() {
         return mDoubleTapToastCount;
     }
diff --git a/core/java/android/webkit/WebTextView.java b/core/java/android/webkit/WebTextView.java
index 19abec1..eb36b5d 100644
--- a/core/java/android/webkit/WebTextView.java
+++ b/core/java/android/webkit/WebTextView.java
@@ -28,6 +28,7 @@
 import android.graphics.drawable.Drawable;
 import android.text.Editable;
 import android.text.InputFilter;
+import android.text.Layout;
 import android.text.Selection;
 import android.text.Spannable;
 import android.text.TextPaint;
@@ -300,6 +301,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 +388,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);
@@ -481,9 +498,10 @@
             // to big for the case of a small textfield.
             int smallerSlop = slop/2;
             if (dx > smallerSlop || dy > smallerSlop) {
-                if (mWebView != null) {
-                    float maxScrollX = (float) Touch.getMaxScrollX(this,
-                                getLayout(), mScrollY);
+                Layout layout = getLayout();
+                if (mWebView != null && layout != null) {
+                    float maxScrollX = (float) Touch.getMaxScrollX(this, layout,
+                            mScrollY);
                     if (DebugFlags.WEB_TEXT_VIEW) {
                         Log.v(LOGTAG, "onTouchEvent x=" + mScrollX + " y="
                                 + mScrollY + " maxX=" + maxScrollX);
@@ -667,6 +685,7 @@
         } else {
             Selection.setSelection(text, selection, selection);
         }
+        if (mWebView != null) mWebView.incrementTextGeneration();
     }
 
     /**
@@ -919,14 +938,4 @@
     /* package */ void updateCachedTextfield() {
         mWebView.updateCachedTextfield(getText().toString());
     }
-
-    @Override
-    public boolean requestRectangleOnScreen(Rect rectangle) {
-        // don't scroll while in zoom animation. When it is done, we will adjust
-        // the WebTextView if it is in editing mode.
-        if (!mWebView.inAnimateZoom()) {
-            return super.requestRectangleOnScreen(rectangle);
-        }
-        return false;
-    }
 }
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 4ca210f..03382c3b 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -22,22 +22,20 @@
 import android.content.DialogInterface;
 import android.content.Intent;
 import android.content.DialogInterface.OnCancelListener;
-import android.content.pm.PackageManager;
 import android.database.DataSetObserver;
 import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.BitmapShader;
 import android.graphics.Canvas;
 import android.graphics.Color;
+import android.graphics.CornerPathEffect;
+import android.graphics.DrawFilter;
 import android.graphics.Interpolator;
-import android.graphics.Matrix;
 import android.graphics.Paint;
+import android.graphics.PaintFlagsDrawFilter;
 import android.graphics.Picture;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.graphics.Region;
-import android.graphics.Shader;
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
 import android.net.http.SslCertificate;
@@ -46,6 +44,7 @@
 import android.os.Message;
 import android.os.ServiceManager;
 import android.os.SystemClock;
+import android.speech.tts.TextToSpeech;
 import android.text.IClipboard;
 import android.text.Selection;
 import android.text.Spannable;
@@ -64,32 +63,29 @@
 import android.view.ViewConfiguration;
 import android.view.ViewGroup;
 import android.view.ViewTreeObserver;
-import android.view.animation.AlphaAnimation;
+import android.view.accessibility.AccessibilityManager;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputConnection;
 import android.view.inputmethod.InputMethodManager;
 import android.webkit.WebTextView.AutoCompleteAdapter;
 import android.webkit.WebViewCore.EventHub;
 import android.webkit.WebViewCore.TouchEventData;
+import android.webkit.WebViewCore.TouchHighlightData;
 import android.widget.AbsoluteLayout;
 import android.widget.Adapter;
 import android.widget.AdapterView;
 import android.widget.ArrayAdapter;
 import android.widget.CheckedTextView;
-import android.widget.FrameLayout;
 import android.widget.LinearLayout;
 import android.widget.ListView;
 import android.widget.Scroller;
 import android.widget.Toast;
-import android.widget.ZoomButtonsController;
-import android.widget.ZoomControls;
 import android.widget.AdapterView.OnItemClickListener;
 
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
-import java.io.IOException;
 import java.net.URLDecoder;
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -310,49 +306,7 @@
 
     static final String LOGTAG = "webview";
 
-    private static class ExtendedZoomControls extends FrameLayout {
-        public ExtendedZoomControls(Context context, AttributeSet attrs) {
-            super(context, attrs);
-            LayoutInflater inflater = (LayoutInflater)
-                    context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
-            inflater.inflate(com.android.internal.R.layout.zoom_magnify, this, true);
-            mPlusMinusZoomControls = (ZoomControls) findViewById(
-                    com.android.internal.R.id.zoomControls);
-            findViewById(com.android.internal.R.id.zoomMagnify).setVisibility(
-                    View.GONE);
-        }
-
-        public void show(boolean showZoom, boolean canZoomOut) {
-            mPlusMinusZoomControls.setVisibility(
-                    showZoom ? View.VISIBLE : View.GONE);
-            fade(View.VISIBLE, 0.0f, 1.0f);
-        }
-
-        public void hide() {
-            fade(View.GONE, 1.0f, 0.0f);
-        }
-
-        private void fade(int visibility, float startAlpha, float endAlpha) {
-            AlphaAnimation anim = new AlphaAnimation(startAlpha, endAlpha);
-            anim.setDuration(500);
-            startAnimation(anim);
-            setVisibility(visibility);
-        }
-
-        public boolean hasFocus() {
-            return mPlusMinusZoomControls.hasFocus();
-        }
-
-        public void setOnZoomInClickListener(OnClickListener listener) {
-            mPlusMinusZoomControls.setOnZoomInClickListener(listener);
-        }
-
-        public void setOnZoomOutClickListener(OnClickListener listener) {
-            mPlusMinusZoomControls.setOnZoomOutClickListener(listener);
-        }
-
-        ZoomControls    mPlusMinusZoomControls;
-    }
+    private ZoomManager mZoomManager;
 
     /**
      *  Transportation object for returning WebView across thread boundaries.
@@ -398,6 +352,8 @@
     // more key events.
     private int mTextGeneration;
 
+    /* package */ void incrementTextGeneration() { mTextGeneration++; }
+
     // Used by WebViewCore to create child views.
     /* package */ final ViewManager mViewManager;
 
@@ -445,6 +401,14 @@
     private float mLastVelX;
     private float mLastVelY;
 
+    // A pointer to the native scrollable layer when dragging layers.  Only
+    // valid when mTouchMode is TOUCH_DRAG_LAYER_MODE.
+    private int mScrollingLayer;
+
+    // only trigger accelerated fling if the new velocity is at least
+    // MINIMUM_VELOCITY_RATIO_FOR_ACCELERATION times of the previous velocity
+    private static final float MINIMUM_VELOCITY_RATIO_FOR_ACCELERATION = 0.2f;
+
     /**
      * Touch mode
      */
@@ -456,8 +420,8 @@
     private static final int TOUCH_SHORTPRESS_MODE = 5;
     private static final int TOUCH_DOUBLE_TAP_MODE = 6;
     private static final int TOUCH_DONE_MODE = 7;
-    private static final int TOUCH_SELECT_MODE = 8;
-    private static final int TOUCH_PINCH_DRAG = 9;
+    private static final int TOUCH_PINCH_DRAG = 8;
+    private static final int TOUCH_DRAG_LAYER_MODE = 9;
 
     // Whether to forward the touch events to WebCore
     private boolean mForwardTouchEvents = false;
@@ -496,10 +460,6 @@
     // true if onPause has been called (and not onResume)
     private boolean mIsPaused;
 
-    // true if, during a transition to a new page, we're delaying
-    // deleting a root layer until there's something to draw of the new page.
-    private boolean mDelayedDeleteRootLayer;
-
     /**
      * Customizable constant
      */
@@ -521,9 +481,6 @@
     private static final int MIN_FLING_TIME = 250;
     // draw unfiltered after drag is held without movement
     private static final int MOTIONLESS_TIME = 100;
-    // The time that the Zoom Controls are visible before fading away
-    private static final long ZOOM_CONTROLS_TIMEOUT =
-            ViewConfiguration.getZoomControlsTimeout();
     // The amount of content to overlap between two screens when going through
     // pages with the space bar, in pixels.
     private static final int PAGE_SCROLL_OVERLAP = 24;
@@ -564,15 +521,24 @@
     private static final int MOTIONLESS_IGNORE          = 3;
     private int mHeldMotionless;
 
-    // whether support multi-touch
-    private boolean mSupportMultiTouch;
-    // use the framework's ScaleGestureDetector to handle multi-touch
-    private ScaleGestureDetector mScaleDetector;
+    // An instance for injecting accessibility in WebViews with disabled
+    // JavaScript or ones for which no accessibility script exists
+    private AccessibilityInjector mAccessibilityInjector;
 
-    // the anchor point in the document space where VIEW_SIZE_CHANGED should
-    // apply to
-    private int mAnchorX;
-    private int mAnchorY;
+    // the color used to highlight the touch rectangles
+    private static final int mHightlightColor = 0x33000000;
+    // the round corner for the highlight path
+    private static final float TOUCH_HIGHLIGHT_ARC = 5.0f;
+    // the region indicating where the user touched on the screen
+    private Region mTouchHighlightRegion = new Region();
+    // the paint for the touch highlight
+    private Paint mTouchHightlightPaint;
+    // debug only
+    private static final boolean DEBUG_TOUCH_HIGHLIGHT = true;
+    private static final int TOUCH_HIGHLIGHT_ELAPSE_TIME = 2000;
+    private Paint mTouchCrossHairColor;
+    private int mTouchHighlightX;
+    private int mTouchHighlightY;
 
     /*
      * Private message ids
@@ -606,7 +572,7 @@
     static final int WEBCORE_INITIALIZED_MSG_ID         = 107;
     static final int UPDATE_TEXTFIELD_TEXT_MSG_ID       = 108;
     static final int UPDATE_ZOOM_RANGE                  = 109;
-    static final int MOVE_OUT_OF_PLUGIN                 = 110;
+    static final int UNHANDLED_NAV_KEY                  = 110;
     static final int CLEAR_TEXT_ENTRY                   = 111;
     static final int UPDATE_TEXT_SELECTION_MSG_ID       = 112;
     static final int SHOW_RECT_MSG_ID                   = 113;
@@ -620,16 +586,19 @@
     static final int SHOW_FULLSCREEN                    = 120;
     static final int HIDE_FULLSCREEN                    = 121;
     static final int DOM_FOCUS_CHANGED                  = 122;
-    static final int IMMEDIATE_REPAINT_MSG_ID           = 123;
-    static final int SET_ROOT_LAYER_MSG_ID              = 124;
+    static final int REPLACE_BASE_CONTENT               = 123;
+    // 124;
     static final int RETURN_LABEL                       = 125;
     static final int FIND_AGAIN                         = 126;
     static final int CENTER_FIT_RECT                    = 127;
     static final int REQUEST_KEYBOARD_WITH_SELECTION_MSG_ID = 128;
     static final int SET_SCROLLBAR_MODES                = 129;
+    static final int SELECTION_STRING_CHANGED           = 130;
+    static final int SET_TOUCH_HIGHLIGHT_RECTS          = 131;
+    static final int SAVE_WEBARCHIVE_FINISHED           = 132;
 
     private static final int FIRST_PACKAGE_MSG_ID = SCROLL_TO_MSG_ID;
-    private static final int LAST_PACKAGE_MSG_ID = SET_SCROLLBAR_MODES;
+    private static final int LAST_PACKAGE_MSG_ID = SET_TOUCH_HIGHLIGHT_RECTS;
 
     static final String[] HandlerPrivateDebugString = {
         "REMEMBER_PASSWORD", //              = 1;
@@ -654,7 +623,7 @@
         "WEBCORE_INITIALIZED_MSG_ID", //     = 107;
         "UPDATE_TEXTFIELD_TEXT_MSG_ID", //   = 108;
         "UPDATE_ZOOM_RANGE", //              = 109;
-        "MOVE_OUT_OF_PLUGIN", //             = 110;
+        "UNHANDLED_NAV_KEY", //              = 110;
         "CLEAR_TEXT_ENTRY", //               = 111;
         "UPDATE_TEXT_SELECTION_MSG_ID", //   = 112;
         "SHOW_RECT_MSG_ID", //               = 113;
@@ -667,13 +636,16 @@
         "SHOW_FULLSCREEN", //                = 120;
         "HIDE_FULLSCREEN", //                = 121;
         "DOM_FOCUS_CHANGED", //              = 122;
-        "IMMEDIATE_REPAINT_MSG_ID", //       = 123;
-        "SET_ROOT_LAYER_MSG_ID", //          = 124;
+        "REPLACE_BASE_CONTENT", //           = 123;
+        "124", //                            = 124;
         "RETURN_LABEL", //                   = 125;
         "FIND_AGAIN", //                     = 126;
         "CENTER_FIT_RECT", //                = 127;
         "REQUEST_KEYBOARD_WITH_SELECTION_MSG_ID", // = 128;
-        "SET_SCROLLBAR_MODES" //             = 129;
+        "SET_SCROLLBAR_MODES", //            = 129;
+        "SELECTION_STRING_CHANGED", //       = 130;
+        "SET_TOUCH_HIGHLIGHT_RECTS", //      = 131;
+        "SAVE_WEBARCHIVE_FINISHED" //        = 132;
     };
 
     // If the site doesn't use the viewport meta tag to specify the viewport,
@@ -686,49 +658,9 @@
     // the minimum preferred width is huge, an upper limit is needed.
     static int sMaxViewportWidth = DEFAULT_VIEWPORT_WIDTH;
 
-    // default scale limit. Depending on the display density
-    private static float DEFAULT_MAX_ZOOM_SCALE;
-    private static float DEFAULT_MIN_ZOOM_SCALE;
-    // scale limit, which can be set through viewport meta tag in the web page
-    private float mMaxZoomScale;
-    private float mMinZoomScale;
-    private boolean mMinZoomScaleFixed = true;
-
     // initial scale in percent. 0 means using default.
     private int mInitialScaleInPercent = 0;
 
-    // while in the zoom overview mode, the page's width is fully fit to the
-    // current window. The page is alive, in another words, you can click to
-    // follow the links. Double tap will toggle between zoom overview mode and
-    // the last zoom scale.
-    boolean mInZoomOverview = false;
-
-    // ideally mZoomOverviewWidth should be mContentWidth. But sites like espn,
-    // engadget always have wider mContentWidth no matter what viewport size is.
-    int mZoomOverviewWidth = DEFAULT_VIEWPORT_WIDTH;
-    float mTextWrapScale;
-
-    // default scale. Depending on the display density.
-    static int DEFAULT_SCALE_PERCENT;
-    private float mDefaultScale;
-
-    private static float MINIMUM_SCALE_INCREMENT = 0.01f;
-
-    // set to true temporarily during ScaleGesture triggered zoom
-    private boolean mPreviewZoomOnly = false;
-
-    // computed scale and inverse, from mZoomWidth.
-    private float mActualScale;
-    private float mInvActualScale;
-    // if this is non-zero, it is used on drawing rather than mActualScale
-    private float mZoomScale;
-    private float mInvInitialZoomScale;
-    private float mInvFinalZoomScale;
-    private int mInitialScrollX;
-    private int mInitialScrollY;
-    private long mZoomStart;
-    private static final int ZOOM_ANIMATION_LENGTH = 500;
-
     private boolean mUserScroll = false;
 
     private int mSnapScrollMode = SNAP_NONE;
@@ -752,6 +684,19 @@
     private int mHorizontalScrollBarMode = SCROLLBAR_AUTO;
     private int mVerticalScrollBarMode = SCROLLBAR_AUTO;
 
+    // the alias via which accessibility JavaScript interface is exposed
+    private static final String ALIAS_ACCESSIBILITY_JS_INTERFACE = "accessibility";
+
+    // JavaScript to inject the script chooser which will
+    // pick the right script for the current URL
+    private static final String ACCESSIBILITY_SCRIPT_CHOOSER_JAVASCRIPT =
+        "javascript:(function() {" +
+        "    var chooser = document.createElement('script');" +
+        "    chooser.type = 'text/javascript';" +
+        "    chooser.src = 'https://ssl.gstatic.com/accessibility/javascript/android/AndroidScriptChooser.user.js';" +
+        "    document.getElementsByTagName('head')[0].appendChild(chooser);" +
+        "  })();";
+
     // Used to match key downs and key ups
     private boolean mGotKeyDown;
 
@@ -856,43 +801,6 @@
         }
     }
 
-    // The View containing the zoom controls
-    private ExtendedZoomControls mZoomControls;
-    private Runnable mZoomControlRunnable;
-
-    // mZoomButtonsController will be lazy initialized in
-    // getZoomButtonsController() to get better performance.
-    private ZoomButtonsController mZoomButtonsController;
-
-    // These keep track of the center point of the zoom.  They are used to
-    // determine the point around which we should zoom.
-    private float mZoomCenterX;
-    private float mZoomCenterY;
-
-    private ZoomButtonsController.OnZoomListener mZoomListener =
-            new ZoomButtonsController.OnZoomListener() {
-
-        public void onVisibilityChanged(boolean visible) {
-            if (visible) {
-                switchOutDrawHistory();
-                // Bring back the hidden zoom controls.
-                mZoomButtonsController.getZoomControls().setVisibility(
-                        View.VISIBLE);
-                updateZoomButtonsEnabled();
-            }
-        }
-
-        public void onZoom(boolean zoomIn) {
-            if (zoomIn) {
-                zoomIn();
-            } else {
-                zoomOut();
-            }
-
-            updateZoomButtonsEnabled();
-        }
-    };
-
     /**
      * Construct a new WebView with a Context object.
      * @param context A Context object used to access application assets.
@@ -928,51 +836,37 @@
      * @param context A Context object used to access application assets.
      * @param attrs An AttributeSet passed to our parent.
      * @param defStyle The default style resource ID.
-     * @param javascriptInterfaces is a Map of intareface names, as keys, and
+     * @param javascriptInterfaces is a Map of interface names, as keys, and
      * object implementing those interfaces, as values.
      * @hide pending API council approval.
      */
     protected WebView(Context context, AttributeSet attrs, int defStyle,
             Map<String, Object> javascriptInterfaces) {
         super(context, attrs, defStyle);
-        init();
+
+        if (AccessibilityManager.getInstance(context).isEnabled()) {
+            if (javascriptInterfaces == null) {
+                javascriptInterfaces = new HashMap<String, Object>();
+            }
+            exposeAccessibilityJavaScriptApi(javascriptInterfaces);
+        }
 
         mCallbackProxy = new CallbackProxy(context, this);
         mViewManager = new ViewManager(this);
         mWebViewCore = new WebViewCore(context, this, mCallbackProxy, javascriptInterfaces);
         mDatabase = WebViewDatabase.getInstance(context);
         mScroller = new Scroller(context);
+        mZoomManager = new ZoomManager(this, mCallbackProxy);
 
+        /* The init method must follow the creation of certain member variables,
+         * such as the mZoomManager.
+         */
+        init();
         updateMultiTouchSupport(context);
     }
 
     void updateMultiTouchSupport(Context context) {
-        WebSettings settings = getSettings();
-        mSupportMultiTouch = context.getPackageManager().hasSystemFeature(
-                PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH)
-                && settings.supportZoom() && settings.getBuiltInZoomControls();
-        if (mSupportMultiTouch && (mScaleDetector == null)) {
-            mScaleDetector = new ScaleGestureDetector(context,
-                    new ScaleDetectorListener());
-        } else if (!mSupportMultiTouch && (mScaleDetector != null)) {
-            mScaleDetector = null;
-        }
-    }
-
-    private void updateZoomButtonsEnabled() {
-        if (mZoomButtonsController == null) return;
-        boolean canZoomIn = mActualScale < mMaxZoomScale;
-        boolean canZoomOut = mActualScale > mMinZoomScale && !mInZoomOverview;
-        if (!canZoomIn && !canZoomOut) {
-            // Hide the zoom in and out buttons, as well as the fit to page
-            // button, if the page cannot zoom
-            mZoomButtonsController.getZoomControls().setVisibility(View.GONE);
-        } else {
-            // Set each one individually, as a page may be able to zoom in
-            // or out.
-            mZoomButtonsController.setZoomInEnabled(canZoomIn);
-            mZoomButtonsController.setZoomOutEnabled(canZoomOut);
-        }
+        mZoomManager.updateMultiTouchSupport(context);
     }
 
     private void init() {
@@ -992,34 +886,38 @@
         // use one line height, 16 based on our current default font, for how
         // far we allow a touch be away from the edge of a link
         mNavSlop = (int) (16 * density);
-        // density adjusted scale factors
-        DEFAULT_SCALE_PERCENT = (int) (100 * density);
-        mDefaultScale = density;
-        mActualScale = density;
-        mInvActualScale = 1 / density;
-        mTextWrapScale = density;
-        DEFAULT_MAX_ZOOM_SCALE = 4.0f * density;
-        DEFAULT_MIN_ZOOM_SCALE = 0.25f * density;
-        mMaxZoomScale = DEFAULT_MAX_ZOOM_SCALE;
-        mMinZoomScale = DEFAULT_MIN_ZOOM_SCALE;
+        mZoomManager.init(density);
         mMaximumFling = configuration.getScaledMaximumFlingVelocity();
+
+        // Compute the inverse of the density squared.
+        DRAG_LAYER_INVERSE_DENSITY_SQUARED = 1 / (density * density);
+    }
+
+    /**
+     * Exposes accessibility APIs to JavaScript by appending them to the JavaScript
+     * interfaces map provided by the WebView client. In case of conflicting
+     * alias with the one of the accessibility API the user specified one wins.
+     *
+     * @param javascriptInterfaces A map with interfaces to be exposed to JavaScript.
+     */
+    private void exposeAccessibilityJavaScriptApi(Map<String, Object> javascriptInterfaces) {
+        if (javascriptInterfaces.containsKey(ALIAS_ACCESSIBILITY_JS_INTERFACE)) {
+            Log.w(LOGTAG, "JavaScript interface mapped to \"" + ALIAS_ACCESSIBILITY_JS_INTERFACE
+                    + "\" overrides the accessibility API JavaScript interface. No accessibility"
+                    + "API will be exposed to JavaScript!");
+            return;
+        }
+
+        // expose the TTS for now ...
+        javascriptInterfaces.put(ALIAS_ACCESSIBILITY_JS_INTERFACE,
+                new TextToSpeech(getContext(), null));
     }
 
     /* package */void updateDefaultZoomDensity(int zoomDensity) {
-        final float density = getContext().getResources().getDisplayMetrics().density
+        final float density = mContext.getResources().getDisplayMetrics().density
                 * 100 / zoomDensity;
-        if (Math.abs(density - mDefaultScale) > 0.01) {
-            float scaleFactor = density / mDefaultScale;
-            // adjust the limits
-            mNavSlop = (int) (16 * density);
-            DEFAULT_SCALE_PERCENT = (int) (100 * density);
-            DEFAULT_MAX_ZOOM_SCALE = 4.0f * density;
-            DEFAULT_MIN_ZOOM_SCALE = 0.25f * density;
-            mDefaultScale = density;
-            mMaxZoomScale *= scaleFactor;
-            mMinZoomScale *= scaleFactor;
-            setNewZoomScale(mActualScale * scaleFactor, true, false);
-        }
+        mNavSlop = (int) (16 * density);
+        mZoomManager.updateDefaultZoomDensity(density);
     }
 
     /* package */ boolean onSavePassword(String schemePlusHost, String username,
@@ -1136,14 +1034,14 @@
      * returns the height of the titlebarview (if any). Does not care about
      * scrolling
      */
-    private int getTitleHeight() {
+    int getTitleHeight() {
         return mTitleBar != null ? mTitleBar.getHeight() : 0;
     }
 
     /*
      * Return the amount of the titlebarview (if any) that is visible
      */
-    private int getVisibleTitleHeight() {
+    int getVisibleTitleHeight() {
         return Math.max(getTitleHeight() - mScrollY, 0);
     }
 
@@ -1404,29 +1302,23 @@
         // now update the bundle
         b.putInt("scrollX", mScrollX);
         b.putInt("scrollY", mScrollY);
-        b.putFloat("scale", mActualScale);
-        b.putFloat("textwrapScale", mTextWrapScale);
-        b.putBoolean("overview", mInZoomOverview);
+        mZoomManager.saveZoomState(b);
         return true;
     }
 
     private void restoreHistoryPictureFields(Picture p, Bundle b) {
         int sx = b.getInt("scrollX", 0);
         int sy = b.getInt("scrollY", 0);
-        float scale = b.getFloat("scale", 1.0f);
+
         mDrawHistory = true;
         mHistoryPicture = p;
         mScrollX = sx;
         mScrollY = sy;
+        mZoomManager.restoreZoomState(b);
+        final float scale = mZoomManager.getScale();
         mHistoryWidth = Math.round(p.getWidth() * scale);
         mHistoryHeight = Math.round(p.getHeight() * scale);
-        // as getWidth() / getHeight() of the view are not available yet, set up
-        // mActualScale, so that when onSizeChanged() is called, the rest will
-        // be set correctly
-        mActualScale = scale;
-        mInvActualScale = 1 / scale;
-        mTextWrapScale = b.getFloat("textwrapScale", scale);
-        mInZoomOverview = b.getBoolean("overview");
+
         invalidate();
     }
 
@@ -1638,6 +1530,45 @@
     }
 
     /**
+     * Saves the current view as a web archive.
+     *
+     * @param filename The filename where the archive should be placed.
+     */
+    public void saveWebArchive(String filename) {
+        saveWebArchive(filename, false, null);
+    }
+
+    /* package */ static class SaveWebArchiveMessage {
+        SaveWebArchiveMessage (String basename, boolean autoname, ValueCallback<String> callback) {
+            mBasename = basename;
+            mAutoname = autoname;
+            mCallback = callback;
+        }
+
+        /* package */ final String mBasename;
+        /* package */ final boolean mAutoname;
+        /* package */ final ValueCallback<String> mCallback;
+        /* package */ String mResultFile;
+    }
+
+    /**
+     * Saves the current view as a web archive.
+     *
+     * @param basename The filename where the archive should be placed.
+     * @param autoname If false, takes basename to be a file. If true, basename
+     *                 is assumed to be a directory in which a filename will be
+     *                 chosen according to the url of the current page.
+     * @param callback Called after the web archive has been saved. The
+     *                 parameter for onReceiveValue will either be the filename
+     *                 under which the file was saved, or null if saving the
+     *                 file failed.
+     */
+    public void saveWebArchive(String basename, boolean autoname, ValueCallback<String> callback) {
+        mWebViewCore.sendMessage(EventHub.SAVE_WEBARCHIVE,
+            new SaveWebArchiveMessage(basename, autoname, callback));
+    }
+
+    /**
      * Stop the current load.
      */
     public void stopLoading() {
@@ -1806,6 +1737,7 @@
     public void clearView() {
         mContentWidth = 0;
         mContentHeight = 0;
+        nativeSetBaseLayer(0);
         mWebViewCore.sendMessage(EventHub.CLEAR_CONTENT);
     }
 
@@ -1819,8 +1751,9 @@
      *         bounds of the view.
      */
     public Picture capturePicture() {
-        if (null == mWebViewCore) return null; // check for out of memory tab
-        return mWebViewCore.copyContentPicture();
+        Picture result = new Picture();
+        nativeCopyBaseContentToPicture(result);
+        return result;
     }
 
     /**
@@ -1849,7 +1782,7 @@
      * @return The current scale.
      */
     public float getScale() {
-        return mActualScale;
+        return mZoomManager.getScale();
     }
 
     /**
@@ -1861,7 +1794,7 @@
      * @param scaleInPercent The initial scale in percent.
      */
     public void setInitialScale(int scaleInPercent) {
-        mInitialScaleInPercent = scaleInPercent;
+        mZoomManager.setInitialScaleInPercent(scaleInPercent);
     }
 
     /**
@@ -1875,13 +1808,7 @@
             return;
         }
         clearTextEntry(false);
-        if (getSettings().getBuiltInZoomControls()) {
-            getZoomButtonsController().setVisible(true);
-        } else {
-            mPrivateHandler.removeCallbacks(mZoomControlRunnable);
-            mPrivateHandler.postDelayed(mZoomControlRunnable,
-                    ZOOM_CONTROLS_TIMEOUT);
-        }
+        mZoomManager.invokeZoomPicker();
     }
 
     /**
@@ -1996,7 +1923,7 @@
         msg.sendToTarget();
     }
 
-    private static int pinLoc(int x, int viewMax, int docMax) {
+    static int pinLoc(int x, int viewMax, int docMax) {
 //        Log.d(LOGTAG, "-- pinLoc " + x + " " + viewMax + " " + docMax);
         if (docMax < viewMax) {   // the doc has room on the sides for "blank"
             // pin the short document to the top/left of the screen
@@ -2013,12 +1940,12 @@
     }
 
     // Expects x in view coordinates
-    private int pinLocX(int x) {
+    int pinLocX(int x) {
         return pinLoc(x, getViewWidth(), computeHorizontalScrollRange());
     }
 
     // Expects y in view coordinates
-    private int pinLocY(int y) {
+    int pinLocY(int y) {
         return pinLoc(y, getViewHeightWithTitle(),
                       computeVerticalScrollRange() + getTitleHeight());
     }
@@ -2068,7 +1995,7 @@
      * height.
      */
     private int viewToContentDimension(int d) {
-        return Math.round(d * mInvActualScale);
+        return Math.round(d * mZoomManager.getInvScale());
     }
 
     /**
@@ -2094,7 +2021,7 @@
      * Returns the result as a float.
      */
     private float viewToContentXf(int x) {
-        return x * mInvActualScale;
+        return x * mZoomManager.getInvScale();
     }
 
     /**
@@ -2103,7 +2030,7 @@
      * embedded into the WebView. Returns the result as a float.
      */
     private float viewToContentYf(int y) {
-        return (y - getTitleHeight()) * mInvActualScale;
+        return (y - getTitleHeight()) * mZoomManager.getInvScale();
     }
 
     /**
@@ -2113,7 +2040,7 @@
      * height.
      */
     /*package*/ int contentToViewDimension(int d) {
-        return Math.round(d * mActualScale);
+        return Math.round(d * mZoomManager.getScale());
     }
 
     /**
@@ -2154,7 +2081,7 @@
     // Called by JNI to invalidate the View, given rectangle coordinates in
     // content space
     private void viewInvalidate(int l, int t, int r, int b) {
-        final float scale = mActualScale;
+        final float scale = mZoomManager.getScale();
         final int dy = getTitleHeight();
         invalidate((int)Math.floor(l * scale),
                    (int)Math.floor(t * scale) + dy,
@@ -2165,7 +2092,7 @@
     // Called by JNI to invalidate the View after a delay, given rectangle
     // coordinates in content space
     private void viewInvalidateDelayed(long delay, int l, int t, int r, int b) {
-        final float scale = mActualScale;
+        final float scale = mZoomManager.getScale();
         final int dy = getTitleHeight();
         postInvalidateDelayed(delay,
                               (int)Math.floor(l * scale),
@@ -2203,13 +2130,7 @@
             // updated when we get out of that mode.
             if (!mDrawHistory) {
                 // repin our scroll, taking into account the new content size
-                int oldX = mScrollX;
-                int oldY = mScrollY;
-                mScrollX = pinLocX(mScrollX);
-                mScrollY = pinLocY(mScrollY);
-                if (oldX != mScrollX || oldY != mScrollY) {
-                    onScrollChanged(mScrollX, mScrollY, oldX, oldY);
-                }
+                updateScrollCoordinates(pinLocX(mScrollX), pinLocY(mScrollY));
                 if (!mScroller.isFinished()) {
                     // We are in the middle of a scroll.  Repin the final scroll
                     // position.
@@ -2221,77 +2142,12 @@
         contentSizeChanged(updateLayout);
     }
 
-    private void setNewZoomScale(float scale, boolean updateTextWrapScale,
-            boolean force) {
-        if (scale < mMinZoomScale) {
-            scale = mMinZoomScale;
-            // set mInZoomOverview for non mobile sites
-            if (scale < mDefaultScale) mInZoomOverview = true;
-        } else if (scale > mMaxZoomScale) {
-            scale = mMaxZoomScale;
-        }
-        if (updateTextWrapScale) {
-            mTextWrapScale = scale;
-            // reset mLastHeightSent to force VIEW_SIZE_CHANGED sent to WebKit
-            mLastHeightSent = 0;
-        }
-        if (scale != mActualScale || force) {
-            if (mDrawHistory) {
-                // If history Picture is drawn, don't update scroll. They will
-                // be updated when we get out of that mode.
-                if (scale != mActualScale && !mPreviewZoomOnly) {
-                    mCallbackProxy.onScaleChanged(mActualScale, scale);
-                }
-                mActualScale = scale;
-                mInvActualScale = 1 / scale;
-                sendViewSizeZoom();
-            } else {
-                // update our scroll so we don't appear to jump
-                // i.e. keep the center of the doc in the center of the view
-
-                int oldX = mScrollX;
-                int oldY = mScrollY;
-                float ratio = scale * mInvActualScale;   // old inverse
-                float sx = ratio * oldX + (ratio - 1) * mZoomCenterX;
-                float sy = ratio * oldY + (ratio - 1)
-                        * (mZoomCenterY - getTitleHeight());
-
-                // now update our new scale and inverse
-                if (scale != mActualScale && !mPreviewZoomOnly) {
-                    mCallbackProxy.onScaleChanged(mActualScale, scale);
-                }
-                mActualScale = scale;
-                mInvActualScale = 1 / scale;
-
-                // Scale all the child views
-                mViewManager.scaleAll();
-
-                // as we don't have animation for scaling, don't do animation
-                // for scrolling, as it causes weird intermediate state
-                //        pinScrollTo(Math.round(sx), Math.round(sy));
-                mScrollX = pinLocX(Math.round(sx));
-                mScrollY = pinLocY(Math.round(sy));
-
-                // update webkit
-                if (oldX != mScrollX || oldY != mScrollY) {
-                    onScrollChanged(mScrollX, mScrollY, oldX, oldY);
-                } else {
-                    // the scroll position is adjusted at the beginning of the
-                    // zoom animation. But we want to update the WebKit at the
-                    // end of the zoom animation. See comments in onScaleEnd().
-                    sendOurVisibleRect();
-                }
-                sendViewSizeZoom();
-            }
-        }
-    }
-
     // Used to avoid sending many visible rect messages.
     private Rect mLastVisibleRectSent;
     private Rect mLastGlobalRect;
 
-    private Rect sendOurVisibleRect() {
-        if (mPreviewZoomOnly) return mLastVisibleRectSent;
+    Rect sendOurVisibleRect() {
+        if (mZoomManager.isPreventingWebkitUpdates()) return mLastVisibleRectSent;
 
         Rect rect = new Rect();
         calcOurContentVisibleRect(rect);
@@ -2324,9 +2180,6 @@
         Point p = new Point();
         getGlobalVisibleRect(r, p);
         r.offset(-p.x, -p.y);
-        if (mFindIsUp) {
-            r.bottom -= mFindHeight;
-        }
     }
 
     // Sets r to be our visible rectangle in content coordinates
@@ -2373,16 +2226,19 @@
 
     /**
      * Compute unzoomed width and height, and if they differ from the last
-     * values we sent, send them to webkit (to be used has new viewport)
+     * values we sent, send them to webkit (to be used as new viewport)
+     *
+     * @param force ensures that the message is sent to webkit even if the width
+     * or height has not changed since the last message
      *
      * @return true if new values were sent
      */
-    private boolean sendViewSizeZoom() {
-        if (mPreviewZoomOnly) return false;
+    boolean sendViewSizeZoom(boolean force) {
+        if (mZoomManager.isPreventingWebkitUpdates()) return false;
 
         int viewWidth = getViewWidth();
-        int newWidth = Math.round(viewWidth * mInvActualScale);
-        int newHeight = Math.round(getViewHeight() * mInvActualScale);
+        int newWidth = Math.round(viewWidth * mZoomManager.getInvScale());
+        int newHeight = Math.round(getViewHeight() * mZoomManager.getInvScale());
         /*
          * Because the native side may have already done a layout before the
          * View system was able to measure us, we have to send a height of 0 to
@@ -2395,19 +2251,20 @@
             newHeight = 0;
         }
         // Avoid sending another message if the dimensions have not changed.
-        if (newWidth != mLastWidthSent || newHeight != mLastHeightSent) {
+        if (newWidth != mLastWidthSent || newHeight != mLastHeightSent || force) {
             ViewSizeData data = new ViewSizeData();
             data.mWidth = newWidth;
             data.mHeight = newHeight;
-            data.mTextWrapWidth = Math.round(viewWidth / mTextWrapScale);;
-            data.mScale = mActualScale;
-            data.mIgnoreHeight = mZoomScale != 0 && !mHeightCanMeasure;
-            data.mAnchorX = mAnchorX;
-            data.mAnchorY = mAnchorY;
+            data.mTextWrapWidth = Math.round(viewWidth / mZoomManager.getTextWrapScale());
+            data.mScale = mZoomManager.getScale();
+            data.mIgnoreHeight = mZoomManager.isFixedLengthAnimationInProgress()
+                    && !mHeightCanMeasure;
+            data.mAnchorX = mZoomManager.getDocumentAnchorX();
+            data.mAnchorY = mZoomManager.getDocumentAnchorY();
             mWebViewCore.sendMessage(EventHub.VIEW_SIZE_CHANGED, data);
             mLastWidthSent = newWidth;
             mLastHeightSent = newHeight;
-            mAnchorX = mAnchorY = 0;
+            mZoomManager.clearDocumentAnchor();
             return true;
         }
         return false;
@@ -2418,12 +2275,12 @@
         if (mDrawHistory) {
             return mHistoryWidth;
         } else if (mHorizontalScrollBarMode == SCROLLBAR_ALWAYSOFF
-                && (mActualScale - mMinZoomScale <= MINIMUM_SCALE_INCREMENT)) {
+                && !mZoomManager.canZoomOut()) {
             // only honor the scrollbar mode when it is at minimum zoom level
             return computeHorizontalScrollExtent();
         } else {
             // to avoid rounding error caused unnecessary scrollbar, use floor
-            return (int) Math.floor(mContentWidth * mActualScale);
+            return (int) Math.floor(mContentWidth * mZoomManager.getScale());
         }
     }
 
@@ -2432,12 +2289,12 @@
         if (mDrawHistory) {
             return mHistoryHeight;
         } else if (mVerticalScrollBarMode == SCROLLBAR_ALWAYSOFF
-                && (mActualScale - mMinZoomScale <= MINIMUM_SCALE_INCREMENT)) {
+                && !mZoomManager.canZoomOut()) {
             // only honor the scrollbar mode when it is at minimum zoom level
             return computeVerticalScrollExtent();
         } else {
             // to avoid rounding error caused unnecessary scrollbar, use floor
-            return (int) Math.floor(mContentHeight * mActualScale);
+            return (int) Math.floor(mContentHeight * mZoomManager.getScale());
         }
     }
 
@@ -2505,7 +2362,9 @@
     }
 
     /**
-     * Get the touch icon url for the apple-touch-icon <link> element.
+     * Get the touch icon url for the apple-touch-icon <link> element, or
+     * a URL on this site's server pointing to the standard location of a
+     * touch icon.
      * @hide
      */
     public String getTouchIconUrl() {
@@ -2682,19 +2541,22 @@
      */
     public void setFindIsUp(boolean isUp) {
         mFindIsUp = isUp;
-        if (isUp) {
-            recordNewContentSize(mContentWidth, mContentHeight + mFindHeight,
-                    false);
-        }
         if (0 == mNativeClass) return; // client isn't initialized
         nativeSetFindIsUp(isUp);
     }
 
+    /**
+     * @hide
+     */
+    public int findIndex() {
+        if (0 == mNativeClass) return -1;
+        return nativeFindIndex();
+    }
+
     // Used to know whether the find dialog is open.  Affects whether
     // or not we draw the highlights for matches.
     private boolean mFindIsUp;
 
-    private int mFindHeight;
     // Keep track of the last string sent, so we can search again after an
     // orientation change or the dismissal of the soft keyboard.
     private String mLastFind;
@@ -2769,8 +2631,6 @@
         }
         clearMatches();
         setFindIsUp(false);
-        recordNewContentSize(mContentWidth, mContentHeight - mFindHeight,
-                false);
         // Now that the dialog has been removed, ensure that we scroll to a
         // location that is not beyond the end of the page.
         pinScrollTo(mScrollX, mScrollY, false, 0);
@@ -2778,16 +2638,6 @@
     }
 
     /**
-     * @hide
-     */
-    public void setFindDialogHeight(int height) {
-        if (DebugFlags.WEB_VIEW) {
-            Log.v(LOGTAG, "setFindDialogHeight height=" + height);
-        }
-        mFindHeight = height;
-    }
-
-    /**
      * Query the document to see if it contains any image references. The
      * message object will be dispatched with arg1 being set to 1 if images
      * were found and 0 if the document does not reference any images.
@@ -2810,6 +2660,11 @@
             postInvalidate();  // So we draw again
             if (oldX != mScrollX || oldY != mScrollY) {
                 onScrollChanged(mScrollX, mScrollY, oldX, oldY);
+            } else {
+                abortAnimation();
+                mPrivateHandler.removeMessages(RESUME_WEBCORE_PRIORITY);
+                WebViewCore.resumePriority();
+                WebViewCore.resumeUpdatePicture(mWebViewCore);
             }
         } else {
             super.computeScroll();
@@ -2898,6 +2753,29 @@
             }
             mPageThatNeedsToSlideTitleBarOffScreen = null;
         }
+
+        injectAccessibilityForUrl(url);
+    }
+
+    /**
+     * This method injects accessibility in the loaded document if accessibility
+     * is enabled. If JavaScript is enabled we try to inject a URL specific script.
+     * If no URL specific script is found or JavaScript is disabled we fallback to
+     * the default {@link AccessibilityInjector} implementation.
+     *
+     * @param url The URL loaded by this {@link WebView}.
+     */
+    private void injectAccessibilityForUrl(String url) {
+        if (AccessibilityManager.getInstance(mContext).isEnabled()) {
+            if (getSettings().getJavaScriptEnabled()) {
+                loadUrl(ACCESSIBILITY_SCRIPT_CHOOSER_JAVASCRIPT);
+            } else if (mAccessibilityInjector == null) {
+                mAccessibilityInjector = new AccessibilityInjector(this);
+            }
+        } else {
+            // it is possible that accessibility was turned off between reloads
+            mAccessibilityInjector = null;
+        }
     }
 
     /**
@@ -3014,7 +2892,7 @@
         } else {
             // If we don't request a layout, try to send our view size to the
             // native side to ensure that WebCore has the correct dimensions.
-            sendViewSizeZoom();
+            sendViewSizeZoom(false);
         }
     }
 
@@ -3144,7 +3022,7 @@
      *         settings.
      */
     public WebSettings getSettings() {
-        return mWebViewCore.getSettings();
+        return (mWebViewCore != null) ? mWebViewCore.getSettings() : null;
     }
 
     /**
@@ -3285,7 +3163,47 @@
         if (AUTO_REDRAW_HACK && mAutoRedraw) {
             invalidate();
         }
+        if (inEditingMode()) mWebTextView.onDrawSubstitute();
         mWebViewCore.signalRepaintDone();
+
+        // paint the highlight in the end
+        if (!mTouchHighlightRegion.isEmpty()) {
+            if (mTouchHightlightPaint == null) {
+                mTouchHightlightPaint = new Paint();
+                mTouchHightlightPaint.setColor(mHightlightColor);
+                mTouchHightlightPaint.setAntiAlias(true);
+                mTouchHightlightPaint.setPathEffect(new CornerPathEffect(
+                        TOUCH_HIGHLIGHT_ARC));
+            }
+            canvas.drawPath(mTouchHighlightRegion.getBoundaryPath(),
+                    mTouchHightlightPaint);
+        }
+        if (DEBUG_TOUCH_HIGHLIGHT) {
+            if (getSettings().getNavDump()) {
+                if ((mTouchHighlightX | mTouchHighlightY) != 0) {
+                    if (mTouchCrossHairColor == null) {
+                        mTouchCrossHairColor = new Paint();
+                        mTouchCrossHairColor.setColor(Color.RED);
+                    }
+                    canvas.drawLine(mTouchHighlightX - mNavSlop,
+                            mTouchHighlightY - mNavSlop, mTouchHighlightX
+                                    + mNavSlop + 1, mTouchHighlightY + mNavSlop
+                                    + 1, mTouchCrossHairColor);
+                    canvas.drawLine(mTouchHighlightX + mNavSlop + 1,
+                            mTouchHighlightY - mNavSlop, mTouchHighlightX
+                                    - mNavSlop,
+                            mTouchHighlightY + mNavSlop + 1,
+                            mTouchCrossHairColor);
+                }
+            }
+        }
+    }
+
+    private void removeTouchHighlight(boolean removePendingMessage) {
+        if (removePendingMessage) {
+            mWebViewCore.removeMessages(EventHub.GET_TOUCH_HIGHLIGHT_RECTS);
+        }
+        mWebViewCore.sendMessage(EventHub.REMOVE_TOUCH_HIGHLIGHT_RECTS);
     }
 
     @Override
@@ -3306,27 +3224,35 @@
             // Send the click so that the textfield is in focus
             centerKeyPressOnTextField();
             rebuildWebTextView();
+        } else {
+            clearTextEntry(true);
         }
         if (inEditingMode()) {
             return mWebTextView.performLongClick();
-        } else {
-            return super.performLongClick();
         }
+        /* if long click brings up a context menu, the super function
+         * returns true and we're done. Otherwise, nothing happened when
+         * the user clicked. */
+        if (super.performLongClick()) {
+            return true;
+        }
+        /* In the case where the application hasn't already handled the long
+         * click action, look for a word under the  click. If one is found,
+         * animate the text selection into view.
+         * FIXME: no animation code yet */
+        if (mSelectingText) return false; // long click does nothing on selection
+        int x = viewToContentX((int) mLastTouchX + mScrollX);
+        int y = viewToContentY((int) mLastTouchY + mScrollY);
+        setUpSelect();
+        if (mNativeClass != 0 && nativeWordSelection(x, y)) {
+            nativeSetExtendSelection();
+            getWebChromeClient().onSelectionStart(this);
+            return true;
+        }
+        notifySelectDialogDismissed();
+        return false;
     }
 
-    boolean inAnimateZoom() {
-        return mZoomScale != 0;
-    }
-
-    /**
-     * Need to adjust the WebTextView after a change in zoom, since mActualScale
-     * has changed.  This is especially important for password fields, which are
-     * drawn by the WebTextView, since it conveys more information than what
-     * webkit draws.  Thus we need to reposition it to show in the correct
-     * place.
-     */
-    private boolean mNeedToAdjustWebTextView;
-
     private boolean didUpdateTextViewBounds(boolean allowIntersect) {
         Rect contentBounds = nativeFocusCandidateNodeBounds();
         Rect vBox = contentToViewRect(contentBounds);
@@ -3354,25 +3280,54 @@
         }
     }
 
-    private void drawExtras(Canvas canvas, int extras, boolean animationsRunning) {
-        // If mNativeClass is 0, we should not reach here, so we do not
-        // need to check it again.
-        if (animationsRunning) {
-            canvas.setDrawFilter(mWebViewCore.mZoomFilter);
+    private void onZoomAnimationStart() {
+        // If it is in password mode, turn it off so it does not draw misplaced.
+        if (inEditingMode() && nativeFocusCandidateIsPassword()) {
+            mWebTextView.setInPassword(false);
         }
-        nativeDrawExtras(canvas, extras);
-        canvas.setDrawFilter(null);
     }
 
+    private void onZoomAnimationEnd() {
+        // adjust the edit text view if needed
+        if (inEditingMode() && didUpdateTextViewBounds(false) && nativeFocusCandidateIsPassword()) {
+            // If it is a password field, start drawing the WebTextView once
+            // again.
+            mWebTextView.setInPassword(true);
+        }
+    }
+
+    void onFixedLengthZoomAnimationStart() {
+        WebViewCore.pauseUpdatePicture(getWebViewCore());
+        onZoomAnimationStart();
+    }
+
+    void onFixedLengthZoomAnimationEnd() {
+        WebViewCore.resumeUpdatePicture(mWebViewCore);
+        onZoomAnimationEnd();
+    }
+
+    private static final int ZOOM_BITS = Paint.FILTER_BITMAP_FLAG |
+                                         Paint.DITHER_FLAG |
+                                         Paint.SUBPIXEL_TEXT_FLAG;
+    private static final int SCROLL_BITS = Paint.FILTER_BITMAP_FLAG |
+                                           Paint.DITHER_FLAG;
+
+    private final DrawFilter mZoomFilter =
+            new PaintFlagsDrawFilter(ZOOM_BITS, Paint.LINEAR_TEXT_FLAG);
+    // If we need to trade better quality for speed, set mScrollFilter to null
+    private final DrawFilter mScrollFilter =
+            new PaintFlagsDrawFilter(SCROLL_BITS, 0);
+
     private void drawCoreAndCursorRing(Canvas canvas, int color,
         boolean drawCursorRing) {
         if (mDrawHistory) {
-            canvas.scale(mActualScale, mActualScale);
+            canvas.scale(mZoomManager.getScale(), mZoomManager.getScale());
             canvas.drawPicture(mHistoryPicture);
             return;
         }
+        if (mNativeClass == 0) return;
 
-        boolean animateZoom = mZoomScale != 0;
+        boolean animateZoom = mZoomManager.isFixedLengthAnimationInProgress();
         boolean animateScroll = ((!mScroller.isFinished()
                 || mVelocityTracker != null)
                 && (mTouchMode != TOUCH_DRAG_MODE ||
@@ -3391,59 +3346,9 @@
             }
         }
         if (animateZoom) {
-            float zoomScale;
-            int interval = (int) (SystemClock.uptimeMillis() - mZoomStart);
-            if (interval < ZOOM_ANIMATION_LENGTH) {
-                float ratio = (float) interval / ZOOM_ANIMATION_LENGTH;
-                zoomScale = 1.0f / (mInvInitialZoomScale
-                        + (mInvFinalZoomScale - mInvInitialZoomScale) * ratio);
-                invalidate();
-            } else {
-                zoomScale = mZoomScale;
-                // set mZoomScale to be 0 as we have done animation
-                mZoomScale = 0;
-                WebViewCore.resumeUpdatePicture(mWebViewCore);
-                // call invalidate() again to draw with the final filters
-                invalidate();
-                if (mNeedToAdjustWebTextView) {
-                    mNeedToAdjustWebTextView = false;
-                    if (didUpdateTextViewBounds(false)
-                            && nativeFocusCandidateIsPassword()) {
-                        // If it is a password field, start drawing the
-                        // WebTextView once again.
-                        mWebTextView.setInPassword(true);
-                    }
-                }
-            }
-            // calculate the intermediate scroll position. As we need to use
-            // zoomScale, we can't use pinLocX/Y directly. Copy the logic here.
-            float scale = zoomScale * mInvInitialZoomScale;
-            int tx = Math.round(scale * (mInitialScrollX + mZoomCenterX)
-                    - mZoomCenterX);
-            tx = -pinLoc(tx, getViewWidth(), Math.round(mContentWidth
-                    * zoomScale)) + mScrollX;
-            int titleHeight = getTitleHeight();
-            int ty = Math.round(scale
-                    * (mInitialScrollY + mZoomCenterY - titleHeight)
-                    - (mZoomCenterY - titleHeight));
-            ty = -(ty <= titleHeight ? Math.max(ty, 0) : pinLoc(ty
-                    - titleHeight, getViewHeight(), Math.round(mContentHeight
-                    * zoomScale)) + titleHeight) + mScrollY;
-            canvas.translate(tx, ty);
-            canvas.scale(zoomScale, zoomScale);
-            if (inEditingMode() && !mNeedToAdjustWebTextView
-                    && mZoomScale != 0) {
-                // The WebTextView is up.  Keep track of this so we can adjust
-                // its size and placement when we finish zooming
-                mNeedToAdjustWebTextView = true;
-                // If it is in password mode, turn it off so it does not draw
-                // misplaced.
-                if (nativeFocusCandidateIsPassword()) {
-                    mWebTextView.setInPassword(false);
-                }
-            }
+            mZoomManager.animateZoom(canvas);
         } else {
-            canvas.scale(mActualScale, mActualScale);
+            canvas.scale(mZoomManager.getScale(), mZoomManager.getScale());
         }
 
         boolean UIAnimationsRunning = false;
@@ -3455,39 +3360,42 @@
             // we ask for a repaint.
             invalidate();
         }
-        mWebViewCore.drawContentPicture(canvas, color,
-                (animateZoom || mPreviewZoomOnly || UIAnimationsRunning),
-                animateScroll);
-        if (mNativeClass == 0) return;
+
         // decide which adornments to draw
         int extras = DRAW_EXTRAS_NONE;
+        if (DebugFlags.WEB_VIEW) {
+            Log.v(LOGTAG, "mFindIsUp=" + mFindIsUp
+                    + " mSelectingText=" + mSelectingText
+                    + " nativePageShouldHandleShiftAndArrows()="
+                    + nativePageShouldHandleShiftAndArrows()
+                    + " animateZoom=" + animateZoom);
+        }
         if (mFindIsUp) {
-            // When the FindDialog is up, only draw the matches if we are not in
-            // the process of scrolling them into view.
-            if (!animateScroll) {
-                extras = DRAW_EXTRAS_FIND;
-            }
-        } else if (mShiftIsPressed && !nativeFocusIsPlugin()) {
-            if (!animateZoom && !mPreviewZoomOnly) {
-                extras = DRAW_EXTRAS_SELECTION;
-                nativeSetSelectionRegion(mTouchSelection || mExtendSelection);
-                nativeSetSelectionPointer(!mTouchSelection, mInvActualScale,
-                        mSelectX, mSelectY - getTitleHeight(),
-                        mExtendSelection);
-            }
+            extras = DRAW_EXTRAS_FIND;
+        } else if (mSelectingText) {
+            extras = DRAW_EXTRAS_SELECTION;
+            nativeSetSelectionPointer(mDrawSelectionPointer,
+                    mZoomManager.getInvScale(),
+                    mSelectX, mSelectY - getTitleHeight());
         } else if (drawCursorRing) {
             extras = DRAW_EXTRAS_CURSOR_RING;
         }
-        drawExtras(canvas, extras, UIAnimationsRunning);
+        DrawFilter df = null;
+        if (mZoomManager.isZoomAnimating() || UIAnimationsRunning) {
+            df = mZoomFilter;
+        } else if (animateScroll) {
+            df = mScrollFilter;
+        }
+        canvas.setDrawFilter(df);
+        int content = nativeDraw(canvas, color, extras, true);
+        canvas.setDrawFilter(null);
+        if (content != 0) {
+            mWebViewCore.sendMessage(EventHub.SPLIT_PICTURE_SET, content, 0);
+        }
 
         if (extras == DRAW_EXTRAS_CURSOR_RING) {
             if (mTouchMode == TOUCH_SHORTPRESS_START_MODE) {
                 mTouchMode = TOUCH_SHORTPRESS_MODE;
-                HitTestResult hitTest = getHitTestResult();
-                if (hitTest == null
-                        || hitTest.mType == HitTestResult.UNKNOWN_TYPE) {
-                    mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS);
-                }
             }
         }
         if (mFocusSizeChanged) {
@@ -3512,10 +3420,14 @@
         return mDrawHistory;
     }
 
+    int getHistoryPictureWidth() {
+        return (mHistoryPicture != null) ? mHistoryPicture.getWidth() : 0;
+    }
+
     // Should only be called in UI thread
     void switchOutDrawHistory() {
         if (null == mWebViewCore) return; // CallbackProxy may trigger this
-        if (mDrawHistory && mWebViewCore.pictureReady()) {
+        if (mDrawHistory && (getProgress() == 100 || nativeHasContent())) {
             mDrawHistory = false;
             mHistoryPicture = null;
             invalidate();
@@ -3566,7 +3478,9 @@
      *  @param  end     End of selection.
      */
     /* package */ void setSelection(int start, int end) {
-        mWebViewCore.sendMessage(EventHub.SET_SELECTION, start, end);
+        if (mWebViewCore != null) {
+            mWebViewCore.sendMessage(EventHub.SET_SELECTION, start, end);
+        }
     }
 
     @Override
@@ -3585,13 +3499,10 @@
                 getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
 
         // bring it back to the default scale so that user can enter text
-        boolean zoom = mActualScale < mDefaultScale;
+        boolean zoom = mZoomManager.getScale() < mZoomManager.getDefaultScale();
         if (zoom) {
-            mInZoomOverview = false;
-            mZoomCenterX = mLastTouchX;
-            mZoomCenterY = mLastTouchY;
-            // do not change text wrap scale so that there is no reflow
-            setNewZoomScale(mDefaultScale, false, false);
+            mZoomManager.setZoomCenter(mLastTouchX, mLastTouchY);
+            mZoomManager.setZoomScale(mZoomManager.getDefaultScale(), false);
         }
         if (isTextView) {
             rebuildWebTextView();
@@ -3817,17 +3728,19 @@
         // Bubble up the key event if
         // 1. it is a system key; or
         // 2. the host application wants to handle it;
+        // 3. the accessibility injector is present and wants to handle it;
         if (event.isSystem()
-                || mCallbackProxy.uiOverrideKeyEvent(event)) {
+                || mCallbackProxy.uiOverrideKeyEvent(event)
+                || (mAccessibilityInjector != null && mAccessibilityInjector.onKeyEvent(event))) {
             return false;
         }
 
         if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT
                 || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) {
-            if (nativeFocusIsPlugin()) {
+            if (nativePageShouldHandleShiftAndArrows()) {
                 mShiftIsPressed = true;
-            } else if (!nativeCursorWantsKeyEvents() && !mShiftIsPressed) {
-                setUpSelectXY();
+            } else if (!nativeCursorWantsKeyEvents() && !mSelectingText) {
+                setUpSelect();
             }
         }
 
@@ -3844,11 +3757,11 @@
         if (keyCode >= KeyEvent.KEYCODE_DPAD_UP
                 && keyCode <= KeyEvent.KEYCODE_DPAD_RIGHT) {
             switchOutDrawHistory();
-            if (nativeFocusIsPlugin()) {
-                letPluginHandleNavKey(keyCode, event.getEventTime(), true);
+            if (nativePageShouldHandleShiftAndArrows()) {
+                letPageHandleNavKey(keyCode, event.getEventTime(), true);
                 return true;
             }
-            if (mShiftIsPressed) {
+            if (mSelectingText) {
                 int xRate = keyCode == KeyEvent.KEYCODE_DPAD_LEFT
                     ? -1 : keyCode == KeyEvent.KEYCODE_DPAD_RIGHT ? 1 : 0;
                 int yRate = keyCode == KeyEvent.KEYCODE_DPAD_UP ?
@@ -3868,7 +3781,7 @@
         if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
             switchOutDrawHistory();
             if (event.getRepeatCount() == 0) {
-                if (mShiftIsPressed && !nativeFocusIsPlugin()) {
+                if (mSelectingText) {
                     return true; // discard press if copy in progress
                 }
                 mGotCenterDown = true;
@@ -3886,10 +3799,8 @@
         if (keyCode != KeyEvent.KEYCODE_SHIFT_LEFT
                 && keyCode != KeyEvent.KEYCODE_SHIFT_RIGHT) {
             // turn off copy select if a shift-key combo is pressed
-            mExtendSelection = mShiftIsPressed = false;
-            if (mTouchMode == TOUCH_SELECT_MODE) {
-                mTouchMode = TOUCH_INIT_MODE;
-            }
+            selectionDone();
+            mShiftIsPressed = false;
         }
 
         if (getSettings().getNavDump()) {
@@ -3971,23 +3882,27 @@
         // Bubble up the key event if
         // 1. it is a system key; or
         // 2. the host application wants to handle it;
-        if (event.isSystem() || mCallbackProxy.uiOverrideKeyEvent(event)) {
+        // 3. the accessibility injector is present and wants to handle it;
+        if (event.isSystem()
+                || mCallbackProxy.uiOverrideKeyEvent(event)
+                || (mAccessibilityInjector != null && mAccessibilityInjector.onKeyEvent(event))) {
             return false;
         }
 
         if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT
                 || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) {
-            if (nativeFocusIsPlugin()) {
+            if (nativePageShouldHandleShiftAndArrows()) {
                 mShiftIsPressed = false;
-            } else if (commitCopy()) {
+            } else if (copySelection()) {
+                selectionDone();
                 return true;
             }
         }
 
         if (keyCode >= KeyEvent.KEYCODE_DPAD_UP
                 && keyCode <= KeyEvent.KEYCODE_DPAD_RIGHT) {
-            if (nativeFocusIsPlugin()) {
-                letPluginHandleNavKey(keyCode, event.getEventTime(), false);
+            if (nativePageShouldHandleShiftAndArrows()) {
+                letPageHandleNavKey(keyCode, event.getEventTime(), false);
                 return true;
             }
             // always handle the navigation keys in the UI thread
@@ -4000,11 +3915,13 @@
             mPrivateHandler.removeMessages(LONG_PRESS_CENTER);
             mGotCenterDown = false;
 
-            if (mShiftIsPressed && !nativeFocusIsPlugin()) {
+            if (mSelectingText) {
                 if (mExtendSelection) {
-                    commitCopy();
+                    copySelection();
+                    selectionDone();
                 } else {
                     mExtendSelection = true;
+                    nativeSetExtendSelection();
                     invalidate(); // draw the i-beam instead of the arrow
                 }
                 return true; // discard press if copy in progress
@@ -4049,9 +3966,18 @@
         return false;
     }
 
-    private void setUpSelectXY() {
+    /**
+     * @hide pending API council approval.
+     */
+    public void setUpSelect() {
+        if (0 == mNativeClass) return; // client isn't initialized
+        if (inFullScreenMode()) return;
+        if (mSelectingText) return;
         mExtendSelection = false;
-        mShiftIsPressed = true;
+        mSelectingText = mDrawSelectionPointer = true;
+        // don't let the picture change during text selection
+        WebViewCore.pauseUpdatePicture(mWebViewCore);
+        nativeResetSelection();
         if (nativeHasCursorNode()) {
             Rect rect = nativeCursorNodeBounds();
             mSelectX = contentToViewX(rect.left);
@@ -4071,40 +3997,82 @@
      * Do not rely on this functionality; it will be deprecated in the future.
      */
     public void emulateShiftHeld() {
-        if (0 == mNativeClass) return; // client isn't initialized
-        setUpSelectXY();
+        setUpSelect();
     }
 
-    private boolean commitCopy() {
+    /**
+     * @hide pending API council approval.
+     */
+    public void selectAll() {
+        if (0 == mNativeClass) return; // client isn't initialized
+        if (inFullScreenMode()) return;
+        if (!mSelectingText) setUpSelect();
+        nativeSelectAll();
+        mDrawSelectionPointer = false;
+        mExtendSelection = true;
+        invalidate();
+    }
+
+    /**
+     * @hide pending API council approval.
+     */
+    public boolean selectDialogIsUp() {
+        return mSelectingText;
+    }
+
+    /**
+     * @hide pending API council approval.
+     */
+    public void notifySelectDialogDismissed() {
+        mSelectingText = false;
+        WebViewCore.resumeUpdatePicture(mWebViewCore);
+    }
+
+    /**
+     * @hide pending API council approval.
+     */
+    public void selectionDone() {
+        if (mSelectingText) {
+            getWebChromeClient().onSelectionDone(this);
+            invalidate(); // redraw without selection
+            notifySelectDialogDismissed();
+        }
+    }
+
+    /**
+     * @hide pending API council approval.
+     */
+    public boolean copySelection() {
         boolean copiedSomething = false;
-        if (mExtendSelection) {
-            String selection = nativeGetSelection();
-            if (selection != "") {
-                if (DebugFlags.WEB_VIEW) {
-                    Log.v(LOGTAG, "commitCopy \"" + selection + "\"");
-                }
-                Toast.makeText(mContext
-                        , com.android.internal.R.string.text_copied
-                        , Toast.LENGTH_SHORT).show();
-                copiedSomething = true;
-                try {
-                    IClipboard clip = IClipboard.Stub.asInterface(
-                            ServiceManager.getService("clipboard"));
-                            clip.setClipboardText(selection);
-                } catch (android.os.RemoteException e) {
-                    Log.e(LOGTAG, "Clipboard failed", e);
-                }
+        String selection = getSelection();
+        if (selection != "") {
+            if (DebugFlags.WEB_VIEW) {
+                Log.v(LOGTAG, "copySelection \"" + selection + "\"");
             }
-            mExtendSelection = false;
+            Toast.makeText(mContext
+                    , com.android.internal.R.string.text_copied
+                    , Toast.LENGTH_SHORT).show();
+            copiedSomething = true;
+            try {
+                IClipboard clip = IClipboard.Stub.asInterface(
+                        ServiceManager.getService("clipboard"));
+                        clip.setClipboardText(selection);
+            } catch (android.os.RemoteException e) {
+                Log.e(LOGTAG, "Clipboard failed", e);
+            }
         }
-        mShiftIsPressed = false;
         invalidate(); // remove selection region and pointer
-        if (mTouchMode == TOUCH_SELECT_MODE) {
-            mTouchMode = TOUCH_INIT_MODE;
-        }
         return copiedSomething;
     }
 
+    /**
+     * @hide pending API council approval.
+     */
+    public String getSelection() {
+        if (mNativeClass == 0) return "";
+        return nativeGetSelection();
+    }
+
     @Override
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
@@ -4114,11 +4082,21 @@
     @Override
     protected void onDetachedFromWindow() {
         clearTextEntry(false);
-        dismissZoomControl();
+        mZoomManager.dismissZoomPicker();
         if (hasWindowFocus()) setActive(false);
         super.onDetachedFromWindow();
     }
 
+    @Override
+    protected void onVisibilityChanged(View changedView, int visibility) {
+        super.onVisibilityChanged(changedView, visibility);
+        // The zoomManager may be null if the webview is created from XML that
+        // specifies the view's visibility param as not visible (see http://b/2794841)
+        if (visibility != View.VISIBLE && mZoomManager != null) {
+            mZoomManager.dismissZoomPicker();
+        }
+    }
+
     /**
      * @deprecated WebView no longer needs to implement
      * ViewGroup.OnHierarchyChangeListener.  This method does nothing now.
@@ -4163,17 +4141,14 @@
                 // false for the first parameter
             }
         } else {
-            if (mWebViewCore != null && getSettings().getBuiltInZoomControls()
-                    && (mZoomButtonsController == null ||
-                            !mZoomButtonsController.isVisible())) {
+            if (!mZoomManager.isZoomPickerVisible()) {
                 /*
-                 * The zoom controls come in their own window, so our window
-                 * loses focus. Our policy is to not draw the cursor ring if
-                 * our window is not focused, but this is an exception since
+                 * The external zoom controls come in their own window, so our
+                 * window loses focus. Our policy is to not draw the cursor ring
+                 * if our window is not focused, but this is an exception since
                  * the user can still navigate the web page with the zoom
                  * controls showing.
                  */
-                // If our window has lost focus, stop drawing the cursor ring
                 mDrawCursorRing = false;
             }
             mGotKeyDown = false;
@@ -4262,81 +4237,24 @@
             // system won't call onSizeChanged if the dimension is not changed.
             // In this case, we need to call sendViewSizeZoom() explicitly to
             // notify the WebKit about the new dimensions.
-            sendViewSizeZoom();
+            sendViewSizeZoom(false);
         }
         return changed;
     }
 
-    private static class PostScale implements Runnable {
-        final WebView mWebView;
-        final boolean mUpdateTextWrap;
-
-        public PostScale(WebView webView, boolean updateTextWrap) {
-            mWebView = webView;
-            mUpdateTextWrap = updateTextWrap;
-        }
-
-        public void run() {
-            if (mWebView.mWebViewCore != null) {
-                // we always force, in case our height changed, in which case we
-                // still want to send the notification over to webkit.
-                mWebView.setNewZoomScale(mWebView.mActualScale,
-                        mUpdateTextWrap, true);
-                // update the zoom buttons as the scale can be changed
-                if (mWebView.getSettings().getBuiltInZoomControls()) {
-                    mWebView.updateZoomButtonsEnabled();
-                }
-            }
-        }
-    }
-
     @Override
     protected void onSizeChanged(int w, int h, int ow, int oh) {
         super.onSizeChanged(w, h, ow, oh);
-        // Center zooming to the center of the screen.
-        if (mZoomScale == 0) { // unless we're already zooming
-            // To anchor at top left corner.
-            mZoomCenterX = 0;
-            mZoomCenterY = getVisibleTitleHeight();
-            mAnchorX = viewToContentX((int) mZoomCenterX + mScrollX);
-            mAnchorY = viewToContentY((int) mZoomCenterY + mScrollY);
-        }
 
         // adjust the max viewport width depending on the view dimensions. This
         // is to ensure the scaling is not going insane. So do not shrink it if
         // the view size is temporarily smaller, e.g. when soft keyboard is up.
-        int newMaxViewportWidth = (int) (Math.max(w, h) / DEFAULT_MIN_ZOOM_SCALE);
+        int newMaxViewportWidth = (int) (Math.max(w, h) / mZoomManager.getDefaultMinZoomScale());
         if (newMaxViewportWidth > sMaxViewportWidth) {
             sMaxViewportWidth = newMaxViewportWidth;
         }
 
-        // update mMinZoomScale if the minimum zoom scale is not fixed
-        if (!mMinZoomScaleFixed) {
-            // when change from narrow screen to wide screen, the new viewWidth
-            // can be wider than the old content width. We limit the minimum
-            // scale to 1.0f. The proper minimum scale will be calculated when
-            // the new picture shows up.
-            mMinZoomScale = Math.min(1.0f, (float) getViewWidth()
-                    / (mDrawHistory ? mHistoryPicture.getWidth()
-                            : mZoomOverviewWidth));
-            if (mInitialScaleInPercent > 0) {
-                // limit the minZoomScale to the initialScale if it is set
-                float initialScale = mInitialScaleInPercent / 100.0f;
-                if (mMinZoomScale > initialScale) {
-                    mMinZoomScale = initialScale;
-                }
-            }
-        }
-
-        dismissZoomControl();
-
-        // onSizeChanged() is called during WebView layout. And any
-        // requestLayout() is blocked during layout. As setNewZoomScale() will
-        // call its child View to reposition itself through ViewManager's
-        // scaleAll(), we need to post a Runnable to ensure requestLayout().
-        // <b/>
-        // only update the text wrap scale if width changed.
-        post(new PostScale(this, w != ow));
+        mZoomManager.onSizeChanged(w, h, ow, oh);
     }
 
     @Override
@@ -4347,7 +4265,7 @@
         // as getVisibleTitleHeight.
         int titleHeight = getTitleHeight();
         if (Math.max(titleHeight - t, 0) != Math.max(titleHeight - oldt, 0)) {
-            sendViewSizeZoom();
+            sendViewSizeZoom(false);
         }
     }
 
@@ -4355,9 +4273,11 @@
     public boolean dispatchKeyEvent(KeyEvent event) {
         boolean dispatch = true;
 
-        // Textfields and plugins need to receive the shift up key even if
-        // another key was released while the shift key was held down.
-        if (!inEditingMode() && (mNativeClass == 0 || !nativeFocusIsPlugin())) {
+        // Textfields, plugins, and contentEditable nodes need to receive the
+        // shift up key even if another key was released while the shift key
+        // was held down.
+        if (!inEditingMode() && (mNativeClass == 0
+                || !nativePageShouldHandleShiftAndArrows())) {
             if (event.getAction() == KeyEvent.ACTION_DOWN) {
                 mGotKeyDown = true;
             } else {
@@ -4591,81 +4511,6 @@
     private DragTracker mDragTracker;
     private DragTrackerHandler mDragTrackerHandler;
 
-    private class ScaleDetectorListener implements
-            ScaleGestureDetector.OnScaleGestureListener {
-
-        public boolean onScaleBegin(ScaleGestureDetector detector) {
-            // cancel the single touch handling
-            cancelTouch();
-            dismissZoomControl();
-            // reset the zoom overview mode so that the page won't auto grow
-            mInZoomOverview = false;
-            // If it is in password mode, turn it off so it does not draw
-            // misplaced.
-            if (inEditingMode() && nativeFocusCandidateIsPassword()) {
-                mWebTextView.setInPassword(false);
-            }
-
-            mViewManager.startZoom();
-
-            return true;
-        }
-
-        public void onScaleEnd(ScaleGestureDetector detector) {
-            if (mPreviewZoomOnly) {
-                mPreviewZoomOnly = false;
-                mAnchorX = viewToContentX((int) mZoomCenterX + mScrollX);
-                mAnchorY = viewToContentY((int) mZoomCenterY + mScrollY);
-                // don't reflow when zoom in; when zoom out, do reflow if the
-                // new scale is almost minimum scale;
-                boolean reflowNow = (mActualScale - mMinZoomScale
-                        <= MINIMUM_SCALE_INCREMENT)
-                        || ((mActualScale <= 0.8 * mTextWrapScale));
-                // force zoom after mPreviewZoomOnly is set to false so that the
-                // new view size will be passed to the WebKit
-                setNewZoomScale(mActualScale, reflowNow, true);
-                // call invalidate() to draw without zoom filter
-                invalidate();
-            }
-            // adjust the edit text view if needed
-            if (inEditingMode() && didUpdateTextViewBounds(false)
-                    && nativeFocusCandidateIsPassword()) {
-                // If it is a password field, start drawing the
-                // WebTextView once again.
-                mWebTextView.setInPassword(true);
-            }
-            // start a drag, TOUCH_PINCH_DRAG, can't use TOUCH_INIT_MODE as it
-            // may trigger the unwanted click, can't use TOUCH_DRAG_MODE as it
-            // may trigger the unwanted fling.
-            mTouchMode = TOUCH_PINCH_DRAG;
-            mConfirmMove = true;
-            startTouch(detector.getFocusX(), detector.getFocusY(),
-                    mLastTouchTime);
-
-            mViewManager.endZoom();
-        }
-
-        public boolean onScale(ScaleGestureDetector detector) {
-            float scale = (float) (Math.round(detector.getScaleFactor()
-                    * mActualScale * 100) / 100.0);
-            if (Math.abs(scale - mActualScale) >= MINIMUM_SCALE_INCREMENT) {
-                mPreviewZoomOnly = true;
-                // limit the scale change per step
-                if (scale > mActualScale) {
-                    scale = Math.min(scale, mActualScale * 1.25f);
-                } else {
-                    scale = Math.max(scale, mActualScale * 0.8f);
-                }
-                mZoomCenterX = detector.getFocusX();
-                mZoomCenterY = detector.getFocusY();
-                setNewZoomScale(scale, false, false);
-                invalidate();
-                return true;
-            }
-            return false;
-        }
-    }
-
     private boolean hitFocusedPlugin(int contentX, int contentY) {
         if (DebugFlags.WEB_VIEW) {
             Log.v(LOGTAG, "nativeFocusIsPlugin()=" + nativeFocusIsPlugin());
@@ -4679,7 +4524,7 @@
 
     private boolean shouldForwardTouchEvent() {
         return mFullScreenHolder != null || (mForwardTouchEvents
-                && mTouchMode != TOUCH_SELECT_MODE
+                && !mSelectingText
                 && mPreventDefault != PREVENT_DEFAULT_IGNORE);
     }
 
@@ -4687,6 +4532,41 @@
         return mFullScreenHolder != null;
     }
 
+    void onPinchToZoomAnimationStart() {
+        // cancel the single touch handling
+        cancelTouch();
+        onZoomAnimationStart();
+    }
+
+    void onPinchToZoomAnimationEnd(ScaleGestureDetector detector) {
+        onZoomAnimationEnd();
+        // start a drag, TOUCH_PINCH_DRAG, can't use TOUCH_INIT_MODE as
+        // it may trigger the unwanted click, can't use TOUCH_DRAG_MODE
+        // as it may trigger the unwanted fling.
+        mTouchMode = TOUCH_PINCH_DRAG;
+        mConfirmMove = true;
+        startTouch(detector.getFocusX(), detector.getFocusY(), mLastTouchTime);
+    }
+
+    private void startScrollingLayer(float gestureX, float gestureY) {
+        if (mTouchMode != TOUCH_DRAG_LAYER_MODE) {
+            int contentX = viewToContentX((int) gestureX + mScrollX);
+            int contentY = viewToContentY((int) gestureY + mScrollY);
+            mScrollingLayer = nativeScrollableLayer(contentX, contentY);
+            if (mScrollingLayer != 0) {
+                mTouchMode = TOUCH_DRAG_LAYER_MODE;
+            }
+        }
+    }
+
+    // 1/(density * density) used to compute the distance between points.
+    // Computed in init().
+    private float DRAG_LAYER_INVERSE_DENSITY_SQUARED;
+
+    // The distance between two points reported in onTouchEvent scaled by the
+    // density of the screen.
+    private static final int DRAG_LAYER_FINGER_DISTANCE = 20000;
+
     @Override
     public boolean onTouchEvent(MotionEvent ev) {
         if (mNativeClass == 0 || !isClickable() || !isLongClickable()) {
@@ -4698,55 +4578,104 @@
                     + mTouchMode);
         }
 
-        int action;
-        float x, y;
+        int action = ev.getAction();
+        float x = ev.getX();
+        float y = ev.getY();
         long eventTime = ev.getEventTime();
 
+        final ScaleGestureDetector detector =
+                mZoomManager.getMultiTouchGestureDetector();
+        boolean skipScaleGesture = false;
+        // Set to the mid-point of a two-finger gesture used to detect if the
+        // user has touched a layer.
+        float gestureX = x;
+        float gestureY = y;
+        if (detector == null || !detector.isInProgress()) {
+            // The gesture for scrolling a layer is two fingers close together.
+            // FIXME: we may consider giving WebKit an option to handle
+            // multi-touch events later.
+            if (ev.getPointerCount() > 1) {
+                float dx = ev.getX(1) - ev.getX(0);
+                float dy = ev.getY(1) - ev.getY(0);
+                float dist = (dx * dx + dy * dy) *
+                        DRAG_LAYER_INVERSE_DENSITY_SQUARED;
+                // Use the approximate center to determine if the gesture is in
+                // a layer.
+                gestureX = ev.getX(0) + (dx * .5f);
+                gestureY = ev.getY(0) + (dy * .5f);
+                // Now use a consistent point for tracking movement.
+                if (ev.getX(0) < ev.getX(1)) {
+                    x = ev.getX(0);
+                    y = ev.getY(0);
+                } else {
+                    x = ev.getX(1);
+                    y = ev.getY(1);
+                }
+                action = ev.getActionMasked();
+                if (dist < DRAG_LAYER_FINGER_DISTANCE) {
+                    skipScaleGesture = true;
+                } else if (mTouchMode == TOUCH_DRAG_LAYER_MODE) {
+                    // Fingers moved too far apart while dragging, the user
+                    // might be trying to zoom.
+                    mTouchMode = TOUCH_INIT_MODE;
+                }
+            }
+        }
+
         // FIXME: we may consider to give WebKit an option to handle multi-touch
         // events later.
-        if (mSupportMultiTouch && ev.getPointerCount() > 1) {
-            if (mMinZoomScale < mMaxZoomScale) {
-                mScaleDetector.onTouchEvent(ev);
-                if (mScaleDetector.isInProgress()) {
-                    mLastTouchTime = eventTime;
-                    return true;
-                }
-                x = mScaleDetector.getFocusX();
-                y = mScaleDetector.getFocusY();
-                action = ev.getAction() & MotionEvent.ACTION_MASK;
-                if (action == MotionEvent.ACTION_POINTER_DOWN) {
-                    cancelTouch();
-                    action = MotionEvent.ACTION_DOWN;
-                } else if (action == MotionEvent.ACTION_POINTER_UP) {
-                    // set mLastTouchX/Y to the remaining point
-                    mLastTouchX = x;
-                    mLastTouchY = y;
-                } else if (action == MotionEvent.ACTION_MOVE) {
-                    // negative x or y indicate it is on the edge, skip it.
-                    if (x < 0 || y < 0) {
-                        return true;
-                    }
-                }
-            } else {
-                // if the page disallow zoom, skip multi-pointer action
+        if (mZoomManager.supportsMultiTouchZoom() && ev.getPointerCount() > 1 &&
+                mTouchMode != TOUCH_DRAG_LAYER_MODE && !skipScaleGesture) {
+
+            // if the page disallows zoom, skip multi-pointer action
+            if (mZoomManager.isZoomScaleFixed()) {
                 return true;
             }
-        } else {
-            action = ev.getAction();
-            x = ev.getX();
-            y = ev.getY();
+
+            if (!detector.isInProgress() &&
+                    ev.getActionMasked() != MotionEvent.ACTION_POINTER_DOWN) {
+                // Insert a fake pointer down event in order to start
+                // the zoom scale detector.
+                MotionEvent temp = MotionEvent.obtain(ev);
+                // Clear the original event and set it to
+                // ACTION_POINTER_DOWN.
+                temp.setAction(temp.getAction() &
+                        ~MotionEvent.ACTION_MASK |
+                        MotionEvent.ACTION_POINTER_DOWN);
+                detector.onTouchEvent(temp);
+            }
+
+            detector.onTouchEvent(ev);
+
+            if (detector.isInProgress()) {
+                mLastTouchTime = eventTime;
+                return true;
+            }
+
+            x = detector.getFocusX();
+            y = detector.getFocusY();
+            action = ev.getAction() & MotionEvent.ACTION_MASK;
+            if (action == MotionEvent.ACTION_POINTER_DOWN) {
+                cancelTouch();
+                action = MotionEvent.ACTION_DOWN;
+            } else if (action == MotionEvent.ACTION_POINTER_UP) {
+                // set mLastTouchX/Y to the remaining point
+                mLastTouchX = x;
+                mLastTouchY = y;
+            } else if (action == MotionEvent.ACTION_MOVE) {
+                // negative x or y indicate it is on the edge, skip it.
+                if (x < 0 || y < 0) {
+                    return true;
+                }
+            }
         }
 
         // Due to the touch screen edge effect, a touch closer to the edge
         // always snapped to the edge. As getViewWidth() can be different from
         // getWidth() due to the scrollbar, adjusting the point to match
         // getViewWidth(). Same applied to the height.
-        if (x > getViewWidth() - 1) {
-            x = getViewWidth() - 1;
-        }
-        if (y > getViewHeightWithTitle() - 1) {
-            y = getViewHeightWithTitle() - 1;
-        }
+        x = Math.min(x, getViewWidth() - 1);
+        y = Math.min(y, getViewHeightWithTitle() - 1);
 
         float fDeltaX = mLastTouchX - x;
         float fDeltaY = mLastTouchY - y;
@@ -4767,18 +4696,11 @@
                     mTouchMode = TOUCH_DRAG_START_MODE;
                     mConfirmMove = true;
                     mPrivateHandler.removeMessages(RESUME_WEBCORE_PRIORITY);
-                } else if (!inFullScreenMode() && mShiftIsPressed) {
-                    mSelectX = mScrollX + (int) x;
-                    mSelectY = mScrollY + (int) y;
-                    mTouchMode = TOUCH_SELECT_MODE;
-                    if (DebugFlags.WEB_VIEW) {
-                        Log.v(LOGTAG, "select=" + mSelectX + "," + mSelectY);
-                    }
-                    nativeMoveSelection(contentX, contentY, false);
-                    mTouchSelection = mExtendSelection = true;
-                    invalidate(); // draw the i-beam instead of the arrow
                 } else if (mPrivateHandler.hasMessages(RELEASE_SINGLE_TAP)) {
                     mPrivateHandler.removeMessages(RELEASE_SINGLE_TAP);
+                    if (getSettings().supportTouchOnly()) {
+                        removeTouchHighlight(true);
+                    }
                     if (deltaX * deltaX + deltaY * deltaY < mDoubleTapSlopSquare) {
                         mTouchMode = TOUCH_DOUBLE_TAP_MODE;
                     } else {
@@ -4790,17 +4712,45 @@
                                 contentX, contentY) : false;
                     }
                 } else { // the normal case
-                    mPreviewZoomOnly = false;
                     mTouchMode = TOUCH_INIT_MODE;
                     mDeferTouchProcess = (!inFullScreenMode()
                             && mForwardTouchEvents) ? hitFocusedPlugin(
                             contentX, contentY) : false;
                     mWebViewCore.sendMessage(
                             EventHub.UPDATE_FRAME_CACHE_IF_LOADING);
+                    if (getSettings().supportTouchOnly()) {
+                        TouchHighlightData data = new TouchHighlightData();
+                        data.mX = contentX;
+                        data.mY = contentY;
+                        data.mSlop = viewToContentDimension(mNavSlop);
+                        mWebViewCore.sendMessageDelayed(
+                                EventHub.GET_TOUCH_HIGHLIGHT_RECTS, data,
+                                ViewConfiguration.getTapTimeout());
+                        if (DEBUG_TOUCH_HIGHLIGHT) {
+                            if (getSettings().getNavDump()) {
+                                mTouchHighlightX = (int) x + mScrollX;
+                                mTouchHighlightY = (int) y + mScrollY;
+                                mPrivateHandler.postDelayed(new Runnable() {
+                                    public void run() {
+                                        mTouchHighlightX = mTouchHighlightY = 0;
+                                        invalidate();
+                                    }
+                                }, TOUCH_HIGHLIGHT_ELAPSE_TIME);
+                            }
+                        }
+                    }
                     if (mLogEvent && eventTime - mLastTouchUpTime < 1000) {
                         EventLog.writeEvent(EventLogTags.BROWSER_DOUBLE_TAP_DURATION,
                                 (eventTime - mLastTouchUpTime), eventTime);
                     }
+                    if (mSelectingText) {
+                        mDrawSelectionPointer = false;
+                        mSelectionStarted = nativeStartSelection(contentX, contentY);
+                        if (DebugFlags.WEB_VIEW) {
+                            Log.v(LOGTAG, "select=" + contentX + "," + contentY);
+                        }
+                        invalidate();
+                    }
                 }
                 // Trigger the link
                 if (mTouchMode == TOUCH_INIT_MODE
@@ -4824,17 +4774,15 @@
                         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.removeMessages(PREVENT_DEFAULT_TIMEOUT);
                             mPrivateHandler.sendMessageDelayed(mPrivateHandler
                                     .obtainMessage(PREVENT_DEFAULT_TIMEOUT,
                                             action, 0), TAP_TIMEOUT);
@@ -4855,24 +4803,24 @@
                     if (mTouchMode == TOUCH_DOUBLE_TAP_MODE) {
                         mTouchMode = TOUCH_INIT_MODE;
                     }
+                    if (getSettings().supportTouchOnly()) {
+                        removeTouchHighlight(true);
+                    }
                 }
                 // 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,
@@ -4892,20 +4840,22 @@
                             + " mTouchMode = " + mTouchMode);
                 }
                 mVelocityTracker.addMovement(ev);
-                if (mTouchMode != TOUCH_DRAG_MODE) {
-                    if (mTouchMode == TOUCH_SELECT_MODE) {
-                        mSelectX = mScrollX + (int) x;
-                        mSelectY = mScrollY + (int) y;
-                        if (DebugFlags.WEB_VIEW) {
-                            Log.v(LOGTAG, "xtend=" + mSelectX + "," + mSelectY);
-                        }
-                        nativeMoveSelection(contentX, contentY, true);
-                        invalidate();
-                        break;
+                if (mSelectingText && mSelectionStarted) {
+                    if (DebugFlags.WEB_VIEW) {
+                        Log.v(LOGTAG, "extend=" + contentX + "," + contentY);
                     }
+                    nativeExtendSelection(contentX, contentY);
+                    invalidate();
+                    break;
+                }
+
+                if (mTouchMode != TOUCH_DRAG_MODE &&
+                        mTouchMode != TOUCH_DRAG_LAYER_MODE) {
+
                     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
@@ -4932,6 +4882,9 @@
                     deltaX = 0;
                     deltaY = 0;
 
+                    if (skipScaleGesture) {
+                        startScrollingLayer(gestureX, gestureY);
+                    }
                     startDrag();
                 }
 
@@ -4940,17 +4893,19 @@
                 }
 
                 // do pan
-                int newScrollX = pinLocX(mScrollX + deltaX);
-                int newDeltaX = newScrollX - mScrollX;
-                if (deltaX != newDeltaX) {
-                    deltaX = newDeltaX;
-                    fDeltaX = (float) newDeltaX;
-                }
-                int newScrollY = pinLocY(mScrollY + deltaY);
-                int newDeltaY = newScrollY - mScrollY;
-                if (deltaY != newDeltaY) {
-                    deltaY = newDeltaY;
-                    fDeltaY = (float) newDeltaY;
+                if (mTouchMode != TOUCH_DRAG_LAYER_MODE) {
+                    int newScrollX = pinLocX(mScrollX + deltaX);
+                    int newDeltaX = newScrollX - mScrollX;
+                    if (deltaX != newDeltaX) {
+                        deltaX = newDeltaX;
+                        fDeltaX = (float) newDeltaX;
+                    }
+                    int newScrollY = pinLocY(mScrollY + deltaY);
+                    int newDeltaY = newScrollY - mScrollY;
+                    if (deltaY != newDeltaY) {
+                        deltaY = newDeltaY;
+                        fDeltaY = (float) newDeltaY;
+                    }
                 }
                 boolean done = false;
                 boolean keepScrollBarsVisible = false;
@@ -5018,7 +4973,9 @@
 
                 doDrag(deltaX, deltaY);
 
-                if (keepScrollBarsVisible) {
+                // Turn off scrollbars when dragging a layer.
+                if (keepScrollBarsVisible &&
+                        mTouchMode != TOUCH_DRAG_LAYER_MODE) {
                     if (mHeldMotionless != MOTIONLESS_TRUE) {
                         mHeldMotionless = MOTIONLESS_TRUE;
                         invalidate();
@@ -5033,6 +4990,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();
@@ -5041,10 +4999,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;
@@ -5059,20 +5013,12 @@
                             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();
+                            mZoomManager.handleDoubleTap(mLastTouchX, mLastTouchY);
                             mTouchMode = TOUCH_DONE_MODE;
                         }
                         break;
-                    case TOUCH_SELECT_MODE:
-                        commitCopy();
-                        mTouchSelection = false;
-                        break;
                     case TOUCH_INIT_MODE: // tap
                     case TOUCH_SHORTPRESS_START_MODE:
                     case TOUCH_SHORTPRESS_MODE:
@@ -5084,9 +5030,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();
@@ -5103,7 +5057,17 @@
                                 break;
                             }
                         } else {
-                            if (mTouchMode == TOUCH_INIT_MODE) {
+                            if (mSelectingText) {
+                                // tapping on selection or controls does nothing
+                                if (!nativeHitSelection(contentX, contentY)) {
+                                    selectionDone();
+                                }
+                                break;
+                            }
+                            // only trigger double tap if the WebView is
+                            // scalable
+                            if (mTouchMode == TOUCH_INIT_MODE
+                                    && (canZoomIn() || canZoomOut())) {
                                 mPrivateHandler.sendEmptyMessageDelayed(
                                         RELEASE_SINGLE_TAP, ViewConfiguration
                                                 .getDoubleTapTimeout());
@@ -5138,6 +5102,7 @@
                         invalidate();
                         // fall through
                     case TOUCH_DRAG_START_MODE:
+                    case TOUCH_DRAG_LAYER_MODE:
                         // TOUCH_DRAG_START_MODE should not happen for the real
                         // device as we almost certain will get a MOVE. But this
                         // is possible on emulator.
@@ -5194,40 +5159,26 @@
         if (!mDragFromTextInput) {
             nativeHideCursor();
         }
-        WebSettings settings = getSettings();
-        if (settings.supportZoom()
-                && settings.getBuiltInZoomControls()
-                && !getZoomButtonsController().isVisible()
-                && mMinZoomScale < mMaxZoomScale
-                && (mHorizontalScrollBarMode != SCROLLBAR_ALWAYSOFF
-                        || mVerticalScrollBarMode != SCROLLBAR_ALWAYSOFF)) {
-            mZoomButtonsController.setVisible(true);
-            int count = settings.getDoubleTapToastCount();
-            if (mInZoomOverview && count > 0) {
-                settings.setDoubleTapToastCount(--count);
-                Toast.makeText(mContext,
-                        com.android.internal.R.string.double_tap_toast,
-                        Toast.LENGTH_LONG).show();
-            }
+
+        if (mHorizontalScrollBarMode != SCROLLBAR_ALWAYSOFF
+                || mVerticalScrollBarMode != SCROLLBAR_ALWAYSOFF) {
+            mZoomManager.invokeZoomPicker();
         }
     }
 
     private void doDrag(int deltaX, int deltaY) {
         if ((deltaX | deltaY) != 0) {
+            if (mTouchMode == TOUCH_DRAG_LAYER_MODE) {
+                deltaX = viewToContentDimension(deltaX);
+                deltaY = viewToContentDimension(deltaY);
+                if (nativeScrollLayer(mScrollingLayer, deltaX, deltaY)) {
+                    invalidate();
+                }
+                return;
+            }
             scrollBy(deltaX, deltaY);
         }
-        if (!getSettings().getBuiltInZoomControls()) {
-            boolean showPlusMinus = mMinZoomScale < mMaxZoomScale;
-            if (mZoomControls != null && showPlusMinus) {
-                if (mZoomControls.getVisibility() == View.VISIBLE) {
-                    mPrivateHandler.removeCallbacks(mZoomControlRunnable);
-                } else {
-                    mZoomControls.show(showPlusMinus, false);
-                }
-                mPrivateHandler.postDelayed(mZoomControlRunnable,
-                        ZOOM_CONTROLS_TIMEOUT);
-            }
-        }
+        mZoomManager.keepZoomPickerVisible();
     }
 
     private void stopTouch() {
@@ -5254,7 +5205,8 @@
             mVelocityTracker.recycle();
             mVelocityTracker = null;
         }
-        if (mTouchMode == TOUCH_DRAG_MODE) {
+        if (mTouchMode == TOUCH_DRAG_MODE ||
+                mTouchMode == TOUCH_DRAG_LAYER_MODE) {
             WebViewCore.resumePriority();
             WebViewCore.resumeUpdatePicture(mWebViewCore);
         }
@@ -5262,6 +5214,9 @@
         mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS);
         mPrivateHandler.removeMessages(DRAG_HELD_MOTIONLESS);
         mPrivateHandler.removeMessages(AWAKEN_SCROLL_BARS);
+        if (getSettings().supportTouchOnly()) {
+            removeTouchHighlight(true);
+        }
         mHeldMotionless = MOTIONLESS_TRUE;
         mTouchMode = TOUCH_DONE_MODE;
         nativeHideCursor();
@@ -5273,8 +5228,10 @@
     private float mTrackballRemainsY = 0.0f;
     private int mTrackballXMove = 0;
     private int mTrackballYMove = 0;
+    private boolean mSelectingText = false;
+    private boolean mSelectionStarted = false;
     private boolean mExtendSelection = false;
-    private boolean mTouchSelection = false;
+    private boolean mDrawSelectionPointer = false;
     private static final int TRACKBALL_KEY_TIMEOUT = 1000;
     private static final int TRACKBALL_TIMEOUT = 200;
     private static final int TRACKBALL_WAIT = 100;
@@ -5313,10 +5270,8 @@
             if (ev.getY() < 0) pageUp(true);
             return true;
         }
-        boolean shiftPressed = mShiftIsPressed && (mNativeClass == 0
-                || !nativeFocusIsPlugin());
         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
-            if (shiftPressed) {
+            if (mSelectingText) {
                 return true; // discard press if copy in progress
             }
             mTrackballDown = true;
@@ -5341,11 +5296,13 @@
             mPrivateHandler.removeMessages(LONG_PRESS_CENTER);
             mTrackballDown = false;
             mTrackballUpTime = time;
-            if (shiftPressed) {
+            if (mSelectingText) {
                 if (mExtendSelection) {
-                    commitCopy();
+                    copySelection();
+                    selectionDone();
                 } else {
                     mExtendSelection = true;
+                    nativeSetExtendSelection();
                     invalidate(); // draw the i-beam instead of the arrow
                 }
                 return true; // discard press if copy in progress
@@ -5412,8 +5369,7 @@
                     + " yRate=" + yRate
                     );
         }
-        nativeMoveSelection(viewToContentX(mSelectX),
-                viewToContentY(mSelectY), mExtendSelection);
+        nativeMoveSelection(viewToContentX(mSelectX), viewToContentY(mSelectY));
         int scrollX = mSelectX < mScrollX ? -SELECT_CURSOR_OFFSET
                 : mSelectX > maxX - SELECT_CURSOR_OFFSET ? SELECT_CURSOR_OFFSET
                 : 0;
@@ -5479,7 +5435,16 @@
         float yRate = mTrackballRemainsY * 1000 / elapsed;
         int viewWidth = getViewWidth();
         int viewHeight = getViewHeight();
-        if (mShiftIsPressed && (mNativeClass == 0 || !nativeFocusIsPlugin())) {
+        if (mSelectingText) {
+            if (!mDrawSelectionPointer) {
+                // The last selection was made by touch, disabling drawing the
+                // selection pointer. Allow the trackball to adjust the
+                // position of the touch control.
+                mSelectX = contentToViewX(nativeSelectionX());
+                mSelectY = contentToViewY(nativeSelectionY());
+                mDrawSelectionPointer = mExtendSelection = true;
+                nativeSetExtendSelection();
+            }
             moveSelection(scaleTrackballX(xRate, viewWidth),
                     scaleTrackballY(yRate, viewHeight));
             mTrackballRemainsX = mTrackballRemainsY = 0;
@@ -5517,11 +5482,11 @@
                         + " mTrackballRemainsX=" + mTrackballRemainsX
                         + " mTrackballRemainsY=" + mTrackballRemainsY);
             }
-            if (mNativeClass != 0 && nativeFocusIsPlugin()) {
+            if (mNativeClass != 0 && nativePageShouldHandleShiftAndArrows()) {
                 for (int i = 0; i < count; i++) {
-                    letPluginHandleNavKey(selectKeyCode, time, true);
+                    letPageHandleNavKey(selectKeyCode, time, true);
                 }
-                letPluginHandleNavKey(selectKeyCode, time, false);
+                letPageHandleNavKey(selectKeyCode, time, false);
             } else if (navHandledKey(selectKeyCode, count, false, time)) {
                 playSoundEffect(keyCodeToSoundsEffect(selectKeyCode));
             }
@@ -5560,6 +5525,19 @@
                 - getViewHeightWithTitle(), 0);
     }
 
+    boolean updateScrollCoordinates(int x, int y) {
+        int oldX = mScrollX;
+        int oldY = mScrollY;
+        mScrollX = x;
+        mScrollY = y;
+        if (oldX != mScrollX || oldY != mScrollY) {
+            onScrollChanged(mScrollX, mScrollY, oldX, oldY);
+            return true;
+        } else {
+            return false;
+        }
+    }
+
     public void flingScroll(int vx, int vy) {
         mScroller.fling(mScrollX, mScrollY, vx, vy, 0, computeMaxScrollX(), 0,
                 computeMaxScrollY());
@@ -5595,13 +5573,16 @@
             return;
         }
         float currentVelocity = mScroller.getCurrVelocity();
-        if (mLastVelocity > 0 && currentVelocity > 0) {
+        float velocity = (float) Math.hypot(vx, vy);
+        if (mLastVelocity > 0 && currentVelocity > 0 && velocity
+                > mLastVelocity * MINIMUM_VELOCITY_RATIO_FOR_ACCELERATION) {
             float deltaR = (float) (Math.abs(Math.atan2(mLastVelY, mLastVelX)
                     - Math.atan2(vy, vx)));
             final float circle = (float) (Math.PI) * 2.0f;
             if (deltaR > circle * 0.9f || deltaR < circle * 0.1f) {
                 vx += currentVelocity * mLastVelX / mLastVelocity;
                 vy += currentVelocity * mLastVelY / mLastVelocity;
+                velocity = (float) Math.hypot(vx, vy);
                 if (DebugFlags.WEB_VIEW) {
                     Log.v(LOGTAG, "doFling vx= " + vx + " vy=" + vy);
                 }
@@ -5617,45 +5598,15 @@
         }
         mLastVelX = vx;
         mLastVelY = vy;
-        mLastVelocity = (float) Math.hypot(vx, vy);
+        mLastVelocity = velocity;
 
         mScroller.fling(mScrollX, mScrollY, -vx, -vy, 0, maxX, 0, maxY);
-        // TODO: duration is calculated based on velocity, if the range is
-        // small, the animation will stop before duration is up. We may
-        // want to calculate how long the animation is going to run to precisely
-        // resume the webcore update.
         final int time = mScroller.getDuration();
         mPrivateHandler.sendEmptyMessageDelayed(RESUME_WEBCORE_PRIORITY, time);
         awakenScrollBars(time);
         invalidate();
     }
 
-    private boolean zoomWithPreview(float scale, boolean updateTextWrapScale) {
-        float oldScale = mActualScale;
-        mInitialScrollX = mScrollX;
-        mInitialScrollY = mScrollY;
-
-        // snap to DEFAULT_SCALE if it is close
-        if (Math.abs(scale - mDefaultScale) < MINIMUM_SCALE_INCREMENT) {
-            scale = mDefaultScale;
-        }
-
-        setNewZoomScale(scale, updateTextWrapScale, false);
-
-        if (oldScale != mActualScale) {
-            // use mZoomPickerScale to see zoom preview first
-            mZoomStart = SystemClock.uptimeMillis();
-            mInvInitialZoomScale = 1.0f / oldScale;
-            mInvFinalZoomScale = 1.0f / mActualScale;
-            mZoomScale = mActualScale;
-            WebViewCore.pauseUpdatePicture(mWebViewCore);
-            invalidate();
-            return true;
-        } else {
-            return false;
-        }
-    }
-
     /**
      * Returns a view containing zoom controls i.e. +/- buttons. The caller is
      * in charge of installing this view to the view hierarchy. This view will
@@ -5675,81 +5626,29 @@
             Log.w(LOGTAG, "This WebView doesn't support zoom.");
             return null;
         }
-        if (mZoomControls == null) {
-            mZoomControls = createZoomControls();
-
-            /*
-             * need to be set to VISIBLE first so that getMeasuredHeight() in
-             * {@link #onSizeChanged()} can return the measured value for proper
-             * layout.
-             */
-            mZoomControls.setVisibility(View.VISIBLE);
-            mZoomControlRunnable = new Runnable() {
-                public void run() {
-
-                    /* Don't dismiss the controls if the user has
-                     * focus on them. Wait and check again later.
-                     */
-                    if (!mZoomControls.hasFocus()) {
-                        mZoomControls.hide();
-                    } else {
-                        mPrivateHandler.removeCallbacks(mZoomControlRunnable);
-                        mPrivateHandler.postDelayed(mZoomControlRunnable,
-                                ZOOM_CONTROLS_TIMEOUT);
-                    }
-                }
-            };
-        }
-        return mZoomControls;
+        return mZoomManager.getExternalZoomPicker();
     }
 
-    private ExtendedZoomControls createZoomControls() {
-        ExtendedZoomControls zoomControls = new ExtendedZoomControls(mContext
-            , null);
-        zoomControls.setOnZoomInClickListener(new OnClickListener() {
-            public void onClick(View v) {
-                // reset time out
-                mPrivateHandler.removeCallbacks(mZoomControlRunnable);
-                mPrivateHandler.postDelayed(mZoomControlRunnable,
-                        ZOOM_CONTROLS_TIMEOUT);
-                zoomIn();
-            }
-        });
-        zoomControls.setOnZoomOutClickListener(new OnClickListener() {
-            public void onClick(View v) {
-                // reset time out
-                mPrivateHandler.removeCallbacks(mZoomControlRunnable);
-                mPrivateHandler.postDelayed(mZoomControlRunnable,
-                        ZOOM_CONTROLS_TIMEOUT);
-                zoomOut();
-            }
-        });
-        return zoomControls;
+    void dismissZoomControl() {
+        mZoomManager.dismissZoomPicker();
+    }
+
+    float getDefaultZoomScale() {
+        return mZoomManager.getDefaultScale();
     }
 
     /**
-     * Gets the {@link ZoomButtonsController} which can be used to add
-     * additional buttons to the zoom controls window.
-     *
-     * @return The instance of {@link ZoomButtonsController} used by this class,
-     *         or null if it is unavailable.
-     * @hide
+     * @return TRUE if the WebView can be zoomed in.
      */
-    public ZoomButtonsController getZoomButtonsController() {
-        if (mZoomButtonsController == null) {
-            mZoomButtonsController = new ZoomButtonsController(this);
-            mZoomButtonsController.setOnZoomListener(mZoomListener);
-            // ZoomButtonsController positions the buttons at the bottom, but in
-            // the middle. Change their layout parameters so they appear on the
-            // right.
-            View controls = mZoomButtonsController.getZoomControls();
-            ViewGroup.LayoutParams params = controls.getLayoutParams();
-            if (params instanceof FrameLayout.LayoutParams) {
-                FrameLayout.LayoutParams frameParams = (FrameLayout.LayoutParams) params;
-                frameParams.gravity = Gravity.RIGHT;
-            }
-        }
-        return mZoomButtonsController;
+    public boolean canZoomIn() {
+        return mZoomManager.canZoomIn();
+    }
+
+    /**
+     * @return TRUE if the WebView can be zoomed out.
+     */
+    public boolean canZoomOut() {
+        return mZoomManager.canZoomOut();
     }
 
     /**
@@ -5757,15 +5656,7 @@
      * @return TRUE if zoom in succeeds. FALSE if no zoom changes.
      */
     public boolean zoomIn() {
-        // TODO: alternatively we can disallow this during draw history mode
-        switchOutDrawHistory();
-        mInZoomOverview = false;
-        // Center zooming to the center of the screen.
-        mZoomCenterX = getViewWidth() * .5f;
-        mZoomCenterY = getViewHeight() * .5f;
-        mAnchorX = viewToContentX((int) mZoomCenterX + mScrollX);
-        mAnchorY = viewToContentY((int) mZoomCenterY + mScrollY);
-        return zoomWithPreview(mActualScale * 1.25f, true);
+        return mZoomManager.zoomIn();
     }
 
     /**
@@ -5773,14 +5664,7 @@
      * @return TRUE if zoom out succeeds. FALSE if no zoom changes.
      */
     public boolean zoomOut() {
-        // TODO: alternatively we can disallow this during draw history mode
-        switchOutDrawHistory();
-        // Center zooming to the center of the screen.
-        mZoomCenterX = getViewWidth() * .5f;
-        mZoomCenterY = getViewHeight() * .5f;
-        mAnchorX = viewToContentX((int) mZoomCenterX + mScrollX);
-        mAnchorY = viewToContentY((int) mZoomCenterY + mScrollY);
-        return zoomWithPreview(mActualScale * 0.8f, true);
+        return mZoomManager.zoomOut();
     }
 
     private void updateSelection() {
@@ -5881,7 +5765,14 @@
         // mLastTouchX and mLastTouchY are the point in the current viewport
         int contentX = viewToContentX((int) mLastTouchX + mScrollX);
         int contentY = viewToContentY((int) mLastTouchY + mScrollY);
-        if (nativePointInNavCache(contentX, contentY, mNavSlop)) {
+        if (getSettings().supportTouchOnly()) {
+            removeTouchHighlight(false);
+            WebViewCore.TouchUpData touchUpData = new WebViewCore.TouchUpData();
+            // use "0" as generation id to inform WebKit to use the same x/y as
+            // it used when processing GET_TOUCH_HIGHLIGHT_RECTS
+            touchUpData.mMoveGeneration = 0;
+            mWebViewCore.sendMessage(EventHub.TOUCH_UP, touchUpData);
+        } else if (nativePointInNavCache(contentX, contentY, mNavSlop)) {
             WebViewCore.MotionUpData motionUpData = new WebViewCore
                     .MotionUpData();
             motionUpData.mFrame = nativeCacheHitFramePointer();
@@ -5909,27 +5800,16 @@
      * Return true if the view (Plugin) is fully visible and maximized inside
      * the WebView.
      */
-    private boolean isPluginFitOnScreen(ViewManager.ChildView view) {
-        int viewWidth = getViewWidth();
-        int viewHeight = getViewHeightWithTitle();
-        float scale = Math.min((float) viewWidth / view.width,
-                (float) viewHeight / view.height);
-        if (scale < mMinZoomScale) {
-            scale = mMinZoomScale;
-        } else if (scale > mMaxZoomScale) {
-            scale = mMaxZoomScale;
-        }
-        if (Math.abs(scale - mActualScale) < MINIMUM_SCALE_INCREMENT) {
-            if (contentToViewX(view.x) >= mScrollX
-                    && contentToViewX(view.x + view.width) <= mScrollX
-                            + viewWidth
-                    && contentToViewY(view.y) >= mScrollY
-                    && contentToViewY(view.y + view.height) <= mScrollY
-                            + viewHeight) {
-                return true;
-            }
-        }
-        return false;
+    boolean isPluginFitOnScreen(ViewManager.ChildView view) {
+        final int viewWidth = getViewWidth();
+        final int viewHeight = getViewHeightWithTitle();
+        float scale = Math.min((float) viewWidth / view.width, (float) viewHeight / view.height);
+        scale = mZoomManager.computeScaleWithLimits(scale);
+        return !mZoomManager.willScaleTriggerZoom(scale)
+                && contentToViewX(view.x) >= mScrollX
+                && contentToViewX(view.x + view.width) <= mScrollX + viewWidth
+                && contentToViewY(view.y) >= mScrollY
+                && contentToViewY(view.y + view.height) <= mScrollY + viewHeight;
     }
 
     /*
@@ -5938,22 +5818,19 @@
      * animated scroll to center it. If the zoom needs to be changed, find the
      * zoom center and do a smooth zoom transition.
      */
-    private void centerFitRect(int docX, int docY, int docWidth, int docHeight) {
+    void centerFitRect(int docX, int docY, int docWidth, int docHeight) {
         int viewWidth = getViewWidth();
         int viewHeight = getViewHeightWithTitle();
         float scale = Math.min((float) viewWidth / docWidth, (float) viewHeight
                 / docHeight);
-        if (scale < mMinZoomScale) {
-            scale = mMinZoomScale;
-        } else if (scale > mMaxZoomScale) {
-            scale = mMaxZoomScale;
-        }
-        if (Math.abs(scale - mActualScale) < MINIMUM_SCALE_INCREMENT) {
+        scale = mZoomManager.computeScaleWithLimits(scale);
+        if (!mZoomManager.willScaleTriggerZoom(scale)) {
             pinScrollTo(contentToViewX(docX + docWidth / 2) - viewWidth / 2,
                     contentToViewY(docY + docHeight / 2) - viewHeight / 2,
                     true, 0);
         } else {
-            float oldScreenX = docX * mActualScale - mScrollX;
+            float actualScale = mZoomManager.getScale();
+            float oldScreenX = docX * actualScale - mScrollX;
             float rectViewX = docX * scale;
             float rectViewWidth = docWidth * scale;
             float newMaxWidth = mContentWidth * scale;
@@ -5964,9 +5841,9 @@
             } else if (newScreenX > (newMaxWidth - rectViewX - rectViewWidth)) {
                 newScreenX = viewWidth - (newMaxWidth - rectViewX);
             }
-            mZoomCenterX = (oldScreenX * scale - newScreenX * mActualScale)
-                    / (scale - mActualScale);
-            float oldScreenY = docY * mActualScale + getTitleHeight()
+            float zoomCenterX = (oldScreenX * scale - newScreenX * actualScale)
+                    / (scale - actualScale);
+            float oldScreenY = docY * actualScale + getTitleHeight()
                     - mScrollY;
             float rectViewY = docY * scale + getTitleHeight();
             float rectViewHeight = docHeight * scale;
@@ -5978,109 +5855,10 @@
             } else if (newScreenY > (newMaxHeight - rectViewY - rectViewHeight)) {
                 newScreenY = viewHeight - (newMaxHeight - rectViewY);
             }
-            mZoomCenterY = (oldScreenY * scale - newScreenY * mActualScale)
-                    / (scale - mActualScale);
-            zoomWithPreview(scale, false);
-        }
-    }
-
-    void dismissZoomControl() {
-        if (mWebViewCore == null) {
-            // maybe called after WebView's destroy(). As we can't get settings,
-            // just hide zoom control for both styles.
-            if (mZoomButtonsController != null) {
-                mZoomButtonsController.setVisible(false);
-            }
-            if (mZoomControls != null) {
-                mZoomControls.hide();
-            }
-            return;
-        }
-        WebSettings settings = getSettings();
-        if (settings.getBuiltInZoomControls()) {
-            if (mZoomButtonsController != null) {
-                mZoomButtonsController.setVisible(false);
-            }
-        } else {
-            if (mZoomControlRunnable != null) {
-                mPrivateHandler.removeCallbacks(mZoomControlRunnable);
-            }
-            if (mZoomControls != null) {
-                mZoomControls.hide();
-            }
-        }
-    }
-
-    // Rule for double tap:
-    // 1. if the current scale is not same as the text wrap scale and layout
-    //    algorithm is NARROW_COLUMNS, fit to column;
-    // 2. if the current state is not overview mode, change to overview mode;
-    // 3. if the current state is overview mode, change to default scale.
-    private void doDoubleTap() {
-        if (mWebViewCore.getSettings().getUseWideViewPort() == false) {
-            return;
-        }
-        mZoomCenterX = mLastTouchX;
-        mZoomCenterY = mLastTouchY;
-        mAnchorX = viewToContentX((int) mZoomCenterX + mScrollX);
-        mAnchorY = viewToContentY((int) mZoomCenterY + mScrollY);
-        WebSettings settings = getSettings();
-        settings.setDoubleTapToastCount(0);
-        // remove the zoom control after double tap
-        dismissZoomControl();
-        ViewManager.ChildView plugin = mViewManager.hitTest(mAnchorX, mAnchorY);
-        if (plugin != null) {
-            if (isPluginFitOnScreen(plugin)) {
-                mInZoomOverview = true;
-                // Force the titlebar fully reveal in overview mode
-                if (mScrollY < getTitleHeight()) mScrollY = 0;
-                zoomWithPreview((float) getViewWidth() / mZoomOverviewWidth,
-                        true);
-            } else {
-                mInZoomOverview = false;
-                centerFitRect(plugin.x, plugin.y, plugin.width, plugin.height);
-            }
-            return;
-        }
-        boolean zoomToDefault = false;
-        if ((settings.getLayoutAlgorithm() == WebSettings.LayoutAlgorithm.NARROW_COLUMNS)
-                && (Math.abs(mActualScale - mTextWrapScale) >= MINIMUM_SCALE_INCREMENT)) {
-            setNewZoomScale(mActualScale, true, true);
-            float overviewScale = (float) getViewWidth() / mZoomOverviewWidth;
-            if (Math.abs(mActualScale - overviewScale) < MINIMUM_SCALE_INCREMENT) {
-                mInZoomOverview = true;
-            }
-        } else if (!mInZoomOverview) {
-            float newScale = (float) getViewWidth() / mZoomOverviewWidth;
-            if (Math.abs(mActualScale - newScale) >= MINIMUM_SCALE_INCREMENT) {
-                mInZoomOverview = true;
-                // Force the titlebar fully reveal in overview mode
-                if (mScrollY < getTitleHeight()) mScrollY = 0;
-                zoomWithPreview(newScale, true);
-            } else if (Math.abs(mActualScale - mDefaultScale) >= MINIMUM_SCALE_INCREMENT) {
-                zoomToDefault = true;
-            }
-        } else {
-            zoomToDefault = true;
-        }
-        if (zoomToDefault) {
-            mInZoomOverview = false;
-            int left = nativeGetBlockLeftEdge(mAnchorX, mAnchorY, mActualScale);
-            if (left != NO_LEFTEDGE) {
-                // add a 5pt padding to the left edge.
-                int viewLeft = contentToViewX(left < 5 ? 0 : (left - 5))
-                        - mScrollX;
-                // Re-calculate the zoom center so that the new scroll x will be
-                // on the left edge.
-                if (viewLeft > 0) {
-                    mZoomCenterX = viewLeft * mDefaultScale
-                            / (mDefaultScale - mActualScale);
-                } else {
-                    scrollBy(viewLeft, 0);
-                    mZoomCenterX = 0;
-                }
-            }
-            zoomWithPreview(mDefaultScale, true);
+            float zoomCenterY = (oldScreenY * scale - newScreenY * actualScale)
+                    / (scale - actualScale);
+            mZoomManager.setZoomCenter(zoomCenterX, zoomCenterY);
+            mZoomManager.startZoomAnimation(scale, false);
         }
     }
 
@@ -6092,6 +5870,9 @@
 
     @Override
     public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
+        // FIXME: If a subwindow is showing find, and the user touches the
+        // background window, it can steal focus.
+        if (mFindIsUp) return false;
         boolean result = false;
         if (inEditingMode()) {
             result = mWebTextView.requestFocus(direction,
@@ -6179,6 +5960,12 @@
     public boolean requestChildRectangleOnScreen(View child,
                                                  Rect rect,
                                                  boolean immediate) {
+        // don't scroll while in zoom animation. When it is done, we will adjust
+        // the necessary components (e.g., WebTextView if it is in editing mode)
+        if (mZoomManager.isFixedLengthAnimationInProgress()) {
+            return false;
+        }
+
         rect.offset(child.getLeft() - child.getScrollX(),
                 child.getTop() - child.getScrollY());
 
@@ -6321,7 +6108,8 @@
                 }
                 case SWITCH_TO_SHORTPRESS: {
                     if (mTouchMode == TOUCH_INIT_MODE) {
-                        if (mPreventDefault != PREVENT_DEFAULT_YES) {
+                        if (!getSettings().supportTouchOnly()
+                                && mPreventDefault != PREVENT_DEFAULT_YES) {
                             mTouchMode = TOUCH_SHORTPRESS_START_MODE;
                             updateSelection();
                         } else {
@@ -6335,6 +6123,9 @@
                     break;
                 }
                 case SWITCH_TO_LONGPRESS: {
+                    if (getSettings().supportTouchOnly()) {
+                        removeTouchHighlight(false);
+                    }
                     if (inFullScreenMode() || mDeferTouchProcess) {
                         TouchEventData ted = new TouchEventData();
                         ted.mAction = WebViewCore.ACTION_LONGPRESS;
@@ -6346,15 +6137,10 @@
                         // 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;
                         performLongClick();
-                        rebuildWebTextView();
                     }
                     break;
                 }
@@ -6387,70 +6173,36 @@
                     spawnContentScrollTo(msg.arg1, msg.arg2);
                     break;
                 case UPDATE_ZOOM_RANGE: {
-                    WebViewCore.RestoreState restoreState
-                            = (WebViewCore.RestoreState) msg.obj;
+                    WebViewCore.ViewState viewState = (WebViewCore.ViewState) msg.obj;
                     // mScrollX contains the new minPrefWidth
-                    updateZoomRange(restoreState, getViewWidth(),
-                            restoreState.mScrollX, false);
+                    mZoomManager.updateZoomRange(viewState, getViewWidth(), viewState.mScrollX);
+                    break;
+                }
+                case REPLACE_BASE_CONTENT: {
+                    nativeReplaceBaseContent(msg.arg1);
                     break;
                 }
                 case NEW_PICTURE_MSG_ID: {
-                    // If we've previously delayed deleting a root
-                    // layer, do it now.
-                    if (mDelayedDeleteRootLayer) {
-                        mDelayedDeleteRootLayer = false;
-                        nativeSetRootLayer(0);
-                    }
-                    WebSettings settings = mWebViewCore.getSettings();
                     // called for new content
-                    final int viewWidth = getViewWidth();
-                    final WebViewCore.DrawData draw =
-                            (WebViewCore.DrawData) msg.obj;
+                    final WebViewCore.DrawData draw = (WebViewCore.DrawData) msg.obj;
+                    nativeSetBaseLayer(draw.mBaseLayer);
                     final Point viewSize = draw.mViewPoint;
-                    boolean useWideViewport = settings.getUseWideViewPort();
-                    WebViewCore.RestoreState restoreState = draw.mRestoreState;
-                    boolean hasRestoreState = restoreState != null;
-                    if (hasRestoreState) {
-                        updateZoomRange(restoreState, viewSize.x,
-                                draw.mMinPrefWidth, true);
+                    WebViewCore.ViewState viewState = draw.mViewState;
+                    boolean isPictureAfterFirstLayout = viewState != null;
+                    if (isPictureAfterFirstLayout) {
+                        // Reset the last sent data here since dealing with new page.
+                        mLastWidthSent = 0;
+                        mZoomManager.onFirstLayout(draw);
                         if (!mDrawHistory) {
-                            mInZoomOverview = false;
-
-                            if (mInitialScaleInPercent > 0) {
-                                setNewZoomScale(mInitialScaleInPercent / 100.0f,
-                                    mInitialScaleInPercent != mTextWrapScale * 100,
-                                    false);
-                            } else if (restoreState.mViewScale > 0) {
-                                mTextWrapScale = restoreState.mTextWrapScale;
-                                setNewZoomScale(restoreState.mViewScale, false,
-                                    false);
-                            } else {
-                                mInZoomOverview = useWideViewport
-                                    && settings.getLoadWithOverviewMode();
-                                float scale;
-                                if (mInZoomOverview) {
-                                    scale = (float) viewWidth
-                                        / DEFAULT_VIEWPORT_WIDTH;
-                                } else {
-                                    scale = restoreState.mTextWrapScale;
-                                }
-                                setNewZoomScale(scale, Math.abs(scale
-                                    - mTextWrapScale) >= MINIMUM_SCALE_INCREMENT,
-                                    false);
-                            }
-                            setContentScrollTo(restoreState.mScrollX,
-                                restoreState.mScrollY);
+                            setContentScrollTo(viewState.mScrollX, viewState.mScrollY);
                             // As we are on a new page, remove the WebTextView. This
                             // is necessary for page loads driven by webkit, and in
                             // particular when the user was on a password field, so
                             // the WebTextView was visible.
                             clearTextEntry(false);
-                            // update the zoom buttons as the scale can be changed
-                            if (getSettings().getBuiltInZoomControls()) {
-                                updateZoomButtonsEnabled();
-                            }
                         }
                     }
+
                     // We update the layout (i.e. request a layout from the
                     // view system) if the last view size that we sent to
                     // WebCore matches the view size of the picture we just
@@ -6458,45 +6210,25 @@
                     final boolean updateLayout = viewSize.x == mLastWidthSent
                             && viewSize.y == mLastHeightSent;
                     recordNewContentSize(draw.mWidthHeight.x,
-                            draw.mWidthHeight.y
-                            + (mFindIsUp ? mFindHeight : 0), updateLayout);
+                            draw.mWidthHeight.y, updateLayout);
                     if (DebugFlags.WEB_VIEW) {
                         Rect b = draw.mInvalRegion.getBounds();
                         Log.v(LOGTAG, "NEW_PICTURE_MSG_ID {" +
                                 b.left+","+b.top+","+b.right+","+b.bottom+"}");
                     }
                     invalidateContentRect(draw.mInvalRegion.getBounds());
+
                     if (mPictureListener != null) {
                         mPictureListener.onNewPicture(WebView.this, capturePicture());
                     }
-                    if (useWideViewport) {
-                        // limit mZoomOverviewWidth upper bound to
-                        // sMaxViewportWidth so that if the page doesn't behave
-                        // well, the WebView won't go insane. limit the lower
-                        // bound to match the default scale for mobile sites.
-                        mZoomOverviewWidth = Math.min(sMaxViewportWidth, Math
-                                .max((int) (viewWidth / mDefaultScale), Math
-                                        .max(draw.mMinPrefWidth,
-                                                draw.mViewPoint.x)));
-                    }
-                    if (!mMinZoomScaleFixed) {
-                        mMinZoomScale = (float) viewWidth / mZoomOverviewWidth;
-                    }
-                    if (!mDrawHistory && mInZoomOverview) {
-                        // fit the content width to the current view. Ignore
-                        // the rounding error case.
-                        if (Math.abs((viewWidth * mInvActualScale)
-                                - mZoomOverviewWidth) > 1) {
-                            setNewZoomScale((float) viewWidth
-                                    / mZoomOverviewWidth, Math.abs(mActualScale
-                                    - mTextWrapScale) < MINIMUM_SCALE_INCREMENT,
-                                    false);
-                        }
-                    }
+
+                    // update the zoom information based on the new picture
+                    mZoomManager.onNewPicture(draw);
+
                     if (draw.mFocusSizeChanged && inEditingMode()) {
                         mFocusSizeChanged = true;
                     }
-                    if (hasRestoreState) {
+                    if (isPictureAfterFirstLayout) {
                         mViewManager.postReadyToDrawAll();
                     }
                     break;
@@ -6530,14 +6262,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;
@@ -6555,7 +6281,7 @@
                         }
                     }
                     break;
-                case MOVE_OUT_OF_PLUGIN:
+                case UNHANDLED_NAV_KEY:
                     navHandledKey(msg.arg1, 1, false, 0);
                     break;
                 case UPDATE_TEXT_ENTRY_MSG_ID:
@@ -6580,23 +6306,6 @@
                     }
                     break;
                 }
-                case IMMEDIATE_REPAINT_MSG_ID: {
-                    invalidate();
-                    break;
-                }
-                case SET_ROOT_LAYER_MSG_ID: {
-                    if (0 == msg.arg1) {
-                        // Null indicates deleting the old layer, but
-                        // don't actually do so until we've got the
-                        // new page to display.
-                        mDelayedDeleteRootLayer = true;
-                    } else {
-                        mDelayedDeleteRootLayer = false;
-                        nativeSetRootLayer(msg.arg1);
-                        invalidate();
-                    }
-                    break;
-                }
                 case REQUEST_FORM_DATA:
                     AutoCompleteAdapter adapter = (AutoCompleteAdapter) msg.obj;
                     if (mWebTextView.isSameTextField(msg.arg1)) {
@@ -6640,33 +6349,40 @@
                             mPreventDefault = msg.arg2 == 1 ? PREVENT_DEFAULT_YES
                                     : PREVENT_DEFAULT_NO;
                         }
+                        if (mPreventDefault == PREVENT_DEFAULT_YES) {
+                            mTouchHighlightRegion.setEmpty();
+                        }
                     } else if (msg.arg2 == 0) {
                         // prevent default is not called in WebCore, so the
                         // message needs to be reprocessed in UI
                         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:
@@ -6680,9 +6396,9 @@
                                 break;
                             case WebViewCore.ACTION_DOUBLETAP:
                                 // doDoubleTap() needs mLastTouchX/Y as anchor
-                                mLastTouchX = ted.mViewX;
-                                mLastTouchY = ted.mViewY;
-                                doDoubleTap();
+                                mLastTouchX = contentToViewX(ted.mX) - mScrollX;
+                                mLastTouchY = contentToViewY(ted.mY) - mScrollY;
+                                mZoomManager.handleDoubleTap(mLastTouchX, mLastTouchY);
                                 mDeferTouchMode = TOUCH_DONE_MODE;
                                 break;
                             case WebViewCore.ACTION_LONGPRESS:
@@ -6690,7 +6406,6 @@
                                 if (hitTest != null && hitTest.mType
                                         != HitTestResult.UNKNOWN_TYPE) {
                                     performLongClick();
-                                    rebuildWebTextView();
                                 }
                                 mDeferTouchMode = TOUCH_DONE_MODE;
                                 break;
@@ -6814,7 +6529,6 @@
 
                 case CENTER_FIT_RECT:
                     Rect r = (Rect)msg.obj;
-                    mInZoomOverview = false;
                     centerFitRect(r.left, r.top, r.width(), r.height());
                     break;
 
@@ -6823,6 +6537,43 @@
                     mVerticalScrollBarMode = msg.arg2;
                     break;
 
+                case SELECTION_STRING_CHANGED:
+                    if (mAccessibilityInjector != null) {
+                        String selectionString = (String) msg.obj;
+                        mAccessibilityInjector.onSelectionStringChange(selectionString);
+                    }
+                    break;
+
+                case SET_TOUCH_HIGHLIGHT_RECTS:
+                    invalidate(mTouchHighlightRegion.getBounds());
+                    mTouchHighlightRegion.setEmpty();
+                    if (msg.obj != null) {
+                        ArrayList<Rect> rects = (ArrayList<Rect>) msg.obj;
+                        for (Rect rect : rects) {
+                            Rect viewRect = contentToViewRect(rect);
+                            // some sites, like stories in nytimes.com, set
+                            // mouse event handler in the top div. It is not
+                            // user friendly to highlight the div if it covers
+                            // more than half of the screen.
+                            if (viewRect.width() < getWidth() >> 1
+                                    || viewRect.height() < getHeight() >> 1) {
+                                mTouchHighlightRegion.union(viewRect);
+                                invalidate(viewRect);
+                            } else {
+                                Log.w(LOGTAG, "Skip the huge selection rect:"
+                                        + viewRect);
+                            }
+                        }
+                    }
+                    break;
+
+                case SAVE_WEBARCHIVE_FINISHED:
+                    SaveWebArchiveMessage saveMessage = (SaveWebArchiveMessage)msg.obj;
+                    if (saveMessage.mCallback != null) {
+                        saveMessage.mCallback.onReceiveValue(saveMessage.mResultFile);
+                    }
+                    break;
+
                 default:
                     super.handleMessage(msg);
                     break;
@@ -7130,37 +6881,6 @@
                 new InvokeListBox(array, enabledArray, selectedArray));
     }
 
-    private void updateZoomRange(WebViewCore.RestoreState restoreState,
-            int viewWidth, int minPrefWidth, boolean updateZoomOverview) {
-        if (restoreState.mMinScale == 0) {
-            if (restoreState.mMobileSite) {
-                if (minPrefWidth > Math.max(0, viewWidth)) {
-                    mMinZoomScale = (float) viewWidth / minPrefWidth;
-                    mMinZoomScaleFixed = false;
-                    if (updateZoomOverview) {
-                        WebSettings settings = getSettings();
-                        mInZoomOverview = settings.getUseWideViewPort() &&
-                                settings.getLoadWithOverviewMode();
-                    }
-                } else {
-                    mMinZoomScale = restoreState.mDefaultScale;
-                    mMinZoomScaleFixed = true;
-                }
-            } else {
-                mMinZoomScale = DEFAULT_MIN_ZOOM_SCALE;
-                mMinZoomScaleFixed = false;
-            }
-        } else {
-            mMinZoomScale = restoreState.mMinScale;
-            mMinZoomScaleFixed = true;
-        }
-        if (restoreState.mMaxScale == 0) {
-            mMaxZoomScale = DEFAULT_MAX_ZOOM_SCALE;
-        } else {
-            mMaxZoomScale = restoreState.mMaxScale;
-        }
-    }
-
     /*
      * Request a dropdown menu for a listbox with single selection or a single
      * <select> element.
@@ -7243,7 +6963,7 @@
         // FIXME the divisor should be retrieved from somewhere
         // the closest thing today is hard-coded into ScrollView.java
         // (from ScrollView.java, line 363)   int maxJump = height/2;
-        return Math.round(height * mInvActualScale);
+        return Math.round(height * mZoomManager.getInvScale());
     }
 
     /**
@@ -7254,10 +6974,10 @@
     }
 
     /**
-     * Pass the key to the plugin.  This assumes that nativeFocusIsPlugin()
-     * returned true.
+     * Pass the key directly to the page.  This assumes that
+     * nativePageShouldHandleShiftAndArrows() returned true.
      */
-    private void letPluginHandleNavKey(int keyCode, long time, boolean down) {
+    private void letPageHandleNavKey(int keyCode, long time, boolean down) {
         int keyEventAction;
         int eventHubAction;
         if (down) {
@@ -7354,7 +7074,7 @@
      * @hide only needs to be accessible to Browser and testing
      */
     public void drawPage(Canvas canvas) {
-        mWebViewCore.drawContentPicture(canvas, 0, false, false);
+        nativeDraw(canvas, 0, 0, false);
     }
 
     /**
@@ -7398,9 +7118,18 @@
     private native boolean  nativeCursorWantsKeyEvents();
     private native void     nativeDebugDump();
     private native void     nativeDestroy();
-    private native boolean  nativeEvaluateLayersAnimations();
-    private native void     nativeDrawExtras(Canvas canvas, int extra);
+
+    /**
+     * Draw the picture set with a background color and extra. If
+     * "splitIfNeeded" is true and the return value is not 0, the return value
+     * MUST be passed to WebViewCore with SPLIT_PICTURE_SET message so that the
+     * native allocation can be freed.
+     */
+    private native int nativeDraw(Canvas canvas, int color, int extra,
+            boolean splitIfNeeded);
     private native void     nativeDumpDisplayTree(String urlOrNull);
+    private native boolean  nativeEvaluateLayersAnimations();
+    private native void     nativeExtendSelection(int x, int y);
     private native int      nativeFindAll(String findLower, String findUpper);
     private native void     nativeFindNext(boolean forward);
     /* package */ native int      nativeFocusCandidateFramePointer();
@@ -7427,6 +7156,7 @@
     private native boolean  nativeHasCursorNode();
     private native boolean  nativeHasFocusNode();
     private native void     nativeHideCursor();
+    private native boolean  nativeHitSelection(int x, int y);
     private native String   nativeImageURI(int x, int y);
     private native void     nativeInstrumentReport();
     /* package */ native boolean nativeMoveCursorToNextTextInput();
@@ -7436,29 +7166,50 @@
     private native boolean  nativeMoveCursor(int keyCode, int count,
             boolean noScroll);
     private native int      nativeMoveGeneration();
-    private native void     nativeMoveSelection(int x, int y,
-            boolean extendSelection);
+    private native void     nativeMoveSelection(int x, int y);
+    /**
+     * @return true if the page should get the shift and arrow keys, rather
+     * than select text/navigation.
+     *
+     * If the focus is a plugin, or if the focus and cursor match and are
+     * a contentEditable element, then the page should handle these keys.
+     */
+    private native boolean  nativePageShouldHandleShiftAndArrows();
     private native boolean  nativePointInNavCache(int x, int y, int slop);
     // Like many other of our native methods, you must make sure that
     // mNativeClass is not null before calling this method.
     private native void     nativeRecordButtons(boolean focused,
             boolean pressed, boolean invalidate);
+    private native void     nativeResetSelection();
+    private native void     nativeSelectAll();
     private native void     nativeSelectBestAt(Rect rect);
+    private native int      nativeSelectionX();
+    private native int      nativeSelectionY();
+    private native int      nativeFindIndex();
+    private native void     nativeSetExtendSelection();
     private native void     nativeSetFindIsEmpty();
     private native void     nativeSetFindIsUp(boolean isUp);
     private native void     nativeSetFollowedLink(boolean followed);
     private native void     nativeSetHeightCanMeasure(boolean measure);
-    private native void     nativeSetRootLayer(int layer);
+    private native void     nativeSetBaseLayer(int layer);
+    private native void     nativeReplaceBaseContent(int content);
+    private native void     nativeCopyBaseContentToPicture(Picture pict);
+    private native boolean  nativeHasContent();
     private native void     nativeSetSelectionPointer(boolean set,
-            float scale, int x, int y, boolean extendSelection);
-    private native void     nativeSetSelectionRegion(boolean set);
+            float scale, int x, int y);
+    private native boolean  nativeStartSelection(int x, int y);
     private native Rect     nativeSubtractLayers(Rect content);
     private native int      nativeTextGeneration();
     // Never call this version except by updateCachedTextfield(String) -
     // we always want to pass in our generation number.
     private native void     nativeUpdateCachedTextfield(String updatedText,
             int generation);
+    private native boolean  nativeWordSelection(int x, int y);
     // return NO_LEFTEDGE means failure.
-    private static final int NO_LEFTEDGE = -1;
-    private native int      nativeGetBlockLeftEdge(int x, int y, float scale);
+    static final int NO_LEFTEDGE = -1;
+    native int nativeGetBlockLeftEdge(int x, int y, float scale);
+
+    // Returns a pointer to the scrollable LayerAndroid at the given point.
+    private native int      nativeScrollableLayer(int x, int y);
+    private native boolean  nativeScrollLayer(int layer, int dx, int dy);
 }
diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java
index 4118119..21af570 100644
--- a/core/java/android/webkit/WebViewCore.java
+++ b/core/java/android/webkit/WebViewCore.java
@@ -17,14 +17,8 @@
 package android.webkit;
 
 import android.content.Context;
-import android.content.Intent;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.database.Cursor;
-import android.graphics.Canvas;
-import android.graphics.DrawFilter;
-import android.graphics.Paint;
-import android.graphics.PaintFlagsDrawFilter;
-import android.graphics.Picture;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.Region;
@@ -33,12 +27,10 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.Process;
-import android.provider.Browser;
-import android.provider.OpenableColumns;
+import android.provider.MediaStore;
 import android.util.Log;
 import android.util.SparseBooleanArray;
 import android.view.KeyEvent;
-import android.view.SurfaceHolder;
 import android.view.SurfaceView;
 import android.view.View;
 
@@ -117,7 +109,7 @@
     private int mViewportDensityDpi = -1;
 
     private int mRestoredScale = 0;
-    private int mRestoredScreenWidthScale = 0;
+    private int mRestoredTextWrapScale = 0;
     private int mRestoredX = 0;
     private int mRestoredY = 0;
 
@@ -279,34 +271,36 @@
 
     /**
      * Called by JNI.  Open a file chooser to upload a file.
-     * @return String version of the URI plus the name of the file.
-     * FIXME: Just return the URI here, and in FileSystem::pathGetFileName, call
-     * into Java to get the filename.
+     * @param acceptType The value of the 'accept' attribute of the
+     *         input tag associated with this file picker.
+     * @return String version of the URI.
      */
-    private String openFileChooser() {
-        Uri uri = mCallbackProxy.openFileChooser();
-        if (uri == null) return "";
-        // Find out the name, and append it to the URI.
-        // Webkit will treat the name as the filename, and
-        // the URI as the path.  The URI will be used
-        // in BrowserFrame to get the actual data.
-        Cursor cursor = mContext.getContentResolver().query(
-                uri,
-                new String[] { OpenableColumns.DISPLAY_NAME },
-                null,
-                null,
-                null);
-        String name = "";
-        if (cursor != null) {
-            try {
-                if (cursor.moveToNext()) {
-                    name = cursor.getString(0);
+    private String openFileChooser(String acceptType) {
+        Uri uri = mCallbackProxy.openFileChooser(acceptType);
+        if (uri != null) {
+            String filePath = "";
+            // Note - querying for MediaStore.Images.Media.DATA
+            // seems to work for all content URIs, not just images
+            Cursor cursor = mContext.getContentResolver().query(
+                    uri,
+                    new String[] { MediaStore.Images.Media.DATA },
+                    null, null, null);
+            if (cursor != null) {
+                try {
+                    if (cursor.moveToNext()) {
+                        filePath = cursor.getString(0);
+                    }
+                } finally {
+                    cursor.close();
                 }
-            } finally {
-                cursor.close();
+            } else {
+                filePath = uri.getLastPathSegment();
             }
+            String uriString = uri.toString();
+            BrowserFrame.sJavaBridge.storeFilePathForContentUri(filePath, uriString);
+            return uriString;
         }
-        return uri.toString() + "/" + name;
+        return "";
     }
 
     /**
@@ -424,6 +418,13 @@
         return mCallbackProxy.onJsTimeout();
     }
 
+    /**
+     * Notify the webview that this is an installable web app.
+     */
+    protected void setInstallableWebApp() {
+        mCallbackProxy.setInstallableWebApp();
+    }
+
     //-------------------------------------------------------------------------
     // JNI methods
     //-------------------------------------------------------------------------
@@ -436,35 +437,18 @@
     private native void nativeClearContent();
 
     /**
-     * Create a flat picture from the set of pictures.
-     */
-    private native void nativeCopyContentToPicture(Picture picture);
-
-    /**
-     * Draw the picture set with a background color. Returns true
-     * if some individual picture took too long to draw and can be
-     * split into parts. Called from the UI thread.
-     */
-    private native boolean nativeDrawContent(Canvas canvas, int color);
-
-    /**
-     * check to see if picture is blank and in progress
-     */
-    private native boolean nativePictureReady();
-
-    /**
      * Redraw a portion of the picture set. The Point wh returns the
      * width and height of the overall picture.
      */
-    private native boolean nativeRecordContent(Region invalRegion, Point wh);
+    private native int nativeRecordContent(Region invalRegion, Point wh);
 
     private native boolean nativeFocusBoundsChanged();
 
     /**
-     * Splits slow parts of the picture set. Called from the webkit
-     * thread after nativeDrawContent returns true.
+     * Splits slow parts of the picture set. Called from the webkit thread after
+     * WebView.nativeDraw() returns content to be split.
      */
-    private native void nativeSplitContent();
+    private native void nativeSplitContent(int content);
 
     private native boolean nativeKey(int keyCode, int unichar,
             int repeatCount, boolean isShift, boolean isAlt, boolean isSym,
@@ -480,12 +464,12 @@
         of layout/line-breaking. These coordinates are in document space,
         which is the same as View coords unless we have zoomed the document
         (see nativeSetZoom).
-        screenWidth is used by layout to wrap column around. If viewport uses
-        fixed size, screenWidth can be different from width with zooming.
+        textWrapWidth is used by layout to wrap column around. If viewport uses
+        fixed size, textWrapWidth can be different from width with zooming.
         should this be called nativeSetViewPortSize?
     */
-    private native void nativeSetSize(int width, int height, int screenWidth,
-            float scale, int realScreenWidth, int screenHeight, int anchorX,
+    private native void nativeSetSize(int width, int height, int textWrapWidth,
+            float scale, int screenWidth, int screenHeight, int anchorX,
             int anchorY, boolean ignoreHeight);
 
     private native int nativeGetContentMinPrefWidth();
@@ -576,7 +560,18 @@
     /**
      * Provide WebCore with the previously visted links from the history database
      */
-    private native void  nativeProvideVisitedHistory(String[] history);
+    private native void nativeProvideVisitedHistory(String[] history);
+
+    /**
+     * Modifies the current selection.
+     *
+     * @param alter Specifies how to alter the selection.
+     * @param direction The direction in which to alter the selection.
+     * @param granularity The granularity of the selection modification.
+     *
+     * @return The selection string.
+     */
+    private native String nativeModifySelection(String alter, String direction, String granularity);
 
     // EventHub for processing messages
     private final EventHub mEventHub;
@@ -697,6 +692,12 @@
         int mY;
     }
 
+    static class TouchHighlightData {
+        int mX;
+        int mY;
+        int mSlop;
+    }
+
     // mAction of TouchEventData can be MotionEvent.getAction() which uses the
     // last two bytes or one of the following values
     static final int ACTION_LONGPRESS = 0x100;
@@ -708,8 +709,6 @@
         int mY;
         int mMetaState;
         boolean mReprocess;
-        float mViewX;
-        float mViewY;
     }
 
     static class GeolocationPermissionsData {
@@ -718,7 +717,11 @@
         boolean mRemember;
     }
 
-
+    static class ModifySelectionData {
+        String mAlter;
+        String mDirection;
+        String mGranularity;
+    }
 
         static final String[] HandlerDebugString = {
             "REQUEST_LABEL", // 97
@@ -771,6 +774,7 @@
             "ON_RESUME",    // = 144
             "FREE_MEMORY",  // = 145
             "VALID_NODE_BOUNDS", // = 146
+            "SAVE_WEBARCHIVE", // = 147
         };
 
     class EventHub {
@@ -837,6 +841,9 @@
         static final int FREE_MEMORY = 145;
         static final int VALID_NODE_BOUNDS = 146;
 
+        // Load and save web archives
+        static final int SAVE_WEBARCHIVE = 147;
+
         // Network-based messaging
         static final int CLEAR_SSL_PREF_TABLE = 150;
 
@@ -865,6 +872,12 @@
         static final int ADD_PACKAGE_NAME = 185;
         static final int REMOVE_PACKAGE_NAME = 186;
 
+        static final int GET_TOUCH_HIGHLIGHT_RECTS = 187;
+        static final int REMOVE_TOUCH_HIGHLIGHT_RECTS = 188;
+
+        // accessibility support
+        static final int MODIFY_SELECTION = 190;
+
         // private message ids
         private static final int DESTROY =     200;
 
@@ -1238,6 +1251,19 @@
                             nativeSetSelection(msg.arg1, msg.arg2);
                             break;
 
+                        case MODIFY_SELECTION:
+                            ModifySelectionData modifySelectionData =
+                                (ModifySelectionData) msg.obj;
+                            String selectionString = nativeModifySelection(
+                                    modifySelectionData.mAlter,
+                                    modifySelectionData.mDirection,
+                                    modifySelectionData.mGranularity);
+
+                            mWebView.mPrivateHandler.obtainMessage(
+                                    WebView.SELECTION_STRING_CHANGED, selectionString)
+                                    .sendToTarget();
+                            break;
+
                         case LISTBOX_CHOICES:
                             SparseBooleanArray choices = (SparseBooleanArray)
                                     msg.obj;
@@ -1278,6 +1304,15 @@
                             nativeSetJsFlags((String)msg.obj);
                             break;
 
+                        case SAVE_WEBARCHIVE:
+                            WebView.SaveWebArchiveMessage saveMessage =
+                                (WebView.SaveWebArchiveMessage)msg.obj;
+                            saveMessage.mResultFile =
+                                saveWebArchive(saveMessage.mBasename, saveMessage.mAutoname);
+                            mWebView.mPrivateHandler.obtainMessage(
+                                WebView.SAVE_WEBARCHIVE_FINISHED, saveMessage).sendToTarget();
+                            break;
+
                         case GEOLOCATION_PERMISSIONS_PROVIDE:
                             GeolocationPermissionsData data =
                                     (GeolocationPermissionsData) msg.obj;
@@ -1291,7 +1326,9 @@
                             break;
 
                         case SPLIT_PICTURE_SET:
-                            nativeSplitContent();
+                            nativeSplitContent(msg.arg1);
+                            mWebView.mPrivateHandler.obtainMessage(
+                                    WebView.REPLACE_BASE_CONTENT, msg.arg1, 0);
                             mSplitPictureIsScheduled = false;
                             break;
 
@@ -1357,6 +1394,21 @@
                             BrowserFrame.sJavaBridge.removePackageName(
                                     (String) msg.obj);
                             break;
+
+                        case GET_TOUCH_HIGHLIGHT_RECTS:
+                            TouchHighlightData d = (TouchHighlightData) msg.obj;
+                            ArrayList<Rect> rects = nativeGetTouchHighlightRects
+                                    (d.mX, d.mY, d.mSlop);
+                            mWebView.mPrivateHandler.obtainMessage(
+                                    WebView.SET_TOUCH_HIGHLIGHT_RECTS, rects)
+                                    .sendToTarget();
+                            break;
+
+                        case REMOVE_TOUCH_HIGHLIGHT_RECTS:
+                            mWebView.mPrivateHandler.obtainMessage(
+                                    WebView.SET_TOUCH_HIGHLIGHT_RECTS, null)
+                                    .sendToTarget();
+                            break;
                     }
                 }
             };
@@ -1562,6 +1614,13 @@
         mBrowserFrame.loadUrl(url, extraHeaders);
     }
 
+    private String saveWebArchive(String filename, boolean autoname) {
+        if (DebugFlags.WEB_VIEW_CORE) {
+            Log.v(LOGTAG, " CORE saveWebArchive " + filename + " " + autoname);
+        }
+        return mBrowserFrame.saveWebArchive(filename, autoname);
+    }
+
     private void key(KeyEvent evt, boolean isDown) {
         if (DebugFlags.WEB_VIEW_CORE) {
             Log.v(LOGTAG, "CORE key at " + System.currentTimeMillis() + ", "
@@ -1575,11 +1634,12 @@
             if (keyCode >= KeyEvent.KEYCODE_DPAD_UP
                     && keyCode <= KeyEvent.KEYCODE_DPAD_RIGHT) {
                 if (DebugFlags.WEB_VIEW_CORE) {
-                    Log.v(LOGTAG, "key: arrow unused by plugin: " + keyCode);
+                    Log.v(LOGTAG, "key: arrow unused by page: " + keyCode);
                 }
                 if (mWebView != null && evt.isDown()) {
                     Message.obtain(mWebView.mPrivateHandler,
-                            WebView.MOVE_OUT_OF_PLUGIN, keyCode).sendToTarget();
+                            WebView.UNHANDLED_NAV_KEY, keyCode,
+                            0).sendToTarget();
                 }
                 return;
             }
@@ -1675,6 +1735,14 @@
         return usedQuota;
     }
 
+    // called from UI thread
+    void splitContent(int content) {
+        if (!mSplitPictureIsScheduled) {
+            mSplitPictureIsScheduled = true;
+            sendMessage(EventHub.SPLIT_PICTURE_SET, content, 0);
+        }
+    }
+
     // Used to avoid posting more than one draw message.
     private boolean mDrawIsScheduled;
 
@@ -1684,11 +1752,11 @@
     // Used to suspend drawing.
     private boolean mDrawIsPaused;
 
-    // mRestoreState is set in didFirstLayout(), and reset in the next
-    // webkitDraw after passing it to the UI thread.
-    private RestoreState mRestoreState = null;
+    // mInitialViewState is set by didFirstLayout() and then reset in the
+    // next webkitDraw after passing the state to the UI thread.
+    private ViewState mInitialViewState = null;
 
-    static class RestoreState {
+    static class ViewState {
         float mMinScale;
         float mMaxScale;
         float mViewScale;
@@ -1701,15 +1769,17 @@
 
     static class DrawData {
         DrawData() {
+            mBaseLayer = 0;
             mInvalRegion = new Region();
             mWidthHeight = new Point();
         }
+        int mBaseLayer;
         Region mInvalRegion;
         Point mViewPoint;
         Point mWidthHeight;
         int mMinPrefWidth;
-        RestoreState mRestoreState; // only non-null if it is for the first
-                                    // picture set after the first layout
+        // only non-null if it is for the first picture set after the first layout
+        ViewState mViewState;
         boolean mFocusSizeChanged;
     }
 
@@ -1717,8 +1787,8 @@
         mDrawIsScheduled = false;
         DrawData draw = new DrawData();
         if (DebugFlags.WEB_VIEW_CORE) Log.v(LOGTAG, "webkitDraw start");
-        if (nativeRecordContent(draw.mInvalRegion, draw.mWidthHeight)
-                == false) {
+        draw.mBaseLayer = nativeRecordContent(draw.mInvalRegion, draw.mWidthHeight);
+        if (draw.mBaseLayer == 0) {
             if (DebugFlags.WEB_VIEW_CORE) Log.v(LOGTAG, "webkitDraw abort");
             return;
         }
@@ -1734,9 +1804,9 @@
                                         : mViewportWidth),
                         nativeGetContentMinPrefWidth());
             }
-            if (mRestoreState != null) {
-                draw.mRestoreState = mRestoreState;
-                mRestoreState = null;
+            if (mInitialViewState != null) {
+                draw.mViewState = mInitialViewState;
+                mInitialViewState = null;
             }
             if (DebugFlags.WEB_VIEW_CORE) Log.v(LOGTAG, "webkitDraw NEW_PICTURE_MSG_ID");
             Message.obtain(mWebView.mPrivateHandler,
@@ -1751,51 +1821,6 @@
         }
     }
 
-    ///////////////////////////////////////////////////////////////////////////
-    // These are called from the UI thread, not our thread
-
-    static final int ZOOM_BITS = Paint.FILTER_BITMAP_FLAG |
-                                         Paint.DITHER_FLAG |
-                                         Paint.SUBPIXEL_TEXT_FLAG;
-    static final int SCROLL_BITS = Paint.FILTER_BITMAP_FLAG |
-                                           Paint.DITHER_FLAG;
-
-    final DrawFilter mZoomFilter =
-                    new PaintFlagsDrawFilter(ZOOM_BITS, Paint.LINEAR_TEXT_FLAG);
-    // If we need to trade better quality for speed, set mScrollFilter to null
-    final DrawFilter mScrollFilter =
-                new PaintFlagsDrawFilter(SCROLL_BITS, 0);
-
-    /* package */ void drawContentPicture(Canvas canvas, int color,
-                                          boolean animatingZoom,
-                                          boolean animatingScroll) {
-        DrawFilter df = null;
-        if (animatingZoom) {
-            df = mZoomFilter;
-        } else if (animatingScroll) {
-            df = mScrollFilter;
-        }
-        canvas.setDrawFilter(df);
-        boolean tookTooLong = nativeDrawContent(canvas, color);
-        canvas.setDrawFilter(null);
-        if (tookTooLong && mSplitPictureIsScheduled == false) {
-            mSplitPictureIsScheduled = true;
-            sendMessage(EventHub.SPLIT_PICTURE_SET);
-        }
-    }
-
-    /* package */ synchronized boolean pictureReady() {
-        return 0 != mNativeClass ? nativePictureReady() : false;
-    }
-
-    /*package*/ synchronized Picture copyContentPicture() {
-        Picture result = new Picture();
-        if (0 != mNativeClass) {
-            nativeCopyContentToPicture(result);
-        }
-        return result;
-    }
-
     static void reducePriority() {
         // remove the pending REDUCE_PRIORITY and RESUME_PRIORITY messages
         sWebCoreHandler.removeMessages(WebCoreThread.REDUCE_PRIORITY);
@@ -1817,6 +1842,8 @@
         // called from UI thread while WEBKIT_DRAW is just pulled out of the
         // queue in WebCore thread to be executed. Then update won't be blocked.
         if (core != null) {
+            if (!core.getSettings().enableSmoothTransition()) return;
+
             synchronized (core) {
                 core.mDrawIsPaused = true;
                 if (core.mDrawIsScheduled) {
@@ -1829,6 +1856,10 @@
 
     static void resumeUpdatePicture(WebViewCore core) {
         if (core != null) {
+            // if mDrawIsPaused is true, ignore the setting, continue to resume
+            if (!core.mDrawIsPaused
+                    && !core.getSettings().enableSmoothTransition()) return;
+
             synchronized (core) {
                 core.mDrawIsPaused = false;
                 if (core.mDrawIsScheduled) {
@@ -1971,24 +2002,6 @@
         mRepaintScheduled = false;
     }
 
-    // called by JNI
-    private void sendImmediateRepaint() {
-        if (mWebView != null && !mRepaintScheduled) {
-            mRepaintScheduled = true;
-            Message.obtain(mWebView.mPrivateHandler,
-                           WebView.IMMEDIATE_REPAINT_MSG_ID).sendToTarget();
-        }
-    }
-
-    // called by JNI
-    private void setRootLayer(int layer) {
-        if (mWebView != null) {
-            Message.obtain(mWebView.mPrivateHandler,
-                           WebView.SET_ROOT_LAYER_MSG_ID,
-                           layer, 0).sendToTarget();
-        }
-    }
-
     /* package */ WebView getWebView() {
         return mWebView;
     }
@@ -2005,18 +2018,24 @@
 
         if (mWebView == null) return;
 
-        boolean updateRestoreState = standardLoad || mRestoredScale > 0;
-        setupViewport(updateRestoreState);
+        boolean updateViewState = standardLoad || mRestoredScale > 0;
+        setupViewport(updateViewState);
         // if updateRestoreState is true, ViewManager.postReadyToDrawAll() will
-        // be called after the WebView restore the state. If updateRestoreState
+        // be called after the WebView updates its state. If updateRestoreState
         // is false, start to draw now as it is ready.
-        if (!updateRestoreState) {
+        if (!updateViewState) {
             mWebView.mViewManager.postReadyToDrawAll();
         }
 
+        // remove the touch highlight when moving to a new page
+        if (getSettings().supportTouchOnly()) {
+            mEventHub.sendMessage(Message.obtain(null,
+                    EventHub.REMOVE_TOUCH_HIGHLIGHT_RECTS));
+        }
+
         // reset the scroll position, the restored offset and scales
         mWebkitScrollX = mWebkitScrollY = mRestoredX = mRestoredY
-                = mRestoredScale = mRestoredScreenWidthScale = 0;
+                = mRestoredScale = mRestoredTextWrapScale = 0;
     }
 
     // called by JNI
@@ -2030,15 +2049,17 @@
         }
     }
 
-    private void setupViewport(boolean updateRestoreState) {
+    private void setupViewport(boolean updateViewState) {
         // set the viewport settings from WebKit
         setViewportSettingsFromNative();
 
         // adjust the default scale to match the densityDpi
         float adjust = 1.0f;
         if (mViewportDensityDpi == -1) {
-            if (WebView.DEFAULT_SCALE_PERCENT != 100) {
-                adjust = WebView.DEFAULT_SCALE_PERCENT / 100.0f;
+            // convert default zoom scale to a integer (percentage) to avoid any
+            // issues with floating point comparisons
+            if (mWebView != null && (int)(mWebView.getDefaultZoomScale() * 100) != 100) {
+                adjust = mWebView.getDefaultZoomScale();
             }
         } else if (mViewportDensityDpi > 0) {
             adjust = (float) mContext.getResources().getDisplayMetrics().densityDpi
@@ -2080,17 +2101,17 @@
         }
 
         // if mViewportWidth is 0, it means device-width, always update.
-        if (mViewportWidth != 0 && !updateRestoreState) {
-            RestoreState restoreState = new RestoreState();
-            restoreState.mMinScale = mViewportMinimumScale / 100.0f;
-            restoreState.mMaxScale = mViewportMaximumScale / 100.0f;
-            restoreState.mDefaultScale = adjust;
+        if (mViewportWidth != 0 && !updateViewState) {
+            ViewState viewState = new ViewState();
+            viewState.mMinScale = mViewportMinimumScale / 100.0f;
+            viewState.mMaxScale = mViewportMaximumScale / 100.0f;
+            viewState.mDefaultScale = adjust;
             // as mViewportWidth is not 0, it is not mobile site.
-            restoreState.mMobileSite = false;
+            viewState.mMobileSite = false;
             // for non-mobile site, we don't need minPrefWidth, set it as 0
-            restoreState.mScrollX = 0;
+            viewState.mScrollX = 0;
             Message.obtain(mWebView.mPrivateHandler,
-                    WebView.UPDATE_ZOOM_RANGE, restoreState).sendToTarget();
+                    WebView.UPDATE_ZOOM_RANGE, viewState).sendToTarget();
             return;
         }
 
@@ -2111,32 +2132,31 @@
         } else {
             webViewWidth = Math.round(viewportWidth * mCurrentViewScale);
         }
-        mRestoreState = new RestoreState();
-        mRestoreState.mMinScale = mViewportMinimumScale / 100.0f;
-        mRestoreState.mMaxScale = mViewportMaximumScale / 100.0f;
-        mRestoreState.mDefaultScale = adjust;
-        mRestoreState.mScrollX = mRestoredX;
-        mRestoreState.mScrollY = mRestoredY;
-        mRestoreState.mMobileSite = (0 == mViewportWidth);
+        mInitialViewState = new ViewState();
+        mInitialViewState.mMinScale = mViewportMinimumScale / 100.0f;
+        mInitialViewState.mMaxScale = mViewportMaximumScale / 100.0f;
+        mInitialViewState.mDefaultScale = adjust;
+        mInitialViewState.mScrollX = mRestoredX;
+        mInitialViewState.mScrollY = mRestoredY;
+        mInitialViewState.mMobileSite = (0 == mViewportWidth);
         if (mRestoredScale > 0) {
-            mRestoreState.mViewScale = mRestoredScale / 100.0f;
-            if (mRestoredScreenWidthScale > 0) {
-                mRestoreState.mTextWrapScale =
-                        mRestoredScreenWidthScale / 100.0f;
+            mInitialViewState.mViewScale = mRestoredScale / 100.0f;
+            if (mRestoredTextWrapScale > 0) {
+                mInitialViewState.mTextWrapScale = mRestoredTextWrapScale / 100.0f;
             } else {
-                mRestoreState.mTextWrapScale = mRestoreState.mViewScale;
+                mInitialViewState.mTextWrapScale = mInitialViewState.mViewScale;
             }
         } else {
             if (mViewportInitialScale > 0) {
-                mRestoreState.mViewScale = mRestoreState.mTextWrapScale =
+                mInitialViewState.mViewScale = mInitialViewState.mTextWrapScale =
                         mViewportInitialScale / 100.0f;
             } else if (mViewportWidth > 0 && mViewportWidth < webViewWidth) {
-                mRestoreState.mViewScale = mRestoreState.mTextWrapScale =
+                mInitialViewState.mViewScale = mInitialViewState.mTextWrapScale =
                         (float) webViewWidth / mViewportWidth;
             } else {
-                mRestoreState.mTextWrapScale = adjust;
+                mInitialViewState.mTextWrapScale = adjust;
                 // 0 will trigger WebView to turn on zoom overview mode
-                mRestoreState.mViewScale = 0;
+                mInitialViewState.mViewScale = 0;
             }
         }
 
@@ -2177,15 +2197,15 @@
                 // mViewScale as 0 means it is in zoom overview mode. So we don't
                 // know the exact scale. If mRestoredScale is non-zero, use it;
                 // otherwise just use mTextWrapScale as the initial scale.
-                data.mScale = mRestoreState.mViewScale == 0
+                data.mScale = mInitialViewState.mViewScale == 0
                         ? (mRestoredScale > 0 ? mRestoredScale / 100.0f
-                                : mRestoreState.mTextWrapScale)
-                        : mRestoreState.mViewScale;
+                                : mInitialViewState.mTextWrapScale)
+                        : mInitialViewState.mViewScale;
                 if (DebugFlags.WEB_VIEW_CORE) {
                     Log.v(LOGTAG, "setupViewport"
                              + " mRestoredScale=" + mRestoredScale
-                             + " mViewScale=" + mRestoreState.mViewScale
-                             + " mTextWrapScale=" + mRestoreState.mTextWrapScale
+                             + " mViewScale=" + mInitialViewState.mViewScale
+                             + " mTextWrapScale=" + mInitialViewState.mTextWrapScale
                              );
                 }
                 data.mWidth = Math.round(webViewWidth / data.mScale);
@@ -2198,7 +2218,7 @@
                         Math.round(mWebView.getViewHeight() / data.mScale)
                         : mCurrentViewHeight * data.mWidth / viewportWidth;
                 data.mTextWrapWidth = Math.round(webViewWidth
-                        / mRestoreState.mTextWrapScale);
+                        / mInitialViewState.mTextWrapScale);
                 data.mIgnoreHeight = false;
                 data.mAnchorX = data.mAnchorY = 0;
                 // send VIEW_SIZE_CHANGED to the front of the queue so that we
@@ -2211,20 +2231,12 @@
     }
 
     // called by JNI
-    private void restoreScale(int scale) {
+    private void restoreScale(int scale, int textWrapScale) {
         if (mBrowserFrame.firstLayoutDone() == false) {
             mRestoredScale = scale;
-        }
-    }
-
-    // called by JNI
-    private void restoreScreenWidthScale(int scale) {
-        if (!mSettings.getUseWideViewPort()) {
-            return;
-        }
-
-        if (mBrowserFrame.firstLayoutDone() == false) {
-            mRestoredScreenWidthScale = scale;
+            if (mSettings.getUseWideViewPort()) {
+                mRestoredTextWrapScale = textWrapScale;
+            }
         }
     }
 
@@ -2469,4 +2481,6 @@
     private native boolean nativeValidNodeAndBounds(int frame, int node,
             Rect bounds);
 
+    private native ArrayList<Rect> nativeGetTouchHighlightRects(int x, int y,
+            int slop);
 }
diff --git a/core/java/android/webkit/WebViewDatabase.java b/core/java/android/webkit/WebViewDatabase.java
index b18419d..d75d421 100644
--- a/core/java/android/webkit/WebViewDatabase.java
+++ b/core/java/android/webkit/WebViewDatabase.java
@@ -173,112 +173,140 @@
 
     private static int mCacheTransactionRefcount;
 
-    private WebViewDatabase() {
+    // Initially true until the background thread completes.
+    private boolean mInitialized = false;
+
+    private WebViewDatabase(final Context context) {
+        new Thread() {
+            @Override
+            public void run() {
+                init(context);
+            }
+        }.start();
+
         // Singleton only, use getInstance()
     }
 
     public static synchronized WebViewDatabase getInstance(Context context) {
         if (mInstance == null) {
-            mInstance = new WebViewDatabase();
-            try {
-                mDatabase = context
-                        .openOrCreateDatabase(DATABASE_FILE, 0, null);
-            } catch (SQLiteException e) {
-                // try again by deleting the old db and create a new one
-                if (context.deleteDatabase(DATABASE_FILE)) {
-                    mDatabase = context.openOrCreateDatabase(DATABASE_FILE, 0,
-                            null);
-                }
-            }
+            mInstance = new WebViewDatabase(context);
+        }
+        return mInstance;
+    }
 
-            // mDatabase should not be null, 
-            // the only case is RequestAPI test has problem to create db 
-            if (mDatabase != null && mDatabase.getVersion() != DATABASE_VERSION) {
-                mDatabase.beginTransaction();
-                try {
-                    upgradeDatabase();
-                    mDatabase.setTransactionSuccessful();
-                } finally {
-                    mDatabase.endTransaction();
-                }
-            }
+    private synchronized void init(Context context) {
+        if (mInitialized) {
+            return;
+        }
 
-            if (mDatabase != null) {
-                // use per table Mutex lock, turn off database lock, this
-                // improves performance as database's ReentrantLock is expansive
-                mDatabase.setLockingEnabled(false);
-            }
-
-            try {
-                mCacheDatabase = context.openOrCreateDatabase(
-                        CACHE_DATABASE_FILE, 0, null);
-            } catch (SQLiteException e) {
-                // try again by deleting the old db and create a new one
-                if (context.deleteDatabase(CACHE_DATABASE_FILE)) {
-                    mCacheDatabase = context.openOrCreateDatabase(
-                            CACHE_DATABASE_FILE, 0, null);
-                }
-            }
-
-            // mCacheDatabase should not be null, 
-            // the only case is RequestAPI test has problem to create db 
-            if (mCacheDatabase != null
-                    && mCacheDatabase.getVersion() != CACHE_DATABASE_VERSION) {
-                mCacheDatabase.beginTransaction();
-                try {
-                    upgradeCacheDatabase();
-                    bootstrapCacheDatabase();
-                    mCacheDatabase.setTransactionSuccessful();
-                } finally {
-                    mCacheDatabase.endTransaction();
-                }
-                // Erase the files from the file system in the 
-                // case that the database was updated and the 
-                // there were existing cache content
-                CacheManager.removeAllCacheFiles();
-            }
-
-            if (mCacheDatabase != null) {
-                // use read_uncommitted to speed up READ
-                mCacheDatabase.execSQL("PRAGMA read_uncommitted = true;");
-                // as only READ can be called in the non-WebViewWorkerThread,
-                // and read_uncommitted is used, we can turn off database lock
-                // to use transaction.
-                mCacheDatabase.setLockingEnabled(false);
-
-                // use InsertHelper for faster insertion
-                mCacheInserter = new DatabaseUtils.InsertHelper(mCacheDatabase,
-                        "cache");
-                mCacheUrlColIndex = mCacheInserter
-                        .getColumnIndex(CACHE_URL_COL);
-                mCacheFilePathColIndex = mCacheInserter
-                        .getColumnIndex(CACHE_FILE_PATH_COL);
-                mCacheLastModifyColIndex = mCacheInserter
-                        .getColumnIndex(CACHE_LAST_MODIFY_COL);
-                mCacheETagColIndex = mCacheInserter
-                        .getColumnIndex(CACHE_ETAG_COL);
-                mCacheExpiresColIndex = mCacheInserter
-                        .getColumnIndex(CACHE_EXPIRES_COL);
-                mCacheExpiresStringColIndex = mCacheInserter
-                        .getColumnIndex(CACHE_EXPIRES_STRING_COL);
-                mCacheMimeTypeColIndex = mCacheInserter
-                        .getColumnIndex(CACHE_MIMETYPE_COL);
-                mCacheEncodingColIndex = mCacheInserter
-                        .getColumnIndex(CACHE_ENCODING_COL);
-                mCacheHttpStatusColIndex = mCacheInserter
-                        .getColumnIndex(CACHE_HTTP_STATUS_COL);
-                mCacheLocationColIndex = mCacheInserter
-                        .getColumnIndex(CACHE_LOCATION_COL);
-                mCacheContentLengthColIndex = mCacheInserter
-                        .getColumnIndex(CACHE_CONTENTLENGTH_COL);
-                mCacheContentDispositionColIndex = mCacheInserter
-                        .getColumnIndex(CACHE_CONTENTDISPOSITION_COL);
-                mCacheCrossDomainColIndex = mCacheInserter
-                        .getColumnIndex(CACHE_CROSSDOMAIN_COL);
+        try {
+            mDatabase = context.openOrCreateDatabase(DATABASE_FILE, 0, null);
+        } catch (SQLiteException e) {
+            // try again by deleting the old db and create a new one
+            if (context.deleteDatabase(DATABASE_FILE)) {
+                mDatabase = context.openOrCreateDatabase(DATABASE_FILE, 0,
+                        null);
             }
         }
 
-        return mInstance;
+        // mDatabase should not be null,
+        // the only case is RequestAPI test has problem to create db
+        if (mDatabase == null) {
+            mInitialized = true;
+            notify();
+            return;
+        }
+
+        if (mDatabase.getVersion() != DATABASE_VERSION) {
+            mDatabase.beginTransaction();
+            try {
+                upgradeDatabase();
+                mDatabase.setTransactionSuccessful();
+            } finally {
+                mDatabase.endTransaction();
+            }
+        }
+
+        // use per table Mutex lock, turn off database lock, this
+        // improves performance as database's ReentrantLock is
+        // expansive
+        mDatabase.setLockingEnabled(false);
+
+        try {
+            mCacheDatabase = context.openOrCreateDatabase(
+                    CACHE_DATABASE_FILE, 0, null);
+        } catch (SQLiteException e) {
+            // try again by deleting the old db and create a new one
+            if (context.deleteDatabase(CACHE_DATABASE_FILE)) {
+                mCacheDatabase = context.openOrCreateDatabase(
+                        CACHE_DATABASE_FILE, 0, null);
+            }
+        }
+
+        // mCacheDatabase should not be null,
+        // the only case is RequestAPI test has problem to create db
+        if (mCacheDatabase == null) {
+            mInitialized = true;
+            notify();
+            return;
+        }
+
+        if (mCacheDatabase.getVersion() != CACHE_DATABASE_VERSION) {
+            mCacheDatabase.beginTransaction();
+            try {
+                upgradeCacheDatabase();
+                bootstrapCacheDatabase();
+                mCacheDatabase.setTransactionSuccessful();
+            } finally {
+                mCacheDatabase.endTransaction();
+            }
+            // Erase the files from the file system in the
+            // case that the database was updated and the
+            // there were existing cache content
+            CacheManager.removeAllCacheFiles();
+        }
+
+        // use read_uncommitted to speed up READ
+        mCacheDatabase.execSQL("PRAGMA read_uncommitted = true;");
+        // as only READ can be called in the
+        // non-WebViewWorkerThread, and read_uncommitted is used,
+        // we can turn off database lock to use transaction.
+        mCacheDatabase.setLockingEnabled(false);
+
+        // use InsertHelper for faster insertion
+        mCacheInserter =
+                new DatabaseUtils.InsertHelper(mCacheDatabase,
+                        "cache");
+        mCacheUrlColIndex = mCacheInserter
+                            .getColumnIndex(CACHE_URL_COL);
+        mCacheFilePathColIndex = mCacheInserter
+                .getColumnIndex(CACHE_FILE_PATH_COL);
+        mCacheLastModifyColIndex = mCacheInserter
+                .getColumnIndex(CACHE_LAST_MODIFY_COL);
+        mCacheETagColIndex = mCacheInserter
+                .getColumnIndex(CACHE_ETAG_COL);
+        mCacheExpiresColIndex = mCacheInserter
+                .getColumnIndex(CACHE_EXPIRES_COL);
+        mCacheExpiresStringColIndex = mCacheInserter
+                .getColumnIndex(CACHE_EXPIRES_STRING_COL);
+        mCacheMimeTypeColIndex = mCacheInserter
+                .getColumnIndex(CACHE_MIMETYPE_COL);
+        mCacheEncodingColIndex = mCacheInserter
+                .getColumnIndex(CACHE_ENCODING_COL);
+        mCacheHttpStatusColIndex = mCacheInserter
+                .getColumnIndex(CACHE_HTTP_STATUS_COL);
+        mCacheLocationColIndex = mCacheInserter
+                .getColumnIndex(CACHE_LOCATION_COL);
+        mCacheContentLengthColIndex = mCacheInserter
+                .getColumnIndex(CACHE_CONTENTLENGTH_COL);
+        mCacheContentDispositionColIndex = mCacheInserter
+                .getColumnIndex(CACHE_CONTENTDISPOSITION_COL);
+        mCacheCrossDomainColIndex = mCacheInserter
+                .getColumnIndex(CACHE_CROSSDOMAIN_COL);
+
+        // Thread done, notify.
+        mInitialized = true;
+        notify();
     }
 
     private static void upgradeDatabase() {
@@ -391,8 +419,25 @@
         }
     }
 
+    // Wait for the background initialization thread to complete and check the
+    // database creation status.
+    private boolean checkInitialized() {
+        synchronized (this) {
+            while (!mInitialized) {
+                try {
+                    wait();
+                } catch (InterruptedException e) {
+                    Log.e(LOGTAG, "Caught exception while checking " +
+                                  "initialization");
+                    Log.e(LOGTAG, Log.getStackTraceString(e));
+                }
+            }
+        }
+        return mDatabase != null;
+    }
+
     private boolean hasEntries(int tableId) {
-        if (mDatabase == null) {
+        if (!checkInitialized()) {
             return false;
         }
 
@@ -422,7 +467,7 @@
      */
     ArrayList<Cookie> getCookiesForDomain(String domain) {
         ArrayList<Cookie> list = new ArrayList<Cookie>();
-        if (domain == null || mDatabase == null) {
+        if (domain == null || !checkInitialized()) {
             return list;
         }
 
@@ -481,7 +526,7 @@
      *            deleted.
      */
     void deleteCookies(String domain, String path, String name) {
-        if (domain == null || mDatabase == null) {
+        if (domain == null || !checkInitialized()) {
             return;
         }
 
@@ -501,7 +546,7 @@
      */
     void addCookie(Cookie cookie) {
         if (cookie.domain == null || cookie.path == null || cookie.name == null
-                || mDatabase == null) {
+                || !checkInitialized()) {
             return;
         }
 
@@ -534,7 +579,7 @@
      * Clear cookie database
      */
     void clearCookies() {
-        if (mDatabase == null) {
+        if (!checkInitialized()) {
             return;
         }
 
@@ -547,7 +592,7 @@
      * Clear session cookies, which means cookie doesn't have EXPIRES.
      */
     void clearSessionCookies() {
-        if (mDatabase == null) {
+        if (!checkInitialized()) {
             return;
         }
 
@@ -564,7 +609,7 @@
      * @param now Time for now
      */
     void clearExpiredCookies(long now) {
-        if (mDatabase == null) {
+        if (!checkInitialized()) {
             return;
         }
 
@@ -620,7 +665,7 @@
      * @return CacheResult The CacheManager.CacheResult
      */
     CacheResult getCache(String url) {
-        if (url == null || mCacheDatabase == null) {
+        if (url == null || !checkInitialized()) {
             return null;
         }
 
@@ -660,7 +705,7 @@
      * @param url The url
      */
     void removeCache(String url) {
-        if (url == null || mCacheDatabase == null) {
+        if (url == null || !checkInitialized()) {
             return;
         }
 
@@ -674,7 +719,7 @@
      * @param c The CacheManager.CacheResult
      */
     void addCache(String url, CacheResult c) {
-        if (url == null || mCacheDatabase == null) {
+        if (url == null || !checkInitialized()) {
             return;
         }
 
@@ -700,7 +745,7 @@
      * Clear cache database
      */
     void clearCache() {
-        if (mCacheDatabase == null) {
+        if (!checkInitialized()) {
             return;
         }
 
@@ -708,7 +753,7 @@
     }
 
     boolean hasCache() {
-        if (mCacheDatabase == null) {
+        if (!checkInitialized()) {
             return false;
         }
 
@@ -831,7 +876,7 @@
      */
     void setUsernamePassword(String schemePlusHost, String username,
                 String password) {
-        if (schemePlusHost == null || mDatabase == null) {
+        if (schemePlusHost == null || !checkInitialized()) {
             return;
         }
 
@@ -853,7 +898,7 @@
      *         String[1] is password. Return null if it can't find anything.
      */
     String[] getUsernamePassword(String schemePlusHost) {
-        if (schemePlusHost == null || mDatabase == null) {
+        if (schemePlusHost == null || !checkInitialized()) {
             return null;
         }
 
@@ -899,7 +944,7 @@
      * Clear password database
      */
     public void clearUsernamePassword() {
-        if (mDatabase == null) {
+        if (!checkInitialized()) {
             return;
         }
 
@@ -924,7 +969,7 @@
      */
     void setHttpAuthUsernamePassword(String host, String realm, String username,
             String password) {
-        if (host == null || realm == null || mDatabase == null) {
+        if (host == null || realm == null || !checkInitialized()) {
             return;
         }
 
@@ -949,7 +994,7 @@
      *         String[1] is password. Return null if it can't find anything.
      */
     String[] getHttpAuthUsernamePassword(String host, String realm) {
-        if (host == null || realm == null || mDatabase == null){
+        if (host == null || realm == null || !checkInitialized()){
             return null;
         }
 
@@ -996,7 +1041,7 @@
      * Clear HTTP authentication password database
      */
     public void clearHttpAuthUsernamePassword() {
-        if (mDatabase == null) {
+        if (!checkInitialized()) {
             return;
         }
 
@@ -1017,7 +1062,7 @@
      * @param formdata The form data in HashMap
      */
     void setFormData(String url, HashMap<String, String> formdata) {
-        if (url == null || formdata == null || mDatabase == null) {
+        if (url == null || formdata == null || !checkInitialized()) {
             return;
         }
 
@@ -1066,7 +1111,7 @@
      */
     ArrayList<String> getFormData(String url, String name) {
         ArrayList<String> values = new ArrayList<String>();
-        if (url == null || name == null || mDatabase == null) {
+        if (url == null || name == null || !checkInitialized()) {
             return values;
         }
 
@@ -1126,7 +1171,7 @@
      * Clear form database
      */
     public void clearFormData() {
-        if (mDatabase == null) {
+        if (!checkInitialized()) {
             return;
         }
 
diff --git a/core/java/android/webkit/ZoomControlBase.java b/core/java/android/webkit/ZoomControlBase.java
new file mode 100644
index 0000000..be9e8f3
--- /dev/null
+++ b/core/java/android/webkit/ZoomControlBase.java
@@ -0,0 +1,41 @@
+/*
+ * 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.webkit;
+
+interface ZoomControlBase {
+
+    /**
+     * Causes the on-screen zoom control to be made visible
+     */
+    public void show();
+
+    /**
+     * Causes the on-screen zoom control to disappear
+     */
+    public void hide();
+
+    /**
+     * Enables the control to update its state if necessary in response to a
+     * change in the pages zoom level. For example, if the max zoom level is
+     * reached then the control can disable the button for zooming in.
+     */
+    public void update();
+
+    /**
+     * Checks to see if the control is currently visible to the user.
+     */
+    public boolean isVisible();
+}
diff --git a/core/java/android/webkit/ZoomControlEmbedded.java b/core/java/android/webkit/ZoomControlEmbedded.java
new file mode 100644
index 0000000..c29e72b
--- /dev/null
+++ b/core/java/android/webkit/ZoomControlEmbedded.java
@@ -0,0 +1,117 @@
+/*
+ * 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.webkit;
+
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.Toast;
+import android.widget.ZoomButtonsController;
+
+class ZoomControlEmbedded implements ZoomControlBase {
+
+    private final ZoomManager mZoomManager;
+    private final WebView mWebView;
+
+    // The controller is lazily initialized in getControls() for performance.
+    private ZoomButtonsController mZoomButtonsController;
+
+    public ZoomControlEmbedded(ZoomManager zoomManager, WebView webView) {
+        mZoomManager = zoomManager;
+        mWebView = webView;
+    }
+
+    public void show() {
+        if (!getControls().isVisible() && !mZoomManager.isZoomScaleFixed()) {
+
+            mZoomButtonsController.setVisible(true);
+
+            WebSettings settings = mWebView.getSettings();
+            int count = settings.getDoubleTapToastCount();
+            if (mZoomManager.isInZoomOverview() && count > 0) {
+                settings.setDoubleTapToastCount(--count);
+                Toast.makeText(mWebView.getContext(),
+                        com.android.internal.R.string.double_tap_toast,
+                        Toast.LENGTH_LONG).show();
+            }
+        }
+    }
+
+    public void hide() {
+        if (mZoomButtonsController != null) {
+            mZoomButtonsController.setVisible(false);
+        }
+    }
+
+    public boolean isVisible() {
+        return mZoomButtonsController != null && mZoomButtonsController.isVisible();
+    }
+
+    public void update() {
+        if (mZoomButtonsController == null) {
+            return;
+        }
+
+        boolean canZoomIn = mZoomManager.canZoomIn();
+        boolean canZoomOut = mZoomManager.canZoomOut() && !mZoomManager.isInZoomOverview();
+        if (!canZoomIn && !canZoomOut) {
+            // Hide the zoom in and out buttons if the page cannot zoom
+            mZoomButtonsController.getZoomControls().setVisibility(View.GONE);
+        } else {
+            // Set each one individually, as a page may be able to zoom in or out
+            mZoomButtonsController.setZoomInEnabled(canZoomIn);
+            mZoomButtonsController.setZoomOutEnabled(canZoomOut);
+        }
+    }
+
+    private ZoomButtonsController getControls() {
+        if (mZoomButtonsController == null) {
+            mZoomButtonsController = new ZoomButtonsController(mWebView);
+            mZoomButtonsController.setOnZoomListener(new ZoomListener());
+            // ZoomButtonsController positions the buttons at the bottom, but in
+            // the middle. Change their layout parameters so they appear on the
+            // right.
+            View controls = mZoomButtonsController.getZoomControls();
+            ViewGroup.LayoutParams params = controls.getLayoutParams();
+            if (params instanceof FrameLayout.LayoutParams) {
+                ((FrameLayout.LayoutParams) params).gravity = Gravity.RIGHT;
+            }
+        }
+        return mZoomButtonsController;
+    }
+
+    private class ZoomListener implements ZoomButtonsController.OnZoomListener {
+
+        public void onVisibilityChanged(boolean visible) {
+            if (visible) {
+                mWebView.switchOutDrawHistory();
+                // Bring back the hidden zoom controls.
+                mZoomButtonsController.getZoomControls().setVisibility(View.VISIBLE);
+                update();
+            }
+        }
+
+        public void onZoom(boolean zoomIn) {
+            if (zoomIn) {
+                mWebView.zoomIn();
+            } else {
+                mWebView.zoomOut();
+            }
+            update();
+        }
+    }
+}
diff --git a/core/java/android/webkit/ZoomControlExternal.java b/core/java/android/webkit/ZoomControlExternal.java
new file mode 100644
index 0000000..d75313e
--- /dev/null
+++ b/core/java/android/webkit/ZoomControlExternal.java
@@ -0,0 +1,159 @@
+/*
+ * 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.webkit;
+
+import android.content.Context;
+import android.os.Handler;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.View.OnClickListener;
+import android.view.animation.AlphaAnimation;
+import android.widget.FrameLayout;
+
+@Deprecated
+class ZoomControlExternal implements ZoomControlBase {
+
+    // The time that the external controls are visible before fading away
+    private static final long ZOOM_CONTROLS_TIMEOUT =
+            ViewConfiguration.getZoomControlsTimeout();
+    // The view containing the external zoom controls
+    private ExtendedZoomControls mZoomControls;
+    private Runnable mZoomControlRunnable;
+    private final Handler mPrivateHandler = new Handler();
+
+    private final WebView mWebView;
+
+    public ZoomControlExternal(WebView webView) {
+        mWebView = webView;
+    }
+
+    public void show() {
+        if(mZoomControlRunnable != null) {
+            mPrivateHandler.removeCallbacks(mZoomControlRunnable);
+        }
+        getControls().show(true);
+        mPrivateHandler.postDelayed(mZoomControlRunnable, ZOOM_CONTROLS_TIMEOUT);
+    }
+
+    public void hide() {
+        if (mZoomControlRunnable != null) {
+            mPrivateHandler.removeCallbacks(mZoomControlRunnable);
+        }
+        if (mZoomControls != null) {
+            mZoomControls.hide();
+        }
+    }
+
+    public boolean isVisible() {
+        return mZoomControls != null && mZoomControls.isShown();
+    }
+
+    public void update() { }
+
+    public ExtendedZoomControls getControls() {
+        if (mZoomControls == null) {
+            mZoomControls = createZoomControls();
+
+            /*
+             * need to be set to VISIBLE first so that getMeasuredHeight() in
+             * {@link #onSizeChanged()} can return the measured value for proper
+             * layout.
+             */
+            mZoomControls.setVisibility(View.VISIBLE);
+            mZoomControlRunnable = new Runnable() {
+                public void run() {
+                    /* Don't dismiss the controls if the user has
+                     * focus on them. Wait and check again later.
+                     */
+                    if (!mZoomControls.hasFocus()) {
+                        mZoomControls.hide();
+                    } else {
+                        mPrivateHandler.removeCallbacks(mZoomControlRunnable);
+                        mPrivateHandler.postDelayed(mZoomControlRunnable,
+                                ZOOM_CONTROLS_TIMEOUT);
+                    }
+                }
+            };
+        }
+        return mZoomControls;
+    }
+
+    private ExtendedZoomControls createZoomControls() {
+        ExtendedZoomControls zoomControls = new ExtendedZoomControls(mWebView.getContext());
+        zoomControls.setOnZoomInClickListener(new OnClickListener() {
+            public void onClick(View v) {
+                // reset time out
+                mPrivateHandler.removeCallbacks(mZoomControlRunnable);
+                mPrivateHandler.postDelayed(mZoomControlRunnable, ZOOM_CONTROLS_TIMEOUT);
+                mWebView.zoomIn();
+            }
+        });
+        zoomControls.setOnZoomOutClickListener(new OnClickListener() {
+            public void onClick(View v) {
+                // reset time out
+                mPrivateHandler.removeCallbacks(mZoomControlRunnable);
+                mPrivateHandler.postDelayed(mZoomControlRunnable, ZOOM_CONTROLS_TIMEOUT);
+                mWebView.zoomOut();
+            }
+        });
+        return zoomControls;
+    }
+
+    private static class ExtendedZoomControls extends FrameLayout {
+
+        private android.widget.ZoomControls mPlusMinusZoomControls;
+
+        public ExtendedZoomControls(Context context) {
+            super(context, null);
+            LayoutInflater inflater = (LayoutInflater)
+                    context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+            inflater.inflate(com.android.internal.R.layout.zoom_magnify, this, true);
+            mPlusMinusZoomControls = (android.widget.ZoomControls) findViewById(
+                    com.android.internal.R.id.zoomControls);
+            findViewById(com.android.internal.R.id.zoomMagnify).setVisibility(
+                    View.GONE);
+        }
+
+        public void show(boolean showZoom) {
+            mPlusMinusZoomControls.setVisibility(showZoom ? View.VISIBLE : View.GONE);
+            fade(View.VISIBLE, 0.0f, 1.0f);
+        }
+
+        public void hide() {
+            fade(View.GONE, 1.0f, 0.0f);
+        }
+
+        private void fade(int visibility, float startAlpha, float endAlpha) {
+            AlphaAnimation anim = new AlphaAnimation(startAlpha, endAlpha);
+            anim.setDuration(500);
+            startAnimation(anim);
+            setVisibility(visibility);
+        }
+
+        public boolean hasFocus() {
+            return mPlusMinusZoomControls.hasFocus();
+        }
+
+        public void setOnZoomInClickListener(OnClickListener listener) {
+            mPlusMinusZoomControls.setOnZoomInClickListener(listener);
+        }
+
+        public void setOnZoomOutClickListener(OnClickListener listener) {
+            mPlusMinusZoomControls.setOnZoomOutClickListener(listener);
+        }
+    }
+}
diff --git a/core/java/android/webkit/ZoomManager.java b/core/java/android/webkit/ZoomManager.java
new file mode 100644
index 0000000..7f7f46e
--- /dev/null
+++ b/core/java/android/webkit/ZoomManager.java
@@ -0,0 +1,902 @@
+/*
+ * 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.webkit;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.graphics.Canvas;
+import android.graphics.Point;
+import android.os.Bundle;
+import android.os.SystemClock;
+import android.util.Log;
+import android.view.ScaleGestureDetector;
+import android.view.View;
+
+/**
+ * The ZoomManager is responsible for maintaining the WebView's current zoom
+ * level state.  It is also responsible for managing the on-screen zoom controls
+ * as well as any animation of the WebView due to zooming.
+ *
+ * Currently, there are two methods for animating the zoom of a WebView.
+ *
+ * (1) The first method is triggered by startZoomAnimation(...) and is a fixed
+ * length animation where the final zoom scale is known at startup.  This type of
+ * animation notifies webkit of the final scale BEFORE it animates. The animation
+ * is then done by scaling the CANVAS incrementally based on a stepping function.
+ *
+ * (2) The second method is triggered by a multi-touch pinch and the new scale
+ * is determined dynamically based on the user's gesture. This type of animation
+ * only notifies webkit of new scale AFTER the gesture is complete. The animation
+ * effect is achieved by scaling the VIEWS (both WebView and ViewManager.ChildView)
+ * to the new scale in response to events related to the user's gesture.
+ */
+class ZoomManager {
+
+    static final String LOGTAG = "webviewZoom";
+
+    private final WebView mWebView;
+    private final CallbackProxy mCallbackProxy;
+
+    // Widgets responsible for the on-screen zoom functions of the WebView.
+    private ZoomControlEmbedded mEmbeddedZoomControl;
+    private ZoomControlExternal mExternalZoomControl;
+
+    /*
+     * The scale factors that determine the upper and lower bounds for the
+     * default zoom scale.
+     */
+    protected static final float DEFAULT_MAX_ZOOM_SCALE_FACTOR = 4.00f;
+    protected static final float DEFAULT_MIN_ZOOM_SCALE_FACTOR = 0.25f;
+
+    // The default scale limits, which are dependent on the display density.
+    private float mDefaultMaxZoomScale;
+    private float mDefaultMinZoomScale;
+
+    // The actual scale limits, which can be set through a webpage's viewport
+    // meta-tag.
+    private float mMaxZoomScale;
+    private float mMinZoomScale;
+
+    // Locks the minimum ZoomScale to the value currently set in mMinZoomScale.
+    private boolean mMinZoomScaleFixed = true;
+
+    /*
+     * When loading a new page the WebView does not initially know the final
+     * width of the page. Therefore, when a new page is loaded in overview mode
+     * the overview scale is initialized to a default value. This flag is then
+     * set and used to notify the ZoomManager to take the width of the next
+     * picture from webkit and use that width to enter into zoom overview mode.
+     */
+    private boolean mInitialZoomOverview = false;
+
+    /*
+     * When in the zoom overview mode, the page's width is fully fit to the
+     * current window. Additionally while the page is in this state it is
+     * active, in other words, you can click to follow the links. We cache a
+     * boolean to enable us to quickly check whether or not we are in overview
+     * mode, but this value should only be modified by changes to the zoom
+     * scale.
+     */
+    private boolean mInZoomOverview = false;
+    private int mZoomOverviewWidth;
+    private float mInvZoomOverviewWidth;
+
+    /*
+     * These variables track the center point of the zoom and they are used to
+     * determine the point around which we should zoom. They are stored in view
+     * coordinates.
+     */
+    private float mZoomCenterX;
+    private float mZoomCenterY;
+
+    /*
+     * These values represent the point around which the screen should be
+     * centered after zooming. In other words it is used to determine the center
+     * point of the visible document after the page has finished zooming. This
+     * is important because the zoom may have potentially reflowed the text and
+     * we need to ensure the proper portion of the document remains on the
+     * screen.
+     */
+    private int mAnchorX;
+    private int mAnchorY;
+
+    // The scale factor that is used to determine the column width for text
+    private float mTextWrapScale;
+
+    /*
+     * The default zoom scale is the scale factor used when the user triggers a
+     * zoom in by double tapping on the WebView. The value is initially set
+     * based on the display density, but can be changed at any time via the
+     * WebSettings.
+     */
+    private float mDefaultScale;
+    private float mInvDefaultScale;
+
+    // the current computed zoom scale and its inverse.
+    private float mActualScale;
+    private float mInvActualScale;
+    
+    /*
+     * The initial scale for the WebView. 0 means default. If initial scale is
+     * greater than 0 the WebView starts with this value as its initial scale. The
+     * value is converted from an integer percentage so it is guarenteed to have
+     * no more than 2 significant digits after the decimal.  This restriction
+     * allows us to convert the scale back to the original percentage by simply
+     * multiplying the value by 100.
+     */
+    private float mInitialScale;
+
+    private static float MINIMUM_SCALE_INCREMENT = 0.01f;
+
+    /*
+     * The following member variables are only to be used for animating zoom. If
+     * mZoomScale is non-zero then we are in the middle of a zoom animation. The
+     * other variables are used as a cache (e.g. inverse) or as a way to store
+     * the state of the view prior to animating (e.g. initial scroll coords).
+     */
+    private float mZoomScale;
+    private float mInvInitialZoomScale;
+    private float mInvFinalZoomScale;
+    private int mInitialScrollX;
+    private int mInitialScrollY;
+    private long mZoomStart;
+
+    private static final int ZOOM_ANIMATION_LENGTH = 500;
+
+    // whether support multi-touch
+    private boolean mSupportMultiTouch;
+
+    // use the framework's ScaleGestureDetector to handle multi-touch
+    private ScaleGestureDetector mScaleDetector;
+    private boolean mPinchToZoomAnimating = false;
+
+    public ZoomManager(WebView webView, CallbackProxy callbackProxy) {
+        mWebView = webView;
+        mCallbackProxy = callbackProxy;
+
+        /*
+         * Ideally mZoomOverviewWidth should be mContentWidth. But sites like
+         * ESPN and Engadget always have wider mContentWidth no matter what the
+         * viewport size is.
+         */
+        setZoomOverviewWidth(WebView.DEFAULT_VIEWPORT_WIDTH);
+    }
+
+    /**
+     * Initialize both the default and actual zoom scale to the given density.
+     *
+     * @param density The logical density of the display. This is a scaling factor
+     * for the Density Independent Pixel unit, where one DIP is one pixel on an
+     * approximately 160 dpi screen (see android.util.DisplayMetrics.density).
+     */
+    public void init(float density) {
+        assert density > 0;
+
+        setDefaultZoomScale(density);
+        mActualScale = density;
+        mInvActualScale = 1 / density;
+        mTextWrapScale = density;
+    }
+
+    /**
+     * Update the default zoom scale using the given density. It will also reset
+     * the current min and max zoom scales to the default boundaries as well as
+     * ensure that the actual scale falls within those boundaries.
+     *
+     * @param density The logical density of the display. This is a scaling factor
+     * for the Density Independent Pixel unit, where one DIP is one pixel on an
+     * approximately 160 dpi screen (see android.util.DisplayMetrics.density).
+     */
+    public void updateDefaultZoomDensity(float density) {
+        assert density > 0;
+
+        if (Math.abs(density - mDefaultScale) > MINIMUM_SCALE_INCREMENT) {
+            // set the new default density
+            setDefaultZoomScale(density);
+            // adjust the scale if it falls outside the new zoom bounds
+            setZoomScale(mActualScale, true);
+        }
+    }
+
+    private void setDefaultZoomScale(float defaultScale) {
+        mDefaultScale = defaultScale;
+        mInvDefaultScale = 1 / defaultScale;
+        mDefaultMaxZoomScale = defaultScale * DEFAULT_MAX_ZOOM_SCALE_FACTOR;
+        mDefaultMinZoomScale = defaultScale * DEFAULT_MIN_ZOOM_SCALE_FACTOR;
+        mMaxZoomScale = mDefaultMaxZoomScale;
+        mMinZoomScale = mDefaultMinZoomScale;
+    }
+
+    public final float getScale() {
+        return mActualScale;
+    }
+
+    public final float getInvScale() {
+        return mInvActualScale;
+    }
+
+    public final float getTextWrapScale() {
+        return mTextWrapScale;
+    }
+
+    public final float getMaxZoomScale() {
+        return mMaxZoomScale;
+    }
+
+    public final float getMinZoomScale() {
+        return mMinZoomScale;
+    }
+
+    public final float getDefaultScale() {
+        return mDefaultScale;
+    }
+
+    public final float getInvDefaultScale() {
+        return mInvDefaultScale;
+    }
+
+    public final float getDefaultMaxZoomScale() {
+        return mDefaultMaxZoomScale;
+    }
+
+    public final float getDefaultMinZoomScale() {
+        return mDefaultMinZoomScale;
+    }
+
+    public final int getDocumentAnchorX() {
+        return mAnchorX;
+    }
+
+    public final int getDocumentAnchorY() {
+        return mAnchorY;
+    }
+
+    public final void clearDocumentAnchor() {
+        mAnchorX = mAnchorY = 0;
+    }
+
+    public final void setZoomCenter(float x, float y) {
+        mZoomCenterX = x;
+        mZoomCenterY = y;
+    }
+
+    public final void setInitialScaleInPercent(int scaleInPercent) {
+        mInitialScale = scaleInPercent * 0.01f;
+    }
+
+    public final float computeScaleWithLimits(float scale) {
+        if (scale < mMinZoomScale) {
+            scale = mMinZoomScale;
+        } else if (scale > mMaxZoomScale) {
+            scale = mMaxZoomScale;
+        }
+        return scale;
+    }
+
+    public final boolean isZoomScaleFixed() {
+        return mMinZoomScale >= mMaxZoomScale;
+    }
+
+    public static final boolean exceedsMinScaleIncrement(float scaleA, float scaleB) {
+        return Math.abs(scaleA - scaleB) >= MINIMUM_SCALE_INCREMENT;
+    }
+
+    public boolean willScaleTriggerZoom(float scale) {
+        return exceedsMinScaleIncrement(scale, mActualScale);
+    }
+
+    public final boolean canZoomIn() {
+        return mMaxZoomScale - mActualScale > MINIMUM_SCALE_INCREMENT;
+    }
+
+    public final boolean canZoomOut() {
+        return mActualScale - mMinZoomScale > MINIMUM_SCALE_INCREMENT;
+    }
+
+    public boolean zoomIn() {
+        return zoom(1.25f);
+    }
+
+    public boolean zoomOut() {
+        return zoom(0.8f);
+    }
+
+    // returns TRUE if zoom out succeeds and FALSE if no zoom changes.
+    private boolean zoom(float zoomMultiplier) {
+        // TODO: alternatively we can disallow this during draw history mode
+        mWebView.switchOutDrawHistory();
+        // Center zooming to the center of the screen.
+        mZoomCenterX = mWebView.getViewWidth() * .5f;
+        mZoomCenterY = mWebView.getViewHeight() * .5f;
+        mAnchorX = mWebView.viewToContentX((int) mZoomCenterX + mWebView.getScrollX());
+        mAnchorY = mWebView.viewToContentY((int) mZoomCenterY + mWebView.getScrollY());
+        return startZoomAnimation(mActualScale * zoomMultiplier, true);
+    }
+
+    /**
+     * Initiates an animated zoom of the WebView.
+     *
+     * @return true if the new scale triggered an animation and false otherwise.
+     */
+    public boolean startZoomAnimation(float scale, boolean reflowText) {
+        float oldScale = mActualScale;
+        mInitialScrollX = mWebView.getScrollX();
+        mInitialScrollY = mWebView.getScrollY();
+
+        // snap to DEFAULT_SCALE if it is close
+        if (!exceedsMinScaleIncrement(scale, mDefaultScale)) {
+            scale = mDefaultScale;
+        }
+
+        setZoomScale(scale, reflowText);
+
+        if (oldScale != mActualScale) {
+            // use mZoomPickerScale to see zoom preview first
+            mZoomStart = SystemClock.uptimeMillis();
+            mInvInitialZoomScale = 1.0f / oldScale;
+            mInvFinalZoomScale = 1.0f / mActualScale;
+            mZoomScale = mActualScale;
+            mWebView.onFixedLengthZoomAnimationStart();
+            mWebView.invalidate();
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * This method is called by the WebView's drawing code when a fixed length zoom
+     * animation is occurring. Its purpose is to animate the zooming of the canvas
+     * to the desired scale which was specified in startZoomAnimation(...).
+     *
+     * A fixed length animation begins when startZoomAnimation(...) is called and
+     * continues until the ZOOM_ANIMATION_LENGTH time has elapsed. During that
+     * interval each time the WebView draws it calls this function which is 
+     * responsible for generating the animation.
+     *
+     * Additionally, the WebView can check to see if such an animation is currently
+     * in progress by calling isFixedLengthAnimationInProgress().
+     */
+    public void animateZoom(Canvas canvas) {
+        if (mZoomScale == 0) {
+            Log.w(LOGTAG, "A WebView is attempting to perform a fixed length "
+                    + "zoom animation when no zoom is in progress");
+            return;
+        }
+
+        float zoomScale;
+        int interval = (int) (SystemClock.uptimeMillis() - mZoomStart);
+        if (interval < ZOOM_ANIMATION_LENGTH) {
+            float ratio = (float) interval / ZOOM_ANIMATION_LENGTH;
+            zoomScale = 1.0f / (mInvInitialZoomScale
+                    + (mInvFinalZoomScale - mInvInitialZoomScale) * ratio);
+            mWebView.invalidate();
+        } else {
+            zoomScale = mZoomScale;
+            // set mZoomScale to be 0 as we have finished animating
+            mZoomScale = 0;
+            mWebView.onFixedLengthZoomAnimationEnd();
+        }
+        // calculate the intermediate scroll position. Since we need to use
+        // zoomScale, we can't use the WebView's pinLocX/Y functions directly.
+        float scale = zoomScale * mInvInitialZoomScale;
+        int tx = Math.round(scale * (mInitialScrollX + mZoomCenterX) - mZoomCenterX);
+        tx = -WebView.pinLoc(tx, mWebView.getViewWidth(), Math.round(mWebView.getContentWidth()
+                * zoomScale)) + mWebView.getScrollX();
+        int titleHeight = mWebView.getTitleHeight();
+        int ty = Math.round(scale
+                * (mInitialScrollY + mZoomCenterY - titleHeight)
+                - (mZoomCenterY - titleHeight));
+        ty = -(ty <= titleHeight ? Math.max(ty, 0) : WebView.pinLoc(ty
+                - titleHeight, mWebView.getViewHeight(), Math.round(mWebView.getContentHeight()
+                * zoomScale)) + titleHeight) + mWebView.getScrollY();
+
+        canvas.translate(tx, ty);
+        canvas.scale(zoomScale, zoomScale);
+    }
+
+    public boolean isZoomAnimating() {
+        return isFixedLengthAnimationInProgress() || mPinchToZoomAnimating;
+    }
+
+    public boolean isFixedLengthAnimationInProgress() {
+        return mZoomScale != 0;
+    }
+
+    public void refreshZoomScale(boolean reflowText) {
+        setZoomScale(mActualScale, reflowText, true);
+    }
+
+    public void setZoomScale(float scale, boolean reflowText) {
+        setZoomScale(scale, reflowText, false);
+    }
+
+    private void setZoomScale(float scale, boolean reflowText, boolean force) {
+        final boolean isScaleLessThanMinZoom = scale < mMinZoomScale;
+        scale = computeScaleWithLimits(scale);
+
+        // determine whether or not we are in the zoom overview mode
+        if (isScaleLessThanMinZoom && mMinZoomScale < mDefaultScale) {
+            mInZoomOverview = true;
+        } else {
+            mInZoomOverview = !exceedsMinScaleIncrement(scale, getZoomOverviewScale());
+        }
+
+        if (reflowText) {
+            mTextWrapScale = scale;
+        }
+
+        if (scale != mActualScale || force) {
+            float oldScale = mActualScale;
+            float oldInvScale = mInvActualScale;
+
+            if (scale != mActualScale && !mPinchToZoomAnimating) {
+                mCallbackProxy.onScaleChanged(mActualScale, scale);
+            }
+
+            mActualScale = scale;
+            mInvActualScale = 1 / scale;
+
+            if (!mWebView.drawHistory()) {
+
+                // If history Picture is drawn, don't update scroll. They will
+                // be updated when we get out of that mode.
+                // update our scroll so we don't appear to jump
+                // i.e. keep the center of the doc in the center of the view
+                int oldX = mWebView.getScrollX();
+                int oldY = mWebView.getScrollY();
+                float ratio = scale * oldInvScale;
+                float sx = ratio * oldX + (ratio - 1) * mZoomCenterX;
+                float sy = ratio * oldY + (ratio - 1)
+                        * (mZoomCenterY - mWebView.getTitleHeight());
+
+                // Scale all the child views
+                mWebView.mViewManager.scaleAll();
+
+                // as we don't have animation for scaling, don't do animation
+                // for scrolling, as it causes weird intermediate state
+                int scrollX = mWebView.pinLocX(Math.round(sx));
+                int scrollY = mWebView.pinLocY(Math.round(sy));
+                if(!mWebView.updateScrollCoordinates(scrollX, scrollY)) {
+                    // the scroll position is adjusted at the beginning of the
+                    // zoom animation. But we want to update the WebKit at the
+                    // end of the zoom animation. See comments in onScaleEnd().
+                    mWebView.sendOurVisibleRect();
+                }
+            }
+
+            // if the we need to reflow the text then force the VIEW_SIZE_CHANGED
+            // event to be sent to WebKit
+            mWebView.sendViewSizeZoom(reflowText);
+        }
+    }
+
+    /**
+     * The double tap gesture can result in different behaviors depending on the
+     * content that is tapped.
+     *
+     * (1) PLUGINS: If the taps occur on a plugin then we maximize the plugin on
+     * the screen. If the plugin is already maximized then zoom the user into
+     * overview mode.
+     *
+     * (2) HTML/OTHER: If the taps occur outside a plugin then the following
+     * heuristic is used.
+     *   A. If the current scale is not the same as the text wrap scale and the
+     *      layout algorithm specifies the use of NARROW_COLUMNS, then fit to
+     *      column by reflowing the text.
+     *   B. If the page is not in overview mode then change to overview mode.
+     *   C. If the page is in overmode then change to the default scale.
+     */
+    public void handleDoubleTap(float lastTouchX, float lastTouchY) {
+        WebSettings settings = mWebView.getSettings();
+        if (settings == null || settings.getUseWideViewPort() == false) {
+            return;
+        }
+
+        setZoomCenter(lastTouchX, lastTouchY);
+        mAnchorX = mWebView.viewToContentX((int) lastTouchX + mWebView.getScrollX());
+        mAnchorY = mWebView.viewToContentY((int) lastTouchY + mWebView.getScrollY());
+        settings.setDoubleTapToastCount(0);
+
+        // remove the zoom control after double tap
+        dismissZoomPicker();
+
+        /*
+         * If the double tap was on a plugin then either zoom to maximize the
+         * plugin on the screen or scale to overview mode.
+         */
+        ViewManager.ChildView plugin = mWebView.mViewManager.hitTest(mAnchorX, mAnchorY);
+        if (plugin != null) {
+            if (mWebView.isPluginFitOnScreen(plugin)) {
+                zoomToOverview();
+            } else {
+                mWebView.centerFitRect(plugin.x, plugin.y, plugin.width, plugin.height);
+            }
+            return;
+        }
+
+        if (settings.getLayoutAlgorithm() == WebSettings.LayoutAlgorithm.NARROW_COLUMNS
+                && willScaleTriggerZoom(mTextWrapScale)) {
+            refreshZoomScale(true);
+        } else if (!mInZoomOverview) {
+            zoomToOverview();
+        } else {
+            zoomToDefaultLevel();
+        }
+    }
+
+    private void setZoomOverviewWidth(int width) {
+        mZoomOverviewWidth = width;
+        mInvZoomOverviewWidth = 1.0f / width;
+    }
+
+    private float getZoomOverviewScale() {
+        return mWebView.getViewWidth() * mInvZoomOverviewWidth;
+    }
+
+    public boolean isInZoomOverview() {
+        return mInZoomOverview;
+    }
+
+    private void zoomToOverview() {
+        if (!willScaleTriggerZoom(getZoomOverviewScale())) return;
+
+        // Force the titlebar fully reveal in overview mode
+        int scrollY = mWebView.getScrollY();
+        if (scrollY < mWebView.getTitleHeight()) {
+            mWebView.updateScrollCoordinates(mWebView.getScrollX(), 0);
+        }
+        startZoomAnimation(getZoomOverviewScale(), true);
+    }
+
+    private void zoomToDefaultLevel() {
+        int left = mWebView.nativeGetBlockLeftEdge(mAnchorX, mAnchorY, mActualScale);
+        if (left != WebView.NO_LEFTEDGE) {
+            // add a 5pt padding to the left edge.
+            int viewLeft = mWebView.contentToViewX(left < 5 ? 0 : (left - 5))
+                    - mWebView.getScrollX();
+            // Re-calculate the zoom center so that the new scroll x will be
+            // on the left edge.
+            if (viewLeft > 0) {
+                mZoomCenterX = viewLeft * mDefaultScale / (mDefaultScale - mActualScale);
+            } else {
+                mWebView.scrollBy(viewLeft, 0);
+                mZoomCenterX = 0;
+            }
+        }
+        startZoomAnimation(mDefaultScale, true);
+    }
+
+    public void updateMultiTouchSupport(Context context) {
+        // check the preconditions
+        assert mWebView.getSettings() != null;
+
+        WebSettings settings = mWebView.getSettings();
+        mSupportMultiTouch = context.getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH)
+                && settings.supportZoom() && settings.getBuiltInZoomControls();
+        if (mSupportMultiTouch && (mScaleDetector == null)) {
+            mScaleDetector = new ScaleGestureDetector(context, new ScaleDetectorListener());
+        } else if (!mSupportMultiTouch && (mScaleDetector != null)) {
+            mScaleDetector = null;
+        }
+    }
+
+    public boolean supportsMultiTouchZoom() {
+        return mSupportMultiTouch;
+    }
+
+    /**
+     * Notifies the caller that the ZoomManager is requesting that scale related
+     * updates should not be sent to webkit. This can occur in cases where the
+     * ZoomManager is performing an animation and does not want webkit to update
+     * until the animation is complete.
+     *
+     * @return true if scale related updates should not be sent to webkit and
+     *         false otherwise.
+     */
+    public boolean isPreventingWebkitUpdates() {
+        // currently only animating a multi-touch zoom prevents updates, but
+        // others can add their own conditions to this method if necessary.
+        return mPinchToZoomAnimating;
+    }
+
+    public ScaleGestureDetector getMultiTouchGestureDetector() {
+        return mScaleDetector;
+    }
+
+    private class ScaleDetectorListener implements ScaleGestureDetector.OnScaleGestureListener {
+
+        public boolean onScaleBegin(ScaleGestureDetector detector) {
+            dismissZoomPicker();
+            mWebView.mViewManager.startZoom();
+            mWebView.onPinchToZoomAnimationStart();
+            return true;
+        }
+
+        public boolean onScale(ScaleGestureDetector detector) {
+            float scale = Math.round(detector.getScaleFactor() * mActualScale * 100) * 0.01f;
+            if (willScaleTriggerZoom(scale)) {
+                mPinchToZoomAnimating = true;
+                // limit the scale change per step
+                if (scale > mActualScale) {
+                    scale = Math.min(scale, mActualScale * 1.25f);
+                } else {
+                    scale = Math.max(scale, mActualScale * 0.8f);
+                }
+                setZoomCenter(detector.getFocusX(), detector.getFocusY());
+                setZoomScale(scale, false);
+                mWebView.invalidate();
+                return true;
+            }
+            return false;
+        }
+
+        public void onScaleEnd(ScaleGestureDetector detector) {
+            if (mPinchToZoomAnimating) {
+                mPinchToZoomAnimating = false;
+                mAnchorX = mWebView.viewToContentX((int) mZoomCenterX + mWebView.getScrollX());
+                mAnchorY = mWebView.viewToContentY((int) mZoomCenterY + mWebView.getScrollY());
+                // don't reflow when zoom in; when zoom out, do reflow if the
+                // new scale is almost minimum scale;
+                boolean reflowNow = !canZoomOut() || (mActualScale <= 0.8 * mTextWrapScale);
+                // force zoom after mPreviewZoomOnly is set to false so that the
+                // new view size will be passed to the WebKit
+                refreshZoomScale(reflowNow);
+                // call invalidate() to draw without zoom filter
+                mWebView.invalidate();
+            }
+
+            mWebView.mViewManager.endZoom();
+            mWebView.onPinchToZoomAnimationEnd(detector);
+        }
+    }
+
+    public void onSizeChanged(int w, int h, int ow, int oh) {
+        // reset zoom and anchor to the top left corner of the screen
+        // unless we are already zooming
+        if (!isFixedLengthAnimationInProgress()) {
+            int visibleTitleHeight = mWebView.getVisibleTitleHeight();
+            mZoomCenterX = 0;
+            mZoomCenterY = visibleTitleHeight;
+            mAnchorX = mWebView.viewToContentX(mWebView.getScrollX());
+            mAnchorY = mWebView.viewToContentY(visibleTitleHeight + mWebView.getScrollY());
+        }
+
+        // update mMinZoomScale if the minimum zoom scale is not fixed
+        if (!mMinZoomScaleFixed) {
+            // when change from narrow screen to wide screen, the new viewWidth
+            // can be wider than the old content width. We limit the minimum
+            // scale to 1.0f. The proper minimum scale will be calculated when
+            // the new picture shows up.
+            mMinZoomScale = Math.min(1.0f, (float) mWebView.getViewWidth()
+                    / (mWebView.drawHistory() ? mWebView.getHistoryPictureWidth()
+                            : mZoomOverviewWidth));
+            // limit the minZoomScale to the initialScale if it is set
+            if (mInitialScale > 0 && mInitialScale < mMinZoomScale) {
+                mMinZoomScale = mInitialScale;
+            }
+        }
+
+        dismissZoomPicker();
+
+        // onSizeChanged() is called during WebView layout. And any
+        // requestLayout() is blocked during layout. As refreshZoomScale() will
+        // cause its child View to reposition itself through ViewManager's
+        // scaleAll(), we need to post a Runnable to ensure requestLayout().
+        // Additionally, only update the text wrap scale if the width changed.
+        mWebView.post(new PostScale(w != ow));
+    }
+
+    private class PostScale implements Runnable {
+        final boolean mUpdateTextWrap;
+
+        public PostScale(boolean updateTextWrap) {
+            mUpdateTextWrap = updateTextWrap;
+        }
+
+        public void run() {
+            if (mWebView.getWebViewCore() != null) {
+                // we always force, in case our height changed, in which case we
+                // still want to send the notification over to webkit.
+                refreshZoomScale(mUpdateTextWrap);
+                // update the zoom buttons as the scale can be changed
+                updateZoomPicker();
+            }
+        }
+    }
+
+    public void updateZoomRange(WebViewCore.ViewState viewState,
+            int viewWidth, int minPrefWidth) {
+        if (viewState.mMinScale == 0) {
+            if (viewState.mMobileSite) {
+                if (minPrefWidth > Math.max(0, viewWidth)) {
+                    mMinZoomScale = (float) viewWidth / minPrefWidth;
+                    mMinZoomScaleFixed = false;
+                } else {
+                    mMinZoomScale = viewState.mDefaultScale;
+                    mMinZoomScaleFixed = true;
+                }
+            } else {
+                mMinZoomScale = mDefaultMinZoomScale;
+                mMinZoomScaleFixed = false;
+            }
+        } else {
+            mMinZoomScale = viewState.mMinScale;
+            mMinZoomScaleFixed = true;
+        }
+        if (viewState.mMaxScale == 0) {
+            mMaxZoomScale = mDefaultMaxZoomScale;
+        } else {
+            mMaxZoomScale = viewState.mMaxScale;
+        }
+    }
+
+    /**
+     * Updates zoom values when Webkit produces a new picture. This method
+     * should only be called from the UI thread's message handler.
+     */
+    public void onNewPicture(WebViewCore.DrawData drawData) {
+        final int viewWidth = mWebView.getViewWidth();
+
+        if (mWebView.getSettings().getUseWideViewPort()) {
+            // limit mZoomOverviewWidth upper bound to
+            // sMaxViewportWidth so that if the page doesn't behave
+            // well, the WebView won't go insane. limit the lower
+            // bound to match the default scale for mobile sites.
+            setZoomOverviewWidth(Math.min(WebView.sMaxViewportWidth,
+                    Math.max((int) (viewWidth * mInvDefaultScale),
+                            Math.max(drawData.mMinPrefWidth, drawData.mViewPoint.x))));
+        }
+
+        final float zoomOverviewScale = getZoomOverviewScale();
+        if (!mMinZoomScaleFixed) {
+            mMinZoomScale = zoomOverviewScale;
+        }
+        // fit the content width to the current view. Ignore the rounding error case.
+        if (!mWebView.drawHistory() && (mInitialZoomOverview || (mInZoomOverview
+                && Math.abs((viewWidth * mInvActualScale) - mZoomOverviewWidth) > 1))) {
+            mInitialZoomOverview = false;
+            setZoomScale(zoomOverviewScale, !willScaleTriggerZoom(mTextWrapScale));
+        }
+    }
+
+    /**
+     * Updates zoom values after Webkit completes the initial page layout. It
+     * is called when visiting a page for the first time as well as when the
+     * user navigates back to a page (in which case we may need to restore the
+     * zoom levels to the state they were when you left the page). This method
+     * should only be called from the UI thread's message handler.
+     */
+    public void onFirstLayout(WebViewCore.DrawData drawData) {
+        // precondition check
+        assert drawData != null;
+        assert drawData.mViewState != null;
+        assert mWebView.getSettings() != null;
+
+        WebViewCore.ViewState viewState = drawData.mViewState;
+        final Point viewSize = drawData.mViewPoint;
+        updateZoomRange(viewState, viewSize.x, drawData.mMinPrefWidth);
+
+        if (!mWebView.drawHistory()) {
+            final float scale;
+            final boolean reflowText;
+
+            if (mInitialScale > 0) {
+                scale = mInitialScale;
+                reflowText = exceedsMinScaleIncrement(mTextWrapScale, scale);
+            } else if (viewState.mViewScale > 0) {
+                mTextWrapScale = viewState.mTextWrapScale;
+                scale = viewState.mViewScale;
+                reflowText = false;
+            } else {
+                WebSettings settings = mWebView.getSettings();
+                if (settings.getUseWideViewPort() && settings.getLoadWithOverviewMode()) {
+                    mInitialZoomOverview = true;
+                    scale = (float) mWebView.getViewWidth() / WebView.DEFAULT_VIEWPORT_WIDTH;
+                } else {
+                    scale = viewState.mTextWrapScale;
+                }
+                reflowText = exceedsMinScaleIncrement(mTextWrapScale, scale);
+            }
+            setZoomScale(scale, reflowText);
+
+            // update the zoom buttons as the scale can be changed
+            updateZoomPicker();
+        }
+    }
+
+    public void saveZoomState(Bundle b) {
+        b.putFloat("scale", mActualScale);
+        b.putFloat("textwrapScale", mTextWrapScale);
+        b.putBoolean("overview", mInZoomOverview);
+    }
+
+    public void restoreZoomState(Bundle b) {
+        // as getWidth() / getHeight() of the view are not available yet, set up
+        // mActualScale, so that when onSizeChanged() is called, the rest will
+        // be set correctly
+        mActualScale = b.getFloat("scale", 1.0f);
+        mInvActualScale = 1 / mActualScale;
+        mTextWrapScale = b.getFloat("textwrapScale", mActualScale);
+        mInZoomOverview = b.getBoolean("overview");
+    }
+
+    private ZoomControlBase getCurrentZoomControl() {
+        if (mWebView.getSettings() != null && mWebView.getSettings().supportZoom()) {
+            if (mWebView.getSettings().getBuiltInZoomControls()) {
+                if (mEmbeddedZoomControl == null) {
+                    mEmbeddedZoomControl = new ZoomControlEmbedded(this, mWebView);
+                }
+                return mEmbeddedZoomControl;
+            } else {
+                if (mExternalZoomControl == null) {
+                    mExternalZoomControl = new ZoomControlExternal(mWebView);
+                }
+                return mExternalZoomControl;
+            }
+        }
+        return null;
+    }
+
+    public void invokeZoomPicker() {
+        ZoomControlBase control = getCurrentZoomControl();
+        if (control != null) {
+            control.show();
+        }
+    }
+
+    public void dismissZoomPicker() {
+        ZoomControlBase control = getCurrentZoomControl();
+        if (control != null) {
+            control.hide();
+        }
+    }
+
+    public boolean isZoomPickerVisible() {
+        ZoomControlBase control = getCurrentZoomControl();
+        return (control != null) ? control.isVisible() : false;
+    }
+
+    public void updateZoomPicker() {
+        ZoomControlBase control = getCurrentZoomControl();
+        if (control != null) {
+            control.update();
+        }
+    }
+
+    /**
+     * The embedded zoom control intercepts touch events and automatically stays
+     * visible. The external control needs to constantly refresh its internal
+     * timer to stay visible.
+     */
+    public void keepZoomPickerVisible() {
+        ZoomControlBase control = getCurrentZoomControl();
+        if (control != null && control == mExternalZoomControl) {
+            control.show();
+        }
+    }
+
+    public View getExternalZoomPicker() {
+        ZoomControlBase control = getCurrentZoomControl();
+        if (control != null && control == mExternalZoomControl) {
+            return mExternalZoomControl.getControls();
+        } else {
+            return null;
+        }
+    }
+}
diff --git a/core/java/android/webruntime/WebRuntimeActivity.java b/core/java/android/webruntime/WebRuntimeActivity.java
new file mode 100644
index 0000000..ec8c60c
--- /dev/null
+++ b/core/java/android/webruntime/WebRuntimeActivity.java
@@ -0,0 +1,204 @@
+/*
+ * 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.webruntime;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.content.ComponentName;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.net.Uri;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.Menu;
+import android.view.View;
+import android.view.Window;
+import android.webkit.GeolocationPermissions;
+import android.webkit.WebChromeClient;
+import android.webkit.WebView;
+import android.webkit.WebViewClient;
+import android.widget.ImageView;
+import android.widget.Toast;
+
+import com.android.internal.R;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+
+/**
+ * The runtime used to display installed web applications.
+ * @hide
+ */
+public class WebRuntimeActivity extends Activity
+{
+    private final static String LOGTAG = "WebRuntimeActivity";
+
+    private WebView mWebView;
+    private URL mBaseUrl;
+    private ImageView mSplashScreen;
+
+    public static class SensitiveFeatures {
+        // All of the sensitive features
+        private boolean mGeolocation;
+        // On Android, the Browser doesn't prompt for database access, so we don't require an
+        // explicit permission here in the WebRuntimeActivity, and there's no Android system
+        // permission required for it either.
+        //private boolean mDatabase;
+
+        public boolean getGeolocation() {
+            return mGeolocation;
+        }
+        public void setGeolocation(boolean geolocation) {
+            mGeolocation = geolocation;
+        }
+    }
+
+    /** Called when the activity is first created. */
+    @Override
+    public void onCreate(Bundle savedInstanceState)
+    {
+        super.onCreate(savedInstanceState);
+
+        // Can't get meta data using getApplicationInfo() as it doesn't pass GET_META_DATA
+        PackageManager packageManager = getPackageManager();
+        ComponentName componentName = new ComponentName(this, getClass());
+        ActivityInfo activityInfo = null;
+        try {
+            activityInfo = packageManager.getActivityInfo(componentName, PackageManager.GET_META_DATA);
+        } catch (PackageManager.NameNotFoundException e) {
+            Log.d(LOGTAG, "Failed to find component");
+            return;
+        }
+        if (activityInfo == null) {
+            Log.d(LOGTAG, "Failed to get activity info");
+            return;
+        }
+
+        Bundle metaData = activityInfo.metaData;
+        if (metaData == null) {
+            Log.d(LOGTAG, "No meta data");
+            return;
+        }
+
+        String url = metaData.getString("android.webruntime.url");
+        if (url == null) {
+            Log.d(LOGTAG, "No URL");
+            return;
+        }
+
+        try {
+            mBaseUrl = new URL(url);
+        } catch (MalformedURLException e) {
+            Log.d(LOGTAG, "Invalid URL");
+        }
+
+        // All false by default, and reading non-existent bundle properties gives false too.
+        final SensitiveFeatures sensitiveFeatures = new SensitiveFeatures();
+        sensitiveFeatures.setGeolocation(metaData.getBoolean("android.webruntime.SensitiveFeaturesGeolocation"));
+
+        getWindow().requestFeature(Window.FEATURE_NO_TITLE);
+        setContentView(R.layout.web_runtime);
+        mWebView = (WebView) findViewById(R.id.webview);
+        mSplashScreen = (ImageView) findViewById(R.id.splashscreen);
+        mSplashScreen.setImageResource(
+                getResources().getIdentifier("splash_screen", "drawable", getPackageName()));
+        mWebView.getSettings().setJavaScriptEnabled(true);
+        mWebView.setWebViewClient(new WebViewClient() {
+            @Override
+            public boolean shouldOverrideUrlLoading(WebView view, String url) {
+                try {
+                    URL newOrigin = new URL(url);
+                    if (areSameOrigin(mBaseUrl, newOrigin)) {
+                        // If simple same origin test passes, load in the webview.
+                        return false;
+                    }
+                } catch(MalformedURLException e) {
+                    // Don't load anything if this wasn't a proper URL.
+                    return true;
+                }
+
+                // Otherwise this is a URL that is not same origin so pass it to the
+                // Browser to load.
+                startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url)));
+                return true;
+            }
+
+            @Override
+            public void onPageFinished(WebView view, String url) {
+                if (mSplashScreen != null && mSplashScreen.getVisibility() == View.VISIBLE) {
+                    mSplashScreen.setVisibility(View.GONE);
+                    mSplashScreen = null;
+                }
+            }
+        });
+
+        // Use a custom WebChromeClient with geolocation permissions handling.
+        mWebView.setWebChromeClient(new WebChromeClient() {
+            public void onGeolocationPermissionsShowPrompt(
+                        String origin, GeolocationPermissions.Callback callback) {
+                // Allow this origin if it has Geolocation permissions, otherwise deny.
+                boolean allowed = false;
+                if (sensitiveFeatures.getGeolocation()) {
+                    try {
+                        URL originUrl = new URL(origin);
+                        allowed = areSameOrigin(mBaseUrl, originUrl);
+                    } catch(MalformedURLException e) {
+                    }
+                }
+                callback.invoke(origin, allowed, false);
+            }
+        });
+
+        // Set the DB location. Optional. Geolocation works without DBs.
+        mWebView.getSettings().setGeolocationDatabasePath(
+                getDir("geolocation", MODE_PRIVATE).getPath());
+
+        String title = metaData.getString("android.webruntime.title");
+        // We turned off the title bar to go full screen so display the
+        // webapp's title as a toast.
+        if (title != null) {
+            Toast.makeText(this, title, Toast.LENGTH_SHORT).show();
+        }
+
+        // Load the webapp's base URL.
+        mWebView.loadUrl(url);
+    }
+
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        if (keyCode == KeyEvent.KEYCODE_BACK && mWebView.canGoBack()) {
+            mWebView.goBack();
+            return true;
+        }
+        return super.onKeyDown(keyCode, event);
+    }
+
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        menu.add(0, 0, 0, "Menu item 1");
+        menu.add(0, 1, 0, "Menu item 2");
+        return true;
+    }
+
+    private static boolean areSameOrigin(URL a, URL b) {
+        int aPort = a.getPort() == -1 ? a.getDefaultPort() : a.getPort();
+        int bPort = b.getPort() == -1 ? b.getDefaultPort() : b.getPort();
+        return a.getProtocol().equals(b.getProtocol()) && aPort == bPort && a.getHost().equals(b.getHost());
+    }
+}
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index 6cfeb68..372cc83 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -19,6 +19,7 @@
 import com.android.internal.R;
 
 import android.content.Context;
+import android.content.Intent;
 import android.content.res.TypedArray;
 import android.graphics.Canvas;
 import android.graphics.Rect;
@@ -71,7 +72,8 @@
  */
 public abstract class AbsListView extends AdapterView<ListAdapter> implements TextWatcher,
         ViewTreeObserver.OnGlobalLayoutListener, Filter.FilterListener,
-        ViewTreeObserver.OnTouchModeChangeListener {
+        ViewTreeObserver.OnTouchModeChangeListener,
+        RemoteViewsAdapter.RemoteAdapterConnectionCallback {
 
     /**
      * Disables the transcript mode.
@@ -180,6 +182,11 @@
     ListAdapter mAdapter;
 
     /**
+     * The remote adapter containing the data to be displayed by this view to be set
+     */
+    private RemoteViewsAdapter mRemoteAdapter;
+
+    /**
      * Indicates whether the list selector should be drawn on top of the children or behind
      */
     boolean mDrawSelectorOnTop = false;
@@ -559,6 +566,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();
     }
@@ -1575,6 +1592,11 @@
                 treeObserver.addOnGlobalLayoutListener(this);
             }
         }
+        
+        if (mAdapter != null && mDataSetObserver == null) {
+            mDataSetObserver = new AdapterDataSetObserver();
+            mAdapter.registerDataSetObserver(mDataSetObserver);
+        }
     }
 
     @Override
@@ -1595,6 +1617,11 @@
                 mGlobalLayoutListenerAddedFilter = false;
             }
         }
+
+        if (mAdapter != null) {
+            mAdapter.unregisterDataSetObserver(mDataSetObserver);
+            mDataSetObserver = null;
+        }
     }
 
     @Override
@@ -1745,7 +1772,7 @@
         }
     }
 
-    private boolean performLongPress(final View child,
+    boolean performLongPress(final View child,
             final int longPressPosition, final long longPressId) {
         boolean handled = false;
 
@@ -2521,6 +2548,7 @@
         private static final int MOVE_UP_POS = 2;
         private static final int MOVE_DOWN_BOUND = 3;
         private static final int MOVE_UP_BOUND = 4;
+        private static final int MOVE_OFFSET = 5;
         
         private int mMode;
         private int mTargetPos;
@@ -2528,6 +2556,8 @@
         private int mLastSeenPos;
         private int mScrollDuration;
         private final int mExtraScroll;
+
+        private int mOffsetFromTop;
         
         PositionScroller() {
             mExtraScroll = ViewConfiguration.get(mContext).getScaledFadingEdgeLength();
@@ -2619,12 +2649,46 @@
             
             post(this);
         }
-        
+
+        void startWithOffset(int position, int offset) {
+            mTargetPos = position;
+            mOffsetFromTop = offset;
+            mBoundPos = INVALID_POSITION;
+            mLastSeenPos = INVALID_POSITION;
+            mMode = MOVE_OFFSET;
+
+            final int firstPos = mFirstPosition;
+            final int childCount = getChildCount();
+            final int lastPos = firstPos + childCount - 1;
+
+            int viewTravelCount = 0;
+            if (position < firstPos) {
+                viewTravelCount = firstPos - position;
+            } else if (position > lastPos) {
+                viewTravelCount = position - lastPos;
+            } else {
+                // On-screen, just scroll.
+                final int targetTop = getChildAt(position - firstPos).getTop();
+                smoothScrollBy(targetTop - offset, SCROLL_DURATION);
+                return;
+            }
+
+            // Estimate how many screens we should travel
+            final float screenTravelCount = viewTravelCount / childCount;
+            mScrollDuration = (int) (SCROLL_DURATION / screenTravelCount);
+            mLastSeenPos = INVALID_POSITION;
+            post(this);
+        }
+
         void stop() {
             removeCallbacks(this);
         }
-        
+
         public void run() {
+            if (mTouchMode != TOUCH_MODE_FLING && mLastSeenPos != INVALID_POSITION) {
+                return;
+            }
+
             final int listHeight = getHeight();
             final int firstPos = mFirstPosition;
             
@@ -2749,6 +2813,27 @@
                 break;
             }
 
+            case MOVE_OFFSET: {
+                final int childCount = getChildCount();
+
+                mLastSeenPos = firstPos;
+                final int position = mTargetPos;
+                final int lastPos = firstPos + childCount - 1;
+
+                if (position < firstPos) {
+                    smoothScrollBy(-getHeight(), mScrollDuration);
+                    post(this);
+                } else if (position > lastPos) {
+                    smoothScrollBy(getHeight(), mScrollDuration);
+                    post(this);
+                } else {
+                    // On-screen, just scroll.
+                    final int targetTop = getChildAt(position - firstPos).getTop();
+                    smoothScrollBy(targetTop - mOffsetFromTop, mScrollDuration);
+                }
+                break;
+            }
+
             default:
                 break;
             }
@@ -2768,6 +2853,24 @@
     }
     
     /**
+     * Smoothly scroll to the specified adapter position. The view will scroll
+     * such that the indicated position is displayed <code>offset</code> pixels from
+     * the top edge of the view. If this is impossible, (e.g. the offset would scroll
+     * the first or last item beyond the boundaries of the list) it will get as close
+     * as possible.
+     *
+     * @param position Position to scroll to
+     * @param offset Desired distance in pixels of <code>position</code> from the top
+     *               of the view when scrolling is finished
+     */
+    public void smoothScrollToPositionFromTop(int position, int offset) {
+        if (mPositionScroller == null) {
+            mPositionScroller = new PositionScroller();
+        }
+        mPositionScroller.startWithOffset(position, offset);
+    }
+
+    /**
      * Smoothly scroll to the specified adapter position. The view will
      * scroll such that the indicated position is displayed, but it will
      * stop early if scrolling further would scroll boundPosition out of
@@ -2797,6 +2900,42 @@
         mFlingRunnable.startScroll(distance, duration);
     }
 
+    /**
+     * Allows RemoteViews to scroll relatively to a position.
+     */
+    void smoothScrollByOffset(int position) {
+        int index = -1;
+        if (position < 0) {
+            index = getFirstVisiblePosition();
+        } else if (position > 0) {
+            index = getLastVisiblePosition();
+        }
+
+        if (index > -1) {
+            View child = getChildAt(index - getFirstVisiblePosition());
+            if (child != null) {
+                Rect visibleRect = new Rect();
+                if (child.getGlobalVisibleRect(visibleRect)) {
+                    // the child is partially visible
+                    int childRectArea = child.getWidth() * child.getHeight();
+                    int visibleRectArea = visibleRect.width() * visibleRect.height();
+                    float visibleArea = (visibleRectArea / (float) childRectArea);
+                    final float visibleThreshold = 0.75f;
+                    if ((position < 0) && (visibleArea < visibleThreshold)) {
+                        // the top index is not perceivably visible so offset
+                        // to account for showing that top index as well
+                        ++index;
+                    } else if ((position > 0) && (visibleArea < visibleThreshold)) {
+                        // the bottom index is not perceivably visible so offset
+                        // to account for showing that bottom index as well
+                        --index;
+                    }
+                }
+                smoothScrollToPosition(Math.max(0, Math.min(getCount(), index + position)));
+            }
+        }
+    }
+
     private void createScrollingCache() {
         if (mScrollingCacheEnabled && !mCachingStarted) {
             setChildrenDrawnWithCacheEnabled(true);
@@ -3155,6 +3294,7 @@
 
         mResurrectToPosition = INVALID_POSITION;
         removeCallbacks(mFlingRunnable);
+        removeCallbacks(mPositionScroller);
         mTouchMode = TOUCH_MODE_REST;
         clearScrollingCache();
         mSpecificTop = selectedTop;
@@ -3808,6 +3948,34 @@
     }
 
     /**
+     * Sets up this AbsListView to use a remote views adapter which connects to a RemoteViewsService
+     * through the specified intent.
+     * @param intent the intent used to identify the RemoteViewsService for the adapter to connect to.
+     */
+    public void setRemoteViewsAdapter(Intent intent) {
+        mRemoteAdapter = new RemoteViewsAdapter(getContext(), intent, this);
+    }
+
+    /**
+     * Called back when the adapter connects to the RemoteViewsService.
+     */
+    public void onRemoteAdapterConnected() {
+        if (mRemoteAdapter != mAdapter) {
+            setAdapter(mRemoteAdapter);
+        }
+    }
+
+    /**
+     * Called back when the adapter disconnects from the RemoteViewsService.
+     */
+    public void onRemoteAdapterDisconnected() {
+        if (mRemoteAdapter == mAdapter) {
+            mRemoteAdapter = null;
+            setAdapter(null);
+        }
+    }
+
+    /**
      * Sets the recycler listener to be notified whenever a View is set aside in
      * the recycler for later reuse. This listener can be used to free resources
      * associated to the View.
@@ -4190,7 +4358,7 @@
                     final ArrayList<View> scrap = mScrapViews[i];
                     final int scrapCount = scrap.size();
                     for (int j = 0; j < scrapCount; j++) {
-                        scrap.get(i).setDrawingCacheBackgroundColor(color);
+                        scrap.get(j).setDrawingCacheBackgroundColor(color);
                     }
                 }
             }
diff --git a/core/java/android/widget/AdapterViewAnimator.java b/core/java/android/widget/AdapterViewAnimator.java
new file mode 100644
index 0000000..a6d5170
--- /dev/null
+++ b/core/java/android/widget/AdapterViewAnimator.java
@@ -0,0 +1,455 @@
+/*
+ * 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.content.Context;
+import android.content.Intent;
+import android.content.res.TypedArray;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.view.animation.Animation;
+import android.view.animation.AnimationUtils;
+
+/**
+ * Base class for a {@link AdapterView} that will perform animations
+ * when switching between its views.
+ *
+ * @attr ref android.R.styleable#AdapterViewAnimator_inAnimation
+ * @attr ref android.R.styleable#AdapterViewAnimator_outAnimation
+ * @attr ref android.R.styleable#AdapterViewAnimator_animateFirstView
+ */
+public class AdapterViewAnimator extends AdapterView<Adapter> implements RemoteViewsAdapter.RemoteAdapterConnectionCallback{
+    private static final String TAG = "RemoteViewAnimator";
+
+    int mWhichChild = 0;
+    boolean mFirstTime = true;
+    boolean mAnimateFirstTime = true;
+
+    AdapterDataSetObserver mDataSetObserver;
+
+    View mPreviousView;
+    View mCurrentView;
+
+    Animation mInAnimation;
+    Animation mOutAnimation;
+    Adapter mAdapter;
+    RemoteViewsAdapter mRemoteViewsAdapter;
+    private Handler mMainQueue;
+
+    public AdapterViewAnimator(Context context) {
+        super(context);
+        initViewAnimator(context, null);
+    }
+
+    public AdapterViewAnimator(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        TypedArray a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.ViewAnimator);
+        int resource = a.getResourceId(com.android.internal.R.styleable.ViewAnimator_inAnimation, 0);
+        if (resource > 0) {
+            setInAnimation(context, resource);
+        }
+
+        resource = a.getResourceId(com.android.internal.R.styleable.ViewAnimator_outAnimation, 0);
+        if (resource > 0) {
+            setOutAnimation(context, resource);
+        }
+
+        boolean flag = a.getBoolean(com.android.internal.R.styleable.ViewAnimator_animateFirstView, true);
+        setAnimateFirstView(flag);
+
+        a.recycle();
+
+        initViewAnimator(context, attrs);
+    }
+
+    /**
+     * Initialize this {@link AdapterViewAnimator}
+     */
+    private void initViewAnimator(Context context, AttributeSet attrs) {
+        mMainQueue = new Handler(Looper.myLooper());
+    }
+
+    /**
+     * Sets which child view will be displayed.
+     *
+     * @param whichChild the index of the child view to display
+     */
+    public void setDisplayedChild(int whichChild) {
+        if (mAdapter != null) {
+            mWhichChild = whichChild;
+            if (whichChild >= mAdapter.getCount()) {
+                mWhichChild = 0;
+            } else if (whichChild < 0) {
+                mWhichChild = mAdapter.getCount() - 1;
+            }
+
+            boolean hasFocus = getFocusedChild() != null;
+            // This will clear old focus if we had it
+            showOnly(mWhichChild);
+            if (hasFocus) {
+                // Try to retake focus if we had it
+                requestFocus(FOCUS_FORWARD);
+            }
+        }
+    }
+
+    /**
+     * Return default inAnimation. To be overriden by subclasses.
+     */
+    public Animation getDefaultInAnimation() {
+        return null;
+    }
+
+    /**
+     * Return default outAnimation. To be overriden by subclasses.
+     */
+    public Animation getDefaultOutAnimation() {
+        return null;
+    }
+
+    /**
+     * Returns the index of the currently displayed child view.
+     */
+    public int getDisplayedChild() {
+        return mWhichChild;
+    }
+
+    /**
+     * Manually shows the next child.
+     */
+    public void showNext() {
+        setDisplayedChild(mWhichChild + 1);
+    }
+
+    /**
+     * Manually shows the previous child.
+     */
+    public void showPrevious() {
+        setDisplayedChild(mWhichChild - 1);
+    }
+
+    /**
+     * Shows only the specified child. The other displays Views exit the screen,
+     * optionally with the with the {@link #getOutAnimation() out animation} and
+     * the specified child enters the screen, optionally with the
+     * {@link #getInAnimation() in animation}.
+     *
+     * @param childIndex The index of the child to be shown.
+     * @param animate Whether or not to use the in and out animations, defaults
+     *            to true.
+     */
+    void showOnly(int childIndex, boolean animate) {
+        showOnly(childIndex, animate, false);
+    }
+
+    private LayoutParams makeLayoutParams() {
+        int width = mMeasuredWidth - mPaddingLeft - mPaddingRight;
+        int height = mMeasuredHeight - mPaddingTop - mPaddingBottom;
+        return new LayoutParams(width, height);
+    }
+
+    protected void showOnly(int childIndex, boolean animate, boolean onLayout) {
+        if (mAdapter != null) {
+            // The previous view should be removed from the ViewGroup
+            if (mPreviousView != null) {
+                mPreviousView.clearAnimation();
+
+                // TODO: this is where we would store the the view for
+                // recycling
+                removeViewInLayout(mPreviousView);
+            }
+
+            // If the current view is still being animated, we should
+            // force the animation to end
+            if (mCurrentView != null) {
+                mCurrentView.clearAnimation();
+            }
+
+            // load the new mCurrentView from our adapter
+            mPreviousView = mCurrentView;
+            mCurrentView = mAdapter.getView(childIndex, null, this);
+            if (mPreviousView != mCurrentView) {
+                addViewInLayout(mCurrentView, 0, makeLayoutParams(), true);
+                mCurrentView.bringToFront();
+            }
+
+
+
+            // Animate as necessary
+            if (mPreviousView != null && mPreviousView != mCurrentView) {
+                if (animate && mOutAnimation != null) {
+                    mPreviousView.startAnimation(mOutAnimation);
+                }
+                // This line results in the view becoming invisible *after*
+                // the above animation is complete, or, if there is no animation
+                // then it becomes invisble immediately
+                mPreviousView.setVisibility(View.GONE);
+            }
+
+            if (mCurrentView != null && animate && mInAnimation != null) {
+                mCurrentView.startAnimation(mInAnimation);
+            }
+
+            mFirstTime = false;
+            if (!onLayout) {
+                requestLayout();
+                invalidate();
+            } else {
+                // If the Adapter tries to layout the current view when we get it using getView above
+                // the layout will end up being ignored since we are currently laying out, so
+                // we post a delayed requestLayout and invalidate
+                mMainQueue.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        mCurrentView.requestLayout();
+                        mCurrentView.invalidate();
+                    }
+                });
+            }
+        }
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        boolean dataChanged = mDataChanged;
+        if (dataChanged) {
+            handleDataChanged();
+
+            // if the data changes, mWhichChild might be out of the bounds of the adapter
+            // in this case, we reset mWhichChild to the beginning
+            if (mWhichChild >= mAdapter.getCount())
+                mWhichChild = 0;
+
+            showOnly(mWhichChild, true, true);
+        }
+
+        final int childCount = getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            final View child = getChildAt(i);
+
+            int childRight = mPaddingLeft + child.getMeasuredWidth();
+            int childBottom = mPaddingTop + child.getMeasuredHeight();
+
+            child.layout(mPaddingLeft, mPaddingTop, childRight, childBottom);
+        }
+
+        mDataChanged = false;
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        final int count = getChildCount();
+
+        int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
+        int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
+
+        Log.v(TAG, "onMeasure");
+
+        for (int i = 0; i < count; i++) {
+            final View child = getChildAt(i);
+
+            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+
+            lp.width = widthSpecSize - mPaddingLeft - mPaddingRight;
+            lp.height = heightSpecSize - mPaddingTop - mPaddingBottom;
+
+            int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(lp.width,
+                    MeasureSpec.EXACTLY);
+            int childheightMeasureSpec = MeasureSpec.makeMeasureSpec(lp.height,
+                    MeasureSpec.EXACTLY);
+
+            child.measure(childWidthMeasureSpec, childheightMeasureSpec);
+        }
+
+        setMeasuredDimension(widthSpecSize, heightSpecSize);
+    }
+
+    /**
+     * Shows only the specified child. The other displays Views exit the screen
+     * with the {@link #getOutAnimation() out animation} and the specified child
+     * enters the screen with the {@link #getInAnimation() in animation}.
+     *
+     * @param childIndex The index of the child to be shown.
+     */
+    void showOnly(int childIndex) {
+        final boolean animate = (!mFirstTime || mAnimateFirstTime);
+        showOnly(childIndex, animate);
+    }
+
+    /**
+     * Returns the View corresponding to the currently displayed child.
+     *
+     * @return The View currently displayed.
+     *
+     * @see #getDisplayedChild()
+     */
+    public View getCurrentView() {
+        return mCurrentView;
+    }
+
+    /**
+     * Returns the current animation used to animate a View that enters the screen.
+     *
+     * @return An Animation or null if none is set.
+     *
+     * @see #setInAnimation(android.view.animation.Animation)
+     * @see #setInAnimation(android.content.Context, int)
+     */
+    public Animation getInAnimation() {
+        return mInAnimation;
+    }
+
+    /**
+     * Specifies the animation used to animate a View that enters the screen.
+     *
+     * @param inAnimation The animation started when a View enters the screen.
+     *
+     * @see #getInAnimation()
+     * @see #setInAnimation(android.content.Context, int)
+     */
+    public void setInAnimation(Animation inAnimation) {
+        mInAnimation = inAnimation;
+    }
+
+    /**
+     * Returns the current animation used to animate a View that exits the screen.
+     *
+     * @return An Animation or null if none is set.
+     *
+     * @see #setOutAnimation(android.view.animation.Animation)
+     * @see #setOutAnimation(android.content.Context, int)
+     */
+    public Animation getOutAnimation() {
+        return mOutAnimation;
+    }
+
+    /**
+     * Specifies the animation used to animate a View that exit the screen.
+     *
+     * @param outAnimation The animation started when a View exit the screen.
+     *
+     * @see #getOutAnimation()
+     * @see #setOutAnimation(android.content.Context, int)
+     */
+    public void setOutAnimation(Animation outAnimation) {
+        mOutAnimation = outAnimation;
+    }
+
+    /**
+     * Specifies the animation used to animate a View that enters the screen.
+     *
+     * @param context The application's environment.
+     * @param resourceID The resource id of the animation.
+     *
+     * @see #getInAnimation()
+     * @see #setInAnimation(android.view.animation.Animation)
+     */
+    public void setInAnimation(Context context, int resourceID) {
+        setInAnimation(AnimationUtils.loadAnimation(context, resourceID));
+    }
+
+    /**
+     * Specifies the animation used to animate a View that exit the screen.
+     *
+     * @param context The application's environment.
+     * @param resourceID The resource id of the animation.
+     *
+     * @see #getOutAnimation()
+     * @see #setOutAnimation(android.view.animation.Animation)
+     */
+    public void setOutAnimation(Context context, int resourceID) {
+        setOutAnimation(AnimationUtils.loadAnimation(context, resourceID));
+    }
+
+    /**
+     * Indicates whether the current View should be animated the first time
+     * the ViewAnimation is displayed.
+     *
+     * @param animate True to animate the current View the first time it is displayed,
+     *                false otherwise.
+     */
+    public void setAnimateFirstView(boolean animate) {
+        mAnimateFirstTime = animate;
+    }
+
+    @Override
+    public int getBaseline() {
+        return (getCurrentView() != null) ? getCurrentView().getBaseline() : super.getBaseline();
+    }
+
+    @Override
+    public Adapter getAdapter() {
+        return mAdapter;
+    }
+
+    @Override
+    public void setAdapter(Adapter adapter) {
+        mAdapter = adapter;
+
+        if (mAdapter != null) {
+            if (mDataSetObserver != null) {
+                mAdapter.unregisterDataSetObserver(mDataSetObserver);
+            }
+
+            mDataSetObserver = new AdapterDataSetObserver();
+            mAdapter.registerDataSetObserver(mDataSetObserver);
+        }
+    }
+
+    /**
+     * Sets up this AdapterViewAnimator to use a remote views adapter which connects to a RemoteViewsService
+     * through the specified intent.
+     * @param intent the intent used to identify the RemoteViewsService for the adapter to connect to.
+     */
+    @android.view.RemotableViewMethod
+    public void setRemoteViewsAdapter(Intent intent) {
+        mRemoteViewsAdapter = new RemoteViewsAdapter(getContext(), intent, this);
+    }
+
+    @Override
+    public void setSelection(int position) {
+        setDisplayedChild(position);
+    }
+
+    @Override
+    public View getSelectedView() {
+        return mCurrentView;
+    }
+
+    /**
+     * Called back when the adapter connects to the RemoteViewsService.
+     */
+    public void onRemoteAdapterConnected() {
+        if (mRemoteViewsAdapter != mAdapter) {
+            setAdapter(mRemoteViewsAdapter);
+        }
+    }
+
+    /**
+     * Called back when the adapter disconnects from the RemoteViewsService.
+     */
+    public void onRemoteAdapterDisconnected() {
+        if (mRemoteViewsAdapter != mAdapter) {
+            mRemoteViewsAdapter = null;
+            setAdapter(mRemoteViewsAdapter);
+        }
+    }
+}
diff --git a/core/java/android/widget/AdapterViewFlipper.java b/core/java/android/widget/AdapterViewFlipper.java
new file mode 100644
index 0000000..901c761
--- /dev/null
+++ b/core/java/android/widget/AdapterViewFlipper.java
@@ -0,0 +1,238 @@
+/*
+ * 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.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.TypedArray;
+import android.os.Handler;
+import android.os.Message;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.animation.AlphaAnimation;
+import android.view.animation.Animation;
+import android.widget.RemoteViews.RemoteView;
+
+/**
+ * Simple {@link ViewAnimator} that will animate between two or more views
+ * that have been added to it.  Only one child is shown at a time.  If
+ * requested, can automatically flip between each child at a regular interval.
+ *
+ * @attr ref android.R.styleable#AdapterViewFlipper_flipInterval
+ * @attr ref android.R.styleable#AdapterViewFlipper_autoStart
+ */
+@RemoteView
+public class AdapterViewFlipper extends AdapterViewAnimator {
+    private static final String TAG = "ViewFlipper";
+    private static final boolean LOGD = false;
+
+    private static final int DEFAULT_INTERVAL = 10000;
+    private static final int DEFAULT_ANIMATION_DURATION = 200;
+
+    private int mFlipInterval = DEFAULT_INTERVAL;
+    private int mAnimationDuration = DEFAULT_ANIMATION_DURATION;
+    private boolean mAutoStart = false;
+
+    private boolean mRunning = false;
+    private boolean mStarted = false;
+    private boolean mVisible = false;
+    private boolean mUserPresent = true;
+
+    public AdapterViewFlipper(Context context) {
+        super(context);
+        initDefaultAnimations();
+    }
+
+    public AdapterViewFlipper(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        TypedArray a = context.obtainStyledAttributes(attrs,
+                com.android.internal.R.styleable.ViewFlipper);
+        mFlipInterval = a.getInt(
+                com.android.internal.R.styleable.ViewFlipper_flipInterval, DEFAULT_INTERVAL);
+        mAutoStart = a.getBoolean(
+                com.android.internal.R.styleable.ViewFlipper_autoStart, false);
+        a.recycle();
+        initDefaultAnimations();
+    }
+
+    private void initDefaultAnimations() {
+        // Set the default animations to be fade in/out
+        if (mInAnimation == null) {
+            mInAnimation = new AlphaAnimation(0.0f, 1.0f);
+            mInAnimation.setDuration(mAnimationDuration);
+        }
+        if (mOutAnimation == null) {
+            mOutAnimation = new AlphaAnimation(1.0f, 0.0f);
+            mOutAnimation.setDuration(mAnimationDuration);
+        }
+    }
+
+    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            final String action = intent.getAction();
+            if (Intent.ACTION_SCREEN_OFF.equals(action)) {
+                mUserPresent = false;
+                updateRunning();
+            } else if (Intent.ACTION_USER_PRESENT.equals(action)) {
+                mUserPresent = true;
+                updateRunning(false);
+            }
+        }
+    };
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+
+        // Listen for broadcasts related to user-presence
+        final IntentFilter filter = new IntentFilter();
+        filter.addAction(Intent.ACTION_SCREEN_OFF);
+        filter.addAction(Intent.ACTION_USER_PRESENT);
+        getContext().registerReceiver(mReceiver, filter);
+
+        if (mAutoStart) {
+            // Automatically start when requested
+            startFlipping();
+        }
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        mVisible = false;
+
+        getContext().unregisterReceiver(mReceiver);
+        updateRunning();
+    }
+
+    @Override
+    protected void onWindowVisibilityChanged(int visibility) {
+        super.onWindowVisibilityChanged(visibility);
+        mVisible = (visibility == VISIBLE);
+        updateRunning(false);
+    }
+
+    @Override
+    public void setAdapter(Adapter adapter) {
+        super.setAdapter(adapter);
+        updateRunning();
+    }
+
+    /**
+     * How long to wait before flipping to the next view
+     *
+     * @param milliseconds
+     *            time in milliseconds
+     */
+    public void setFlipInterval(int milliseconds) {
+        mFlipInterval = milliseconds;
+    }
+
+    /**
+     * Start a timer to cycle through child views
+     */
+    public void startFlipping() {
+        mStarted = true;
+        updateRunning();
+    }
+
+    /**
+     * No more flips
+     */
+    public void stopFlipping() {
+        mStarted = false;
+        updateRunning();
+    }
+
+    /**
+     * Internal method to start or stop dispatching flip {@link Message} based
+     * on {@link #mRunning} and {@link #mVisible} state.
+     */
+    private void updateRunning() {
+        // by default when we update running, we want the
+        // current view to animate in
+        updateRunning(true);
+    }
+
+    /**
+     * Internal method to start or stop dispatching flip {@link Message} based
+     * on {@link #mRunning} and {@link #mVisible} state.
+     *
+     * @param flipNow Determines whether or not to execute the animation now, in
+     *            addition to queuing future flips. If omitted, defaults to
+     *            true.
+     */
+    private void updateRunning(boolean flipNow) {
+        boolean running = mVisible && mStarted && mUserPresent && mAdapter != null;
+        if (running != mRunning) {
+            if (running) {
+                showOnly(mWhichChild, flipNow);
+                Message msg = mHandler.obtainMessage(FLIP_MSG);
+                mHandler.sendMessageDelayed(msg, mFlipInterval);
+            } else {
+                mHandler.removeMessages(FLIP_MSG);
+            }
+            mRunning = running;
+        }
+        if (LOGD) {
+            Log.d(TAG, "updateRunning() mVisible=" + mVisible + ", mStarted=" + mStarted
+                    + ", mUserPresent=" + mUserPresent + ", mRunning=" + mRunning);
+        }
+    }
+
+    /**
+     * Returns true if the child views are flipping.
+     */
+    public boolean isFlipping() {
+        return mStarted;
+    }
+
+    /**
+     * Set if this view automatically calls {@link #startFlipping()} when it
+     * becomes attached to a window.
+     */
+    public void setAutoStart(boolean autoStart) {
+        mAutoStart = autoStart;
+    }
+
+    /**
+     * Returns true if this view automatically calls {@link #startFlipping()}
+     * when it becomes attached to a window.
+     */
+    public boolean isAutoStart() {
+        return mAutoStart;
+    }
+
+    private final int FLIP_MSG = 1;
+
+    private final Handler mHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            if (msg.what == FLIP_MSG) {
+                if (mRunning) {
+                    showNext();
+                    msg = obtainMessage(FLIP_MSG);
+                    sendMessageDelayed(msg, mFlipInterval);
+                }
+            }
+        }
+    };
+}
diff --git a/core/java/android/widget/Adapters.java b/core/java/android/widget/Adapters.java
new file mode 100644
index 0000000..7fd7fb5
--- /dev/null
+++ b/core/java/android/widget/Adapters.java
@@ -0,0 +1,1232 @@
+/*
+ * 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 org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+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 java.io.IOException;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+/**
+ * <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 implicitly
+ * 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. Note that <code>@</code> which are not used to reference resources
+ *  should be backslash protected as in <code>\@</code>.</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> children
+ * 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><code>tag</code>: The content of the column is interpreted as a string and will be set as
+ *  the tag (using {@link View#setTag(Object)} of the associated View. This can be used to
+ *  associate meta-data to your view, that can be used for instance by a listener.</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
+ *  application 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
+ *  transformations cannot 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_TAG = "tag";
+        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, android.R.styleable.CursorAdapter);
+
+            String uri = a.getString(android.R.styleable.CursorAdapter_uri);
+            String selection = a.getString(android.R.styleable.CursorAdapter_selection);
+            String sortOrder = a.getString(android.R.styleable.CursorAdapter_sortOrder);
+            int layout = a.getResourceId(android.R.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,
+                    android.R.styleable.CursorAdapter_SelectItem);
+
+            String fromName = a.getString(android.R.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,
+                    android.R.styleable.CursorAdapter_BindItem);
+
+            String fromName = a.getString(android.R.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(android.R.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(android.R.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_TAG.equals(type)) {
+                return new TagBinder(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,
+                    android.R.styleable.CursorAdapter_MapItem);
+
+            String from = a.getString(android.R.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(android.R.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(android.R.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,
+                    android.R.styleable.CursorAdapter_TransformItem);
+
+            String className = a.getString(android.R.styleable.CursorAdapter_TransformItem_withClass);
+            if (className == null) {
+                String expression = a.getString(
+                        android.R.styleable.CursorAdapter_TransformItem_withExpression);
+                transformation = createExpressionTransformation(expression);
+            } else if (!mContext.isRestricted()) {
+                try {
+                    final Class<?> klas = Class.forName(className, true, mContext.getClassLoader());
+                    if (CursorTransformation.class.isAssignableFrom(klas)) {
+                        final Constructor<?> c = klas.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 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) {
+            if (view instanceof TextView) {
+                final String text = mTransformation.transform(cursor, columnIndex);
+                ((TextView) view).setText(text);
+                return true;
+            }
+            return false;
+        }
+    }
+
+    /**
+     * 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) {
+            if (view instanceof ImageView) {
+                final byte[] data = cursor.getBlob(columnIndex);
+                ((ImageView) view).setImageBitmap(BitmapFactory.decodeByteArray(data, 0,
+                        data.length));
+                return true;
+            }
+            return false;
+        }
+    }
+
+    private static class TagBinder extends CursorBinder {
+        public TagBinder(Context context, CursorTransformation transformation) {
+            super(context, transformation);
+        }
+
+        @Override
+        public boolean bind(View view, Cursor cursor, int columnIndex) {
+            final String text = mTransformation.transform(cursor, columnIndex);
+            view.setTag(text);
+            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) {
+            if (view instanceof ImageView) {
+                ((ImageView) view).setImageURI(Uri.parse(
+                        mTransformation.transform(cursor, columnIndex)));
+                return true;
+            }
+            return false;
+        }
+    }
+
+    /**
+     * 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) {
+            if (view instanceof ImageView) {
+                final int resource = mTransformation.transformToResource(cursor, columnIndex);
+                if (resource == 0) return false;
+
+                ((ImageView) view).setImageResource(resource);
+                return true;
+            }
+            return false;
+        }
+    }
+}
diff --git a/core/java/android/widget/ArrayAdapter.java b/core/java/android/widget/ArrayAdapter.java
index 03ada94..b4ece24 100644
--- a/core/java/android/widget/ArrayAdapter.java
+++ b/core/java/android/widget/ArrayAdapter.java
@@ -30,17 +30,17 @@
 import java.util.Collections;
 
 /**
- * A ListAdapter that manages a ListView backed by an array of arbitrary
+ * A concrete BaseAdapter that is backed by an array of arbitrary
  * objects.  By default this class expects that the provided resource id references
  * a single TextView.  If you want to use a more complex layout, use the constructors that
  * also takes a field id.  That field id should reference a TextView in the larger layout
  * resource.
  *
- * However the TextView is referenced, it will be filled with the toString() of each object in
+ * <p>However the TextView is referenced, it will be filled with the toString() of each object in
  * the array. You can add lists or arrays of custom objects. Override the toString() method
  * of your objects to determine what text will be displayed for the item in the list.
  *
- * To use something other than TextViews for the array display, for instance, ImageViews,
+ * <p>To use something other than TextViews for the array display, for instance, ImageViews,
  * or to have some of data besides toString() results fill the views,
  * override {@link #getView(int, View, ViewGroup)} to return the type of view you want.
  */
diff --git a/core/java/android/widget/AutoCompleteTextView.java b/core/java/android/widget/AutoCompleteTextView.java
index e15a520..34aef99 100644
--- a/core/java/android/widget/AutoCompleteTextView.java
+++ b/core/java/android/widget/AutoCompleteTextView.java
@@ -29,13 +29,12 @@
 import android.util.Log;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
-import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.WindowManager;
 import android.view.inputmethod.CompletionInfo;
-import android.view.inputmethod.InputMethodManager;
 import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputMethodManager;
 
 import com.android.internal.R;
 
@@ -90,45 +89,21 @@
     static final boolean DEBUG = false;
     static final String TAG = "AutoCompleteTextView";
 
-    private static final int HINT_VIEW_ID = 0x17;
-
-    /**
-     * This value controls the length of time that the user
-     * must leave a pointer down without scrolling to expand
-     * the autocomplete dropdown list to cover the IME.
-     */
-    private static final int EXPAND_LIST_TIMEOUT = 250;
-
     private CharSequence mHintText;
+    private TextView mHintView;
     private int mHintResource;
 
     private ListAdapter mAdapter;
     private Filter mFilter;
     private int mThreshold;
 
-    private PopupWindow mPopup;
-    private DropDownListView mDropDownList;
-    private int mDropDownVerticalOffset;
-    private int mDropDownHorizontalOffset;
+    private ListPopupWindow mPopup;
     private int mDropDownAnchorId;
-    private View mDropDownAnchorView;  // view is retrieved lazily from id once needed
-    private int mDropDownWidth;
-    private int mDropDownHeight;
-    private final Rect mTempRect = new Rect();
-
-    private Drawable mDropDownListHighlight;
 
     private AdapterView.OnItemClickListener mItemClickListener;
     private AdapterView.OnItemSelectedListener mItemSelectedListener;
 
-    private final DropDownItemClickListener mDropDownItemClickListener =
-            new DropDownItemClickListener();
-
-    private boolean mDropDownAlwaysVisible = false;
-
     private boolean mDropDownDismissedOnCompletion = true;
-    
-    private boolean mForceIgnoreOutsideTouch = false;
 
     private int mLastKeyCode = KeyEvent.KEYCODE_UNKNOWN;
     private boolean mOpenBefore;
@@ -137,10 +112,6 @@
 
     private boolean mBlockCompletion;
 
-    private ListSelectorHider mHideSelector;
-    private Runnable mShowDropDownRunnable;
-    private Runnable mResizePopupRunnable = new ResizePopupRunnable();
-
     private PassThroughClickListener mPassThroughClickListener;
     private PopupDataSetObserver mObserver;
 
@@ -155,9 +126,10 @@
     public AutoCompleteTextView(Context context, AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
 
-        mPopup = new PopupWindow(context, attrs,
+        mPopup = new ListPopupWindow(context, attrs,
                 com.android.internal.R.attr.autoCompleteTextViewStyle);
         mPopup.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
+        mPopup.setPromptPosition(ListPopupWindow.POSITION_PROMPT_BELOW);
 
         TypedArray a =
             context.obtainStyledAttributes(
@@ -166,14 +138,11 @@
         mThreshold = a.getInt(
                 R.styleable.AutoCompleteTextView_completionThreshold, 2);
 
-        mHintText = a.getText(R.styleable.AutoCompleteTextView_completionHint);
-
-        mDropDownListHighlight = a.getDrawable(
-                R.styleable.AutoCompleteTextView_dropDownSelector);
-        mDropDownVerticalOffset = (int)
-                a.getDimension(R.styleable.AutoCompleteTextView_dropDownVerticalOffset, 0.0f);
-        mDropDownHorizontalOffset = (int)
-                a.getDimension(R.styleable.AutoCompleteTextView_dropDownHorizontalOffset, 0.0f);
+        mPopup.setListSelector(a.getDrawable(R.styleable.AutoCompleteTextView_dropDownSelector));
+        mPopup.setVerticalOffset((int)
+                a.getDimension(R.styleable.AutoCompleteTextView_dropDownVerticalOffset, 0.0f));
+        mPopup.setHorizontalOffset((int)
+                a.getDimension(R.styleable.AutoCompleteTextView_dropDownHorizontalOffset, 0.0f));
         
         // Get the anchor's id now, but the view won't be ready, so wait to actually get the
         // view and store it in mDropDownAnchorView lazily in getDropDownAnchorView later.
@@ -184,13 +153,18 @@
         
         // For dropdown width, the developer can specify a specific width, or MATCH_PARENT
         // (for full screen width) or WRAP_CONTENT (to match the width of the anchored view).
-        mDropDownWidth = a.getLayoutDimension(R.styleable.AutoCompleteTextView_dropDownWidth,
-                ViewGroup.LayoutParams.WRAP_CONTENT);
-        mDropDownHeight = a.getLayoutDimension(R.styleable.AutoCompleteTextView_dropDownHeight,
-                ViewGroup.LayoutParams.WRAP_CONTENT);
+        mPopup.setWidth(a.getLayoutDimension(
+                R.styleable.AutoCompleteTextView_dropDownWidth,
+                ViewGroup.LayoutParams.WRAP_CONTENT));
+        mPopup.setHeight(a.getLayoutDimension(
+                R.styleable.AutoCompleteTextView_dropDownHeight,
+                ViewGroup.LayoutParams.WRAP_CONTENT));
 
         mHintResource = a.getResourceId(R.styleable.AutoCompleteTextView_completionHintView,
                 R.layout.simple_dropdown_hint);
+        
+        mPopup.setOnItemClickListener(new DropDownItemClickListener());
+        setCompletionHint(a.getText(R.styleable.AutoCompleteTextView_completionHint));
 
         // Always turn on the auto complete input type flag, since it
         // makes no sense to use this widget without it.
@@ -238,6 +212,20 @@
      */
     public void setCompletionHint(CharSequence hint) {
         mHintText = hint;
+        if (hint != null) {
+            if (mHintView == null) {
+                final TextView hintView = (TextView) LayoutInflater.from(getContext()).inflate(
+                        mHintResource, null).findViewById(com.android.internal.R.id.text1);
+                hintView.setText(mHintText);
+                mHintView = hintView;
+                mPopup.setPromptView(hintView);
+            } else {
+                mHintView.setText(hint);
+            }
+        } else {
+            mPopup.setPromptView(null);
+            mHintView = null;
+        }
     }
     
     /**
@@ -250,7 +238,7 @@
      * @attr ref android.R.styleable#AutoCompleteTextView_dropDownWidth
      */
     public int getDropDownWidth() {
-        return mDropDownWidth;
+        return mPopup.getWidth();
     }
     
     /**
@@ -263,7 +251,7 @@
      * @attr ref android.R.styleable#AutoCompleteTextView_dropDownWidth
      */
     public void setDropDownWidth(int width) {
-        mDropDownWidth = width;
+        mPopup.setWidth(width);
     }
 
     /**
@@ -277,7 +265,7 @@
      * @attr ref android.R.styleable#AutoCompleteTextView_dropDownHeight
      */
     public int getDropDownHeight() {
-        return mDropDownHeight;
+        return mPopup.getHeight();
     }
 
     /**
@@ -291,7 +279,7 @@
      * @attr ref android.R.styleable#AutoCompleteTextView_dropDownHeight
      */
     public void setDropDownHeight(int height) {
-        mDropDownHeight = height;
+        mPopup.setHeight(height);
     }
     
     /**
@@ -316,7 +304,7 @@
      */
     public void setDropDownAnchor(int id) {
         mDropDownAnchorId = id;
-        mDropDownAnchorView = null;
+        mPopup.setAnchorView(null);
     }
     
     /**
@@ -358,7 +346,7 @@
      * @param offset the vertical offset
      */
     public void setDropDownVerticalOffset(int offset) {
-        mDropDownVerticalOffset = offset;
+        mPopup.setVerticalOffset(offset);
     }
     
     /**
@@ -367,7 +355,7 @@
      * @return the vertical offset
      */
     public int getDropDownVerticalOffset() {
-        return mDropDownVerticalOffset;
+        return mPopup.getVerticalOffset();
     }
     
     /**
@@ -376,7 +364,7 @@
      * @param offset the horizontal offset
      */
     public void setDropDownHorizontalOffset(int offset) {
-        mDropDownHorizontalOffset = offset;
+        mPopup.setHorizontalOffset(offset);
     }
     
     /**
@@ -385,7 +373,7 @@
      * @return the horizontal offset
      */
     public int getDropDownHorizontalOffset() {
-        return mDropDownHorizontalOffset;
+        return mPopup.getHorizontalOffset();
     }
 
      /**
@@ -422,7 +410,7 @@
      * @hide Pending API council approval
      */
     public boolean isDropDownAlwaysVisible() {
-        return mDropDownAlwaysVisible;
+        return mPopup.isDropDownAlwaysVisible();
     }
 
     /**
@@ -439,7 +427,7 @@
      * @hide Pending API council approval
      */
     public void setDropDownAlwaysVisible(boolean dropDownAlwaysVisible) {
-        mDropDownAlwaysVisible = dropDownAlwaysVisible;
+        mPopup.setDropDownAlwaysVisible(dropDownAlwaysVisible);
     }
    
     /**
@@ -606,15 +594,13 @@
             mFilter = null;
         }
 
-        if (mDropDownList != null) {
-            mDropDownList.setAdapter(mAdapter);
-        }
+        mPopup.setAdapter(mAdapter);
     }
 
     @Override
     public boolean onKeyPreIme(int keyCode, KeyEvent event) {
         if (keyCode == KeyEvent.KEYCODE_BACK && isPopupShowing()
-                && !mDropDownAlwaysVisible) {
+                && !mPopup.isDropDownAlwaysVisible()) {
             // special case for the back key, we do not even try to send it
             // to the drop down list but instead, consume it immediately
             if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
@@ -633,18 +619,16 @@
 
     @Override
     public boolean onKeyUp(int keyCode, KeyEvent event) {
-        if (isPopupShowing() && mDropDownList.getSelectedItemPosition() >= 0) {
-            boolean consumed = mDropDownList.onKeyUp(keyCode, event);
-            if (consumed) {
-                switch (keyCode) {
-                    // if the list accepts the key events and the key event
-                    // was a click, the text view gets the selected item
-                    // from the drop down as its content
-                    case KeyEvent.KEYCODE_ENTER:
-                    case KeyEvent.KEYCODE_DPAD_CENTER:
-                        performCompletion();
-                        return true;
-                }
+        boolean consumed = mPopup.onKeyUp(keyCode, event);
+        if (consumed) {
+            switch (keyCode) {
+            // if the list accepts the key events and the key event
+            // was a click, the text view gets the selected item
+            // from the drop down as its content
+            case KeyEvent.KEYCODE_ENTER:
+            case KeyEvent.KEYCODE_DPAD_CENTER:
+                performCompletion();
+                return true;
             }
         }
         return super.onKeyUp(keyCode, event);
@@ -652,87 +636,11 @@
 
     @Override
     public boolean onKeyDown(int keyCode, KeyEvent event) {
-        // when the drop down is shown, we drive it directly
-        if (isPopupShowing()) {
-            // the key events are forwarded to the list in the drop down view
-            // note that ListView handles space but we don't want that to happen
-            // also if selection is not currently in the drop down, then don't
-            // let center or enter presses go there since that would cause it
-            // to select one of its items
-            if (keyCode != KeyEvent.KEYCODE_SPACE
-                    && (mDropDownList.getSelectedItemPosition() >= 0
-                            || (keyCode != KeyEvent.KEYCODE_ENTER
-                                    && keyCode != KeyEvent.KEYCODE_DPAD_CENTER))) {
-                int curIndex = mDropDownList.getSelectedItemPosition();
-                boolean consumed;
-
-                final boolean below = !mPopup.isAboveAnchor();
-
-                final ListAdapter adapter = mAdapter;
-                
-                boolean allEnabled;
-                int firstItem = Integer.MAX_VALUE;
-                int lastItem = Integer.MIN_VALUE;
-
-                if (adapter != null) {
-                    allEnabled = adapter.areAllItemsEnabled();
-                    firstItem = allEnabled ? 0 :
-                            mDropDownList.lookForSelectablePosition(0, true);
-                    lastItem = allEnabled ? adapter.getCount() - 1 :
-                            mDropDownList.lookForSelectablePosition(adapter.getCount() - 1, false);                    
-                }
-                
-                if ((below && keyCode == KeyEvent.KEYCODE_DPAD_UP && curIndex <= firstItem) ||
-                        (!below && keyCode == KeyEvent.KEYCODE_DPAD_DOWN && curIndex >= lastItem)) {
-                    // When the selection is at the top, we block the key
-                    // event to prevent focus from moving.
-                    clearListSelection();
-                    mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);
-                    showDropDown();
-                    return true;
-                } else {
-                    // WARNING: Please read the comment where mListSelectionHidden
-                    //          is declared
-                    mDropDownList.mListSelectionHidden = false;
-                }
-
-                consumed = mDropDownList.onKeyDown(keyCode, event);
-                if (DEBUG) Log.v(TAG, "Key down: code=" + keyCode + " list consumed=" + consumed);
-
-                if (consumed) {
-                    // If it handled the key event, then the user is
-                    // navigating in the list, so we should put it in front.
-                    mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
-                    // Here's a little trick we need to do to make sure that
-                    // the list view is actually showing its focus indicator,
-                    // by ensuring it has focus and getting its window out
-                    // of touch mode.
-                    mDropDownList.requestFocusFromTouch();
-                    showDropDown();
-
-                    switch (keyCode) {
-                        // avoid passing the focus from the text view to the
-                        // next component
-                        case KeyEvent.KEYCODE_ENTER:
-                        case KeyEvent.KEYCODE_DPAD_CENTER:
-                        case KeyEvent.KEYCODE_DPAD_DOWN:
-                        case KeyEvent.KEYCODE_DPAD_UP:
-                            return true;
-                    }
-                } else {
-                    if (below && keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
-                        // when the selection is at the bottom, we block the
-                        // event to avoid going to the next focusable widget
-                        if (curIndex == lastItem) {
-                            return true;
-                        }
-                    } else if (!below && keyCode == KeyEvent.KEYCODE_DPAD_UP &&
-                            curIndex == firstItem) {
-                        return true;
-                    }
-                }
-            }
-        } else {
+        if (mPopup.onKeyDown(keyCode, event)) {
+            return true;
+        }
+        
+        if (!isPopupShowing()) {
             switch(keyCode) {
             case KeyEvent.KEYCODE_DPAD_DOWN:
                 performValidation();
@@ -743,7 +651,7 @@
         boolean handled = super.onKeyDown(keyCode, event);
         mLastKeyCode = KeyEvent.KEYCODE_UNKNOWN;
 
-        if (handled && isPopupShowing() && mDropDownList != null) {
+        if (handled && isPopupShowing()) {
             clearListSelection();
         }
 
@@ -804,11 +712,12 @@
         if (enoughToFilter()) {
             if (mFilter != null) {
                 performFiltering(getText(), mLastKeyCode);
+                buildImeCompletions();
             }
         } else {
             // drop down is automatically dismissed when enough characters
             // are deleted from the text view
-            if (!mDropDownAlwaysVisible) dismissDropDown();
+            if (!mPopup.isDropDownAlwaysVisible()) dismissDropDown();
             if (mFilter != null) {
                 mFilter.filter(null);
             }
@@ -841,13 +750,7 @@
      * it back.
      */
     public void clearListSelection() {
-        final DropDownListView list = mDropDownList;
-        if (list != null) {
-            // WARNING: Please read the comment where mListSelectionHidden is declared
-            list.mListSelectionHidden = true;
-            list.hideSelector();
-            list.requestLayout();
-        }
+        mPopup.clearListSelection();
     }
     
     /**
@@ -856,11 +759,7 @@
      * @param position The position to move the selector to.
      */
     public void setListSelection(int position) {
-        if (mPopup.isShowing() && (mDropDownList != null)) {
-            mDropDownList.mListSelectionHidden = false;
-            mDropDownList.setSelection(position);
-            // ListView.setSelection() will call requestLayout()
-        }
+        mPopup.setSelection(position);
     }
 
     /**
@@ -874,10 +773,7 @@
      * @see ListView#getSelectedItemPosition()
      */
     public int getListSelection() {
-        if (mPopup.isShowing() && (mDropDownList != null)) {
-            return mDropDownList.getSelectedItemPosition();
-        }
-        return ListView.INVALID_POSITION;
+        return mPopup.getSelectedItemPosition();
     }
 
     /**
@@ -911,13 +807,7 @@
             replaceText(completion.getText());
             mBlockCompletion = false;
 
-            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());
-            }
+            mPopup.performItemClick(completion.getPosition());
         }
     }
 
@@ -925,7 +815,7 @@
         if (isPopupShowing()) {
             Object selectedItem;
             if (position < 0) {
-                selectedItem = mDropDownList.getSelectedItem();
+                selectedItem = mPopup.getSelectedItem();
             } else {
                 selectedItem = mAdapter.getItem(position);
             }
@@ -939,18 +829,18 @@
             mBlockCompletion = false;            
 
             if (mItemClickListener != null) {
-                final DropDownListView list = mDropDownList;
+                final ListPopupWindow list = mPopup;
 
                 if (selectedView == null || position < 0) {
                     selectedView = list.getSelectedView();
                     position = list.getSelectedItemPosition();
                     id = list.getSelectedItemId();
                 }
-                mItemClickListener.onItemClick(list, selectedView, position, id);
+                mItemClickListener.onItemClick(list.getListView(), selectedView, position, id);
             }
         }
 
-        if (mDropDownDismissedOnCompletion && !mDropDownAlwaysVisible) {
+        if (mDropDownDismissedOnCompletion && !mPopup.isDropDownAlwaysVisible()) {
             dismissDropDown();
         }
     }
@@ -1000,7 +890,6 @@
     /** {@inheritDoc} */
     public void onFilterComplete(int count) {
         updateDropDownForFilter(count);
-
     }
 
     private void updateDropDownForFilter(int count) {
@@ -1014,11 +903,12 @@
          * to filter.
          */
 
-        if ((count > 0 || mDropDownAlwaysVisible) && enoughToFilter()) {
+        final boolean dropDownAlwaysVisible = mPopup.isDropDownAlwaysVisible();
+        if ((count > 0 || dropDownAlwaysVisible) && enoughToFilter()) {
             if (hasFocus() && hasWindowFocus()) {
                 showDropDown();
             }
-        } else if (!mDropDownAlwaysVisible) {
+        } else if (!dropDownAlwaysVisible) {
             dismissDropDown();
         }
     }
@@ -1026,7 +916,7 @@
     @Override
     public void onWindowFocusChanged(boolean hasWindowFocus) {
         super.onWindowFocusChanged(hasWindowFocus);
-        if (!hasWindowFocus && !mDropDownAlwaysVisible) {
+        if (!hasWindowFocus && !mPopup.isDropDownAlwaysVisible()) {
             dismissDropDown();
         }
     }
@@ -1036,7 +926,7 @@
         super.onDisplayHint(hint);
         switch (hint) {
             case INVISIBLE:
-                if (!mDropDownAlwaysVisible) {
+                if (!mPopup.isDropDownAlwaysVisible()) {
                     dismissDropDown();
                 }
                 break;
@@ -1050,7 +940,7 @@
         if (!focused) {
             performValidation();
         }
-        if (!focused && !mDropDownAlwaysVisible) {
+        if (!focused && !mPopup.isDropDownAlwaysVisible()) {
             dismissDropDown();
         }
     }
@@ -1075,8 +965,6 @@
             imm.displayCompletions(this, null);
         }
         mPopup.dismiss();
-        mPopup.setContentView(null);
-        mDropDownList = null;
     }
 
     @Override
@@ -1089,18 +977,6 @@
 
         return result;
     }
-    
-    /**
-     * <p>Used for lazy instantiation of the anchor view from the id we have. If the value of
-     * the id is NO_ID or we can't find a view for the given id, we return this TextView as
-     * the default anchoring point.</p>
-     */
-    private View getDropDownAnchorView() {
-        if (mDropDownAnchorView == null && mDropDownAnchorId != View.NO_ID) {
-            mDropDownAnchorView = getRootView().findViewById(mDropDownAnchorId);
-        }
-        return mDropDownAnchorView == null ? this : mDropDownAnchorView;
-    }
 
     /**
      * Issues a runnable to show the dropdown as soon as possible.
@@ -1108,7 +984,7 @@
      * @hide internal used only by SearchDialog
      */
     public void showDropDownAfterLayout() {
-        post(mShowDropDownRunnable);
+        mPopup.postShow();
     }
     
     /**
@@ -1119,7 +995,7 @@
      */
     public void ensureImeVisible(boolean visible) {
         mPopup.setInputMethodMode(visible
-                ? PopupWindow.INPUT_METHOD_NEEDED : PopupWindow.INPUT_METHOD_NOT_NEEDED);
+                ? ListPopupWindow.INPUT_METHOD_NEEDED : ListPopupWindow.INPUT_METHOD_NOT_NEEDED);
         showDropDown();
     }
 
@@ -1127,89 +1003,21 @@
      * @hide internal used only here and SearchDialog
      */
     public boolean isInputMethodNotNeeded() {
-        return mPopup.getInputMethodMode() == PopupWindow.INPUT_METHOD_NOT_NEEDED;
+        return mPopup.getInputMethodMode() == ListPopupWindow.INPUT_METHOD_NOT_NEEDED;
     }
 
     /**
      * <p>Displays the drop down on screen.</p>
      */
     public void showDropDown() {
-        int height = buildDropDown();
-
-        int widthSpec = 0;
-        int heightSpec = 0;
-
-        boolean noInputMethod = isInputMethodNotNeeded();
-
-        if (mPopup.isShowing()) {
-            if (mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT) {
-                // The call to PopupWindow's update method below can accept -1 for any
-                // value you do not want to update.
-                widthSpec = -1;
-            } else if (mDropDownWidth == ViewGroup.LayoutParams.WRAP_CONTENT) {
-                widthSpec = getDropDownAnchorView().getWidth();
+        if (mPopup.getAnchorView() == null) {
+            if (mDropDownAnchorId != View.NO_ID) {
+                mPopup.setAnchorView(getRootView().findViewById(mDropDownAnchorId));
             } else {
-                widthSpec = mDropDownWidth;
+                mPopup.setAnchorView(this);
             }
-
-            if (mDropDownHeight == ViewGroup.LayoutParams.MATCH_PARENT) {
-                // The call to PopupWindow's update method below can accept -1 for any
-                // value you do not want to update.
-                heightSpec = noInputMethod ? height : ViewGroup.LayoutParams.MATCH_PARENT;
-                if (noInputMethod) {
-                    mPopup.setWindowLayoutMode(
-                            mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT ?
-                                    ViewGroup.LayoutParams.MATCH_PARENT : 0, 0);
-                } else {
-                    mPopup.setWindowLayoutMode(
-                            mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT ?
-                                    ViewGroup.LayoutParams.MATCH_PARENT : 0,
-                            ViewGroup.LayoutParams.MATCH_PARENT);
-                }
-            } else if (mDropDownHeight == ViewGroup.LayoutParams.WRAP_CONTENT) {
-                heightSpec = height;
-            } else {
-                heightSpec = mDropDownHeight;
-            }
-
-            mPopup.setOutsideTouchable(!mForceIgnoreOutsideTouch && !mDropDownAlwaysVisible);
-
-            mPopup.update(getDropDownAnchorView(), mDropDownHorizontalOffset,
-                    mDropDownVerticalOffset, widthSpec, heightSpec);
-        } else {
-            if (mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT) {
-                widthSpec = ViewGroup.LayoutParams.MATCH_PARENT;
-            } else {
-                if (mDropDownWidth == ViewGroup.LayoutParams.WRAP_CONTENT) {
-                    mPopup.setWidth(getDropDownAnchorView().getWidth());
-                } else {
-                    mPopup.setWidth(mDropDownWidth);
-                }
-            }
-
-            if (mDropDownHeight == ViewGroup.LayoutParams.MATCH_PARENT) {
-                heightSpec = ViewGroup.LayoutParams.MATCH_PARENT;
-            } else {
-                if (mDropDownHeight == ViewGroup.LayoutParams.WRAP_CONTENT) {
-                    mPopup.setHeight(height);
-                } else {
-                    mPopup.setHeight(mDropDownHeight);
-                }
-            }
-
-            mPopup.setWindowLayoutMode(widthSpec, heightSpec);
-            mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);
-            
-            // use outside touchable to dismiss drop down when touching outside of it, so
-            // only set this if the dropdown is not always visible
-            mPopup.setOutsideTouchable(!mForceIgnoreOutsideTouch && !mDropDownAlwaysVisible);
-            mPopup.setTouchInterceptor(new PopupTouchInterceptor());
-            mPopup.showAsDropDown(getDropDownAnchorView(),
-                    mDropDownHorizontalOffset, mDropDownVerticalOffset);
-            mDropDownList.setSelection(ListView.INVALID_POSITION);
-            clearListSelection();
-            post(mHideSelector);
         }
+        mPopup.show();
     }
     
     /**
@@ -1220,19 +1028,10 @@
      * @hide used only by SearchDialog
      */
     public void setForceIgnoreOutsideTouch(boolean forceIgnoreOutsideTouch) {
-        mForceIgnoreOutsideTouch = forceIgnoreOutsideTouch;
+        mPopup.setForceIgnoreOutsideTouch(forceIgnoreOutsideTouch);
     }
-
-    /**
-     * <p>Builds the popup window's content and returns the height the popup
-     * should have. Returns -1 when the content already exists.</p>
-     *
-     * @return the content's height or -1 if content already exists
-     */
-    private int buildDropDown() {
-        ViewGroup dropDownView;
-        int otherHeights = 0;
-
+    
+    private void buildImeCompletions() {
         final ListAdapter adapter = mAdapter;
         if (adapter != null) {
             InputMethodManager imm = InputMethodManager.peekInstance();
@@ -1260,135 +1059,6 @@
                 imm.displayCompletions(this, completions);
             }
         }
-
-        if (mDropDownList == null) {
-            Context context = getContext();
-
-            mHideSelector = new ListSelectorHider();
-
-            /**
-             * This Runnable exists for the sole purpose of checking if the view layout has got
-             * completed and if so call showDropDown to display the drop down. This is used to show
-             * the drop down as soon as possible after user opens up the search dialog, without
-             * waiting for the normal UI pipeline to do it's job which is slower than this method.
-             */
-            mShowDropDownRunnable = new Runnable() {
-                public void run() {
-                    // View layout should be all done before displaying the drop down.
-                    View view = getDropDownAnchorView();
-                    if (view != null && view.getWindowToken() != null) {
-                        showDropDown();
-                    }
-                }
-            };
-
-            mDropDownList = new DropDownListView(context);
-            mDropDownList.setSelector(mDropDownListHighlight);
-            mDropDownList.setAdapter(adapter);
-            mDropDownList.setVerticalFadingEdgeEnabled(true);
-            mDropDownList.setOnItemClickListener(mDropDownItemClickListener);
-            mDropDownList.setFocusable(true);
-            mDropDownList.setFocusableInTouchMode(true);
-            mDropDownList.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
-                public void onItemSelected(AdapterView<?> parent, View view,
-                        int position, long id) {
-
-                    if (position != -1) {
-                        DropDownListView dropDownList = mDropDownList;
-
-                        if (dropDownList != null) {
-                            dropDownList.mListSelectionHidden = false;
-                        }
-                    }
-                }
-
-                public void onNothingSelected(AdapterView<?> parent) {
-                }
-            });
-            mDropDownList.setOnScrollListener(new PopupScrollListener());
-
-            if (mItemSelectedListener != null) {
-                mDropDownList.setOnItemSelectedListener(mItemSelectedListener);
-            }
-
-            dropDownView = mDropDownList;
-
-            View hintView = getHintView(context);
-            if (hintView != null) {
-                // if an hint has been specified, we accomodate more space for it and
-                // add a text view in the drop down menu, at the bottom of the list
-                LinearLayout hintContainer = new LinearLayout(context);
-                hintContainer.setOrientation(LinearLayout.VERTICAL);
-
-                LinearLayout.LayoutParams hintParams = new LinearLayout.LayoutParams(
-                        ViewGroup.LayoutParams.MATCH_PARENT, 0, 1.0f
-                );
-                hintContainer.addView(dropDownView, hintParams);
-                hintContainer.addView(hintView);
-
-                // measure the hint's height to find how much more vertical space
-                // we need to add to the drop down's height
-                int widthSpec = MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.AT_MOST);
-                int heightSpec = MeasureSpec.UNSPECIFIED;
-                hintView.measure(widthSpec, heightSpec);
-
-                hintParams = (LinearLayout.LayoutParams) hintView.getLayoutParams();
-                otherHeights = hintView.getMeasuredHeight() + hintParams.topMargin
-                        + hintParams.bottomMargin;
-
-                dropDownView = hintContainer;
-            }
-
-            mPopup.setContentView(dropDownView);
-        } else {
-            dropDownView = (ViewGroup) mPopup.getContentView();
-            final View view = dropDownView.findViewById(HINT_VIEW_ID);
-            if (view != null) {
-                LinearLayout.LayoutParams hintParams =
-                        (LinearLayout.LayoutParams) view.getLayoutParams();
-                otherHeights = view.getMeasuredHeight() + hintParams.topMargin
-                        + hintParams.bottomMargin;
-            }
-        }
-
-        // Max height available on the screen for a popup.
-        boolean ignoreBottomDecorations =
-                mPopup.getInputMethodMode() == PopupWindow.INPUT_METHOD_NOT_NEEDED;
-        final int maxHeight = mPopup.getMaxAvailableHeight(
-                getDropDownAnchorView(), mDropDownVerticalOffset, ignoreBottomDecorations);
-
-        // getMaxAvailableHeight() subtracts the padding, so we put it back,
-        // to get the available height for the whole window
-        int padding = 0;
-        Drawable background = mPopup.getBackground();
-        if (background != null) {
-            background.getPadding(mTempRect);
-            padding = mTempRect.top + mTempRect.bottom;
-        }
-
-        if (mDropDownAlwaysVisible || mDropDownHeight == ViewGroup.LayoutParams.MATCH_PARENT) {
-            return maxHeight + padding;
-        }
-
-        final int listContent = mDropDownList.measureHeightOfChildren(MeasureSpec.UNSPECIFIED,
-                0, ListView.NO_POSITION, maxHeight - otherHeights, 2);
-        // add padding only if the list has items in it, that way we don't show
-        // the popup if it is not needed
-        if (listContent > 0) otherHeights += padding;
-
-        return listContent + otherHeights;
-    }
-
-    private View getHintView(Context context) {
-        if (mHintText != null && mHintText.length() > 0) {
-            final TextView hintView = (TextView) LayoutInflater.from(context).inflate(
-                    mHintResource, null).findViewById(com.android.internal.R.id.text1);
-            hintView.setText(mHintText);
-            hintView.setId(HINT_VIEW_ID);
-            return hintView;
-        } else {
-            return null;
-        }
     }
 
     /**
@@ -1440,47 +1110,6 @@
         return mFilter;
     }
 
-    private class ListSelectorHider implements Runnable {
-        public void run() {
-            clearListSelection();
-        }
-    }
-
-    private class ResizePopupRunnable implements Runnable {
-        public void run() {
-            mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
-            showDropDown();
-        }
-    }
-
-    private class PopupTouchInterceptor implements OnTouchListener {
-        public boolean onTouch(View v, MotionEvent event) {
-            final int action = event.getAction();
-            if (action == MotionEvent.ACTION_DOWN &&
-                    mPopup != null && mPopup.isShowing()) {
-                postDelayed(mResizePopupRunnable, EXPAND_LIST_TIMEOUT);
-            } else if (action == MotionEvent.ACTION_UP) {
-                removeCallbacks(mResizePopupRunnable);
-            }
-            return false;
-        }
-    }
-    
-    private class PopupScrollListener implements ListView.OnScrollListener {
-        public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
-                int totalItemCount) {
-
-        }
-
-        public void onScrollStateChanged(AbsListView view, int scrollState) {
-            if (scrollState == SCROLL_STATE_TOUCH_SCROLL &&
-                    !isInputMethodNotNeeded() && mPopup.getContentView() != null) {
-                removeCallbacks(mResizePopupRunnable);
-                mResizePopupRunnable.run();
-            }
-        }
-    }
-
     private class DropDownItemClickListener implements AdapterView.OnItemClickListener {
         public void onItemClick(AdapterView parent, View v, int position, long id) {
             performCompletion(v, position, id);
@@ -1488,123 +1117,6 @@
     }
 
     /**
-     * <p>Wrapper class for a ListView. This wrapper hijacks the focus to
-     * make sure the list uses the appropriate drawables and states when
-     * displayed on screen within a drop down. The focus is never actually
-     * passed to the drop down; the list only looks focused.</p>
-     */
-    private static class DropDownListView extends ListView {
-        /*
-         * WARNING: This is a workaround for a touch mode issue.
-         *
-         * Touch mode is propagated lazily to windows. This causes problems in
-         * the following scenario:
-         * - Type something in the AutoCompleteTextView and get some results
-         * - Move down with the d-pad to select an item in the list
-         * - Move up with the d-pad until the selection disappears
-         * - Type more text in the AutoCompleteTextView *using the soft keyboard*
-         *   and get new results; you are now in touch mode
-         * - The selection comes back on the first item in the list, even though
-         *   the list is supposed to be in touch mode
-         *
-         * Using the soft keyboard triggers the touch mode change but that change
-         * is propagated to our window only after the first list layout, therefore
-         * after the list attempts to resurrect the selection.
-         *
-         * The trick to work around this issue is to pretend the list is in touch
-         * mode when we know that the selection should not appear, that is when
-         * we know the user moved the selection away from the list.
-         *
-         * This boolean is set to true whenever we explicitely hide the list's
-         * selection and reset to false whenver we know the user moved the
-         * selection back to the list.
-         *
-         * When this boolean is true, isInTouchMode() returns true, otherwise it
-         * returns super.isInTouchMode().
-         */
-        private boolean mListSelectionHidden;
-
-        /**
-         * <p>Creates a new list view wrapper.</p>
-         *
-         * @param context this view's context
-         */
-        public DropDownListView(Context context) {
-            super(context, null, com.android.internal.R.attr.dropDownListViewStyle);
-        }
-
-        /**
-         * <p>Avoids jarring scrolling effect by ensuring that list elements
-         * made of a text view fit on a single line.</p>
-         *
-         * @param position the item index in the list to get a view for
-         * @return the view for the specified item
-         */
-        @Override
-        View obtainView(int position, boolean[] isScrap) {
-            View view = super.obtainView(position, isScrap);
-
-            if (view instanceof TextView) {
-                ((TextView) view).setHorizontallyScrolling(true);
-            }
-
-            return view;
-        }
-
-        @Override
-        public boolean isInTouchMode() {
-            // WARNING: Please read the comment where mListSelectionHidden is declared
-            return mListSelectionHidden || super.isInTouchMode();
-        }
-
-        /**
-         * <p>Returns the focus state in the drop down.</p>
-         *
-         * @return true always
-         */
-        @Override
-        public boolean hasWindowFocus() {
-            return true;
-        }
-
-        /**
-         * <p>Returns the focus state in the drop down.</p>
-         *
-         * @return true always
-         */
-        @Override
-        public boolean isFocused() {
-            return true;
-        }
-
-        /**
-         * <p>Returns the focus state in the drop down.</p>
-         *
-         * @return true always
-         */
-        @Override
-        public boolean hasFocus() {
-            return true;
-        }
-        
-        protected int[] onCreateDrawableState(int extraSpace) {
-            int[] res = super.onCreateDrawableState(extraSpace);
-            //noinspection ConstantIfStatement
-            if (false) {
-                StringBuilder sb = new StringBuilder("Created drawable state: [");
-                for (int i=0; i<res.length; i++) {
-                    if (i > 0) sb.append(", ");
-                    sb.append("0x");
-                    sb.append(Integer.toHexString(res[i]));
-                }
-                sb.append("]");
-                Log.i(TAG, sb.toString());
-            }
-            return res;
-        }
-    }
-
-    /**
      * This interface is used to make sure that the text entered in this TextView complies to
      * a certain format.  Since there is no foolproof way to prevent the user from leaving
      * this View with an incorrect value in it, all we can do is try to fix it ourselves
@@ -1652,10 +1164,7 @@
     private class PopupDataSetObserver extends DataSetObserver {
         @Override
         public void onChanged() {
-            if (isPopupShowing()) {
-                // This will resize the popup to fit the new adapter's content
-                showDropDown();
-            } else if (mAdapter != null) {
+            if (mAdapter != null) {
                 // If the popup is not showing already, showing it will cause
                 // the list of data set observers attached to the adapter to
                 // change. We can't do it from here, because we are in the middle
@@ -1670,14 +1179,5 @@
                 });
             }
         }
-
-        @Override
-        public void onInvalidated() {
-            if (!mDropDownAlwaysVisible) {
-                // There's no data to display so make sure we're not showing
-                // the drop down and its list
-                dismissDropDown();
-            }
-        }
     }
 }
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 1ed6b16..c47292f 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/GridView.java b/core/java/android/widget/GridView.java
index d2829db..a5b3ed5 100644
--- a/core/java/android/widget/GridView.java
+++ b/core/java/android/widget/GridView.java
@@ -17,21 +17,25 @@
 package android.widget;
 
 import android.content.Context;
+import android.content.Intent;
 import android.content.res.TypedArray;
 import android.graphics.Rect;
 import android.util.AttributeSet;
 import android.view.Gravity;
 import android.view.KeyEvent;
-import android.view.View;
-import android.view.ViewGroup;
 import android.view.SoundEffectConstants;
+import android.view.View;
+import android.view.ViewDebug;
+import android.view.ViewGroup;
 import android.view.animation.GridLayoutAnimationController;
+import android.widget.RemoteViews.RemoteView;
 
 
 /**
  * A view that shows items in two-dimensional scrolling grid. The items in the
  * grid come from the {@link ListAdapter} associated with this view.
  */
+@RemoteView
 public class GridView extends AbsListView {
     public static final int NO_STRETCH = 0;
     public static final int STRETCH_SPACING = 1;
@@ -106,13 +110,23 @@
     }
 
     /**
+     * Sets up this AbsListView to use a remote views adapter which connects to a RemoteViewsService
+     * through the specified intent.
+     * @param intent the intent used to identify the RemoteViewsService for the adapter to connect to.
+     */
+    @android.view.RemotableViewMethod
+    public void setRemoteViewsAdapter(Intent intent) {
+        super.setRemoteViewsAdapter(intent);
+    }
+
+    /**
      * Sets the data behind this GridView.
      *
      * @param adapter the adapter providing the grid's data
      */
     @Override
     public void setAdapter(ListAdapter adapter) {
-        if (null != mAdapter) {
+        if (mAdapter != null && mDataSetObserver != null) {
             mAdapter.unregisterDataSetObserver(mDataSetObserver);
         }
 
@@ -739,6 +753,26 @@
     }
 
     /**
+     * Smoothly scroll to the specified adapter position. The view will
+     * scroll such that the indicated position is displayed.
+     * @param position Scroll to this adapter position.
+     */
+    @android.view.RemotableViewMethod
+    public void smoothScrollToPosition(int position) {
+        super.smoothScrollToPosition(position);
+    }
+
+    /**
+     * Smoothly scroll to the specified adapter position offset. The view will
+     * scroll such that the indicated position is displayed.
+     * @param offset The amount to offset from the adapter position to scroll to.
+     */
+    @android.view.RemotableViewMethod
+    public void smoothScrollByOffset(int offset) {
+        super.smoothScrollByOffset(offset);
+    }
+
+    /**
      * Fills the grid based on positioning the new selection relative to the old
      * selection. The new selection will be placed at, above, or below the
      * location of the new selection depending on how the selection is moving.
@@ -1774,6 +1808,19 @@
             requestLayoutIfNecessary();
         }
     }
+    
+    /**
+     * Get the number of columns in the grid. 
+     * Returns {@link #AUTO_FIT} if the Grid has never been laid out.
+     *
+     * @attr ref android.R.styleable#GridView_numColumns
+     * 
+     * @see #setNumColumns(int)
+     */
+    @ViewDebug.ExportedProperty
+    public int getNumColumns() {  
+        return mNumColumns;
+    }
 
     /**
      * Make sure views are touching the top or bottom edge, as appropriate for
diff --git a/core/java/android/widget/LinearLayout.java b/core/java/android/widget/LinearLayout.java
index bd07e1f..7254c3c 100644
--- a/core/java/android/widget/LinearLayout.java
+++ b/core/java/android/widget/LinearLayout.java
@@ -40,6 +40,13 @@
  * <p>
  * Also see {@link LinearLayout.LayoutParams android.widget.LinearLayout.LayoutParams}
  * for layout attributes </p>
+ *
+ * @attr ref android.R.styleable#LinearLayout_baselineAligned
+ * @attr ref android.R.styleable#LinearLayout_baselineAlignedChildIndex
+ * @attr ref android.R.styleable#LinearLayout_gravity
+ * @attr ref android.R.styleable#LinearLayout_measureWithLargestChild
+ * @attr ref android.R.styleable#LinearLayout_orientation
+ * @attr ref android.R.styleable#LinearLayout_weightSum
  */
 @RemoteView
 public class LinearLayout extends ViewGroup {
@@ -112,7 +119,11 @@
     }
 
     public LinearLayout(Context context, AttributeSet attrs) {
-        super(context, attrs);
+        this(context, attrs, 0);
+    }
+    
+    public LinearLayout(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
 
         TypedArray a = 
             context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.LinearLayout);
@@ -137,8 +148,7 @@
         mBaselineAlignedChildIndex =
                 a.getInt(com.android.internal.R.styleable.LinearLayout_baselineAlignedChildIndex, -1);
 
-        // TODO: Better name, add Java APIs, make it public
-        mUseLargestChild = a.getBoolean(R.styleable.LinearLayout_useLargestChild, false);
+        mUseLargestChild = a.getBoolean(R.styleable.LinearLayout_measureWithLargestChild, false);
 
         a.recycle();
     }
@@ -167,6 +177,33 @@
         mBaselineAligned = baselineAligned;
     }
 
+    /**
+     * When true, all children with a weight will be considered having
+     * the minimum size of the largest child. If false, all children are
+     * measured normally.
+     * 
+     * @return True to measure children with a weight using the minimum
+     *         size of the largest child, false otherwise.
+     */
+    public boolean isMeasureWithLargestChildEnabled() {
+        return mUseLargestChild;
+    }
+
+    /**
+     * When set to true, all children with a weight will be considered having
+     * the minimum size of the largest child. If false, all children are
+     * measured normally.
+     * 
+     * Disabled by default.
+     * 
+     * @param enabled True to measure children with a weight using the
+     *        minimum size of the largest child, false otherwise.
+     */
+    @android.view.RemotableViewMethod
+    public void setMeasureWithLargestChildEnabled(boolean enabled) {
+        mUseLargestChild = enabled;
+    }
+
     @Override
     public int getBaseline() {
         if (mBaselineAlignedChildIndex < 0) {
diff --git a/core/java/android/widget/ListPopupWindow.java b/core/java/android/widget/ListPopupWindow.java
new file mode 100644
index 0000000..5c34c2c
--- /dev/null
+++ b/core/java/android/widget/ListPopupWindow.java
@@ -0,0 +1,1228 @@
+/*
+ * 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.content.Context;
+import android.database.DataSetObserver;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+import android.view.View.MeasureSpec;
+import android.view.View.OnTouchListener;
+
+/**
+ * A ListPopupWindow anchors itself to a host view and displays a
+ * list of choices. When one is selected, the popup is dismissed.
+ * 
+ * <p>ListPopupWindow contains a number of tricky behaviors surrounding
+ * positioning, scrolling parents to fit the dropdown, interacting
+ * sanely with the IME if present, and others.
+ * 
+ * @see android.widget.AutoCompleteTextView
+ * @see android.widget.Spinner
+ */
+public class ListPopupWindow {
+    private static final String TAG = "ListPopupWindow";
+    private static final boolean DEBUG = false;
+
+    /**
+     * This value controls the length of time that the user
+     * must leave a pointer down without scrolling to expand
+     * the autocomplete dropdown list to cover the IME.
+     */
+    private static final int EXPAND_LIST_TIMEOUT = 250;
+
+    private Context mContext;
+    private PopupWindow mPopup;
+    private ListAdapter mAdapter;
+    private DropDownListView mDropDownList;
+
+    private int mDropDownHeight = ViewGroup.LayoutParams.WRAP_CONTENT;
+    private int mDropDownWidth = ViewGroup.LayoutParams.WRAP_CONTENT;
+    private int mDropDownHorizontalOffset;
+    private int mDropDownVerticalOffset;
+
+    private boolean mDropDownAlwaysVisible = false;
+    private boolean mForceIgnoreOutsideTouch = false;
+
+    private View mPromptView;
+    private int mPromptPosition = POSITION_PROMPT_ABOVE;
+
+    private DataSetObserver mObserver;
+
+    private View mDropDownAnchorView;
+
+    private Drawable mDropDownListHighlight;
+
+    private AdapterView.OnItemClickListener mItemClickListener;
+    private AdapterView.OnItemSelectedListener mItemSelectedListener;
+
+    private final ResizePopupRunnable mResizePopupRunnable = new ResizePopupRunnable();
+    private final PopupTouchInterceptor mTouchInterceptor = new PopupTouchInterceptor();
+    private final PopupScrollListener mScrollListener = new PopupScrollListener();
+    private final ListSelectorHider mHideSelector = new ListSelectorHider();
+    private Runnable mShowDropDownRunnable;
+
+    private Handler mHandler = new Handler();
+
+    private Rect mTempRect = new Rect();
+
+    private boolean mModal;
+
+    /**
+     * The provided prompt view should appear above list content.
+     * 
+     * @see #setPromptPosition(int)
+     * @see #getPromptPosition()
+     * @see #setPromptView(View)
+     */
+    public static final int POSITION_PROMPT_ABOVE = 0;
+
+    /**
+     * The provided prompt view should appear below list content.
+     * 
+     * @see #setPromptPosition(int)
+     * @see #getPromptPosition()
+     * @see #setPromptView(View)
+     */
+    public static final int POSITION_PROMPT_BELOW = 1;
+
+    /**
+     * Alias for {@link ViewGroup.LayoutParams#MATCH_PARENT}.
+     * If used to specify a popup width, the popup will match the width of the anchor view.
+     * If used to specify a popup height, the popup will fill available space.
+     */
+    public static final int MATCH_PARENT = ViewGroup.LayoutParams.MATCH_PARENT;
+    
+    /**
+     * Alias for {@link ViewGroup.LayoutParams#WRAP_CONTENT}.
+     * If used to specify a popup width, the popup will use the width of its content.
+     */
+    public static final int WRAP_CONTENT = ViewGroup.LayoutParams.WRAP_CONTENT;
+    
+    /**
+     * Mode for {@link #setInputMethodMode(int)}: the requirements for the
+     * input method should be based on the focusability of the popup.  That is
+     * if it is focusable than it needs to work with the input method, else
+     * it doesn't.
+     */
+    public static final int INPUT_METHOD_FROM_FOCUSABLE = PopupWindow.INPUT_METHOD_FROM_FOCUSABLE;
+    
+    /**
+     * Mode for {@link #setInputMethodMode(int)}: this popup always needs to
+     * work with an input method, regardless of whether it is focusable.  This
+     * means that it will always be displayed so that the user can also operate
+     * the input method while it is shown.
+     */
+    public static final int INPUT_METHOD_NEEDED = PopupWindow.INPUT_METHOD_NEEDED;
+    
+    /**
+     * Mode for {@link #setInputMethodMode(int)}: this popup never needs to
+     * work with an input method, regardless of whether it is focusable.  This
+     * means that it will always be displayed to use as much space on the
+     * screen as needed, regardless of whether this covers the input method.
+     */
+    public static final int INPUT_METHOD_NOT_NEEDED = PopupWindow.INPUT_METHOD_NOT_NEEDED;
+
+    /**
+     * Create a new, empty popup window capable of displaying items from a ListAdapter.
+     * Backgrounds should be set using {@link #setBackgroundDrawable(Drawable)}.
+     * 
+     * @param context Context used for contained views.
+     */
+    public ListPopupWindow(Context context) {
+        this(context, null, 0, 0);
+    }
+
+    /**
+     * Create a new, empty popup window capable of displaying items from a ListAdapter.
+     * Backgrounds should be set using {@link #setBackgroundDrawable(Drawable)}.
+     * 
+     * @param context Context used for contained views.
+     * @param attrs Attributes from inflating parent views used to style the popup.
+     */
+    public ListPopupWindow(Context context, AttributeSet attrs) {
+        this(context, attrs, 0, 0);
+    }
+
+    /**
+     * Create a new, empty popup window capable of displaying items from a ListAdapter.
+     * Backgrounds should be set using {@link #setBackgroundDrawable(Drawable)}.
+     * 
+     * @param context Context used for contained views.
+     * @param attrs Attributes from inflating parent views used to style the popup.
+     * @param defStyleAttr Default style attribute to use for popup content.
+     */
+    public ListPopupWindow(Context context, AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
+    }
+
+    /**
+     * Create a new, empty popup window capable of displaying items from a ListAdapter.
+     * Backgrounds should be set using {@link #setBackgroundDrawable(Drawable)}.
+     * 
+     * @param context Context used for contained views.
+     * @param attrs Attributes from inflating parent views used to style the popup.
+     * @param defStyleAttr Style attribute to read for default styling of popup content.
+     * @param defStyleRes Style resource ID to use for default styling of popup content.
+     */
+    public ListPopupWindow(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+        mContext = context;
+        mPopup = new PopupWindow(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    /**
+     * Sets the adapter that provides the data and the views to represent the data
+     * in this popup window.
+     *
+     * @param adapter The adapter to use to create this window's content.
+     */
+    public void setAdapter(ListAdapter adapter) {
+        if (mObserver == null) {
+            mObserver = new PopupDataSetObserver();
+        } else if (mAdapter != null) {
+            mAdapter.unregisterDataSetObserver(mObserver);
+        }
+        mAdapter = adapter;
+        if (mAdapter != null) {
+            adapter.registerDataSetObserver(mObserver);
+        }
+        
+        if (mDropDownList != null) {
+            mDropDownList.setAdapter(mAdapter);
+        }
+    }
+
+    /**
+     * Set where the optional prompt view should appear. The default is
+     * {@link #POSITION_PROMPT_ABOVE}.
+     * 
+     * @param position A position constant declaring where the prompt should be displayed.
+     * 
+     * @see #POSITION_PROMPT_ABOVE
+     * @see #POSITION_PROMPT_BELOW
+     */
+    public void setPromptPosition(int position) {
+        mPromptPosition = position;
+    }
+
+    /**
+     * @return Where the optional prompt view should appear.
+     * 
+     * @see #POSITION_PROMPT_ABOVE
+     * @see #POSITION_PROMPT_BELOW
+     */
+    public int getPromptPosition() {
+        return mPromptPosition;
+    }
+
+    /**
+     * Set whether this window should be modal when shown.
+     * 
+     * <p>If a popup window is modal, it will receive all touch and key input.
+     * If the user touches outside the popup window's content area the popup window
+     * will be dismissed.
+     * 
+     * @param modal {@code true} if the popup window should be modal, {@code false} otherwise.
+     */
+    public void setModal(boolean modal) {
+        mModal = true;
+        mPopup.setFocusable(modal);
+    }
+
+    /**
+     * Returns whether the popup window will be modal when shown.
+     * 
+     * @return {@code true} if the popup window will be modal, {@code false} otherwise.
+     */
+    public boolean isModal() {
+        return mModal;
+    }
+
+    /**
+     * Forces outside touches to be ignored. Normally if {@link #isDropDownAlwaysVisible()} is
+     * false, we allow outside touch to dismiss the dropdown. If this is set to true, then we
+     * ignore outside touch even when the drop down is not set to always visible.
+     * 
+     * @hide Used only by AutoCompleteTextView to handle some internal special cases.
+     */
+    public void setForceIgnoreOutsideTouch(boolean forceIgnoreOutsideTouch) {
+        mForceIgnoreOutsideTouch = forceIgnoreOutsideTouch;
+    }
+
+    /**
+     * Sets whether the drop-down should remain visible under certain conditions.
+     *
+     * The drop-down will occupy the entire screen below {@link #getAnchorView} regardless
+     * of the size or content of the list.  {@link #getBackground()} will fill any space
+     * that is not used by the list.
+     *
+     * @param dropDownAlwaysVisible Whether to keep the drop-down visible.
+     *
+     * @hide Only used by AutoCompleteTextView under special conditions.
+     */
+    public void setDropDownAlwaysVisible(boolean dropDownAlwaysVisible) {
+        mDropDownAlwaysVisible = dropDownAlwaysVisible;
+    }
+
+    /**
+     * @return Whether the drop-down is visible under special conditions.
+     *
+     * @hide Only used by AutoCompleteTextView under special conditions.
+     */
+    public boolean isDropDownAlwaysVisible() {
+        return mDropDownAlwaysVisible;
+    }
+
+    /**
+     * Sets the operating mode for the soft input area.
+     *
+     * @param mode The desired mode, see
+     *        {@link android.view.WindowManager.LayoutParams#softInputMode}
+     *        for the full list
+     *
+     * @see android.view.WindowManager.LayoutParams#softInputMode
+     * @see #getSoftInputMode()
+     */
+    public void setSoftInputMode(int mode) {
+        mPopup.setSoftInputMode(mode);
+    }
+
+    /**
+     * Returns the current value in {@link #setSoftInputMode(int)}.
+     *
+     * @see #setSoftInputMode(int)
+     * @see android.view.WindowManager.LayoutParams#softInputMode
+     */
+    public int getSoftInputMode() {
+        return mPopup.getSoftInputMode();
+    }
+
+    /**
+     * Sets a drawable to use as the list item selector.
+     * 
+     * @param selector List selector drawable to use in the popup.
+     */
+    public void setListSelector(Drawable selector) {
+        mDropDownListHighlight = selector;
+    }
+
+    /**
+     * @return The background drawable for the popup window.
+     */
+    public Drawable getBackground() {
+        return mPopup.getBackground();
+    }
+
+    /**
+     * Sets a drawable to be the background for the popup window.
+     * 
+     * @param d A drawable to set as the background.
+     */
+    public void setBackgroundDrawable(Drawable d) {
+        mPopup.setBackgroundDrawable(d);
+    }
+
+    /**
+     * Set an animation style to use when the popup window is shown or dismissed.
+     * 
+     * @param animationStyle Animation style to use.
+     */
+    public void setAnimationStyle(int animationStyle) {
+        mPopup.setAnimationStyle(animationStyle);
+    }
+
+    /**
+     * Returns the animation style that will be used when the popup window is
+     * shown or dismissed.
+     * 
+     * @return Animation style that will be used.
+     */
+    public int getAnimationStyle() {
+        return mPopup.getAnimationStyle();
+    }
+
+    /**
+     * Returns the view that will be used to anchor this popup.
+     * 
+     * @return The popup's anchor view
+     */
+    public View getAnchorView() {
+        return mDropDownAnchorView;
+    }
+
+    /**
+     * Sets the popup's anchor view. This popup will always be positioned relative to
+     * the anchor view when shown.
+     * 
+     * @param anchor The view to use as an anchor.
+     */
+    public void setAnchorView(View anchor) {
+        mDropDownAnchorView = anchor;
+    }
+
+    /**
+     * @return The horizontal offset of the popup from its anchor in pixels.
+     */
+    public int getHorizontalOffset() {
+        return mDropDownHorizontalOffset;
+    }
+
+    /**
+     * Set the horizontal offset of this popup from its anchor view in pixels.
+     * 
+     * @param offset The horizontal offset of the popup from its anchor.
+     */
+    public void setHorizontalOffset(int offset) {
+        mDropDownHorizontalOffset = offset;
+    }
+
+    /**
+     * @return The vertical offset of the popup from its anchor in pixels.
+     */
+    public int getVerticalOffset() {
+        return mDropDownVerticalOffset;
+    }
+
+    /**
+     * Set the vertical offset of this popup from its anchor view in pixels.
+     * 
+     * @param offset The vertical offset of the popup from its anchor.
+     */
+    public void setVerticalOffset(int offset) {
+        mDropDownVerticalOffset = offset;
+    }
+
+    /**
+     * @return The width of the popup window in pixels.
+     */
+    public int getWidth() {
+        return mDropDownWidth;
+    }
+
+    /**
+     * Sets the width of the popup window in pixels. Can also be {@link #MATCH_PARENT}
+     * or {@link #WRAP_CONTENT}.
+     * 
+     * @param width Width of the popup window.
+     */
+    public void setWidth(int width) {
+        mDropDownWidth = width;
+    }
+
+    /**
+     * Sets the width of the popup window by the size of its content. The final width may be
+     * larger to accommodate styled window dressing.
+     *
+     * @param width Desired width of content in pixels.
+     */
+    public void setContentWidth(int width) {
+        Drawable popupBackground = mPopup.getBackground();
+        if (popupBackground != null) {
+            mDropDownWidth = popupBackground.getIntrinsicWidth() + width;
+        }
+    }
+
+    /**
+     * @return The height of the popup window in pixels.
+     */
+    public int getHeight() {
+        return mDropDownHeight;
+    }
+
+    /**
+     * Sets the height of the popup window in pixels. Can also be {@link #MATCH_PARENT}.
+     * 
+     * @param height Height of the popup window.
+     */
+    public void setHeight(int height) {
+        mDropDownHeight = height;
+    }
+
+    /**
+     * Sets a listener to receive events when a list item is clicked.
+     * 
+     * @param clickListener Listener to register
+     * 
+     * @see ListView#setOnItemClickListener(android.widget.AdapterView.OnItemClickListener)
+     */
+    public void setOnItemClickListener(AdapterView.OnItemClickListener clickListener) {
+        mItemClickListener = clickListener;
+    }
+
+    /**
+     * Sets a listener to receive events when a list item is selected.
+     * 
+     * @param selectedListener Listener to register.
+     * 
+     * @see ListView#setOnItemSelectedListener(android.widget.AdapterView.OnItemSelectedListener)
+     */
+    public void setOnItemSelectedListener(AdapterView.OnItemSelectedListener selectedListener) {
+        mItemSelectedListener = selectedListener;
+    }
+
+    /**
+     * Set a view to act as a user prompt for this popup window. Where the prompt view will appear
+     * is controlled by {@link #setPromptPosition(int)}.
+     * 
+     * @param prompt View to use as an informational prompt.
+     */
+    public void setPromptView(View prompt) {
+        boolean showing = isShowing();
+        if (showing) {
+            removePromptView();
+        }
+        mPromptView = prompt;
+        if (showing) {
+            show();
+        }
+    }
+
+    /**
+     * Post a {@link #show()} call to the UI thread.
+     */
+    public void postShow() {
+        mHandler.post(mShowDropDownRunnable);
+    }
+
+    /**
+     * Show the popup list. If the list is already showing, this method
+     * will recalculate the popup's size and position.
+     */
+    public void show() {
+        int height = buildDropDown();
+
+        int widthSpec = 0;
+        int heightSpec = 0;
+
+        boolean noInputMethod = isInputMethodNotNeeded();
+
+        if (mPopup.isShowing()) {
+            if (mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT) {
+                // The call to PopupWindow's update method below can accept -1 for any
+                // value you do not want to update.
+                widthSpec = -1;
+            } else if (mDropDownWidth == ViewGroup.LayoutParams.WRAP_CONTENT) {
+                widthSpec = getAnchorView().getWidth();
+            } else {
+                widthSpec = mDropDownWidth;
+            }
+
+            if (mDropDownHeight == ViewGroup.LayoutParams.MATCH_PARENT) {
+                // The call to PopupWindow's update method below can accept -1 for any
+                // value you do not want to update.
+                heightSpec = noInputMethod ? height : ViewGroup.LayoutParams.MATCH_PARENT;
+                if (noInputMethod) {
+                    mPopup.setWindowLayoutMode(
+                            mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT ?
+                                    ViewGroup.LayoutParams.MATCH_PARENT : 0, 0);
+                } else {
+                    mPopup.setWindowLayoutMode(
+                            mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT ?
+                                    ViewGroup.LayoutParams.MATCH_PARENT : 0,
+                            ViewGroup.LayoutParams.MATCH_PARENT);
+                }
+            } else if (mDropDownHeight == ViewGroup.LayoutParams.WRAP_CONTENT) {
+                heightSpec = height;
+            } else {
+                heightSpec = mDropDownHeight;
+            }
+
+            mPopup.setOutsideTouchable(!mForceIgnoreOutsideTouch && !mDropDownAlwaysVisible);
+
+            mPopup.update(getAnchorView(), mDropDownHorizontalOffset,
+                    mDropDownVerticalOffset, widthSpec, heightSpec);
+        } else {
+            if (mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT) {
+                widthSpec = ViewGroup.LayoutParams.MATCH_PARENT;
+            } else {
+                if (mDropDownWidth == ViewGroup.LayoutParams.WRAP_CONTENT) {
+                    mPopup.setWidth(getAnchorView().getWidth());
+                } else {
+                    mPopup.setWidth(mDropDownWidth);
+                }
+            }
+
+            if (mDropDownHeight == ViewGroup.LayoutParams.MATCH_PARENT) {
+                heightSpec = ViewGroup.LayoutParams.MATCH_PARENT;
+            } else {
+                if (mDropDownHeight == ViewGroup.LayoutParams.WRAP_CONTENT) {
+                    mPopup.setHeight(height);
+                } else {
+                    mPopup.setHeight(mDropDownHeight);
+                }
+            }
+
+            mPopup.setWindowLayoutMode(widthSpec, heightSpec);
+            mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);
+            
+            // use outside touchable to dismiss drop down when touching outside of it, so
+            // only set this if the dropdown is not always visible
+            mPopup.setOutsideTouchable(!mForceIgnoreOutsideTouch && !mDropDownAlwaysVisible);
+            mPopup.setTouchInterceptor(mTouchInterceptor);
+            mPopup.showAsDropDown(getAnchorView(),
+                    mDropDownHorizontalOffset, mDropDownVerticalOffset);
+            mDropDownList.setSelection(ListView.INVALID_POSITION);
+            
+            if (!mModal || mDropDownList.isInTouchMode()) {
+                clearListSelection();
+            }
+            if (!mModal) {
+                mHandler.post(mHideSelector);
+            }
+        }
+    }
+
+    /**
+     * Dismiss the popup window.
+     */
+    public void dismiss() {
+        mPopup.dismiss();
+        removePromptView();
+        mPopup.setContentView(null);
+        mDropDownList = null;
+    }
+
+    private void removePromptView() {
+        if (mPromptView != null) {
+            final ViewParent parent = mPromptView.getParent();
+            if (parent instanceof ViewGroup) {
+                final ViewGroup group = (ViewGroup) parent;
+                group.removeView(mPromptView);
+            }
+        }
+    }
+
+    /**
+     * Control how the popup operates with an input method: one of
+     * {@link #INPUT_METHOD_FROM_FOCUSABLE}, {@link #INPUT_METHOD_NEEDED},
+     * or {@link #INPUT_METHOD_NOT_NEEDED}.
+     * 
+     * <p>If the popup is showing, calling this method will take effect only
+     * the next time the popup is shown or through a manual call to the {@link #show()}
+     * method.</p>
+     * 
+     * @see #getInputMethodMode()
+     * @see #show()
+     */
+    public void setInputMethodMode(int mode) {
+        mPopup.setInputMethodMode(mode);
+    }
+
+    /**
+     * Return the current value in {@link #setInputMethodMode(int)}.
+     * 
+     * @see #setInputMethodMode(int)
+     */
+    public int getInputMethodMode() {
+        return mPopup.getInputMethodMode();
+    }
+
+    /**
+     * Set the selected position of the list.
+     * Only valid when {@link #isShowing()} == {@code true}.
+     * 
+     * @param position List position to set as selected.
+     */
+    public void setSelection(int position) {
+        DropDownListView list = mDropDownList;
+        if (isShowing() && list != null) {
+            list.mListSelectionHidden = false;
+            list.setSelection(position);
+            if (list.getChoiceMode() != ListView.CHOICE_MODE_NONE) {
+                list.setItemChecked(position, true);
+            }
+        }
+    }
+
+    /**
+     * Clear any current list selection.
+     * Only valid when {@link #isShowing()} == {@code true}.
+     */
+    public void clearListSelection() {
+        final DropDownListView list = mDropDownList;
+        if (list != null) {
+            // WARNING: Please read the comment where mListSelectionHidden is declared
+            list.mListSelectionHidden = true;
+            list.hideSelector();
+            list.requestLayout();
+        }
+    }
+
+    /**
+     * @return {@code true} if the popup is currently showing, {@code false} otherwise.
+     */
+    public boolean isShowing() {
+        return mPopup.isShowing();
+    }
+
+    /**
+     * @return {@code true} if this popup is configured to assume the user does not need
+     * to interact with the IME while it is showing, {@code false} otherwise.
+     */
+    public boolean isInputMethodNotNeeded() {
+        return mPopup.getInputMethodMode() == INPUT_METHOD_NOT_NEEDED;
+    }
+
+    /**
+     * Perform an item click operation on the specified list adapter position.
+     * 
+     * @param position Adapter position for performing the click
+     * @return true if the click action could be performed, false if not.
+     *         (e.g. if the popup was not showing, this method would return false.)
+     */
+    public boolean performItemClick(int position) {
+        if (isShowing()) {
+            if (mItemClickListener != null) {
+                final DropDownListView list = mDropDownList;
+                final View child = list.getChildAt(position - list.getFirstVisiblePosition());
+                mItemClickListener.onItemClick(list, child, position, child.getId());
+            }
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * @return The currently selected item or null if the popup is not showing.
+     */
+    public Object getSelectedItem() {
+        if (!isShowing()) {
+            return null;
+        }
+        return mDropDownList.getSelectedItem();
+    }
+
+    /**
+     * @return The position of the currently selected item or {@link ListView#INVALID_POSITION}
+     * if {@link #isShowing()} == {@code false}.
+     * 
+     * @see ListView#getSelectedItemPosition()
+     */
+    public int getSelectedItemPosition() {
+        if (!isShowing()) {
+            return ListView.INVALID_POSITION;
+        }
+        return mDropDownList.getSelectedItemPosition();
+    }
+
+    /**
+     * @return The ID of the currently selected item or {@link ListView#INVALID_ROW_ID}
+     * if {@link #isShowing()} == {@code false}.
+     * 
+     * @see ListView#getSelectedItemId()
+     */
+    public long getSelectedItemId() {
+        if (!isShowing()) {
+            return ListView.INVALID_ROW_ID;
+        }
+        return mDropDownList.getSelectedItemId();
+    }
+
+    /**
+     * @return The View for the currently selected item or null if
+     * {@link #isShowing()} == {@code false}.
+     * 
+     * @see ListView#getSelectedView()
+     */
+    public View getSelectedView() {
+        if (!isShowing()) {
+            return null;
+        }
+        return mDropDownList.getSelectedView();
+    }
+
+    /**
+     * @return The {@link ListView} displayed within the popup window.
+     * Only valid when {@link #isShowing()} == {@code true}.
+     */
+    public ListView getListView() {
+        return mDropDownList;
+    }
+
+    /**
+     * Filter key down events. By forwarding key up events to this function,
+     * views using non-modal ListPopupWindow can have it handle key selection of items.
+     *  
+     * @param keyCode keyCode param passed to the host view's onKeyDown
+     * @param event event param passed to the host view's onKeyDown
+     * @return true if the event was handled, false if it was ignored.
+     * 
+     * @see #setModal(boolean)
+     */
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        // when the drop down is shown, we drive it directly
+        if (isShowing()) {
+            // the key events are forwarded to the list in the drop down view
+            // note that ListView handles space but we don't want that to happen
+            // also if selection is not currently in the drop down, then don't
+            // let center or enter presses go there since that would cause it
+            // to select one of its items
+            if (keyCode != KeyEvent.KEYCODE_SPACE
+                    && (mDropDownList.getSelectedItemPosition() >= 0
+                            || (keyCode != KeyEvent.KEYCODE_ENTER
+                                    && keyCode != KeyEvent.KEYCODE_DPAD_CENTER))) {
+                int curIndex = mDropDownList.getSelectedItemPosition();
+                boolean consumed;
+
+                final boolean below = !mPopup.isAboveAnchor();
+
+                final ListAdapter adapter = mAdapter;
+                
+                boolean allEnabled;
+                int firstItem = Integer.MAX_VALUE;
+                int lastItem = Integer.MIN_VALUE;
+
+                if (adapter != null) {
+                    allEnabled = adapter.areAllItemsEnabled();
+                    firstItem = allEnabled ? 0 :
+                            mDropDownList.lookForSelectablePosition(0, true);
+                    lastItem = allEnabled ? adapter.getCount() - 1 :
+                            mDropDownList.lookForSelectablePosition(adapter.getCount() - 1, false);                    
+                }
+                
+                if ((below && keyCode == KeyEvent.KEYCODE_DPAD_UP && curIndex <= firstItem) ||
+                        (!below && keyCode == KeyEvent.KEYCODE_DPAD_DOWN && curIndex >= lastItem)) {
+                    // When the selection is at the top, we block the key
+                    // event to prevent focus from moving.
+                    clearListSelection();
+                    mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);
+                    show();
+                    return true;
+                } else {
+                    // WARNING: Please read the comment where mListSelectionHidden
+                    //          is declared
+                    mDropDownList.mListSelectionHidden = false;
+                }
+
+                consumed = mDropDownList.onKeyDown(keyCode, event);
+                if (DEBUG) Log.v(TAG, "Key down: code=" + keyCode + " list consumed=" + consumed);
+
+                if (consumed) {
+                    // If it handled the key event, then the user is
+                    // navigating in the list, so we should put it in front.
+                    mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
+                    // Here's a little trick we need to do to make sure that
+                    // the list view is actually showing its focus indicator,
+                    // by ensuring it has focus and getting its window out
+                    // of touch mode.
+                    mDropDownList.requestFocusFromTouch();
+                    show();
+
+                    switch (keyCode) {
+                        // avoid passing the focus from the text view to the
+                        // next component
+                        case KeyEvent.KEYCODE_ENTER:
+                        case KeyEvent.KEYCODE_DPAD_CENTER:
+                        case KeyEvent.KEYCODE_DPAD_DOWN:
+                        case KeyEvent.KEYCODE_DPAD_UP:
+                            return true;
+                    }
+                } else {
+                    if (below && keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
+                        // when the selection is at the bottom, we block the
+                        // event to avoid going to the next focusable widget
+                        if (curIndex == lastItem) {
+                            return true;
+                        }
+                    } else if (!below && keyCode == KeyEvent.KEYCODE_DPAD_UP &&
+                            curIndex == firstItem) {
+                        return true;
+                    }
+                }
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Filter key down events. By forwarding key up events to this function,
+     * views using non-modal ListPopupWindow can have it handle key selection of items.
+     *  
+     * @param keyCode keyCode param passed to the host view's onKeyUp
+     * @param event event param passed to the host view's onKeyUp
+     * @return true if the event was handled, false if it was ignored.
+     * 
+     * @see #setModal(boolean)
+     */
+    public boolean onKeyUp(int keyCode, KeyEvent event) {
+        if (isShowing() && mDropDownList.getSelectedItemPosition() >= 0) {
+            boolean consumed = mDropDownList.onKeyUp(keyCode, event);
+            if (consumed) {
+                switch (keyCode) {
+                    // if the list accepts the key events and the key event
+                    // was a click, the text view gets the selected item
+                    // from the drop down as its content
+                    case KeyEvent.KEYCODE_ENTER:
+                    case KeyEvent.KEYCODE_DPAD_CENTER:
+                        dismiss();
+                        break;
+                }
+            }
+            return consumed;
+        }
+        return false;
+    }
+
+    /**
+     * Filter pre-IME key events. By forwarding {@link View#onKeyPreIme(int, KeyEvent)}
+     * events to this function, views using ListPopupWindow can have it dismiss the popup
+     * when the back key is pressed.
+     *  
+     * @param keyCode keyCode param passed to the host view's onKeyPreIme
+     * @param event event param passed to the host view's onKeyPreIme
+     * @return true if the event was handled, false if it was ignored.
+     * 
+     * @see #setModal(boolean)
+     */
+    public boolean onKeyPreIme(int keyCode, KeyEvent event) {
+        if (keyCode == KeyEvent.KEYCODE_BACK && isShowing()) {
+            // special case for the back key, we do not even try to send it
+            // to the drop down list but instead, consume it immediately
+            final View anchorView = mDropDownAnchorView;
+            if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
+                anchorView.getKeyDispatcherState().startTracking(event, this);
+                return true;
+            } else if (event.getAction() == KeyEvent.ACTION_UP) {
+                anchorView.getKeyDispatcherState().handleUpEvent(event);
+                if (event.isTracking() && !event.isCanceled()) {
+                    dismiss();
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * <p>Builds the popup window's content and returns the height the popup
+     * should have. Returns -1 when the content already exists.</p>
+     *
+     * @return the content's height or -1 if content already exists
+     */
+    private int buildDropDown() {
+        ViewGroup dropDownView;
+        int otherHeights = 0;
+
+        if (mDropDownList == null) {
+            Context context = mContext;
+
+            /**
+             * This Runnable exists for the sole purpose of checking if the view layout has got
+             * completed and if so call showDropDown to display the drop down. This is used to show
+             * the drop down as soon as possible after user opens up the search dialog, without
+             * waiting for the normal UI pipeline to do it's job which is slower than this method.
+             */
+            mShowDropDownRunnable = new Runnable() {
+                public void run() {
+                    // View layout should be all done before displaying the drop down.
+                    View view = getAnchorView();
+                    if (view != null && view.getWindowToken() != null) {
+                        show();
+                    }
+                }
+            };
+
+            mDropDownList = new DropDownListView(context, !mModal);
+            if (mDropDownListHighlight != null) {
+                mDropDownList.setSelector(mDropDownListHighlight);
+            }
+            mDropDownList.setAdapter(mAdapter);
+            mDropDownList.setVerticalFadingEdgeEnabled(true);
+            mDropDownList.setOnItemClickListener(mItemClickListener);
+            mDropDownList.setFocusable(true);
+            mDropDownList.setFocusableInTouchMode(true);
+            mDropDownList.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
+                public void onItemSelected(AdapterView<?> parent, View view,
+                        int position, long id) {
+
+                    if (position != -1) {
+                        DropDownListView dropDownList = mDropDownList;
+
+                        if (dropDownList != null) {
+                            dropDownList.mListSelectionHidden = false;
+                        }
+                    }
+                }
+
+                public void onNothingSelected(AdapterView<?> parent) {
+                }
+            });
+            mDropDownList.setOnScrollListener(mScrollListener);
+
+            if (mItemSelectedListener != null) {
+                mDropDownList.setOnItemSelectedListener(mItemSelectedListener);
+            }
+
+            dropDownView = mDropDownList;
+
+            View hintView = mPromptView;
+            if (hintView != null) {
+                // if an hint has been specified, we accomodate more space for it and
+                // add a text view in the drop down menu, at the bottom of the list
+                LinearLayout hintContainer = new LinearLayout(context);
+                hintContainer.setOrientation(LinearLayout.VERTICAL);
+
+                LinearLayout.LayoutParams hintParams = new LinearLayout.LayoutParams(
+                        ViewGroup.LayoutParams.MATCH_PARENT, 0, 1.0f
+                );
+                
+                switch (mPromptPosition) {
+                case POSITION_PROMPT_BELOW:
+                    hintContainer.addView(dropDownView, hintParams);
+                    hintContainer.addView(hintView);
+                    break;
+                    
+                case POSITION_PROMPT_ABOVE:
+                    hintContainer.addView(hintView);
+                    hintContainer.addView(dropDownView, hintParams);
+                    break;
+                    
+                default:
+                    Log.e(TAG, "Invalid hint position " + mPromptPosition);
+                    break;
+                }
+
+                // measure the hint's height to find how much more vertical space
+                // we need to add to the drop down's height
+                int widthSpec = MeasureSpec.makeMeasureSpec(mDropDownWidth, MeasureSpec.AT_MOST);
+                int heightSpec = MeasureSpec.UNSPECIFIED;
+                hintView.measure(widthSpec, heightSpec);
+
+                hintParams = (LinearLayout.LayoutParams) hintView.getLayoutParams();
+                otherHeights = hintView.getMeasuredHeight() + hintParams.topMargin
+                        + hintParams.bottomMargin;
+
+                dropDownView = hintContainer;
+            }
+
+            mPopup.setContentView(dropDownView);
+        } else {
+            dropDownView = (ViewGroup) mPopup.getContentView();
+            final View view = mPromptView;
+            if (view != null) {
+                LinearLayout.LayoutParams hintParams =
+                        (LinearLayout.LayoutParams) view.getLayoutParams();
+                otherHeights = view.getMeasuredHeight() + hintParams.topMargin
+                        + hintParams.bottomMargin;
+            }
+        }
+
+        // Max height available on the screen for a popup.
+        boolean ignoreBottomDecorations =
+                mPopup.getInputMethodMode() == PopupWindow.INPUT_METHOD_NOT_NEEDED;
+        final int maxHeight = mPopup.getMaxAvailableHeight(
+                getAnchorView(), mDropDownVerticalOffset, ignoreBottomDecorations);
+
+        // getMaxAvailableHeight() subtracts the padding, so we put it back,
+        // to get the available height for the whole window
+        int padding = 0;
+        Drawable background = mPopup.getBackground();
+        if (background != null) {
+            background.getPadding(mTempRect);
+            padding = mTempRect.top + mTempRect.bottom;
+        }
+
+        if (mDropDownAlwaysVisible || mDropDownHeight == ViewGroup.LayoutParams.MATCH_PARENT) {
+            return maxHeight + padding;
+        }
+
+        final int listContent = mDropDownList.measureHeightOfChildren(MeasureSpec.UNSPECIFIED,
+                0, ListView.NO_POSITION, maxHeight - otherHeights, 2);
+        // add padding only if the list has items in it, that way we don't show
+        // the popup if it is not needed
+        if (listContent > 0) otherHeights += padding;
+
+        return listContent + otherHeights;
+    }
+
+    /**
+     * <p>Wrapper class for a ListView. This wrapper can hijack the focus to
+     * make sure the list uses the appropriate drawables and states when
+     * displayed on screen within a drop down. The focus is never actually
+     * passed to the drop down in this mode; the list only looks focused.</p>
+     */
+    private static class DropDownListView extends ListView {
+        private static final String TAG = ListPopupWindow.TAG + ".DropDownListView";
+        /*
+         * WARNING: This is a workaround for a touch mode issue.
+         *
+         * Touch mode is propagated lazily to windows. This causes problems in
+         * the following scenario:
+         * - Type something in the AutoCompleteTextView and get some results
+         * - Move down with the d-pad to select an item in the list
+         * - Move up with the d-pad until the selection disappears
+         * - Type more text in the AutoCompleteTextView *using the soft keyboard*
+         *   and get new results; you are now in touch mode
+         * - The selection comes back on the first item in the list, even though
+         *   the list is supposed to be in touch mode
+         *
+         * Using the soft keyboard triggers the touch mode change but that change
+         * is propagated to our window only after the first list layout, therefore
+         * after the list attempts to resurrect the selection.
+         *
+         * The trick to work around this issue is to pretend the list is in touch
+         * mode when we know that the selection should not appear, that is when
+         * we know the user moved the selection away from the list.
+         *
+         * This boolean is set to true whenever we explicitly hide the list's
+         * selection and reset to false whenever we know the user moved the
+         * selection back to the list.
+         *
+         * When this boolean is true, isInTouchMode() returns true, otherwise it
+         * returns super.isInTouchMode().
+         */
+        private boolean mListSelectionHidden;
+        
+        /**
+         * True if this wrapper should fake focus.
+         */
+        private boolean mHijackFocus;
+
+        /**
+         * <p>Creates a new list view wrapper.</p>
+         *
+         * @param context this view's context
+         */
+        public DropDownListView(Context context, boolean hijackFocus) {
+            super(context, null, com.android.internal.R.attr.dropDownListViewStyle);
+            mHijackFocus = hijackFocus;
+        }
+
+        /**
+         * <p>Avoids jarring scrolling effect by ensuring that list elements
+         * made of a text view fit on a single line.</p>
+         *
+         * @param position the item index in the list to get a view for
+         * @return the view for the specified item
+         */
+        @Override
+        View obtainView(int position, boolean[] isScrap) {
+            View view = super.obtainView(position, isScrap);
+
+            if (view instanceof TextView) {
+                ((TextView) view).setHorizontallyScrolling(true);
+            }
+
+            return view;
+        }
+
+        @Override
+        public boolean isInTouchMode() {
+            // WARNING: Please read the comment where mListSelectionHidden is declared
+            return (mHijackFocus && mListSelectionHidden) || super.isInTouchMode();
+        }
+
+        /**
+         * <p>Returns the focus state in the drop down.</p>
+         *
+         * @return true always if hijacking focus
+         */
+        @Override
+        public boolean hasWindowFocus() {
+            return mHijackFocus || super.hasWindowFocus();
+        }
+
+        /**
+         * <p>Returns the focus state in the drop down.</p>
+         *
+         * @return true always if hijacking focus
+         */
+        @Override
+        public boolean isFocused() {
+            return mHijackFocus || super.isFocused();
+        }
+
+        /**
+         * <p>Returns the focus state in the drop down.</p>
+         *
+         * @return true always if hijacking focus
+         */
+        @Override
+        public boolean hasFocus() {
+            return mHijackFocus || super.hasFocus();
+        }
+    }
+
+    private class PopupDataSetObserver extends DataSetObserver {
+        @Override
+        public void onChanged() {
+            if (isShowing()) {
+                // Resize the popup to fit new content
+                show();
+            }
+        }
+        
+        @Override
+        public void onInvalidated() {
+            dismiss();
+        }
+    }
+
+    private class ListSelectorHider implements Runnable {
+        public void run() {
+            clearListSelection();
+        }
+    }
+
+    private class ResizePopupRunnable implements Runnable {
+        public void run() {
+            mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
+            show();
+        }
+    }
+
+    private class PopupTouchInterceptor implements OnTouchListener {
+        public boolean onTouch(View v, MotionEvent event) {
+            final int action = event.getAction();
+            final int x = (int) event.getX();
+            final int y = (int) event.getY();
+            
+            if (action == MotionEvent.ACTION_DOWN &&
+                    mPopup != null && mPopup.isShowing() &&
+                    (x >= 0 && x < getWidth() && y >= 0 && y < getHeight())) {
+                mHandler.postDelayed(mResizePopupRunnable, EXPAND_LIST_TIMEOUT);
+            } else if (action == MotionEvent.ACTION_UP) {
+                mHandler.removeCallbacks(mResizePopupRunnable);
+            }
+            return false;
+        }
+    }
+
+    private class PopupScrollListener implements ListView.OnScrollListener {
+        public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
+                int totalItemCount) {
+
+        }
+
+        public void onScrollStateChanged(AbsListView view, int scrollState) {
+            if (scrollState == SCROLL_STATE_TOUCH_SCROLL &&
+                    !isInputMethodNotNeeded() && mPopup.getContentView() != null) {
+                mHandler.removeCallbacks(mResizePopupRunnable);
+                mResizePopupRunnable.run();
+            }
+        }
+    }
+}
diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java
index 892c44a..0bb41e5 100644
--- a/core/java/android/widget/ListView.java
+++ b/core/java/android/widget/ListView.java
@@ -20,6 +20,7 @@
 import com.google.android.collect.Lists;
 
 import android.content.Context;
+import android.content.Intent;
 import android.content.res.TypedArray;
 import android.graphics.Canvas;
 import android.graphics.Paint;
@@ -32,8 +33,12 @@
 import android.util.AttributeSet;
 import android.util.LongSparseArray;
 import android.util.SparseBooleanArray;
+import android.view.ActionMode;
 import android.view.FocusFinder;
+import android.view.HapticFeedbackConstants;
 import android.view.KeyEvent;
+import android.view.Menu;
+import android.view.MenuItem;
 import android.view.MotionEvent;
 import android.view.SoundEffectConstants;
 import android.view.View;
@@ -41,6 +46,7 @@
 import android.view.ViewGroup;
 import android.view.ViewParent;
 import android.view.accessibility.AccessibilityEvent;
+import android.widget.RemoteViews.RemoteView;
 
 import java.util.ArrayList;
 
@@ -65,6 +71,7 @@
  * @attr ref android.R.styleable#ListView_headerDividersEnabled
  * @attr ref android.R.styleable#ListView_footerDividersEnabled
  */
+@RemoteView
 public class ListView extends AbsListView {
     /**
      * Used to indicate a no preference for a position type.
@@ -87,6 +94,11 @@
     public static final int CHOICE_MODE_MULTIPLE = 2;
 
     /**
+     * The list allows multiple choices in a modal selection mode
+     */
+    public static final int CHOICE_MODE_MULTIPLE_MODAL = 3;
+
+    /**
      * When arrow scrolling, ListView will never scroll more than this factor
      * times the height of the list.
      */
@@ -144,7 +156,12 @@
 
     // Keeps focused children visible through resizes
     private FocusSelector mFocusSelector;
-    
+
+    // Controls CHOICE_MODE_MULTIPLE_MODAL. null when inactive.
+    private ActionMode mChoiceActionMode;
+    private MultiChoiceModeWrapper mMultiChoiceModeCallback;
+    private int mCheckedItemCount;
+
     public ListView(Context context) {
         this(context, null);
     }
@@ -401,6 +418,16 @@
     }
 
     /**
+     * Sets up this AbsListView to use a remote views adapter which connects to a RemoteViewsService
+     * through the specified intent.
+     * @param intent the intent used to identify the RemoteViewsService for the adapter to connect to.
+     */
+    @android.view.RemotableViewMethod
+    public void setRemoteViewsAdapter(Intent intent) {
+        super.setRemoteViewsAdapter(intent);
+    }
+
+    /**
      * Sets the data behind this ListView.
      *
      * The adapter passed to this method may be wrapped by a {@link WrapperListAdapter},
@@ -415,7 +442,7 @@
      */
     @Override
     public void setAdapter(ListAdapter adapter) {
-        if (null != mAdapter) {
+        if (mAdapter != null && mDataSetObserver != null) {
             mAdapter.unregisterDataSetObserver(mDataSetObserver);
         }
 
@@ -863,6 +890,25 @@
         return topSelectionPixel;
     }
 
+    /**
+     * Smoothly scroll to the specified adapter position. The view will
+     * scroll such that the indicated position is displayed.
+     * @param position Scroll to this adapter position.
+     */
+    @android.view.RemotableViewMethod
+    public void smoothScrollToPosition(int position) {
+        super.smoothScrollToPosition(position);
+    }
+
+    /**
+     * Smoothly scroll to the specified adapter position offset. The view will
+     * scroll such that the indicated position is displayed.
+     * @param offset The amount to offset from the adapter position to scroll to.
+     */
+    @android.view.RemotableViewMethod
+    public void smoothScrollByOffset(int offset) {
+        super.smoothScrollByOffset(offset);
+    }
 
     /**
      * Fills the list based on positioning the new selection relative to the old
@@ -2968,7 +3014,7 @@
             // fill a rect where the dividers would be for non-selectable items
             // If the list is opaque and the background is also opaque, we don't
             // need to draw anything since the background will do it for us
-            final boolean fillForMissingDividers = drawDividers && isOpaque() && !super.isOpaque();
+            final boolean fillForMissingDividers = isOpaque() && !super.isOpaque();
 
             if (fillForMissingDividers && mDividerPaint == null && mIsCacheColorOpaque) {
                 mDividerPaint = new Paint();
@@ -2978,7 +3024,7 @@
 
             final int listBottom = mBottom - mTop - mListPadding.bottom + mScrollY;
             if (!mStackFromBottom) {
-                int bottom = 0;
+                int bottom;
                 
                 final int scrollY = mScrollY;
                 for (int i = 0; i < count; i++) {
@@ -2987,18 +3033,16 @@
                         View child = getChildAt(i);
                         bottom = child.getBottom();
                         // Don't draw dividers next to items that are not enabled
-                        if (drawDividers) {
-                            if ((areAllItemsSelectable ||
-                                    (adapter.isEnabled(first + i) && (i == count - 1 ||
-                                            adapter.isEnabled(first + i + 1))))) {
-                                bounds.top = bottom;
-                                bounds.bottom = bottom + dividerHeight;
-                                drawDivider(canvas, bounds, i);
-                            } else if (fillForMissingDividers) {
-                                bounds.top = bottom;
-                                bounds.bottom = bottom + dividerHeight;
-                                canvas.drawRect(bounds, paint);
-                            }
+                        if ((areAllItemsSelectable ||
+                                (adapter.isEnabled(first + i) && (i == count - 1 ||
+                                        adapter.isEnabled(first + i + 1))))) {
+                            bounds.top = bottom;
+                            bounds.bottom = bottom + dividerHeight;
+                            drawDivider(canvas, bounds, i);
+                        } else if (fillForMissingDividers) {
+                            bounds.top = bottom;
+                            bounds.bottom = bottom + dividerHeight;
+                            canvas.drawRect(bounds, paint);
                         }
                     }
                 }
@@ -3014,7 +3058,7 @@
                         View child = getChildAt(i);
                         top = child.getTop();
                         // Don't draw dividers next to items that are not enabled
-                        if (drawDividers && top > listTop) {
+                        if (top > listTop) {
                             if ((areAllItemsSelectable ||
                                     (adapter.isEnabled(first + i) && (i == count - 1 ||
                                             adapter.isEnabled(first + i + 1))))) {
@@ -3034,7 +3078,7 @@
                     }
                 }
                 
-                if (count > 0 && scrollY > 0 && drawDividers) {
+                if (count > 0 && scrollY > 0) {
                     bounds.top = listBottom;
                     bounds.bottom = listBottom + dividerHeight;
                     drawDivider(canvas, bounds, -1);
@@ -3335,6 +3379,10 @@
      */
     public void setChoiceMode(int choiceMode) {
         mChoiceMode = choiceMode;
+        if (mChoiceActionMode != null) {
+            mChoiceActionMode.finish();
+            mChoiceActionMode = null;
+        }
         if (mChoiceMode != CHOICE_MODE_NONE) {
             if (mCheckStates == null) {
                 mCheckStates = new SparseBooleanArray();
@@ -3342,9 +3390,47 @@
             if (mCheckedIdStates == null && mAdapter != null && mAdapter.hasStableIds()) {
                 mCheckedIdStates = new LongSparseArray<Boolean>();
             }
+            // Modal multi-choice mode only has choices when the mode is active. Clear them.
+            if (mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL) {
+                clearChoices();
+                setLongClickable(true);
+            }
         }
     }
 
+    /**
+     * Set a {@link MultiChoiceModeListener} that will manage the lifecycle of the
+     * selection {@link ActionMode}. Only used when the choice mode is set to
+     * {@link #CHOICE_MODE_MULTIPLE_MODAL}.
+     *
+     * @param listener Listener that will manage the selection mode
+     *
+     * @see #setChoiceMode(int)
+     */
+    public void setMultiChoiceModeListener(MultiChoiceModeListener listener) {
+        if (mMultiChoiceModeCallback == null) {
+            mMultiChoiceModeCallback = new MultiChoiceModeWrapper();
+        }
+        mMultiChoiceModeCallback.setWrapped(listener);
+    }
+
+    @Override
+    boolean performLongPress(final View child,
+            final int longPressPosition, final long longPressId) {
+        boolean handled = false;
+        if (mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL) {
+            handled = true;
+            if (mChoiceActionMode == null) {
+                mChoiceActionMode = startActionMode(mMultiChoiceModeCallback);
+                setItemChecked(longPressPosition, true);
+            }
+            // TODO Should we select the long pressed item if we were already in
+            // selection mode? (i.e. treat it like an item click?)
+            performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
+        }
+        return handled | super.performLongPress(child, longPressPosition, longPressId);
+    }
+
     @Override
     public boolean performItemClick(View view, int position, long id) {
         boolean handled = false;
@@ -3352,7 +3438,8 @@
         if (mChoiceMode != CHOICE_MODE_NONE) {
             handled = true;
 
-            if (mChoiceMode == CHOICE_MODE_MULTIPLE) {
+            if (mChoiceMode == CHOICE_MODE_MULTIPLE ||
+                    (mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL && mChoiceActionMode != null)) {
                 boolean newValue = !mCheckStates.get(position, false);
                 mCheckStates.put(position, newValue);
                 if (mCheckedIdStates != null && mAdapter.hasStableIds()) {
@@ -3362,7 +3449,16 @@
                         mCheckedIdStates.delete(mAdapter.getItemId(position));
                     }
                 }
-            } else {
+                if (newValue) {
+                    mCheckedItemCount++;
+                } else {
+                    mCheckedItemCount--;
+                }
+                if (mChoiceActionMode != null) {
+                    mMultiChoiceModeCallback.onItemCheckedStateChanged(mChoiceActionMode,
+                            position, id, newValue);
+                }
+            } else if (mChoiceMode == CHOICE_MODE_SINGLE) {
                 boolean newValue = !mCheckStates.get(position, false);
                 if (newValue) {
                     mCheckStates.clear();
@@ -3371,7 +3467,10 @@
                         mCheckedIdStates.clear();
                         mCheckedIdStates.put(mAdapter.getItemId(position), Boolean.TRUE);
                     }
-                } 
+                    mCheckedItemCount = 1;
+                } else if (mCheckStates.size() == 0 || !mCheckStates.valueAt(0)) {
+                    mCheckedItemCount = 0;
+                }
             }
 
             mDataChanged = true;
@@ -3397,7 +3496,13 @@
             return;
         }
 
-        if (mChoiceMode == CHOICE_MODE_MULTIPLE) {
+        // Start selection mode if needed. We don't need to if we're unchecking something.
+        if (value && mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL && mChoiceActionMode == null) {
+            mChoiceActionMode = startActionMode(mMultiChoiceModeCallback);
+        }
+
+        if (mChoiceMode == CHOICE_MODE_MULTIPLE || mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL) {
+            boolean oldValue = mCheckStates.get(position);
             mCheckStates.put(position, value);
             if (mCheckedIdStates != null && mAdapter.hasStableIds()) {
                 if (value) {
@@ -3406,6 +3511,18 @@
                     mCheckedIdStates.delete(mAdapter.getItemId(position));
                 }
             }
+            if (oldValue != value) {
+                if (value) {
+                    mCheckedItemCount++;
+                } else {
+                    mCheckedItemCount--;
+                }
+            }
+            if (mChoiceActionMode != null) {
+                final long id = mAdapter.getItemId(position);
+                mMultiChoiceModeCallback.onItemCheckedStateChanged(mChoiceActionMode,
+                        position, id, value);
+            }
         } else {
             boolean updateIds = mCheckedIdStates != null && mAdapter.hasStableIds();
             // Clear all values if we're checking something, or unchecking the currently
@@ -3423,6 +3540,9 @@
                 if (updateIds) {
                     mCheckedIdStates.put(mAdapter.getItemId(position), Boolean.TRUE);
                 }
+                mCheckedItemCount = 1;
+            } else if (mCheckStates.size() == 0 || !mCheckStates.valueAt(0)) {
+                mCheckedItemCount = 0;
             }
         }
 
@@ -3435,6 +3555,23 @@
     }
 
     /**
+     * Returns the number of items currently selected. This will only be valid
+     * if the choice mode is not {@link #CHOICE_MODE_NONE} (default).
+     * 
+     * <p>To determine the specific items that are currently selected, use one of
+     * the <code>getChecked*</code> methods.
+     *
+     * @return The number of items currently selected
+     *
+     * @see #getCheckedItemPosition()
+     * @see #getCheckedItemPositions()
+     * @see #getCheckedItemIds()
+     */
+    public int getCheckedItemCount() {
+        return mCheckedItemCount;
+    }
+
+    /**
      * Returns the checked state of the specified position. The result is only
      * valid if the choice mode has been set to {@link #CHOICE_MODE_SINGLE}
      * or {@link #CHOICE_MODE_MULTIPLE}.
@@ -3495,6 +3632,7 @@
      *         
      * @deprecated Use {@link #getCheckedItemIds()} instead.
      */
+    @Deprecated
     public long[] getCheckItemIds() {
         // Use new behavior that correctly handles stable ID mapping.
         if (mAdapter != null && mAdapter.hasStableIds()) {
@@ -3564,6 +3702,76 @@
         if (mCheckedIdStates != null) {
             mCheckedIdStates.clear();
         }
+        mCheckedItemCount = 0;
+    }
+
+    /**
+     * A MultiChoiceModeListener receives events for {@link ListView#CHOICE_MODE_MULTIPLE_MODAL}.
+     * It acts as the {@link ActionMode.Callback} for the selection mode and also receives
+     * {@link #onItemCheckedStateChanged(ActionMode, int, long, boolean)} events when the user
+     * selects and deselects list items.
+     */
+    public interface MultiChoiceModeListener extends ActionMode.Callback {
+        /**
+         * Called when an item is checked or unchecked during selection mode.
+         *
+         * @param mode The {@link ActionMode} providing the selection mode
+         * @param position Adapter position of the item that was checked or unchecked
+         * @param id Adapter ID of the item that was checked or unchecked
+         * @param checked <code>true</code> if the item is now checked, <code>false</code>
+         *                if the item is now unchecked.
+         */
+        public void onItemCheckedStateChanged(ActionMode mode,
+                int position, long id, boolean checked);
+    }
+
+    private class MultiChoiceModeWrapper implements MultiChoiceModeListener {
+        private MultiChoiceModeListener mWrapped;
+
+        public void setWrapped(MultiChoiceModeListener wrapped) {
+            mWrapped = wrapped;
+        }
+
+        public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+            if (mWrapped.onCreateActionMode(mode, menu)) {
+                // Initialize checked graphic state?
+                setLongClickable(false);
+                return true;
+            }
+            return false;
+        }
+
+        public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
+            return mWrapped.onPrepareActionMode(mode, menu);
+        }
+
+        public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
+            return mWrapped.onActionItemClicked(mode, item);
+        }
+
+        public void onDestroyActionMode(ActionMode mode) {
+            mWrapped.onDestroyActionMode(mode);
+            mChoiceActionMode = null;
+
+            // Ending selection mode means deselecting everything.
+            clearChoices();
+
+            mDataChanged = true;
+            rememberSyncState();
+            requestLayout();
+
+            setLongClickable(true);
+        }
+
+        public void onItemCheckedStateChanged(ActionMode mode,
+                int position, long id, boolean checked) {
+            mWrapped.onItemCheckedStateChanged(mode, position, id, checked);
+
+            // If there are no items selected we no longer need the selection mode.
+            if (getCheckedItemCount() == 0) {
+                mode.finish();
+            }
+        }
     }
 
     static class SavedState extends BaseSavedState {
diff --git a/core/java/android/widget/PopupMenu.java b/core/java/android/widget/PopupMenu.java
new file mode 100644
index 0000000..82770ad
--- /dev/null
+++ b/core/java/android/widget/PopupMenu.java
@@ -0,0 +1,154 @@
+/*
+ * 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 com.android.internal.view.menu.MenuBuilder;
+import com.android.internal.view.menu.MenuPopupHelper;
+import com.android.internal.view.menu.SubMenuBuilder;
+
+import android.content.Context;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+
+/**
+ * A PopupMenu displays a {@link Menu} in a modal popup window anchored to a {@link View}.
+ * The popup will appear below the anchor view if there is room, or above it if there is not.
+ * If the IME is visible the popup will not overlap it until it is touched. Touching outside
+ * of the popup will dismiss it.
+ */
+public class PopupMenu implements MenuBuilder.Callback {
+    private Context mContext;
+    private MenuBuilder mMenu;
+    private View mAnchor;
+    private MenuPopupHelper mPopup;
+    private OnMenuItemClickListener mMenuItemClickListener;
+
+    /**
+     * Construct a new PopupMenu.
+     *
+     * @param context Context for the PopupMenu.
+     * @param anchor Anchor view for this popup. The popup will appear below the anchor if there
+     *               is room, or above it if there is not.
+     */
+    public PopupMenu(Context context, View anchor) {
+        // TODO Theme?
+        mContext = context;
+        mMenu = new MenuBuilder(context);
+        mMenu.setCallback(this);
+        mAnchor = anchor;
+        mPopup = new MenuPopupHelper(context, mMenu, anchor);
+    }
+
+    /**
+     * @return the {@link Menu} associated with this popup. Populate the returned Menu with
+     * items before calling {@link #show()}.
+     *
+     * @see #show()
+     * @see #getMenuInflater()
+     */
+    public Menu getMenu() {
+        return mMenu;
+    }
+
+    /**
+     * @return a {@link MenuInflater} that can be used to inflate menu items from XML into the
+     * menu returned by {@link #getMenu()}.
+     *
+     * @see #getMenu()
+     */
+    public MenuInflater getMenuInflater() {
+        return new MenuInflater(mContext);
+    }
+
+    /**
+     * Show the menu popup anchored to the view specified during construction.
+     * @see #dismiss()
+     */
+    public void show() {
+        mPopup.show();
+    }
+
+    /**
+     * Dismiss the menu popup.
+     * @see #show()
+     */
+    public void dismiss() {
+        mPopup.dismiss();
+    }
+
+    public void setOnMenuItemClickListener(OnMenuItemClickListener listener) {
+        mMenuItemClickListener = listener;
+    }
+
+    /**
+     * @hide
+     */
+    public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) {
+        if (mMenuItemClickListener != null) {
+            return mMenuItemClickListener.onMenuItemClick(item);
+        }
+        return false;
+    }
+
+    /**
+     * @hide
+     */
+    public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
+    }
+
+    /**
+     * @hide
+     */
+    public boolean onSubMenuSelected(SubMenuBuilder subMenu) {
+        if (!subMenu.hasVisibleItems()) {
+            return true;
+        }
+
+        // Current menu will be dismissed by the normal helper, submenu will be shown in its place.
+        new MenuPopupHelper(mContext, subMenu, mAnchor).show();
+        return true;
+    }
+
+    /**
+     * @hide
+     */
+    public void onCloseSubMenu(SubMenuBuilder menu) {
+    }
+
+    /**
+     * @hide
+     */
+    public void onMenuModeChange(MenuBuilder menu) {
+    }
+
+    /**
+     * Interface responsible for receiving menu item click events if the items themselves
+     * do not have individual item click listeners.
+     */
+    public interface OnMenuItemClickListener {
+        /**
+         * This method will be invoked when a menu item is clicked if the item itself did
+         * not already handle the event.
+         *
+         * @param item {@link MenuItem} that was clicked
+         * @return <code>true</code> if the event was handled, <code>false</code> otherwise.
+         */
+        public boolean onMenuItemClick(MenuItem item);
+    }
+}
diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java
index 0378328..d404ce7 100644
--- a/core/java/android/widget/PopupWindow.java
+++ b/core/java/android/widget/PopupWindow.java
@@ -16,27 +16,28 @@
 
 package android.widget;
 
-import com.android.internal.R;
+import java.lang.ref.WeakReference;
 
 import android.content.Context;
 import android.content.res.TypedArray;
-import android.view.KeyEvent;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.WindowManager;
-import android.view.Gravity;
-import android.view.ViewGroup;
-import android.view.ViewTreeObserver;
-import android.view.ViewTreeObserver.OnScrollChangedListener;
-import android.view.View.OnTouchListener;
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.StateListDrawable;
 import android.os.IBinder;
 import android.util.AttributeSet;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
+import android.view.WindowManager;
+import android.view.View.OnTouchListener;
+import android.view.ViewTreeObserver.OnScrollChangedListener;
 
-import java.lang.ref.WeakReference;
+import com.android.internal.R;
 
 /**
  * <p>A popup window that can be used to display an arbitrary view. The popup
@@ -157,12 +158,21 @@
      * <p>The popup does provide a background.</p>
      */
     public PopupWindow(Context context, AttributeSet attrs, int defStyle) {
+        this(context, attrs, defStyle, 0);
+    }
+    
+    /**
+     * <p>Create a new, empty, non focusable popup window of dimension (0,0).</p>
+     * 
+     * <p>The popup does not provide a background.</p>
+     */
+    public PopupWindow(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
         mContext = context;
         mWindowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
 
         TypedArray a =
             context.obtainStyledAttributes(
-                attrs, com.android.internal.R.styleable.PopupWindow, defStyle, 0);
+                attrs, com.android.internal.R.styleable.PopupWindow, defStyleAttr, defStyleRes);
 
         mBackground = a.getDrawable(R.styleable.PopupWindow_popupBackground);
 
@@ -1315,6 +1325,7 @@
     }
 
     private class PopupViewContainer extends FrameLayout {
+        private static final String TAG = "PopupWindow.PopupViewContainer";
 
         public PopupViewContainer(Context context) {
             super(context);
diff --git a/core/java/android/widget/ProgressBar.java b/core/java/android/widget/ProgressBar.java
index 8e9eb05..71f0c2f 100644
--- a/core/java/android/widget/ProgressBar.java
+++ b/core/java/android/widget/ProgressBar.java
@@ -715,8 +715,8 @@
             mAnimation.setDuration(mDuration);
             mAnimation.setInterpolator(mInterpolator);
             mAnimation.setStartTime(Animation.START_ON_FIRST_FRAME);
-            postInvalidate();
         }
+        postInvalidate();
     }
 
     /**
@@ -729,6 +729,7 @@
             ((Animatable) mIndeterminateDrawable).stop();
             mShouldStartAnimationDrawable = false;
         }
+        postInvalidate();
     }
 
     /**
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/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 7a70c80..50745dc0 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -60,12 +60,12 @@
      * The package name of the package containing the layout 
      * resource. (Added to the parcel)
      */
-    private String mPackage;
+    private final String mPackage;
     
     /**
      * The resource ID of the layout file. (Added to the parcel)
      */
-    private int mLayoutId;
+    private final int mLayoutId;
 
     /**
      * An array of actions to perform on the view tree once it has been
@@ -285,6 +285,7 @@
         static final int URI = 11;
         static final int BITMAP = 12;
         static final int BUNDLE = 13;
+        static final int INTENT = 14;
 
         int viewId;
         String methodName;
@@ -347,6 +348,9 @@
                 case BUNDLE:
                     this.value = in.readBundle();
                     break;
+                case INTENT:
+                    this.value = Intent.CREATOR.createFromParcel(in);
+                    break;
                 default:
                     break;
             }
@@ -402,6 +406,9 @@
                 case BUNDLE:
                     out.writeBundle((Bundle) this.value);
                     break;
+                case INTENT:
+                    ((Intent)this.value).writeToParcel(out, flags);
+                    break;
                 default:
                     break;
             }
@@ -435,6 +442,8 @@
                     return Bitmap.class;
                 case BUNDLE:
                     return Bundle.class;
+                case INTENT:
+                    return Intent.class;
                 default:
                     return null;
             }
@@ -569,6 +578,7 @@
         }
     }
 
+    @Override
     public RemoteViews clone() {
         final RemoteViews that = new RemoteViews(mPackage, mLayoutId);
         if (mActions != null) {
@@ -769,6 +779,37 @@
     }
 
     /**
+     * Equivalent to calling {@link android.widget.AbsListView#setRemoteViewsAdapter(Intent)}.
+     *
+     * @param viewId The id of the view whose text should change
+     * @param intent The intent of the service which will be
+     *            providing data to the RemoteViewsAdapter
+     */
+    public void setRemoteAdapter(int viewId, Intent intent) {
+        setIntent(viewId, "setRemoteViewsAdapter", intent);
+    }
+
+    /**
+     * Equivalent to calling {@link android.widget.AbsListView#smoothScrollToPosition(int, int)}.
+     *
+     * @param viewId The id of the view whose text should change
+     * @param position Scroll to this adapter position
+     */
+    public void setScrollPosition(int viewId, int position) {
+        setInt(viewId, "smoothScrollToPosition", position);
+    }
+
+    /**
+     * Equivalent to calling {@link android.widget.AbsListView#smoothScrollToPosition(int, int)}.
+     *
+     * @param viewId The id of the view whose text should change
+     * @param offset Scroll by this adapter position offset
+     */
+    public void setRelativeScrollPosition(int viewId, int offset) {
+        setInt(viewId, "smoothScrollByOffset", offset);
+    }
+
+    /**
      * Call a method taking one boolean on a view in the layout for this RemoteViews.
      *
      * @param viewId The id of the view whose text should change
@@ -915,6 +956,16 @@
     }
 
     /**
+     *
+     * @param viewId
+     * @param methodName
+     * @param value
+     */
+    public void setIntent(int viewId, String methodName, Intent value) {
+        addAction(new ReflectionAction(viewId, methodName, ReflectionAction.INTENT, value));
+    }
+
+    /**
      * Inflates the view hierarchy represented by this object and applies
      * all of the actions.
      * 
diff --git a/core/java/android/widget/RemoteViewsAdapter.java b/core/java/android/widget/RemoteViewsAdapter.java
new file mode 100644
index 0000000..52635e8
--- /dev/null
+++ b/core/java/android/widget/RemoteViewsAdapter.java
@@ -0,0 +1,687 @@
+/*
+ * 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;
+
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.Map;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.graphics.Color;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.View.MeasureSpec;
+
+import com.android.internal.widget.IRemoteViewsFactory;
+
+/**
+ * An adapter to a RemoteViewsService which fetches and caches RemoteViews
+ * to be later inflated as child views.
+ */
+/** @hide */
+public class RemoteViewsAdapter extends BaseAdapter {
+
+    private static final String LOG_TAG = "RemoteViewsAdapter";
+
+    private Context mContext;
+    private Intent mIntent;
+    private RemoteViewsAdapterServiceConnection mServiceConnection;
+    private RemoteViewsCache mViewCache;
+
+    private HandlerThread mWorkerThread;
+    // items may be interrupted within the normally processed queues
+    private Handler mWorkerQueue;
+    private Handler mMainQueue;
+    // items are never dequeued from the priority queue and must run
+    private Handler mWorkerPriorityQueue;
+    private Handler mMainPriorityQueue;
+
+    /**
+     * An interface for the RemoteAdapter to notify other classes when adapters
+     * are actually connected to/disconnected from their actual services.
+     */
+    public interface RemoteAdapterConnectionCallback {
+        public void onRemoteAdapterConnected();
+
+        public void onRemoteAdapterDisconnected();
+    }
+
+    /**
+     * The service connection that gets populated when the RemoteViewsService is
+     * bound.
+     */
+    private class RemoteViewsAdapterServiceConnection implements ServiceConnection {
+        private boolean mConnected;
+        private IRemoteViewsFactory mRemoteViewsFactory;
+        private RemoteAdapterConnectionCallback mCallback;
+
+        public RemoteViewsAdapterServiceConnection(RemoteAdapterConnectionCallback callback) {
+            mCallback = callback;
+        }
+
+        public void onServiceConnected(ComponentName name, IBinder service) {
+            mRemoteViewsFactory = IRemoteViewsFactory.Stub.asInterface(service);
+            mConnected = true;
+
+            // notifyDataSetChanged should be called first, to ensure that the
+            // views are not updated twice
+            notifyDataSetChanged();
+
+            // post a new runnable to load the appropriate data, then callback
+            mWorkerPriorityQueue.post(new Runnable() {
+                @Override
+                public void run() {
+                    // we need to get the viewTypeCount specifically, so just get all the
+                    // metadata
+                    mViewCache.requestMetaData();
+
+                    // post a runnable to call the callback on the main thread
+                    mMainPriorityQueue.post(new Runnable() {
+                        @Override
+                        public void run() {
+                            if (mCallback != null)
+                                mCallback.onRemoteAdapterConnected();
+                        }
+                    });
+                }
+            });
+
+            // start the background loader
+            mViewCache.startBackgroundLoader();
+        }
+
+        public void onServiceDisconnected(ComponentName name) {
+            mRemoteViewsFactory = null;
+            mConnected = false;
+
+            // clear the main/worker queues
+            mMainQueue.removeMessages(0);
+            
+            // stop the background loader
+            mViewCache.stopBackgroundLoader();
+
+            if (mCallback != null)
+                mCallback.onRemoteAdapterDisconnected();
+        }
+
+        public IRemoteViewsFactory getRemoteViewsFactory() {
+            return mRemoteViewsFactory;
+        }
+
+        public boolean isConnected() {
+            return mConnected;
+        }
+    }
+
+    /**
+     * An internal cache of remote views.
+     */
+    private class RemoteViewsCache {
+        private RemoteViewsInfo mViewCacheInfo;
+        private RemoteViewsIndexInfo[] mViewCache;
+        private int[] mTmpViewCacheLoadIndices;
+        private LinkedList<Integer> mViewCacheLoadIndices;
+        private boolean mBackgroundLoaderEnabled;
+
+        // if a user loading view is not provided, then we create a temporary one
+        // for the user using the height of the first view
+        private RemoteViews mUserLoadingView;
+        private RemoteViews mFirstView;
+        private int mFirstViewHeight;
+
+        // determines when the current cache window needs to be updated with new
+        // items (ie. when there is not enough slack)
+        private int mViewCacheStartPosition;
+        private int mViewCacheEndPosition;
+        private int mHalfCacheSize;
+        private int mCacheSlack;
+        private final float mCacheSlackPercentage = 0.75f;
+
+        /**
+         * The data structure stored at each index of the cache. Any member 
+         * that is not invalidated persists throughout the lifetime of the cache.
+         */
+        private class RemoteViewsIndexInfo {
+            FrameLayout flipper;
+            RemoteViews view;
+            long itemId;
+            int typeId;
+
+            RemoteViewsIndexInfo() {
+                invalidate();
+            }
+
+            void set(RemoteViews v, long id) {
+                view = v;
+                itemId = id;
+                if (v != null)
+                    typeId = v.getLayoutId();
+                else
+                    typeId = 0;
+            }
+
+            void invalidate() {
+                view = null;
+                itemId = 0;
+                typeId = 0;
+            }
+
+            final boolean isValid() {
+                return (view != null);
+            }
+        }
+
+        /**
+         * Remote adapter metadata. Useful for when we have to lock on something
+         * before updating the metadata.
+         */
+        private class RemoteViewsInfo {
+            int count;
+            int viewTypeCount;
+            boolean hasStableIds;
+            Map<Integer, Integer> mTypeIdIndexMap;
+
+            RemoteViewsInfo() {
+                count = 0;
+                // by default there is at least one dummy view type
+                viewTypeCount = 1;
+                hasStableIds = true;
+                mTypeIdIndexMap = new HashMap<Integer, Integer>();
+            }
+        }
+
+        public RemoteViewsCache(int halfCacheSize) {
+            mHalfCacheSize = halfCacheSize;
+            mCacheSlack = Math.round(mCacheSlackPercentage * mHalfCacheSize);
+            mViewCacheStartPosition = 0;
+            mViewCacheEndPosition = -1;
+            mBackgroundLoaderEnabled = false;
+
+            // initialize the cache
+            int cacheSize = 2 * mHalfCacheSize + 1;
+            mViewCacheInfo = new RemoteViewsInfo();
+            mViewCache = new RemoteViewsIndexInfo[cacheSize];
+            for (int i = 0; i < mViewCache.length; ++i) {
+                mViewCache[i] = new RemoteViewsIndexInfo();
+            }
+            mTmpViewCacheLoadIndices = new int[cacheSize];
+            mViewCacheLoadIndices = new LinkedList<Integer>();
+        }
+
+        private final boolean contains(int position) {
+            return (mViewCacheStartPosition <= position) && (position <= mViewCacheEndPosition);
+        }
+
+        private final boolean containsAndIsValid(int position) {
+            if (contains(position)) {
+                RemoteViewsIndexInfo indexInfo = mViewCache[getCacheIndex(position)];
+                if (indexInfo.isValid()) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        private final int getCacheIndex(int position) {
+            // take the modulo of the position
+            return (mViewCache.length + (position % mViewCache.length)) % mViewCache.length;
+        }
+
+        public void requestMetaData() {
+            if (mServiceConnection.isConnected()) {
+                try {
+                    IRemoteViewsFactory factory = mServiceConnection.getRemoteViewsFactory();
+
+                    // get the properties/first view (so that we can use it to
+                    // measure our dummy views)
+                    boolean hasStableIds = factory.hasStableIds();
+                    int viewTypeCount = factory.getViewTypeCount();
+                    int count = factory.getCount();
+                    RemoteViews loadingView = factory.getLoadingView();
+                    RemoteViews firstView = null;
+                    if ((count > 0) && (loadingView == null)) {
+                        firstView = factory.getViewAt(0);
+                    }
+                    synchronized (mViewCacheInfo) {
+                        RemoteViewsInfo info = mViewCacheInfo;
+                        info.hasStableIds = hasStableIds;
+                        info.viewTypeCount = viewTypeCount + 1;
+                        info.count = count;
+                        mUserLoadingView = loadingView;
+                        if (firstView != null) {
+                            mFirstView = firstView;
+                            mFirstViewHeight = -1;
+                        }
+                    }
+                } catch (RemoteException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+
+        protected void updateRemoteViewsInfo(int position) {
+            if (mServiceConnection.isConnected()) {
+                IRemoteViewsFactory factory = mServiceConnection.getRemoteViewsFactory();
+
+                // load the item information
+                RemoteViews remoteView = null;
+                long itemId = 0;
+                try {
+                    remoteView = factory.getViewAt(position);
+                    itemId = factory.getItemId(position);
+                } catch (RemoteException e) {
+                    e.printStackTrace();
+                }
+
+                synchronized (mViewCache) {
+                    // skip if the window has moved
+                    if (position < mViewCacheStartPosition || position > mViewCacheEndPosition)
+                        return;
+
+                    final int positionIndex = position;
+                    final int cacheIndex = getCacheIndex(position);
+                    mViewCache[cacheIndex].set(remoteView, itemId);
+
+                    // notify the main thread when done loading
+                    // flush pending updates
+                    mMainQueue.post(new Runnable() {
+                        @Override
+                        public void run() {
+                            // swap the loader view for this view
+                            synchronized (mViewCache) {
+                                if (containsAndIsValid(positionIndex)) {
+                                    RemoteViewsIndexInfo indexInfo = mViewCache[cacheIndex];
+                                    FrameLayout flipper = indexInfo.flipper;
+
+                                    // update the flipper
+                                    flipper.getChildAt(0).setVisibility(View.GONE);
+                                    boolean addNewView = true;
+                                    if (flipper.getChildCount() > 1) {
+                                        View v = flipper.getChildAt(1);
+                                        int typeId = ((Integer) v.getTag()).intValue();
+                                        if (typeId == indexInfo.typeId) {
+                                            // we can reapply since it is the same type
+                                            indexInfo.view.reapply(mContext, v);
+                                            v.setVisibility(View.VISIBLE);
+                                            if (v.getAnimation() != null) 
+                                                v.buildDrawingCache();
+                                            addNewView = false;
+                                        } else {
+                                            flipper.removeViewAt(1);
+                                        }
+                                    }
+                                    if (addNewView) {
+                                        View v = indexInfo.view.apply(mContext, flipper);
+                                        v.setTag(new Integer(indexInfo.typeId));
+                                        flipper.addView(v);
+                                    }
+                                }
+                            }
+                        }
+                    });
+                }
+            }
+        }
+
+        private RemoteViewsIndexInfo requestCachedIndexInfo(final int position) {
+            int indicesToLoadCount = 0;
+
+            synchronized (mViewCache) {
+                if (containsAndIsValid(position)) {
+                    // return the info if it exists in the window and is loaded
+                    return mViewCache[getCacheIndex(position)];
+                }
+
+                // if necessary update the window and load the new information
+                int centerPosition = (mViewCacheEndPosition + mViewCacheStartPosition) / 2;
+                if ((mViewCacheEndPosition <= mViewCacheStartPosition) || (Math.abs(position - centerPosition) > mCacheSlack)) {
+                    int newStartPosition = position - mHalfCacheSize;
+                    int newEndPosition = position + mHalfCacheSize;
+                    int frameSize = mHalfCacheSize / 4;
+                    int frameCount = (int) Math.ceil(mViewCache.length / (float) frameSize);
+
+                    // prune/add before the current start position
+                    int effectiveStart = Math.max(newStartPosition, 0);
+                    int effectiveEnd = Math.min(newEndPosition, getCount() - 1);
+
+                    // invalidate items in the queue
+                    int overlapStart = Math.max(mViewCacheStartPosition, effectiveStart);
+                    int overlapEnd = Math.min(Math.max(mViewCacheStartPosition, mViewCacheEndPosition), effectiveEnd);
+                    for (int i = 0; i < (frameSize * frameCount); ++i) {
+                        int index = newStartPosition + ((i % frameSize) * frameCount + (i / frameSize));
+                        
+                        if (index <= newEndPosition) {
+                            if ((overlapStart <= index) && (index <= overlapEnd)) {
+                                // load the stuff in the middle that has not already
+                                // been loaded
+                                if (!mViewCache[getCacheIndex(index)].isValid()) {
+                                    mTmpViewCacheLoadIndices[indicesToLoadCount++] = index;
+                                }
+                            } else if ((effectiveStart <= index) && (index <= effectiveEnd)) {
+                                // invalidate and load all new effective items
+                                mViewCache[getCacheIndex(index)].invalidate();
+                                mTmpViewCacheLoadIndices[indicesToLoadCount++] = index;
+                            } else {
+                                // invalidate all other cache indices (outside the effective start/end)
+                                // but don't load
+                                mViewCache[getCacheIndex(index)].invalidate();
+                            }
+                        }
+                    }
+
+                    mViewCacheStartPosition = newStartPosition;
+                    mViewCacheEndPosition = newEndPosition;
+                }
+            }
+
+            // post items to be loaded
+            int length = 0;
+            synchronized (mViewCacheInfo) {
+                length = mViewCacheInfo.count;
+            }
+            if (indicesToLoadCount > 0) {
+                synchronized (mViewCacheLoadIndices) {
+                    mViewCacheLoadIndices.clear();
+                    for (int i = 0; i < indicesToLoadCount; ++i) {
+                        final int index = mTmpViewCacheLoadIndices[i];
+                        if (0 <= index && index < length) {
+                            mViewCacheLoadIndices.addLast(index);
+                        }
+                    }
+                }
+            }
+
+            // return null so that a dummy view can be retrieved
+            return null;
+        }
+
+        public View getView(int position, View convertView, ViewGroup parent) {
+            if (mServiceConnection.isConnected()) {
+                // create the flipper views if necessary (we have to do this now
+                // for all the flippers while we have the reference to the parent)
+                initializeLoadingViews(parent);
+
+                // request the item from the cache (queueing it to load if not
+                // in the cache already)
+                RemoteViewsIndexInfo indexInfo = requestCachedIndexInfo(position);
+
+                // update the flipper appropriately
+                synchronized (mViewCache) {
+                    int cacheIndex = getCacheIndex(position);
+                    FrameLayout flipper = mViewCache[cacheIndex].flipper;
+                    flipper.setVisibility(View.VISIBLE);
+
+                    if (indexInfo == null) {
+                        // hide the item view and show the loading view
+                        flipper.getChildAt(0).setVisibility(View.VISIBLE);
+                        for (int i = 1; i < flipper.getChildCount(); ++i) {
+                            flipper.getChildAt(i).setVisibility(View.GONE);
+                        }
+                    } else {
+                        // hide the loading view and show the item view
+                        for (int i = 0; i < flipper.getChildCount() - 1; ++i) {
+                            flipper.getChildAt(i).setVisibility(View.GONE);
+                        }
+                        flipper.getChildAt(flipper.getChildCount() - 1).setVisibility(View.VISIBLE);
+                    }
+                    return flipper;
+                }
+            }
+            return new View(mContext);
+        }
+
+        private void initializeLoadingViews(ViewGroup parent) {
+            // ensure that the cache has the appropriate initial flipper
+            synchronized (mViewCache) {
+                if (mViewCache[0].flipper == null) {
+                    for (int i = 0; i < mViewCache.length; ++i) {
+                        FrameLayout flipper = new FrameLayout(mContext);
+                        if (mUserLoadingView != null) {
+                            // use the user-specified loading view
+                            flipper.addView(mUserLoadingView.apply(mContext, parent));
+                        } else {
+                            // calculate the original size of the first row for the loader view
+                            synchronized (mViewCacheInfo) {
+                                if (mFirstViewHeight < 0) {
+                                    View firstView = mFirstView.apply(mContext, parent);
+                                    firstView.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
+                                            MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
+                                    mFirstViewHeight = firstView.getMeasuredHeight();
+                                }
+                            }
+
+                            // construct a new loader and add it to the flipper as the fallback
+                            // default view
+                            TextView textView = new TextView(mContext);
+                            textView.setText("Loading...");
+                            textView.setHeight(mFirstViewHeight);
+                            textView.setGravity(Gravity.CENTER_HORIZONTAL | Gravity.CENTER_VERTICAL);
+                            textView.setTextSize(18.0f);
+                            textView.setTextColor(Color.argb(96, 255, 255, 255));
+                            textView.setShadowLayer(2.0f, 0.0f, 1.0f, Color.BLACK);
+
+                            flipper.addView(textView);
+                        }
+                        mViewCache[i].flipper = flipper;
+                    }
+                }
+            }
+        }
+
+        public void startBackgroundLoader() {
+            // initialize the worker runnable
+            mBackgroundLoaderEnabled = true;
+            mWorkerQueue.post(new Runnable() {
+                @Override
+                public void run() {
+                    while (mBackgroundLoaderEnabled) {
+                        int index = -1;
+                        synchronized (mViewCacheLoadIndices) {
+                            if (!mViewCacheLoadIndices.isEmpty()) {
+                                index = mViewCacheLoadIndices.removeFirst();
+                            }
+                        }
+                        if (index < 0) {
+                            // there were no items to load, so sleep for a bit
+                            try {
+                                Thread.sleep(10);
+                            } catch (InterruptedException e) {
+                                e.printStackTrace();
+                            }
+                        } else {
+                            // otherwise, try and load the item
+                            updateRemoteViewsInfo(index);
+
+                            // sleep for a bit to allow things to catch up after the load
+                            try {
+                                Thread.sleep(50);
+                            } catch (InterruptedException e) {
+                                e.printStackTrace();
+                            }
+                        }
+                    }
+                }
+            });
+        }
+
+        public void stopBackgroundLoader() {
+            // clear the items to be loaded
+            mBackgroundLoaderEnabled = false;
+            synchronized (mViewCacheLoadIndices) {
+                mViewCacheLoadIndices.clear();
+            }
+        }
+
+        public long getItemId(int position) {
+            synchronized (mViewCache) {
+                if (containsAndIsValid(position)) {
+                    return mViewCache[getCacheIndex(position)].itemId;
+                }
+            }
+            return 0;
+        }
+
+        public int getItemViewType(int position) {
+            // synchronize to ensure that the type id/index map is updated synchronously
+            synchronized (mViewCache) {
+                if (containsAndIsValid(position)) {
+                    int viewId = mViewCache[getCacheIndex(position)].typeId;
+                    Map<Integer, Integer> typeMap = mViewCacheInfo.mTypeIdIndexMap;
+                    // we +1 because the default dummy view get view type 0
+                    if (typeMap.containsKey(viewId)) {
+                        return typeMap.get(viewId);
+                    } else {
+                        int newIndex = typeMap.size() + 1;
+                        typeMap.put(viewId, newIndex);
+                        return newIndex;
+                    }
+                }
+            }
+            // return the type of the default item
+            return 0;
+        }
+
+        public int getCount() {
+            synchronized (mViewCacheInfo) {
+                return mViewCacheInfo.count;
+            }
+        }
+
+        public int getViewTypeCount() {
+            synchronized (mViewCacheInfo) {
+                return mViewCacheInfo.viewTypeCount;
+            }
+        }
+
+        public boolean hasStableIds() {
+            synchronized (mViewCacheInfo) {
+                return mViewCacheInfo.hasStableIds;
+            }
+        }
+
+        public void flushCache() {
+            // clear the items to be loaded
+            synchronized (mViewCacheLoadIndices) {
+                mViewCacheLoadIndices.clear();
+            }
+
+            synchronized (mViewCache) {
+                // flush the internal cache and invalidate the adapter for future loads
+                mMainQueue.removeMessages(0);
+
+                for (int i = 0; i < mViewCache.length; ++i) {
+                    mViewCache[i].invalidate();
+                }
+
+                mViewCacheStartPosition = 0;
+                mViewCacheEndPosition = -1;
+            }
+        }
+    }
+
+    public RemoteViewsAdapter(Context context, Intent intent, RemoteAdapterConnectionCallback callback) {
+        mContext = context;
+        mIntent = intent;
+
+        // initialize the worker thread
+        mWorkerThread = new HandlerThread("RemoteViewsCache-loader");
+        mWorkerThread.start();
+        mWorkerQueue = new Handler(mWorkerThread.getLooper());
+        mWorkerPriorityQueue = new Handler(mWorkerThread.getLooper());
+        mMainQueue = new Handler(Looper.myLooper());
+        mMainPriorityQueue = new Handler(Looper.myLooper());
+
+        // initialize the cache and the service connection on startup
+        mViewCache = new RemoteViewsCache(25);
+        mServiceConnection = new RemoteViewsAdapterServiceConnection(callback);
+        requestBindService();
+    }
+
+    protected void finalize() throws Throwable {
+        // remember to unbind from the service when finalizing
+        unbindService();
+    }
+
+    public int getCount() {
+        requestBindService();
+        return mViewCache.getCount();
+    }
+
+    public Object getItem(int position) {
+        // disallow arbitrary object to be associated with an item for the time being
+        return null;
+    }
+
+    public long getItemId(int position) {
+        requestBindService();
+        return mViewCache.getItemId(position);
+    }
+
+    public int getItemViewType(int position) {
+        requestBindService();
+        return mViewCache.getItemViewType(position);
+    }
+
+    public View getView(int position, View convertView, ViewGroup parent) {
+        requestBindService();
+        return mViewCache.getView(position, convertView, parent);
+    }
+
+    public int getViewTypeCount() {
+        requestBindService();
+        return mViewCache.getViewTypeCount();
+    }
+
+    public boolean hasStableIds() {
+        requestBindService();
+        return mViewCache.hasStableIds();
+    }
+
+    public boolean isEmpty() {
+        return getCount() <= 0;
+    }
+
+    public void notifyDataSetChanged() {
+        // flush the cache so that we can reload new items from the service
+        mViewCache.flushCache();
+        super.notifyDataSetChanged();
+    }
+
+    private boolean requestBindService() {
+        // try binding the service (which will start it if it's not already running)
+        if (!mServiceConnection.isConnected()) {
+            mContext.bindService(mIntent, mServiceConnection, Context.BIND_AUTO_CREATE);
+        }
+
+        return mServiceConnection.isConnected();
+    }
+
+    private void unbindService() {
+        if (mServiceConnection.isConnected()) {
+            mContext.unbindService(mServiceConnection);
+        }
+    }
+}
diff --git a/core/java/android/widget/RemoteViewsService.java b/core/java/android/widget/RemoteViewsService.java
new file mode 100644
index 0000000..7c9d7ff
--- /dev/null
+++ b/core/java/android/widget/RemoteViewsService.java
@@ -0,0 +1,140 @@
+/*
+ * 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;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+import android.util.Pair;
+
+import com.android.internal.widget.IRemoteViewsFactory;
+
+/**
+ * The service to be connected to for a remote adapter to request RemoteViews.  Users should
+ * extend the RemoteViewsService to provide the appropriate RemoteViewsFactory's used to
+ * populate the remote collection view (ListView, GridView, etc).
+ */
+public abstract class RemoteViewsService extends Service {
+
+    private static final String LOG_TAG = "RemoteViewsService";
+
+    // multimap implementation for reference counting
+    private HashMap<Intent, Pair<RemoteViewsFactory, Integer>> mRemoteViewFactories;
+    private final Object mLock = new Object();
+
+    /**
+     * An interface for an adapter between a remote collection view (ListView, GridView, etc) and
+     * the underlying data for that view.  The implementor is responsible for making a RemoteView
+     * for each item in the data set.
+     */
+    public interface RemoteViewsFactory {
+        public void onCreate();
+        public void onDestroy();
+
+        public int getCount();
+        public RemoteViews getViewAt(int position);
+        public RemoteViews getLoadingView();
+        public int getViewTypeCount();
+        public long getItemId(int position);
+        public boolean hasStableIds();
+    }
+
+    /**
+     * A private proxy class for the private IRemoteViewsFactory interface through the
+     * public RemoteViewsFactory interface.
+     */
+    private class RemoteViewsFactoryAdapter extends IRemoteViewsFactory.Stub {
+        public RemoteViewsFactoryAdapter(RemoteViewsFactory factory) {
+            mFactory = factory;
+        }
+
+        public int getCount() {
+            return mFactory.getCount();
+        }
+        public RemoteViews getViewAt(int position) {
+            return mFactory.getViewAt(position);
+        }
+        public RemoteViews getLoadingView() {
+            return mFactory.getLoadingView();
+        }
+        public int getViewTypeCount() {
+            return mFactory.getViewTypeCount();
+        }
+        public long getItemId(int position) {
+            return mFactory.getItemId(position);
+        }
+        public boolean hasStableIds() {
+            return mFactory.hasStableIds();
+        }
+
+        private RemoteViewsFactory mFactory;
+    }
+
+    public RemoteViewsService() {
+        mRemoteViewFactories = new HashMap<Intent, Pair<RemoteViewsFactory, Integer>>();
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        synchronized (mLock) {
+            // increment the reference count to the particular factory associated with this intent
+            Pair<RemoteViewsFactory, Integer> factoryRef = null;
+            RemoteViewsFactory factory = null;
+            if (!mRemoteViewFactories.containsKey(intent)) {
+                factory = onGetViewFactory(intent);
+                factoryRef = new Pair<RemoteViewsFactory, Integer>(factory, 1);
+                mRemoteViewFactories.put(intent, factoryRef);
+                factory.onCreate();
+            } else {
+                Pair<RemoteViewsFactory, Integer> oldFactoryRef = mRemoteViewFactories.get(intent);
+                factory = oldFactoryRef.first;
+                int newRefCount = oldFactoryRef.second.intValue() + 1;
+                factoryRef = new Pair<RemoteViewsFactory, Integer>(oldFactoryRef.first, newRefCount);
+                mRemoteViewFactories.put(intent, factoryRef);
+            }
+            return new RemoteViewsFactoryAdapter(factory);
+        }
+    }
+
+    @Override
+    public boolean onUnbind(Intent intent) {
+        synchronized (mLock) {
+            if (mRemoteViewFactories.containsKey(intent)) {
+                // this alleviates the user's responsibility of having to clear all factories
+                Pair<RemoteViewsFactory, Integer> oldFactoryRef = mRemoteViewFactories.get(intent);
+                int newRefCount = oldFactoryRef.second.intValue() - 1;
+                if (newRefCount <= 0) {
+                    oldFactoryRef.first.onDestroy();
+                    mRemoteViewFactories.remove(intent);
+                } else {
+                    Pair<RemoteViewsFactory, Integer> factoryRef = new Pair<RemoteViewsFactory, Integer>(oldFactoryRef.first, newRefCount);
+                    mRemoteViewFactories.put(intent, factoryRef);
+                }
+            }
+        }
+        return super.onUnbind(intent);
+    }
+
+    /**
+     * To be implemented by the derived service to generate appropriate factories for
+     * the data.
+     */
+    public abstract RemoteViewsFactory onGetViewFactory(Intent intent);
+}
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/Spinner.java b/core/java/android/widget/Spinner.java
index 2f6dd1e..60e8568 100644
--- a/core/java/android/widget/Spinner.java
+++ b/core/java/android/widget/Spinner.java
@@ -24,6 +24,7 @@
 import android.content.res.TypedArray;
 import android.database.DataSetObserver;
 import android.util.AttributeSet;
+import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 
@@ -37,10 +38,21 @@
  */
 @Widget
 public class Spinner extends AbsSpinner implements OnClickListener {
+    private static final String TAG = "Spinner";
     
-    private CharSequence mPrompt;
-    private AlertDialog mPopup;
-
+    /**
+     * Use a dialog window for selecting spinner options.
+     */
+    public static final int MODE_DIALOG = 0;
+    
+    /**
+     * Use a dropdown anchored to the Spinner for selecting spinner options.
+     */
+    public static final int MODE_DROPDOWN = 1;
+    
+    private SpinnerPopup mPopup;
+    private DropDownAdapter mTempAdapter;
+    
     public Spinner(Context context) {
         this(context, null);
     }
@@ -55,9 +67,54 @@
         TypedArray a = context.obtainStyledAttributes(attrs,
                 com.android.internal.R.styleable.Spinner, defStyle, 0);
         
-        mPrompt = a.getString(com.android.internal.R.styleable.Spinner_prompt);
+        final int mode = a.getInt(com.android.internal.R.styleable.Spinner_spinnerMode,
+                MODE_DIALOG);
+        
+        switch (mode) {
+        case MODE_DIALOG: {
+            mPopup = new DialogPopup();
+            break;
+        }
+        
+        case MODE_DROPDOWN: {
+            final int hintResource = a.getResourceId(
+                    com.android.internal.R.styleable.Spinner_popupPromptView, 0);
+
+            DropdownPopup popup = new DropdownPopup(context, attrs, defStyle, hintResource);
+
+            popup.setBackgroundDrawable(a.getDrawable(
+                    com.android.internal.R.styleable.Spinner_popupBackground));
+            popup.setVerticalOffset(a.getDimensionPixelOffset(
+                    com.android.internal.R.styleable.Spinner_dropDownVerticalOffset, 0));
+            popup.setHorizontalOffset(a.getDimensionPixelOffset(
+                    com.android.internal.R.styleable.Spinner_dropDownHorizontalOffset, 0));
+
+            mPopup = popup;
+            break;
+        }
+        }
+        
+        mPopup.setPromptText(a.getString(com.android.internal.R.styleable.Spinner_prompt));
 
         a.recycle();
+
+        // Base constructor can call setAdapter before we initialize mPopup.
+        // Finish setting things up if this happened.
+        if (mTempAdapter != null) {
+            mPopup.setAdapter(mTempAdapter);
+            mTempAdapter = null;
+        }
+    }
+    
+    @Override
+    public void setAdapter(SpinnerAdapter adapter) {
+        super.setAdapter(adapter);
+
+        if (mPopup != null) {
+            mPopup.setAdapter(new DropDownAdapter(adapter));
+        } else {
+            mTempAdapter = new DropDownAdapter(adapter);
+        }
     }
 
     @Override
@@ -194,8 +251,6 @@
         return child;
     }
 
-
-
     /**
      * Helper for makeAndAddView to set the position of a view
      * and fill out its layout paramters.
@@ -246,15 +301,10 @@
         
         if (!handled) {
             handled = true;
-            Context context = getContext();
-            
-            final DropDownAdapter adapter = new DropDownAdapter(getAdapter());
 
-            AlertDialog.Builder builder = new AlertDialog.Builder(context);
-            if (mPrompt != null) {
-                builder.setTitle(mPrompt);
+            if (!mPopup.isShowing()) {
+                mPopup.show();
             }
-            mPopup = builder.setSingleChoiceItems(adapter, getSelectedItemPosition(), this).show();
         }
 
         return handled;
@@ -271,7 +321,7 @@
      * @param prompt the prompt to set
      */
     public void setPrompt(CharSequence prompt) {
-        mPrompt = prompt;
+        mPopup.setPromptText(prompt);
     }
 
     /**
@@ -279,14 +329,14 @@
      * @param promptId the resource ID of the prompt to display when the dialog is shown
      */
     public void setPromptId(int promptId) {
-        mPrompt = getContext().getText(promptId);
+        setPrompt(getContext().getText(promptId));
     }
 
     /**
      * @return The prompt to display when the dialog is shown
      */
     public CharSequence getPrompt() {
-        return mPrompt;
+        return mPopup.getHintText();
     }
     
     /**
@@ -384,4 +434,123 @@
             return getCount() == 0;
         }
     }
+    
+    /**
+     * Implements some sort of popup selection interface for selecting a spinner option.
+     * Allows for different spinner modes.
+     */
+    private interface SpinnerPopup {
+        public void setAdapter(ListAdapter adapter);
+        
+        /**
+         * Show the popup
+         */
+        public void show();
+        
+        /**
+         * Dismiss the popup
+         */
+        public void dismiss();
+        
+        /**
+         * @return true if the popup is showing, false otherwise.
+         */
+        public boolean isShowing();
+        
+        /**
+         * Set hint text to be displayed to the user. This should provide
+         * a description of the choice being made.
+         * @param hintText Hint text to set.
+         */
+        public void setPromptText(CharSequence hintText);
+        public CharSequence getHintText();
+    }
+    
+    private class DialogPopup implements SpinnerPopup, DialogInterface.OnClickListener {
+        private AlertDialog mPopup;
+        private ListAdapter mListAdapter;
+        private CharSequence mPrompt;
+
+        public void dismiss() {
+            mPopup.dismiss();
+            mPopup = null;
+        }
+
+        public boolean isShowing() {
+            return mPopup != null ? mPopup.isShowing() : false;
+        }
+
+        public void setAdapter(ListAdapter adapter) {
+            mListAdapter = adapter;
+        }
+
+        public void setPromptText(CharSequence hintText) {
+            mPrompt = hintText;
+        }
+        
+        public CharSequence getHintText() {
+            return mPrompt;
+        }
+
+        public void show() {
+            AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
+            if (mPrompt != null) {
+                builder.setTitle(mPrompt);
+            }
+            mPopup = builder.setSingleChoiceItems(mListAdapter,
+                    getSelectedItemPosition(), this).show();
+        }
+        
+        public void onClick(DialogInterface dialog, int which) {
+            setSelection(which);
+            dismiss();
+        }
+    }
+    
+    private class DropdownPopup extends ListPopupWindow implements SpinnerPopup {
+        private CharSequence mHintText;
+        private TextView mHintView;
+        private int mHintResource;
+        
+        public DropdownPopup(Context context, AttributeSet attrs,
+                int defStyleRes, int hintResource) {
+            super(context, attrs, 0, defStyleRes);
+            
+            mHintResource = hintResource;
+            
+            setAnchorView(Spinner.this);
+            setModal(true);
+            setPromptPosition(POSITION_PROMPT_BELOW);
+            setOnItemClickListener(new OnItemClickListener() {
+                public void onItemClick(AdapterView parent, View v, int position, long id) {
+                    Spinner.this.setSelection(position);
+                    dismiss();
+                }
+            });
+        }
+        
+        public CharSequence getHintText() {
+            return mHintText;
+        }
+        
+        public void setPromptText(CharSequence hintText) {
+            mHintText = hintText;
+            if (mHintView != null) {
+                mHintView.setText(hintText);
+            }
+        }
+        
+        public void show() {
+            if (mHintView == null) {
+                final TextView textView = (TextView) LayoutInflater.from(getContext()).inflate(
+                        mHintResource, null).findViewById(com.android.internal.R.id.text1);
+                textView.setText(mHintText);
+                setPromptView(textView);
+                mHintView = textView;
+            }
+            super.show();
+            getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE);
+            setSelection(Spinner.this.getSelectedItemPosition());
+        }
+    }
 }
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index e6ed70a..24f14dc 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -22,7 +22,7 @@
 import org.xmlpull.v1.XmlPullParserException;
 
 import android.content.Context;
-import android.content.Intent;
+import android.content.pm.PackageManager;
 import android.content.res.ColorStateList;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
@@ -61,6 +61,7 @@
 import android.text.TextPaint;
 import android.text.TextUtils;
 import android.text.TextWatcher;
+import android.text.method.ArrowKeyMovementMethod;
 import android.text.method.DateKeyListener;
 import android.text.method.DateTimeKeyListener;
 import android.text.method.DialerKeyListener;
@@ -82,17 +83,21 @@
 import android.util.FloatMath;
 import android.util.Log;
 import android.util.TypedValue;
+import android.view.ActionMode;
 import android.view.ContextMenu;
 import android.view.Gravity;
+import android.view.HapticFeedbackConstants;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
+import android.view.Menu;
 import android.view.MenuItem;
 import android.view.MotionEvent;
 import android.view.View;
+import android.view.ViewConfiguration;
 import android.view.ViewDebug;
+import android.view.ViewGroup.LayoutParams;
 import android.view.ViewRoot;
 import android.view.ViewTreeObserver;
-import android.view.ViewGroup.LayoutParams;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityManager;
 import android.view.animation.AnimationUtils;
@@ -185,7 +190,7 @@
  */
 @RemoteView
 public class TextView extends View implements ViewTreeObserver.OnPreDrawListener {
-    static final String TAG = "TextView";
+    static final String LOG_TAG = "TextView";
     static final boolean DEBUG_EXTRACT = false;
     
     private static int PRIORITY = 100;
@@ -321,6 +326,7 @@
         this(context, attrs, com.android.internal.R.attr.textViewStyle);
     }
 
+    @SuppressWarnings("deprecation")
     public TextView(Context context,
                     AttributeSet attrs,
                     int defStyle) {
@@ -695,9 +701,9 @@
                 try {
                     setInputExtras(a.getResourceId(attr, 0));
                 } catch (XmlPullParserException e) {
-                    Log.w("TextView", "Failure reading input extras", e);
+                    Log.w(LOG_TAG, "Failure reading input extras", e);
                 } catch (IOException e) {
-                    Log.w("TextView", "Failure reading input extras", e);
+                    Log.w(LOG_TAG, "Failure reading input extras", e);
                 }
                 break;
             }
@@ -714,7 +720,7 @@
         }
 
         if (inputMethod != null) {
-            Class c;
+            Class<?> c;
 
             try {
                 c = Class.forName(inputMethod.toString());
@@ -923,6 +929,8 @@
         setFocusable(focusable);
         setClickable(clickable);
         setLongClickable(longClickable);
+
+        prepareCursorControllers();
     }
 
     private void setTypefaceByIndex(int typefaceIndex, int styleIndex) {
@@ -1128,6 +1136,9 @@
             setText(mText);
 
         fixFocusableAndClickableSettings();
+
+        // SelectionModifierCursorController depends on canSelectText, which depends on mMovement
+        prepareCursorControllers();
     }
 
     private void fixFocusableAndClickableSettings() {
@@ -2335,6 +2346,7 @@
             return str + "}";
         }
 
+        @SuppressWarnings("hiding")
         public static final Parcelable.Creator<SavedState> CREATOR
                 = new Parcelable.Creator<SavedState>() {
             public SavedState createFromParcel(Parcel in) {
@@ -2369,8 +2381,8 @@
         int end = 0;
 
         if (mText != null) {
-            start = Selection.getSelectionStart(mText);
-            end = Selection.getSelectionEnd(mText);
+            start = getSelectionStart();
+            end = getSelectionEnd();
             if (start >= 0 || end >= 0) {
                 // Or save state if there is a selection
                 save = true;
@@ -2442,7 +2454,7 @@
                         restored = "(restored) ";
                     }
 
-                    Log.e("TextView", "Saved cursor position " + ss.selStart +
+                    Log.e(LOG_TAG, "Saved cursor position " + ss.selStart +
                           "/" + ss.selEnd + " out of range for " + restored +
                           "text " + mText);
                 } else {
@@ -2694,6 +2706,9 @@
         if (needEditableForNotification) {
             sendAfterTextChanged((Editable) text);
         }
+
+        // SelectionModifierCursorController depends on canSelectText, which depends on text
+        prepareCursorControllers();
     }
 
     /**
@@ -2756,6 +2771,7 @@
             return mChars[off + mStart];
         }
 
+        @Override
         public String toString() {
             return new String(mChars, mStart, mLength);
         }
@@ -2781,6 +2797,14 @@
             c.drawText(mChars, start + mStart, end - start, x, y, p);
         }
 
+        public void drawTextRun(Canvas c, int start, int end,
+                int contextStart, int contextEnd, float x, float y, int flags, Paint p) {
+            int count = end - start;
+            int contextCount = contextEnd - contextStart;
+            c.drawTextRun(mChars, start + mStart, count, contextStart + mStart,
+                    contextCount, x, y, flags, p);
+        }
+
         public float measureText(int start, int end, Paint p) {
             return p.measureText(mChars, start + mStart, end - start);
         }
@@ -2788,6 +2812,23 @@
         public int getTextWidths(int start, int end, float[] widths, Paint p) {
             return p.getTextWidths(mChars, start + mStart, end - start, widths);
         }
+
+        public float getTextRunAdvances(int start, int end, int contextStart,
+                int contextEnd, int flags, float[] advances, int advancesIndex,
+                Paint p) {
+            int count = end - start;
+            int contextCount = contextEnd - contextStart;
+            return p.getTextRunAdvances(mChars, start + mStart, count,
+                    contextStart + mStart, contextCount, flags, advances,
+                    advancesIndex);
+        }
+
+        public int getTextRunCursor(int contextStart, int contextEnd, int flags,
+                int offset, int cursorOpt, Paint p) {
+            int contextCount = contextEnd - contextStart;
+            return p.getTextRunCursor(mChars, contextStart + mStart,
+                    contextCount, flags, offset + mStart, cursorOpt);
+        }
     }
 
     /**
@@ -2981,7 +3022,7 @@
         } else {
             input = TextKeyListener.getInstance();
         }
-        mInputType = type;
+        setRawInputType(type);
         if (direct) mInput = input;
         else {
             setKeyListenerOnly(input);
@@ -3198,7 +3239,7 @@
      *
      * @param create If true, the extras will be created if they don't already
      * exist.  Otherwise, null will be returned if none have been created.
-     * @see #setInputExtras(int)View
+     * @see #setInputExtras(int)
      * @see EditorInfo#extras
      * @attr ref android.R.styleable#TextView_editorExtras
      */
@@ -3312,7 +3353,7 @@
 
     private static class ErrorPopup extends PopupWindow {
         private boolean mAbove = false;
-        private TextView mView;
+        private final TextView mView;
 
         ErrorPopup(TextView v, int width, int height) {
             super(v, width, height);
@@ -3585,7 +3626,7 @@
     }
 
     private void invalidateCursor() {
-        int where = Selection.getSelectionEnd(mText);
+        int where = getSelectionEnd();
 
         invalidateCursor(where, where, where);
     }
@@ -3661,7 +3702,18 @@
         boolean changed = false;
 
         if (mMovement != null) {
-            int curs = Selection.getSelectionEnd(mText);
+            /* This code also provides auto-scrolling when a cursor is moved using a
+             * CursorController (insertion point or selection limits).
+             * For selection, ensure start or end is visible depending on controller's state.
+             */
+            int curs = getSelectionEnd();
+            if (mSelectionModifierCursorController != null) {
+                SelectionModifierCursorController selectionController =
+                    (SelectionModifierCursorController) mSelectionModifierCursorController;
+                if (selectionController.isSelectionStartDragged()) {
+                    curs = getSelectionStart();
+                }
+            }
 
             /*
              * TODO: This should really only keep the end in view if
@@ -3680,6 +3732,10 @@
             changed = bringTextIntoView();
         }
 
+        if (mShouldStartSelectionActionMode) {
+            startSelectionActionMode();
+            mShouldStartSelectionActionMode = false;
+        }
         mPreDrawState = PREDRAW_DONE;
         return !changed;
     }
@@ -3954,8 +4010,8 @@
         //  XXX This is not strictly true -- a program could set the
         //  selection manually if it really wanted to.
         if (mMovement != null && (isFocused() || isPressed())) {
-            selStart = Selection.getSelectionStart(mText);
-            selEnd = Selection.getSelectionEnd(mText);
+            selStart = getSelectionStart();
+            selEnd = getSelectionEnd();
 
             if (mCursorVisible && selStart >= 0 && isEnabled()) {
                 if (mHighlightPath == null)
@@ -4061,6 +4117,13 @@
         */
 
         canvas.restore();
+
+        if (mInsertionPointCursorController != null) {
+            mInsertionPointCursorController.draw(canvas);
+        }
+        if (mSelectionModifierCursorController != null) {
+            mSelectionModifierCursorController.draw(canvas);
+        }
     }
 
     @Override
@@ -4475,8 +4538,8 @@
             outAttrs.hintText = mHint;
             if (mText instanceof Editable) {
                 InputConnection ic = new EditableInputConnection(this);
-                outAttrs.initialSelStart = Selection.getSelectionStart(mText);
-                outAttrs.initialSelEnd = Selection.getSelectionEnd(mText);
+                outAttrs.initialSelStart = getSelectionStart();
+                outAttrs.initialSelEnd = getSelectionEnd();
                 outAttrs.initialCapsMode = ic.getCursorCapsMode(mInputType);
                 return ic;
             }
@@ -4561,8 +4624,8 @@
                 outText.flags |= ExtractedText.FLAG_SINGLE_LINE;
             }
             outText.startOffset = 0;
-            outText.selectionStart = Selection.getSelectionStart(content);
-            outText.selectionEnd = Selection.getSelectionEnd(content);
+            outText.selectionStart = getSelectionStart();
+            outText.selectionEnd = getSelectionEnd();
             return true;
         }
         return false;
@@ -4579,7 +4642,7 @@
                 if (req != null) {
                     InputMethodManager imm = InputMethodManager.peekInstance();
                     if (imm != null) {
-                        if (DEBUG_EXTRACT) Log.v(TAG, "Retrieving extracted start="
+                        if (DEBUG_EXTRACT) Log.v(LOG_TAG, "Retrieving extracted start="
                                 + ims.mChangedStart + " end=" + ims.mChangedEnd
                                 + " delta=" + ims.mChangedDelta);
                         if (ims.mChangedStart < 0 && !contentChanged) {
@@ -4587,7 +4650,7 @@
                         }
                         if (extractTextInternal(req, ims.mChangedStart, ims.mChangedEnd,
                                 ims.mChangedDelta, ims.mTmpExtracted)) {
-                            if (DEBUG_EXTRACT) Log.v(TAG, "Reporting extracted start="
+                            if (DEBUG_EXTRACT) Log.v(LOG_TAG, "Reporting extracted start="
                                     + ims.mTmpExtracted.partialStartOffset
                                     + " end=" + ims.mTmpExtracted.partialEndOffset
                                     + ": " + ims.mTmpExtracted.text);
@@ -4741,7 +4804,7 @@
     
     void updateAfterEdit() {
         invalidate();
-        int curs = Selection.getSelectionStart(mText);
+        int curs = getSelectionStart();
 
         if (curs >= 0 || (mGravity & Gravity.VERTICAL_GRAVITY_MASK) ==
                              Gravity.BOTTOM) {
@@ -4756,7 +4819,7 @@
                 makeBlink();
             }
         }
-        
+
         checkForResize();
     }
     
@@ -4847,6 +4910,8 @@
                 break;
 
             case Gravity.RIGHT:
+                // Note, Layout resolves ALIGN_OPPOSITE to left or
+                // right based on the paragraph direction.
                 alignment = Layout.Alignment.ALIGN_OPPOSITE;
                 break;
 
@@ -4883,7 +4948,6 @@
                                 w, alignment, mSpacingMult, mSpacingAdd,
                                 boring, mIncludePad);
                     }
-                    // Log.e("aaa", "Boring: " + mTransformed);
 
                     mSavedLayout = (BoringLayout) mLayout;
                 } else if (shouldEllipsize && boring.width <= w) {
@@ -4909,7 +4973,6 @@
                     mLayout = new StaticLayout(mTransformed, mTextPaint,
                             w, alignment, mSpacingMult, mSpacingAdd,
                             mIncludePad);
-                    // Log.e("aaa", "Boring but wide: " + mTransformed);
                 }
             } else if (shouldEllipsize) {
                 mLayout = new StaticLayout(mTransformed,
@@ -5007,6 +5070,9 @@
                 }
             }
         }
+
+        // CursorControllers need a non-null mLayout
+        prepareCursorControllers();
     }
 
     private boolean compressText(float width) {
@@ -5478,7 +5544,7 @@
         // FIXME: Is it okay to truncate this, or should we round?
         final int x = (int)mLayout.getPrimaryHorizontal(offset);
         final int top = mLayout.getLineTop(line);
-        final int bottom = mLayout.getLineTop(line+1);
+        final int bottom = mLayout.getLineTop(line + 1);
 
         int left = (int) FloatMath.floor(mLayout.getLineLeft(line));
         int right = (int) FloatMath.ceil(mLayout.getLineRight(line));
@@ -5615,8 +5681,8 @@
             // viewport coordinates, but requestRectangleOnScreen()
             // is in terms of content coordinates.
 
-            Rect r = new Rect();
-            getInterestingRect(r, x, top, bottom, line);
+            Rect r = new Rect(x, top, x + 1, bottom);
+            getInterestingRect(r, line);
             r.offset(mScrollX, mScrollY);
 
             if (requestRectangleOnScreen(r)) {
@@ -5632,13 +5698,15 @@
      * to the user.  This will not move the cursor if it represents more than
      * one character (a selection range).  This will only work if the
      * TextView contains spannable text; otherwise it will do nothing.
+     *
+     * @return True if the cursor was actually moved, false otherwise.
      */
     public boolean moveCursorToVisibleOffset() {
         if (!(mText instanceof Spannable)) {
             return false;
         }
-        int start = Selection.getSelectionStart(mText);
-        int end = Selection.getSelectionEnd(mText);
+        int start = getSelectionStart();
+        int end = getSelectionEnd();
         if (start != end) {
             return false;
         }
@@ -5648,7 +5716,7 @@
         int line = mLayout.getLineForOffset(start);
 
         final int top = mLayout.getLineTop(line);
-        final int bottom = mLayout.getLineTop(line+1);
+        final int bottom = mLayout.getLineTop(line + 1);
         final int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
         int vslack = (bottom - top) / 2;
         if (vslack > vspace / 4)
@@ -5668,11 +5736,15 @@
         final int leftChar = mLayout.getOffsetForHorizontal(line, hs);
         final int rightChar = mLayout.getOffsetForHorizontal(line, hspace+hs);
         
+        // line might contain bidirectional text
+        final int lowChar = leftChar < rightChar ? leftChar : rightChar;
+        final int highChar = leftChar > rightChar ? leftChar : rightChar;
+
         int newStart = start;
-        if (newStart < leftChar) {
-            newStart = leftChar;
-        } else if (newStart > rightChar) {
-            newStart = rightChar;
+        if (newStart < lowChar) {
+            newStart = lowChar;
+        } else if (newStart > highChar) {
+            newStart = highChar;
         }
         
         if (newStart != start) {
@@ -5694,22 +5766,28 @@
         }
     }
 
-    private void getInterestingRect(Rect r, int h, int top, int bottom,
-                                    int line) {
+    private void getInterestingRect(Rect r, int line) {
+        convertFromViewportToContentCoordinates(r);
+
+        // Rectangle can can be expanded on first and last line to take
+        // padding into account.
+        // TODO Take left/right padding into account too?
+        if (line == 0) r.top -= getExtendedPaddingTop();
+        if (line == mLayout.getLineCount() - 1) r.bottom += getExtendedPaddingBottom();
+    }
+
+    private void convertFromViewportToContentCoordinates(Rect r) {
         int paddingTop = getExtendedPaddingTop();
         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
             paddingTop += getVerticalOffset(false);
         }
-        top += paddingTop;
-        bottom += paddingTop;
-        h += getCompoundPaddingLeft();
+        r.top += paddingTop;
+        r.bottom += paddingTop;
 
-        if (line == 0)
-            top -= getExtendedPaddingTop();
-        if (line == mLayout.getLineCount() - 1)
-            bottom += getExtendedPaddingBottom();
+        int paddingLeft = getCompoundPaddingLeft();
+        r.left += paddingLeft;
+        r.right += paddingLeft;
 
-        r.set(h, top, h+1, bottom);
         r.offset(-mScrollX, -mScrollY);
     }
 
@@ -5877,6 +5955,9 @@
         } else if (mBlink != null) {
             mBlink.removeCallbacks(mBlink);
         }
+
+        // InsertionPointCursorController depends on mCursorVisible
+        prepareCursorControllers();
     }
 
     private boolean canMarquee() {
@@ -5935,7 +6016,7 @@
         private final WeakReference<TextView> mView;
 
         private byte mStatus = MARQUEE_STOPPED;
-        private float mScrollUnit;
+        private final float mScrollUnit;
         private float mMaxScroll;
         float mMaxFadeScroll;
         private float mGhostStart;
@@ -5947,7 +6028,7 @@
 
         Marquee(TextView v) {
             final float density = v.getContext().getResources().getDisplayMetrics().density;
-            mScrollUnit = (MARQUEE_PIXELS_PER_SECOND * density) / (float) MARQUEE_RESOLUTION;
+            mScrollUnit = (MARQUEE_PIXELS_PER_SECOND * density) / MARQUEE_RESOLUTION;
             mView = new WeakReference<TextView>(v);
         }
 
@@ -6291,7 +6372,7 @@
                         }
                     }
                 } else {
-                    if (DEBUG_EXTRACT) Log.v(TAG, "Span change outside of batch: "
+                    if (DEBUG_EXTRACT) Log.v(LOG_TAG, "Span change outside of batch: "
                             + oldStart + "-" + oldEnd + ","
                             + newStart + "-" + newEnd + what);
                     ims.mContentChanged = true;
@@ -6307,7 +6388,7 @@
 
         public void beforeTextChanged(CharSequence buffer, int start,
                                       int before, int after) {
-            if (DEBUG_EXTRACT) Log.v(TAG, "beforeTextChanged start=" + start
+            if (DEBUG_EXTRACT) Log.v(LOG_TAG, "beforeTextChanged start=" + start
                     + " before=" + before + " after=" + after + ": " + buffer);
 
             if (AccessibilityManager.getInstance(mContext).isEnabled()
@@ -6320,7 +6401,7 @@
 
         public void onTextChanged(CharSequence buffer, int start,
                                   int before, int after) {
-            if (DEBUG_EXTRACT) Log.v(TAG, "onTextChanged start=" + start
+            if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onTextChanged start=" + start
                     + " before=" + before + " after=" + after + ": " + buffer);
             TextView.this.handleTextChanged(buffer, start, before, after);
 
@@ -6330,10 +6411,15 @@
                 sendAccessibilityEventTypeViewTextChanged(mBeforeText, start, before, after);
                 mBeforeText = null;
             }
+
+            // TODO. The cursor controller should hide as soon as text is typed.
+            // But this method is also used for cosmetic changes (underline current word when
+            // spell corrections are displayed. There is currently no way to make the difference
+            // between these cosmetic changes and actual text modifications.
         }
 
         public void afterTextChanged(Editable buffer) {
-            if (DEBUG_EXTRACT) Log.v(TAG, "afterTextChanged: " + buffer);
+            if (DEBUG_EXTRACT) Log.v(LOG_TAG, "afterTextChanged: " + buffer);
             TextView.this.sendAfterTextChanged(buffer);
 
             if (MetaKeyKeyListener.getMetaState(buffer,
@@ -6344,19 +6430,19 @@
 
         public void onSpanChanged(Spannable buf,
                                   Object what, int s, int e, int st, int en) {
-            if (DEBUG_EXTRACT) Log.v(TAG, "onSpanChanged s=" + s + " e=" + e
+            if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanChanged s=" + s + " e=" + e
                     + " st=" + st + " en=" + en + " what=" + what + ": " + buf);
             TextView.this.spanChange(buf, what, s, st, e, en);
         }
 
         public void onSpanAdded(Spannable buf, Object what, int s, int e) {
-            if (DEBUG_EXTRACT) Log.v(TAG, "onSpanAdded s=" + s + " e=" + e
+            if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanAdded s=" + s + " e=" + e
                     + " what=" + what + ": " + buf);
             TextView.this.spanChange(buf, what, -1, s, -1, e);
         }
 
         public void onSpanRemoved(Spannable buf, Object what, int s, int e) {
-            if (DEBUG_EXTRACT) Log.v(TAG, "onSpanRemoved s=" + s + " e=" + e
+            if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanRemoved s=" + s + " e=" + e
                     + " what=" + what + ": " + buf);
             TextView.this.spanChange(buf, what, s, -1, e, -1);
         }
@@ -6460,12 +6546,22 @@
             if (mError != null) {
                 showError();
             }
+
+            // We cannot start the selection mode immediately. The layout may be null here and is
+            // needed by the cursor controller. Layout creation is deferred up to drawing. The
+            // selection action mode will be started in onPreDraw().
+            if (selStart != selEnd) {
+                mShouldStartSelectionActionMode = true;
+            }
         } else {
             if (mError != null) {
                 hideError();
             }
             // Don't leave us in the middle of a batch edit.
             onEndBatchEdit();
+
+            hideInsertionPointCursorController();
+            stopSelectionActionMode();
         }
 
         startStopMarquee(focused);
@@ -6532,24 +6628,52 @@
     }
 
     class CommitSelectionReceiver extends ResultReceiver {
-        int mNewStart;
-        int mNewEnd;
-        
-        CommitSelectionReceiver() {
+        private final int mPrevStart, mPrevEnd;
+        private final int mNewStart, mNewEnd;
+
+        public CommitSelectionReceiver(int mPrevStart, int mPrevEnd, int mNewStart, int mNewEnd) {
             super(getHandler());
+            this.mPrevStart = mPrevStart;
+            this.mPrevEnd = mPrevEnd;
+            this.mNewStart = mNewStart;
+            this.mNewEnd = mNewEnd;
         }
-        
+
+        @Override
         protected void onReceiveResult(int resultCode, Bundle resultData) {
-            if (resultCode != InputMethodManager.RESULT_SHOWN) {
-                final int len = mText.length();
-                if (mNewStart > len) {
-                    mNewStart = len;
+            int start = mNewStart;
+            int end = mNewEnd;
+
+            // Move the cursor to the new position, unless this tap was actually
+            // use to show the IMM. Leave cursor unchanged in that case.
+            if (resultCode == InputMethodManager.RESULT_SHOWN) {
+                start = mPrevStart;
+                end = mPrevEnd;
+            } else {
+                if ((mPrevStart != mPrevEnd) && (start == end)) {
+                    if ((start >= mPrevStart) && (start <= mPrevEnd)) {
+                        // Tapping inside the selection does nothing
+                        Selection.setSelection((Spannable) mText, mPrevStart, mPrevEnd);
+                        return;
+                    } else {
+                        // Tapping outside stops selection mode, if any
+                        finishSelectionActionMode();
+                    }
                 }
-                if (mNewEnd > len) {
-                    mNewEnd = len;
+
+                if (mInsertionPointCursorController != null) {
+                    mInsertionPointCursorController.show();
                 }
-                Selection.setSelection((Spannable)mText, mNewStart, mNewEnd);
             }
+
+            final int len = mText.length();
+            if (start > len) {
+                start = len;
+            }
+            if (end > len) {
+                end = len;
+            }
+            Selection.setSelection((Spannable)mText, start, end);
         }
     }
     
@@ -6562,7 +6686,7 @@
             mTouchFocusSelected = false;
             mScrolled = false;
         }
-        
+
         final boolean superResult = super.onTouchEvent(event);
 
         /*
@@ -6575,44 +6699,40 @@
             return superResult;
         }
 
-        if ((mMovement != null || onCheckIsTextEditor()) && mText instanceof Spannable && mLayout != null) {
+        if ((mMovement != null || onCheckIsTextEditor()) &&
+                mText instanceof Spannable && mLayout != null) {
             
+            int oldSelStart = getSelectionStart();
+            int oldSelEnd = getSelectionEnd();
+
+            if (mInsertionPointCursorController != null) {
+                mInsertionPointCursorController.onTouchEvent(event);
+            }
+            if (mSelectionModifierCursorController != null) {
+                mSelectionModifierCursorController.onTouchEvent(event);
+            }
+
             boolean handled = false;
-            
-            int oldSelStart = Selection.getSelectionStart(mText);
-            int oldSelEnd = Selection.getSelectionEnd(mText);
-            
+
             if (mMovement != null) {
                 handled |= mMovement.onTouchEvent(this, (Spannable) mText, event);
             }
 
-            if (mText instanceof Editable && onCheckIsTextEditor()) {
+            if (isTextEditable()) {
                 if (action == MotionEvent.ACTION_UP && isFocused() && !mScrolled) {
                     InputMethodManager imm = (InputMethodManager)
                             getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
-                    
-                    // This is going to be gross...  if tapping on the text view
-                    // causes the IME to be displayed, we don't want the selection
-                    // to change.  But the selection has already changed, and
-                    // we won't know right away whether the IME is getting
-                    // displayed, so...
-                    
-                    int newSelStart = Selection.getSelectionStart(mText);
-                    int newSelEnd = Selection.getSelectionEnd(mText);
+
+                    final int newSelStart = getSelectionStart();
+                    final int newSelEnd = getSelectionEnd();
+
                     CommitSelectionReceiver csr = null;
                     if (newSelStart != oldSelStart || newSelEnd != oldSelEnd) {
-                        csr = new CommitSelectionReceiver();
-                        csr.mNewStart = newSelStart;
-                        csr.mNewEnd = newSelEnd;
+                        csr = new CommitSelectionReceiver(oldSelStart, oldSelEnd,
+                                newSelStart, newSelEnd);
                     }
-                    
-                    if (imm.showSoftInput(this, 0, csr) && csr != null) {
-                        // The IME might get shown -- revert to the old
-                        // selection, and change to the new when we finally
-                        // find out of it is okay.
-                        Selection.setSelection((Spannable)mText, oldSelStart, oldSelEnd);
-                        handled = true;
-                    }
+
+                    handled |= imm.showSoftInput(this, 0, csr) && (csr != null);
                 }
             }
 
@@ -6624,6 +6744,44 @@
         return superResult;
     }
 
+    private void prepareCursorControllers() {
+        boolean atLeastOneController = false;
+
+        // TODO Add an extra android:cursorController flag to disable the controller?
+        if (mCursorVisible && mLayout != null) {
+            atLeastOneController = true;
+            if (mInsertionPointCursorController == null) {
+                mInsertionPointCursorController = new InsertionPointCursorController();
+            }
+        } else {
+            mInsertionPointCursorController = null;
+        }
+
+        if (canSelectText() && mLayout != null) {
+            atLeastOneController = true;
+            if (mSelectionModifierCursorController == null) {
+                mSelectionModifierCursorController = new SelectionModifierCursorController();
+            }
+        } else {
+            mSelectionModifierCursorController = null;
+            // Stop selection mode if the controller becomes unavailable.
+            finishSelectionActionMode();
+        }
+
+        if (atLeastOneController) {
+            Resources res = mContext.getResources();
+            mCursorControllerVerticalOffset = res.getDimensionPixelOffset(
+                    com.android.internal.R.dimen.cursor_controller_vertical_offset);
+        }
+    }
+
+    /**
+     * @return True iff this TextView contains a text that can be edited.
+     */
+    private boolean isTextEditable() {
+        return mText instanceof Editable && onCheckIsTextEditor();
+    }
+
     /**
      * Returns true, only while processing a touch gesture, if the initial
      * touch down event caused focus to move to the text view and as a result
@@ -6657,7 +6815,7 @@
     }
 
     private static class Blink extends Handler implements Runnable {
-        private WeakReference<TextView> mView;
+        private final WeakReference<TextView> mView;
         private boolean mCancelled;
 
         public Blink(TextView v) {
@@ -6674,8 +6832,8 @@
             TextView tv = mView.get();
 
             if (tv != null && tv.isFocused()) {
-                int st = Selection.getSelectionStart(tv.mText);
-                int en = Selection.getSelectionEnd(tv.mText);
+                int st = tv.getSelectionStart();
+                int en = tv.getSelectionEnd();
 
                 if (st == en && st >= 0 && en >= 0) {
                     if (tv.mLayout != null) {
@@ -6752,8 +6910,10 @@
 
     @Override
     protected int computeHorizontalScrollRange() {
-        if (mLayout != null)
-            return mLayout.getWidth();
+        if (mLayout != null) {
+            return mSingleLine && (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.LEFT ?
+                    (int) mLayout.getLineWidth(0) : mLayout.getWidth();
+        }
 
         return super.computeHorizontalScrollRange();
     }
@@ -6856,21 +7016,17 @@
     }
 
     private boolean canSelectAll() {
-        if (mText instanceof Spannable && mText.length() != 0 &&
-            mMovement != null && mMovement.canSelectArbitrarily()) {
-            return true;
-        }
-
-        return false;
+        return canSelectText();
     }
 
     private boolean canSelectText() {
-        if (mText instanceof Spannable && mText.length() != 0 &&
-            mMovement != null && mMovement.canSelectArbitrarily()) {
-            return true;
-        }
-
-        return false;
+        // prepareCursorController() relies on this method.
+        // If you change this condition, make sure prepareCursorController is called anywhere
+        // the value of this condition might be changed.
+        return (mText instanceof Spannable &&
+                mText.length() != 0 &&
+                mMovement != null &&
+                mMovement.canSelectArbitrarily());
     }
 
     private boolean canCut() {
@@ -6900,23 +7056,23 @@
     }
 
     private boolean canPaste() {
-        if (mText instanceof Editable && mInput != null &&
-            getSelectionStart() >= 0 && getSelectionEnd() >= 0) {
-            ClipboardManager clip = (ClipboardManager)getContext()
-                    .getSystemService(Context.CLIPBOARD_SERVICE);
-            if (clip.hasText()) {
-                return true;
-            }
-        }
-
-        return false;
+        return (mText instanceof Editable &&
+                mInput != null &&
+                getSelectionStart() >= 0 &&
+                getSelectionEnd() >= 0 &&
+                ((ClipboardManager)getContext().getSystemService(Context.CLIPBOARD_SERVICE)).
+                hasText());
     }
 
     /**
-     * Returns a word to add to the dictionary from the context menu,
-     * or null if there is no cursor or no word at the cursor.
+     * Returns the offsets delimiting the 'word' located at position offset.
+     *
+     * @param offset An offset in the text.
+     * @return The offsets for the start and end of the word located at <code>offset</code>.
+     * The two ints offsets are packed in a long, with the starting offset shifted by 32 bits.
+     * Returns a negative value if no valid word was found.
      */
-    private String getWordForDictionary() {
+    private long getWordLimitsAt(int offset) {
         /*
          * Quick return if the input type is one where adding words
          * to the dictionary doesn't make any sense.
@@ -6925,7 +7081,7 @@
         if (klass == InputType.TYPE_CLASS_NUMBER ||
             klass == InputType.TYPE_CLASS_PHONE ||
             klass == InputType.TYPE_CLASS_DATETIME) {
-            return null;
+            return -1;
         }
 
         int variation = mInputType & InputType.TYPE_MASK_VARIATION;
@@ -6934,13 +7090,13 @@
             variation == InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD ||
             variation == InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS ||
             variation == InputType.TYPE_TEXT_VARIATION_FILTER) {
-            return null;
+            return -1;
         }
 
-        int end = getSelectionEnd();
+        int end = offset;
 
         if (end < 0) {
-            return null;
+            return -1;
         }
 
         int start = end;
@@ -6974,6 +7130,14 @@
             }
         }
 
+        if (start == end) {
+            return -1;
+        }
+
+        if (end - start > 48) {
+            return -1;
+        }
+
         boolean hasLetter = false;
         for (int i = start; i < end; i++) {
             if (Character.isLetter(mTransformed.charAt(i))) {
@@ -6981,19 +7145,13 @@
                 break;
             }
         }
+
         if (!hasLetter) {
-            return null;
+            return -1;
         }
 
-        if (start == end) {
-            return null;
-        }
-
-        if (end - start > 48) {
-            return null;
-        }
-
-        return TextUtils.substring(mTransformed, start, end);
+        // Two ints packed in a long
+        return (((long) start) << 32) | end;
     }
 
     @Override
@@ -7037,84 +7195,8 @@
         super.onCreateContextMenu(menu);
         boolean added = false;
 
-        if (!isFocused()) {
-            if (isFocusable() && mInput != null) {
-                if (canCopy()) {
-                    MenuHandler handler = new MenuHandler();
-                    int name = com.android.internal.R.string.copyAll;
-
-                    menu.add(0, ID_COPY, 0, name).
-                        setOnMenuItemClickListener(handler).
-                        setAlphabeticShortcut('c');
-                    menu.setHeaderTitle(com.android.internal.R.string.
-                        editTextMenuTitle);
-                }
-            }
-
-            return;
-        }
-
         MenuHandler handler = new MenuHandler();
 
-        if (canSelectAll()) {
-            menu.add(0, ID_SELECT_ALL, 0,
-                    com.android.internal.R.string.selectAll).
-                setOnMenuItemClickListener(handler).
-                setAlphabeticShortcut('a');
-            added = true;
-        }
-
-        boolean selection = getSelectionStart() != getSelectionEnd();
-
-        if (canSelectText()) {
-            if (MetaKeyKeyListener.getMetaState(mText, MetaKeyKeyListener.META_SELECTING) != 0) {
-                menu.add(0, ID_STOP_SELECTING_TEXT, 0,
-                        com.android.internal.R.string.stopSelectingText).
-                    setOnMenuItemClickListener(handler);
-                added = true;
-            } else {
-                menu.add(0, ID_START_SELECTING_TEXT, 0,
-                        com.android.internal.R.string.selectText).
-                    setOnMenuItemClickListener(handler);
-                added = true;
-            }
-        }
-
-        if (canCut()) {
-            int name;
-            if (selection) {
-                name = com.android.internal.R.string.cut;
-            } else {
-                name = com.android.internal.R.string.cutAll;
-            }
-
-            menu.add(0, ID_CUT, 0, name).
-                setOnMenuItemClickListener(handler).
-                setAlphabeticShortcut('x');
-            added = true;
-        }
-
-        if (canCopy()) {
-            int name;
-            if (selection) {
-                name = com.android.internal.R.string.copy;
-            } else {
-                name = com.android.internal.R.string.copyAll;
-            }
-
-            menu.add(0, ID_COPY, 0, name).
-                setOnMenuItemClickListener(handler).
-                setAlphabeticShortcut('c');
-            added = true;
-        }
-
-        if (canPaste()) {
-            menu.add(0, ID_PASTE, 0, com.android.internal.R.string.paste).
-                    setOnMenuItemClickListener(handler).
-                    setAlphabeticShortcut('v');
-            added = true;
-        }
-
         if (mText instanceof Spanned) {
             int selStart = getSelectionStart();
             int selEnd = getSelectionEnd();
@@ -7128,25 +7210,21 @@
                 menu.add(0, ID_COPY_URL, 0,
                          com.android.internal.R.string.copyUrl).
                             setOnMenuItemClickListener(handler);
+
                 added = true;
             }
         }
-
-        if (isInputMethodTarget()) {
-            menu.add(1, ID_SWITCH_INPUT_METHOD, 0, com.android.internal.R.string.inputMethod).
-                    setOnMenuItemClickListener(handler);
+        
+        // The context menu is not empty, which will prevent the selection mode from starting.
+        // Add a entry to start it in the context menu.
+        // TODO Does not handle the case where a subclass does not call super.thisMethod or
+        // populates the menu AFTER this call.
+        if (menu.size() > 0) {
+            menu.add(0, ID_SELECTION_MODE, 0, com.android.internal.R.string.selectTextMode).
+            setOnMenuItemClickListener(handler);
             added = true;
         }
 
-        String word = getWordForDictionary();
-        if (word != null) {
-            menu.add(1, ID_ADD_TO_DICTIONARY, 0,
-                     getContext().getString(com.android.internal.R.string.addToDictionary, word)).
-                    setOnMenuItemClickListener(handler);
-            added = true;
-
-        }
-
         if (added) {
             menu.setHeaderTitle(com.android.internal.R.string.editTextMenuTitle);
         }
@@ -7161,15 +7239,14 @@
         return imm != null && imm.isActive(this);
     }
     
+    // Selection context mode
     private static final int ID_SELECT_ALL = android.R.id.selectAll;
-    private static final int ID_START_SELECTING_TEXT = android.R.id.startSelectingText;
-    private static final int ID_STOP_SELECTING_TEXT = android.R.id.stopSelectingText;
     private static final int ID_CUT = android.R.id.cut;
     private static final int ID_COPY = android.R.id.copy;
     private static final int ID_PASTE = android.R.id.paste;
+    // Context menu entries
     private static final int ID_COPY_URL = android.R.id.copyUrl;
-    private static final int ID_SWITCH_INPUT_METHOD = android.R.id.switchInputMethod;
-    private static final int ID_ADD_TO_DICTIONARY = android.R.id.addToDictionary;
+    private static final int ID_SELECTION_MODE = android.R.id.selectTextMode;
 
     private class MenuHandler implements MenuItem.OnMenuItemClickListener {
         public boolean onMenuItemClick(MenuItem item) {
@@ -7179,11 +7256,7 @@
 
     /**
      * Called when a context menu option for the text view is selected.  Currently
-     * this will be one of: {@link android.R.id#selectAll},
-     * {@link android.R.id#startSelectingText}, {@link android.R.id#stopSelectingText},
-     * {@link android.R.id#cut}, {@link android.R.id#copy},
-     * {@link android.R.id#paste}, {@link android.R.id#copyUrl},
-     * or {@link android.R.id#switchInputMethod}.
+     * this will be {@link android.R.id#copyUrl} or {@link android.R.id#selectTextMode}.
      */
     public boolean onTextContextMenuItem(int id) {
         int selStart = getSelectionStart();
@@ -7194,112 +7267,776 @@
             selEnd = mText.length();
         }
 
-        int min = Math.min(selStart, selEnd);
-        int max = Math.max(selStart, selEnd);
-
-        if (min < 0) {
-            min = 0;
-        }
-        if (max < 0) {
-            max = 0;
-        }
+        int min = Math.max(0, Math.min(selStart, selEnd));
+        int max = Math.max(0, Math.max(selStart, selEnd));
 
         ClipboardManager clip = (ClipboardManager)getContext()
                 .getSystemService(Context.CLIPBOARD_SERVICE);
 
         switch (id) {
-            case ID_SELECT_ALL:
-                Selection.setSelection((Spannable) mText, 0,
-                        mText.length());
-                return true;
-
-            case ID_START_SELECTING_TEXT:
-                MetaKeyKeyListener.startSelecting(this, (Spannable) mText);
-                return true;
-
-            case ID_STOP_SELECTING_TEXT:
-                MetaKeyKeyListener.stopSelecting(this, (Spannable) mText);
-                Selection.setSelection((Spannable) mText, getSelectionEnd());
-                return true;
-
-            case ID_CUT:
-                MetaKeyKeyListener.stopSelecting(this, (Spannable) mText);
-
-                if (min == max) {
-                    min = 0;
-                    max = mText.length();
-                }
-
-                clip.setText(mTransformed.subSequence(min, max));
-                ((Editable) mText).delete(min, max);
-                return true;
-
-            case ID_COPY:
-                MetaKeyKeyListener.stopSelecting(this, (Spannable) mText);
-
-                if (min == max) {
-                    min = 0;
-                    max = mText.length();
-                }
-
-                clip.setText(mTransformed.subSequence(min, max));
-                return true;
-
-            case ID_PASTE:
-                MetaKeyKeyListener.stopSelecting(this, (Spannable) mText);
-
-                CharSequence paste = clip.getText();
-
-                if (paste != null) {
-                    Selection.setSelection((Spannable) mText, max);
-                    ((Editable) mText).replace(min, max, paste);
-                }
-
-                return true;
-
             case ID_COPY_URL:
                 MetaKeyKeyListener.stopSelecting(this, (Spannable) mText);
 
-                URLSpan[] urls = ((Spanned) mText).getSpans(min, max,
-                                                       URLSpan.class);
+                URLSpan[] urls = ((Spanned) mText).getSpans(min, max, URLSpan.class);
                 if (urls.length == 1) {
                     clip.setText(urls[0].getURL());
                 }
-
                 return true;
 
-            case ID_SWITCH_INPUT_METHOD:
-                InputMethodManager imm = InputMethodManager.peekInstance();
-                if (imm != null) {
-                    imm.showInputMethodPicker();
-                }
-                return true;
-
-            case ID_ADD_TO_DICTIONARY:
-                String word = getWordForDictionary();
-
-                if (word != null) {
-                    Intent i = new Intent("com.android.settings.USER_DICTIONARY_INSERT");
-                    i.putExtra("word", word);
-                    i.setFlags(i.getFlags() | Intent.FLAG_ACTIVITY_NEW_TASK);
-                    getContext().startActivity(i);
-                }
-
+            case ID_SELECTION_MODE:
+                startSelectionActionMode();
                 return true;
             }
 
         return false;
     }
 
+    @Override
     public boolean performLongClick() {
         if (super.performLongClick()) {
             mEatTouchRelease = true;
             return true;
         }
+        
+        if (startSelectionActionMode()) {
+            performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
+            mEatTouchRelease = true;
+            return true;
+        }
 
         return false;
     }
 
+    private boolean touchPositionIsInSelection() {
+        int selectionStart = getSelectionStart();
+        int selectionEnd = getSelectionEnd();
+
+        if (selectionStart == selectionEnd) {
+            return false;
+        }
+
+        if (selectionStart > selectionEnd) {
+            int tmp = selectionStart;
+            selectionStart = selectionEnd;
+            selectionEnd = tmp;
+            Selection.setSelection((Spannable) mText, selectionStart, selectionEnd);
+        }
+
+        SelectionModifierCursorController selectionModifierCursorController =
+            ((SelectionModifierCursorController) mSelectionModifierCursorController);
+        int minOffset = selectionModifierCursorController.getMinTouchOffset();
+        int maxOffset = selectionModifierCursorController.getMaxTouchOffset();
+
+        return ((minOffset >= selectionStart) && (maxOffset < selectionEnd));
+    }
+
+    /**
+     * Provides the callback used to start a selection action mode.
+     *
+     * @return A callback instance that will be used to start selection mode, or null if selection
+     * mode is not available.
+     */
+    private ActionMode.Callback getActionModeCallback() {
+        // Long press in the current selection.
+        // Should initiate a drag. Return false, to rely on context menu for now.
+        if (canSelectText() && !touchPositionIsInSelection()) {
+            return new SelectionActionModeCallback();
+        }
+        return null;
+    }
+
+    /**
+     *
+     * @return true if the selection mode was actually started.
+     */
+    private boolean startSelectionActionMode() {
+        if (mSelectionActionMode != null) {
+            // Selection action mode is already started
+            return false;
+        }
+
+        ActionMode.Callback actionModeCallback = getActionModeCallback();
+        if (actionModeCallback != null) {
+            mSelectionActionMode = startActionMode(actionModeCallback);
+            return mSelectionActionMode != null;
+        }
+
+        return false;
+    }
+
+    /**
+     * Same as {@link #finishSelectionActionMode()}, except that there is no cursor controller
+     * fade out animation. Needed since the drawable and their alpha values are shared by all
+     * TextViews. Switching from one TextView to another would fade the cursor controllers in the
+     * new one otherwise.
+     */
+    private void stopSelectionActionMode() {
+        finishSelectionActionMode();
+        if (mSelectionModifierCursorController != null) {
+            SelectionModifierCursorController selectionModifierCursorController =
+                (SelectionModifierCursorController) mSelectionModifierCursorController;
+            selectionModifierCursorController.cancelFadeOutAnimation();
+        }
+    }
+
+    private void finishSelectionActionMode() {
+        if (mSelectionActionMode != null) {
+            mSelectionActionMode.finish();
+        }
+    }
+
+    private class SelectionActionModeCallback implements ActionMode.Callback {
+
+        @Override
+        public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+            if (mSelectionModifierCursorController == null) {
+                Log.w(LOG_TAG, "TextView has no selection controller. Action mode cancelled.");
+                return false;
+            }
+
+            if (!requestFocus()) {
+                return false;
+            }
+
+            mode.setTitle(mContext.getString(com.android.internal.R.string.textSelectionCABTitle));
+            mode.setSubtitle(null);
+
+            selectCurrentWord();
+
+            boolean atLeastOne = false;
+
+            if (canSelectAll()) {
+                menu.add(0, ID_SELECT_ALL, 0, com.android.internal.R.string.selectAll).
+                    setIcon(com.android.internal.R.drawable.ic_menu_chat_dashboard).
+                    setAlphabeticShortcut('a');
+                atLeastOne = true;
+            }
+
+            if (canCut()) {
+                menu.add(0, ID_CUT, 0, com.android.internal.R.string.cut).
+                    setIcon(com.android.internal.R.drawable.ic_menu_compose).
+                    setAlphabeticShortcut('x');
+                atLeastOne = true;
+            }
+
+            if (canCopy()) {
+                menu.add(0, ID_COPY, 0, com.android.internal.R.string.copy).
+                    setIcon(com.android.internal.R.drawable.ic_menu_attachment).
+                    setAlphabeticShortcut('c');
+                atLeastOne = true;
+            }
+
+            if (canPaste()) {
+                menu.add(0, ID_PASTE, 0, com.android.internal.R.string.paste).
+                        setIcon(com.android.internal.R.drawable.ic_menu_camera).
+                        setAlphabeticShortcut('v');
+                atLeastOne = true;
+            }
+
+            if (atLeastOne) {
+                mSelectionModifierCursorController.show();
+                return true;
+            } else {
+                return false;
+            }
+        }
+
+        private void selectCurrentWord() {
+            // In case selection mode is started after an orientation change, use the current
+            // selection instead of creating one
+            if (hasSelection()) {
+                return;
+            }
+
+            int selectionStart, selectionEnd;
+
+            SelectionModifierCursorController selectionModifierCursorController =
+                ((SelectionModifierCursorController) mSelectionModifierCursorController);
+            int minOffset = selectionModifierCursorController.getMinTouchOffset();
+            int maxOffset = selectionModifierCursorController.getMaxTouchOffset();
+
+            long wordLimits = getWordLimitsAt(minOffset);
+            if (wordLimits >= 0) {
+                selectionStart = (int) (wordLimits >>> 32);
+            } else {
+                selectionStart = Math.max(minOffset - 5, 0);
+            }
+
+            wordLimits = getWordLimitsAt(maxOffset);
+            if (wordLimits >= 0) {
+                selectionEnd = (int) (wordLimits & 0x00000000FFFFFFFFL);
+            } else {
+                selectionEnd = Math.min(maxOffset + 5, mText.length());
+            }
+
+            Selection.setSelection((Spannable) mText, selectionStart, selectionEnd);
+        }
+
+        @Override
+        public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
+            return true;
+        }
+
+        @Override
+        public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
+            final int itemId = item.getItemId();
+
+            if (itemId == ID_SELECT_ALL) {
+                Selection.setSelection((Spannable) mText, 0, mText.length());
+                // Update controller positions after selection change.
+                if (mSelectionModifierCursorController != null) {
+                    mSelectionModifierCursorController.show();
+                }
+                return true;
+            }
+
+            ClipboardManager clip = (ClipboardManager) getContext().
+                                                        getSystemService(Context.CLIPBOARD_SERVICE);
+
+            int min = 0;
+            int max = mText.length();
+
+            if (isFocused()) {
+                final int selStart = getSelectionStart();
+                final int selEnd = getSelectionEnd();
+
+                min = Math.max(0, Math.min(selStart, selEnd));
+                max = Math.max(0, Math.max(selStart, selEnd));
+            }
+
+            switch (item.getItemId()) {
+                case ID_PASTE:
+                    CharSequence paste = clip.getText();
+
+                    if (paste != null) {
+                        Selection.setSelection((Spannable) mText, max);
+                        ((Editable) mText).replace(min, max, paste);
+                        finishSelectionActionMode();
+                    }
+                    return true;
+
+                case ID_CUT:
+                    clip.setText(mTransformed.subSequence(min, max));
+                    ((Editable) mText).delete(min, max);
+                    finishSelectionActionMode();
+                    return true;
+
+                case ID_COPY:
+                    clip.setText(mTransformed.subSequence(min, max));
+                    finishSelectionActionMode();
+                    return true;
+            }
+
+            return false;
+        }
+
+        @Override
+        public void onDestroyActionMode(ActionMode mode) {
+            Selection.setSelection((Spannable) mText, getSelectionStart());
+            if (mSelectionModifierCursorController != null) {
+                mSelectionModifierCursorController.hide();
+            }
+            mSelectionActionMode = null;
+        }
+    }
+
+    /**
+     * A CursorController instance can be used to control a cursor in the text.
+     *
+     * It can be passed to an {@link ArrowKeyMovementMethod} which can intercepts events
+     * and send them to this object instead of the cursor.
+     */
+    public interface CursorController {
+        /* Cursor fade-out animation duration, in milliseconds. */
+        static final int FADE_OUT_DURATION = 400;
+
+        /**
+         * Makes the cursor controller visible on screen. Will be drawn by {@link #draw(Canvas)}.
+         * See also {@link #hide()}.
+         */
+        public void show();
+
+        /**
+         * Hide the cursor controller from screen.
+         * See also {@link #show()}.
+         */
+        public void hide();
+
+        /**
+         * Update the controller's position.
+         */
+        public void updatePosition(int offset);
+
+        /**
+         * The controller and the cursor's positions can be link by a fixed offset,
+         * computed when the controller is touched, and then maintained as it moves
+         * @return Horizontal offset between the controller and the cursor.
+         */
+        public float getOffsetX();
+
+        /**
+         * @return Vertical offset between the controller and the cursor.
+         */
+        public float getOffsetY();
+
+        /**
+         * This method is called by {@link #onTouchEvent(MotionEvent)} and gives the controller
+         * a chance to become active and/or visible.
+         * @param event The touch event
+         */
+        public void onTouchEvent(MotionEvent event);
+
+        /**
+         * Draws a visual representation of the controller on the canvas.
+         *
+         * Called at the end of {@link #draw(Canvas)}, in the content coordinates system.
+         * @param canvas The Canvas used by this TextView.
+         */
+        public void draw(Canvas canvas);
+    }
+
+    class InsertionPointCursorController implements CursorController {
+        private static final int DELAY_BEFORE_FADE_OUT = 2100;
+
+        // Whether or not the cursor control is currently visible
+        private boolean mIsVisible = false;
+        // Starting time of the fade timer
+        private long mFadeOutTimerStart;
+        // The cursor controller image
+        private final Drawable mDrawable;
+        // Used to detect a tap (vs drag) on the controller
+        private long mOnDownTimerStart;
+        // Offset between finger hot point on cursor controller and actual cursor
+        private float mOffsetX, mOffsetY;
+
+        InsertionPointCursorController() {
+            Resources res = mContext.getResources();
+            mDrawable = res.getDrawable(com.android.internal.R.drawable.cursor_controller);
+        }
+
+        public void show() {
+            updateDrawablePosition();
+            // Has to be done after updatePosition, so that previous position invalidate
+            // in only done if necessary.
+            mIsVisible = true;
+        }
+
+        public void hide() {
+            if (mIsVisible) {
+                long time = System.currentTimeMillis();
+                // Start fading out, only if not already in progress
+                if (time - mFadeOutTimerStart < DELAY_BEFORE_FADE_OUT) {
+                    mFadeOutTimerStart = time - DELAY_BEFORE_FADE_OUT;
+                    postInvalidate(mDrawable);
+                }
+            }
+        }
+
+        public void draw(Canvas canvas) {
+            if (mIsVisible) {
+                int time = (int) (System.currentTimeMillis() - mFadeOutTimerStart);
+                if (time <= DELAY_BEFORE_FADE_OUT) {
+                    postInvalidateDelayed(DELAY_BEFORE_FADE_OUT - time, mDrawable);
+                } else {
+                    time -= DELAY_BEFORE_FADE_OUT;
+                    if (time <= FADE_OUT_DURATION) {
+                        final int alpha = 255 * (FADE_OUT_DURATION - time) / FADE_OUT_DURATION;
+                        mDrawable.setAlpha(alpha);
+                        postInvalidateDelayed(30, mDrawable);
+                    } else {
+                        mDrawable.setAlpha(0);
+                        mIsVisible = false;
+                    }
+                }
+                mDrawable.draw(canvas);
+            }
+        }
+
+        public void updatePosition(int offset) {
+            if (offset == getSelectionStart()) {
+                return; // No change, no need to redraw
+            }
+            Selection.setSelection((Spannable) mText, offset);
+            updateDrawablePosition();
+        }
+
+        private void updateDrawablePosition() {
+            if (mIsVisible) {
+                // Clear previous cursor controller before bounds are updated
+                postInvalidate(mDrawable);
+            }
+
+            final int offset = getSelectionStart();
+
+            if (offset < 0) {
+                // Should never happen, safety check.
+                Log.w(LOG_TAG, "Update cursor controller position called with no cursor");
+                mIsVisible = false;
+                return;
+            }
+
+            positionDrawableUnderCursor(offset, mDrawable);
+
+            mFadeOutTimerStart = System.currentTimeMillis();
+            mDrawable.setAlpha(255);
+        }
+
+        public void onTouchEvent(MotionEvent event) {
+            if (isFocused() && isTextEditable() && mIsVisible) {
+                switch (event.getActionMasked()) {
+                    case MotionEvent.ACTION_DOWN : {
+                        final float x = event.getX();
+                        final float y = event.getY();
+
+                        if (fingerIsOnDrawable(x, y, mDrawable)) {
+                            show();
+
+                            if (mMovement instanceof ArrowKeyMovementMethod) {
+                                ((ArrowKeyMovementMethod)mMovement).setCursorController(this);
+                            }
+
+                            if (mParent != null) {
+                                // Prevent possible scrollView parent from scrolling, so that
+                                // we can use auto-scrolling.
+                                mParent.requestDisallowInterceptTouchEvent(true);
+                            }
+
+                            final Rect bounds = mDrawable.getBounds();
+                            mOffsetX = (bounds.left + bounds.right) / 2.0f - x;
+                            mOffsetY = bounds.top - mCursorControllerVerticalOffset - y;
+
+                            mOnDownTimerStart = event.getEventTime();
+                        }
+                        break;
+                    }
+
+                    case MotionEvent.ACTION_UP : {
+                        int time = (int) (event.getEventTime() - mOnDownTimerStart);
+
+                        if (time <= ViewConfiguration.getTapTimeout()) {
+                            // A tap on the controller (not a drag) will move the cursor
+                            int offset = getOffset((int) event.getX(), (int) event.getY());
+                            Selection.setSelection((Spannable) mText, offset);
+
+                            // Modified by cancelLongPress and prevents the cursor from changing
+                            mScrolled = false;
+                        }
+                        break;
+                    }
+                }
+            }
+        }
+
+        public float getOffsetX() {
+            return mOffsetX;
+        }
+
+        public float getOffsetY() {
+            return mOffsetY;
+        }
+    }
+
+    class SelectionModifierCursorController implements CursorController {
+        // Whether or not the selection controls are currently visible
+        private boolean mIsVisible = false;
+        // Whether that start or the end of selection controller is dragged
+        private boolean mStartIsDragged = false;
+        // Starting time of the fade timer
+        private long mFadeOutTimerStart;
+        // The cursor controller images
+        private final Drawable mStartDrawable, mEndDrawable;
+        // Offset between finger hot point on active cursor controller and actual cursor
+        private float mOffsetX, mOffsetY;
+        // The offsets of that last touch down event. Remembered to start selection there.
+        private int mMinTouchOffset, mMaxTouchOffset;
+
+        SelectionModifierCursorController() {
+            Resources res = mContext.getResources();
+            mStartDrawable = res.getDrawable(com.android.internal.R.drawable.selection_start_handle);
+            mEndDrawable = res.getDrawable(com.android.internal.R.drawable.selection_end_handle);
+        }
+
+        public void show() {
+            updateDrawablesPositions();
+            // Has to be done after updatePosition, so that previous position invalidate
+            // in only done if necessary.
+            mIsVisible = true;
+            mFadeOutTimerStart = -1;
+            hideInsertionPointCursorController();
+        }
+
+        public void hide() {
+            if (mIsVisible && (mFadeOutTimerStart < 0)) {
+                mFadeOutTimerStart = System.currentTimeMillis();
+                postInvalidate(mStartDrawable);
+                postInvalidate(mEndDrawable);
+            }
+        }
+
+        public void cancelFadeOutAnimation() {
+            mIsVisible = false;
+            postInvalidate(mStartDrawable);
+            postInvalidate(mEndDrawable);
+        }
+
+        public void draw(Canvas canvas) {
+            if (mIsVisible) {
+                if (mFadeOutTimerStart >= 0) {
+                    int time = (int) (System.currentTimeMillis() - mFadeOutTimerStart);
+                    if (time <= FADE_OUT_DURATION) {
+                        final int alpha = 255 * (FADE_OUT_DURATION - time) / FADE_OUT_DURATION;
+                        mStartDrawable.setAlpha(alpha);
+                        mEndDrawable.setAlpha(alpha);
+                        postInvalidateDelayed(30, mStartDrawable);
+                        postInvalidateDelayed(30, mEndDrawable);
+                    } else {
+                        mStartDrawable.setAlpha(0);
+                        mEndDrawable.setAlpha(0);
+                        mIsVisible = false;
+                    }
+                }
+                mStartDrawable.draw(canvas);
+                mEndDrawable.draw(canvas);
+            }
+        }
+
+        public void updatePosition(int offset) {
+            int selectionStart = getSelectionStart();
+            int selectionEnd = getSelectionEnd();
+
+            // Handle the case where start and end are swapped, making sure start <= end
+            if (mStartIsDragged) {
+                if (offset <= selectionEnd) {
+                    if (selectionStart == offset) {
+                        return; // no change, no need to redraw;
+                    }
+                    selectionStart = offset;
+                } else {
+                    selectionStart = selectionEnd;
+                    selectionEnd = offset;
+                    mStartIsDragged = false;
+                }
+            } else {
+                if (offset >= selectionStart) {
+                    if (selectionEnd == offset) {
+                        return; // no change, no need to redraw;
+                    }
+                    selectionEnd = offset;
+                } else {
+                    selectionEnd = selectionStart;
+                    selectionStart = offset;
+                    mStartIsDragged = true;
+                }
+            }
+
+            Selection.setSelection((Spannable) mText, selectionStart, selectionEnd);
+            updateDrawablesPositions();
+        }
+
+        private void updateDrawablesPositions() {
+            if (mIsVisible) {
+                // Clear previous cursor controller before bounds are updated
+                postInvalidate(mStartDrawable);
+                postInvalidate(mEndDrawable);
+            }
+
+            final int selectionStart = getSelectionStart();
+            final int selectionEnd = getSelectionEnd();
+
+            if ((selectionStart < 0) || (selectionEnd < 0)) {
+                // Should never happen, safety check.
+                Log.w(LOG_TAG, "Update selection controller position called with no cursor");
+                mIsVisible = false;
+                return;
+            }
+
+            positionDrawableUnderCursor(selectionStart, mStartDrawable);
+            positionDrawableUnderCursor(selectionEnd, mEndDrawable);
+
+            mStartDrawable.setAlpha(255);
+            mEndDrawable.setAlpha(255);
+        }
+
+        public void onTouchEvent(MotionEvent event) {
+            if (isTextEditable()) {
+                switch (event.getActionMasked()) {
+                    case MotionEvent.ACTION_DOWN:
+                        final int x = (int) event.getX();
+                        final int y = (int) event.getY();
+
+                        // Remember finger down position, to be able to start selection on that point
+                        mMinTouchOffset = mMaxTouchOffset = getOffset(x, y);
+
+                        if (mIsVisible) {
+                            if (mMovement instanceof ArrowKeyMovementMethod) {
+                                boolean isOnStart = fingerIsOnDrawable(x, y, mStartDrawable);
+                                boolean isOnEnd = fingerIsOnDrawable(x, y, mEndDrawable);
+                                if (isOnStart || isOnEnd) {
+                                    if (mParent != null) {
+                                        // Prevent possible scrollView parent from scrolling, so that
+                                        // we can use auto-scrolling.
+                                        mParent.requestDisallowInterceptTouchEvent(true);
+                                    }
+
+                                    // Start handle will be dragged in case BOTH controller are under finger
+                                    mStartIsDragged = isOnStart;
+                                    final Rect bounds =
+                                        (mStartIsDragged ? mStartDrawable : mEndDrawable).getBounds();
+                                    mOffsetX = (bounds.left + bounds.right) / 2.0f - x;
+                                    mOffsetY = bounds.top - mCursorControllerVerticalOffset - y;
+
+                                    ((ArrowKeyMovementMethod)mMovement).setCursorController(this);
+                                }
+                            }
+                        }
+                        break;
+
+                    case MotionEvent.ACTION_POINTER_DOWN:
+                    case MotionEvent.ACTION_POINTER_UP:
+                        // Handle multi-point gestures. Keep min and max offset positions.
+                        // Only activated for devices that correctly handle multi-touch.
+                        if (mContext.getPackageManager().hasSystemFeature(
+                                PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT)) {
+                            updateMinAndMaxOffsets(event);
+                        }
+                        break;
+                }
+            }
+        }
+
+        /**
+         * @param event
+         */
+        private void updateMinAndMaxOffsets(MotionEvent event) {
+            int pointerCount = event.getPointerCount();
+            for (int index = 0; index < pointerCount; index++) {
+                final int x = (int) event.getX(index);
+                final int y = (int) event.getY(index);
+                int offset = getOffset(x, y);
+                if (offset < mMinTouchOffset) mMinTouchOffset = offset;
+                if (offset > mMaxTouchOffset) mMaxTouchOffset = offset;
+            }
+        }
+
+        public int getMinTouchOffset() {
+            return mMinTouchOffset;
+        }
+
+        public int getMaxTouchOffset() {
+            return mMaxTouchOffset;
+        }
+
+        public float getOffsetX() {
+            return mOffsetX;
+        }
+
+        public float getOffsetY() {
+            return mOffsetY;
+        }
+
+        /**
+         * @return true iff this controller is currently used to move the selection start.
+         */
+        public boolean isSelectionStartDragged() {
+            return mIsVisible && mStartIsDragged;
+        }
+    }
+
+    // Helper methods used by CursorController implementations
+
+    private void positionDrawableUnderCursor(final int offset, Drawable drawable) {
+        final int drawableWidth = drawable.getIntrinsicWidth();
+        final int drawableHeight = drawable.getIntrinsicHeight();
+        final int line = mLayout.getLineForOffset(offset);
+
+        final Rect bounds = sCursorControllerTempRect;
+        bounds.left = (int) (mLayout.getPrimaryHorizontal(offset) - 0.5 - drawableWidth / 2.0);
+        bounds.top = mLayout.getLineTop(line + 1);
+
+        // Move cursor controller a little bit up when editing the last line of text
+        // (or a single line) so that it is visible and easier to grab.
+        if (line == mLayout.getLineCount() - 1) {
+            bounds.top -= Math.max(0, drawableHeight / 2 - getExtendedPaddingBottom());
+        }
+
+        bounds.right = bounds.left + drawableWidth;
+        bounds.bottom = bounds.top + drawableHeight;
+
+        convertFromViewportToContentCoordinates(bounds);
+        drawable.setBounds(bounds);
+        postInvalidate(bounds.left, bounds.top, bounds.right, bounds.bottom);
+    }
+
+    private boolean fingerIsOnDrawable(float x, float y, Drawable drawable) {
+        // Simulate a 'fat finger' to ease grabbing of the controller.
+        // Expands according to controller image size instead of using density.
+        // Assumes controller imager has a sensible size, proportionnal to density.
+        final int drawableWidth = drawable.getIntrinsicWidth();
+        final int drawableHeight = drawable.getIntrinsicHeight();
+        final Rect fingerRect = sCursorControllerTempRect;
+        fingerRect.set((int) (x - drawableWidth / 2.0),
+                       (int) (y - drawableHeight),
+                       (int) (x + drawableWidth / 2.0),
+                       (int) y);
+        return Rect.intersects(drawable.getBounds(), fingerRect);
+    }
+
+    private void postInvalidate(Drawable drawable) {
+        final Rect bounds = drawable.getBounds();
+        postInvalidate(bounds.left, bounds.top, bounds.right, bounds.bottom);
+    }
+
+    private void postInvalidateDelayed(long delay, Drawable drawable) {
+        final Rect bounds = drawable.getBounds();
+        postInvalidateDelayed(delay, bounds.left, bounds.top, bounds.right, bounds.bottom);
+    }
+
+    private void hideInsertionPointCursorController() {
+        if (mInsertionPointCursorController != null) {
+            mInsertionPointCursorController.hide();
+        }
+    }
+
+    /**
+     * Get the offset character closest to the specified absolute position.
+     *
+     * @param x The horizontal absolute position of a point on screen
+     * @param y The vertical absolute position of a point on screen
+     * @return the character offset for the character whose position is closest to the specified
+     *  position. Returns -1 if there is no layout.
+     *
+     * @hide
+     */
+    public int getOffset(int x, int y) {
+        x -= getTotalPaddingLeft();
+        y -= getTotalPaddingTop();
+
+        // Clamp the position to inside of the view.
+        if (x < 0) {
+            x = 0;
+        } else if (x >= (getWidth() - getTotalPaddingRight())) {
+            x = getWidth()-getTotalPaddingRight() - 1;
+        }
+        if (y < 0) {
+            y = 0;
+        } else if (y >= (getHeight() - getTotalPaddingBottom())) {
+            y = getHeight()-getTotalPaddingBottom() - 1;
+        }
+
+        x += getScrollX();
+        y += getScrollY();
+
+        Layout layout = getLayout();
+        if (layout != null) {
+            final int line = layout.getLineForVertical(y);
+            final int offset = layout.getOffsetForHorizontal(line, x);
+            return offset;
+        } else {
+            return -1;
+        }
+    }
+
+
     @ViewDebug.ExportedProperty
     private CharSequence            mText;
     private CharSequence            mTransformed;
@@ -7318,16 +8055,27 @@
     private ArrayList<TextWatcher>  mListeners = null;
 
     // display attributes
-    private TextPaint               mTextPaint;
+    private final TextPaint         mTextPaint;
     private boolean                 mUserSetTextScaleX;
-    private Paint                   mHighlightPaint;
-    private int                     mHighlightColor = 0xFFBBDDFF;
+    private final Paint             mHighlightPaint;
+    private int                     mHighlightColor = 0xD077A14B;
     private Layout                  mLayout;
 
     private long                    mShowCursor;
     private Blink                   mBlink;
     private boolean                 mCursorVisible = true;
 
+    // Cursor Controllers. Null when disabled.
+    private CursorController        mInsertionPointCursorController;
+    private CursorController        mSelectionModifierCursorController;
+    // Stored once and for all.
+    private int                     mCursorControllerVerticalOffset;
+    private boolean                 mShouldStartSelectionActionMode = false;
+    private ActionMode              mSelectionActionMode;
+    // Created once and shared by different CursorController helper methods.
+    // Only one cursor controller is active at any time which prevent race conditions.
+    private static Rect             sCursorControllerTempRect = new Rect();
+
     private boolean                 mSelectAllOnFocus = false;
 
     private int                     mGravity = Gravity.TOP | Gravity.LEFT;
diff --git a/core/java/android/widget/ViewAnimator.java b/core/java/android/widget/ViewAnimator.java
index 907cfb3..7b66893 100644
--- a/core/java/android/widget/ViewAnimator.java
+++ b/core/java/android/widget/ViewAnimator.java
@@ -31,11 +31,13 @@
  *
  * @attr ref android.R.styleable#ViewAnimator_inAnimation
  * @attr ref android.R.styleable#ViewAnimator_outAnimation
+ * @attr ref android.R.styleable#ViewAnimator_animateFirstView
  */
 public class ViewAnimator extends FrameLayout {
 
     int mWhichChild = 0;
     boolean mFirstTime = true;
+
     boolean mAnimateFirstTime = true;
 
     Animation mInAnimation;
@@ -59,6 +61,10 @@
         if (resource > 0) {
             setOutAnimation(context, resource);
         }
+
+        boolean flag = a.getBoolean(com.android.internal.R.styleable.ViewAnimator_animateFirstView, true);
+        setAnimateFirstView(flag);
+
         a.recycle();
 
         initViewAnimator(context, attrs);
@@ -84,10 +90,10 @@
         setMeasureAllChildren(measureAllChildren);
         a.recycle();
     }
-    
+
     /**
      * Sets which child view will be displayed.
-     * 
+     *
      * @param whichChild the index of the child view to display
      */
     public void setDisplayedChild(int whichChild) {
@@ -105,14 +111,14 @@
             requestFocus(FOCUS_FORWARD);
         }
     }
-    
+
     /**
      * Returns the index of the currently displayed child view.
      */
     public int getDisplayedChild() {
         return mWhichChild;
     }
-    
+
     /**
      * Manually shows the next child.
      */
@@ -128,6 +134,35 @@
     }
 
     /**
+     * Shows only the specified child. The other displays Views exit the screen,
+     * optionally with the with the {@link #getOutAnimation() out animation} and
+     * the specified child enters the screen, optionally with the
+     * {@link #getInAnimation() in animation}.
+     *
+     * @param childIndex The index of the child to be shown.
+     * @param animate Whether or not to use the in and out animations, defaults
+     *            to true.
+     */
+    void showOnly(int childIndex, boolean animate) {
+        final int count = getChildCount();
+        for (int i = 0; i < count; i++) {
+            final View child = getChildAt(i);
+            if (i == childIndex) {
+                if (animate && mInAnimation != null) {
+                    child.startAnimation(mInAnimation);
+                }
+                child.setVisibility(View.VISIBLE);
+                mFirstTime = false;
+            } else {
+                if (animate && mOutAnimation != null && child.getVisibility() == View.VISIBLE) {
+                    child.startAnimation(mOutAnimation);
+                } else if (child.getAnimation() == mInAnimation)
+                    child.clearAnimation();
+                child.setVisibility(View.GONE);
+            }
+        }
+    }
+    /**
      * Shows only the specified child. The other displays Views exit the screen
      * with the {@link #getOutAnimation() out animation} and the specified child
      * enters the screen with the {@link #getInAnimation() in animation}.
@@ -135,24 +170,8 @@
      * @param childIndex The index of the child to be shown.
      */
     void showOnly(int childIndex) {
-        final int count = getChildCount();
-        for (int i = 0; i < count; i++) {
-            final View child = getChildAt(i);
-            final boolean checkForFirst = (!mFirstTime || mAnimateFirstTime);
-            if (i == childIndex) {
-                if (checkForFirst && mInAnimation != null) {
-                    child.startAnimation(mInAnimation);
-                }
-                child.setVisibility(View.VISIBLE);
-                mFirstTime = false;
-            } else {
-                if (checkForFirst && mOutAnimation != null && child.getVisibility() == View.VISIBLE) {
-                    child.startAnimation(mOutAnimation);
-                } else if (child.getAnimation() == mInAnimation)
-                    child.clearAnimation();
-                child.setVisibility(View.GONE);
-            }
-        }
+        final boolean animate = (!mFirstTime || mAnimateFirstTime);
+        showOnly(childIndex, animate);
     }
 
     @Override
diff --git a/core/java/android/widget/ViewFlipper.java b/core/java/android/widget/ViewFlipper.java
index 8034961..c6f6e81 100644
--- a/core/java/android/widget/ViewFlipper.java
+++ b/core/java/android/widget/ViewFlipper.java
@@ -75,7 +75,7 @@
                 updateRunning();
             } else if (Intent.ACTION_USER_PRESENT.equals(action)) {
                 mUserPresent = true;
-                updateRunning();
+                updateRunning(false);
             }
         }
     };
@@ -109,7 +109,7 @@
     protected void onWindowVisibilityChanged(int visibility) {
         super.onWindowVisibilityChanged(visibility);
         mVisible = visibility == VISIBLE;
-        updateRunning();
+        updateRunning(false);
     }
 
     /**
@@ -144,10 +144,22 @@
      * on {@link #mRunning} and {@link #mVisible} state.
      */
     private void updateRunning() {
+        updateRunning(true);
+    }
+
+    /**
+     * Internal method to start or stop dispatching flip {@link Message} based
+     * on {@link #mRunning} and {@link #mVisible} state.
+     *
+     * @param flipNow Determines whether or not to execute the animation now, in
+     *            addition to queuing future flips. If omitted, defaults to
+     *            true.
+     */
+    private void updateRunning(boolean flipNow) {
         boolean running = mVisible && mStarted && mUserPresent;
         if (running != mRunning) {
             if (running) {
-                showOnly(mWhichChild);
+                showOnly(mWhichChild, flipNow);
                 Message msg = mHandler.obtainMessage(FLIP_MSG);
                 mHandler.sendMessageDelayed(msg, mFlipInterval);
             } else {
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/app/ActionBarImpl.java b/core/java/com/android/internal/app/ActionBarImpl.java
new file mode 100644
index 0000000..281f32c
--- /dev/null
+++ b/core/java/com/android/internal/app/ActionBarImpl.java
@@ -0,0 +1,552 @@
+/*
+ * 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.internal.app;
+
+import com.android.internal.view.menu.MenuBuilder;
+import com.android.internal.view.menu.MenuPopupHelper;
+import com.android.internal.view.menu.SubMenuBuilder;
+import com.android.internal.widget.ActionBarContextView;
+import com.android.internal.widget.ActionBarView;
+
+import android.app.ActionBar;
+import android.app.Activity;
+import android.app.Fragment;
+import android.app.FragmentTransaction;
+import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.view.ActionMode;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.LinearLayout;
+import android.widget.SpinnerAdapter;
+import android.widget.ViewAnimator;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+
+/**
+ * ActionBarImpl is the ActionBar implementation used
+ * by devices of all screen sizes. If it detects a compatible decor,
+ * it will split contextual modes across both the ActionBarView at
+ * the top of the screen and a horizontal LinearLayout at the bottom
+ * which is normally hidden.
+ */
+public class ActionBarImpl extends ActionBar {
+    private static final int NORMAL_VIEW = 0;
+    private static final int CONTEXT_VIEW = 1;
+    
+    private static final int TAB_SWITCH_SHOW_HIDE = 0;
+    private static final int TAB_SWITCH_ADD_REMOVE = 1;
+
+    private Activity mActivity;
+
+    private ViewAnimator mAnimatorView;
+    private ActionBarView mActionView;
+    private ActionBarContextView mUpperContextView;
+    private LinearLayout mLowerContextView;
+
+    private ArrayList<TabImpl> mTabs = new ArrayList<TabImpl>();
+
+    private int mTabContainerViewId = android.R.id.content;
+    private TabImpl mSelectedTab;
+    private int mTabSwitchMode = TAB_SWITCH_ADD_REMOVE;
+    
+    private ActionMode mActionMode;
+    
+    private static final int CONTEXT_DISPLAY_NORMAL = 0;
+    private static final int CONTEXT_DISPLAY_SPLIT = 1;
+    
+    private int mContextDisplayMode;
+
+    private boolean mClosingContext;
+
+    final Handler mHandler = new Handler();
+    final Runnable mCloseContext = new Runnable() {
+        public void run() {
+            mUpperContextView.closeMode();
+            if (mLowerContextView != null) {
+                mLowerContextView.removeAllViews();
+            }
+            mClosingContext = false;
+        }
+    };
+
+    public ActionBarImpl(Activity activity) {
+        final View decor = activity.getWindow().getDecorView();
+        mActivity = activity;
+        mActionView = (ActionBarView) decor.findViewById(com.android.internal.R.id.action_bar);
+        mUpperContextView = (ActionBarContextView) decor.findViewById(
+                com.android.internal.R.id.action_context_bar);
+        mLowerContextView = (LinearLayout) decor.findViewById(
+                com.android.internal.R.id.lower_action_context_bar);
+        mAnimatorView = (ViewAnimator) decor.findViewById(
+                com.android.internal.R.id.action_bar_animator);
+        
+        if (mActionView == null || mUpperContextView == null || mAnimatorView == null) {
+            throw new IllegalStateException(getClass().getSimpleName() + " can only be used " +
+                    "with a compatible window decor layout");
+        }
+
+        mContextDisplayMode = mLowerContextView == null ?
+                CONTEXT_DISPLAY_NORMAL : CONTEXT_DISPLAY_SPLIT;
+    }
+
+    @Override
+    public void setStandardNavigationMode(int titleResId, int subtitleResId) {
+        setStandardNavigationMode(mActivity.getString(titleResId),
+                mActivity.getString(subtitleResId));
+    }
+
+    @Override
+    public void setStandardNavigationMode(int titleResId) {
+        setStandardNavigationMode(mActivity.getString(titleResId));
+    }
+
+    @Override
+    public void setTitle(int resId) {
+        setTitle(mActivity.getString(resId));
+    }
+
+    @Override
+    public void setSubtitle(int resId) {
+        setSubtitle(mActivity.getString(resId));
+    }
+
+    public void setCustomNavigationMode(View view) {
+        cleanupTabs();
+        mActionView.setCustomNavigationView(view);
+        mActionView.setCallback(null);
+    }
+
+    public void setDropdownNavigationMode(SpinnerAdapter adapter, NavigationCallback callback) {
+        setDropdownNavigationMode(adapter, callback, -1);
+    }
+
+    public void setDropdownNavigationMode(SpinnerAdapter adapter, NavigationCallback callback,
+            int defaultSelectedPosition) {
+        cleanupTabs();
+        mActionView.setNavigationMode(NAVIGATION_MODE_DROPDOWN_LIST);
+        mActionView.setDropdownAdapter(adapter);
+        if (defaultSelectedPosition >= 0) {
+            mActionView.setDropdownSelectedPosition(defaultSelectedPosition);
+        }
+        mActionView.setCallback(callback);
+    }
+
+    public void setStandardNavigationMode() {
+        cleanupTabs();
+        mActionView.setNavigationMode(NAVIGATION_MODE_STANDARD);
+        mActionView.setCallback(null);
+    }
+
+    public void setStandardNavigationMode(CharSequence title) {
+        cleanupTabs();
+        setStandardNavigationMode(title, null);
+    }
+
+    public void setStandardNavigationMode(CharSequence title, CharSequence subtitle) {
+        cleanupTabs();
+        mActionView.setNavigationMode(NAVIGATION_MODE_STANDARD);
+        mActionView.setTitle(title);
+        mActionView.setSubtitle(subtitle);
+        mActionView.setCallback(null);
+    }
+
+    public void setSelectedNavigationItem(int position) {
+        switch (mActionView.getNavigationMode()) {
+        case NAVIGATION_MODE_TABS:
+            selectTab(mTabs.get(position));
+            break;
+        case NAVIGATION_MODE_DROPDOWN_LIST:
+            mActionView.setDropdownSelectedPosition(position);
+            break;
+        default:
+            throw new IllegalStateException(
+                    "setSelectedNavigationItem not valid for current navigation mode");
+        }
+    }
+
+    public int getSelectedNavigationItem() {
+        switch (mActionView.getNavigationMode()) {
+        case NAVIGATION_MODE_TABS:
+            return mSelectedTab.getPosition();
+        case NAVIGATION_MODE_DROPDOWN_LIST:
+            return mActionView.getDropdownSelectedPosition();
+        default:
+            return -1;
+        }
+    }
+
+    private void cleanupTabs() {
+        if (mSelectedTab != null) {
+            selectTab(null);
+        }
+        if (!mTabs.isEmpty()) {
+            if (mTabSwitchMode == TAB_SWITCH_SHOW_HIDE) {
+                final FragmentTransaction trans = mActivity.openFragmentTransaction();
+                final int tabCount = mTabs.size();
+                for (int i = 0; i < tabCount; i++) {
+                    trans.remove(mTabs.get(i).getFragment());
+                }
+                trans.commit();
+            }
+            mTabs.clear();
+        }
+    }
+
+    public void setTitle(CharSequence title) {
+        mActionView.setTitle(title);
+    }
+
+    public void setSubtitle(CharSequence subtitle) {
+        mActionView.setSubtitle(subtitle);
+    }
+
+    public void setDisplayOptions(int options) {
+        mActionView.setDisplayOptions(options);
+    }
+
+    public void setDisplayOptions(int options, int mask) {
+        final int current = mActionView.getDisplayOptions(); 
+        mActionView.setDisplayOptions((options & mask) | (current & ~mask));
+    }
+
+    public void setBackgroundDrawable(Drawable d) {
+        mActionView.setBackgroundDrawable(d);
+    }
+
+    public View getCustomNavigationView() {
+        return mActionView.getCustomNavigationView();
+    }
+
+    public CharSequence getTitle() {
+        return mActionView.getTitle();
+    }
+
+    public CharSequence getSubtitle() {
+        return mActionView.getSubtitle();
+    }
+
+    public int getNavigationMode() {
+        return mActionView.getNavigationMode();
+    }
+
+    public int getDisplayOptions() {
+        return mActionView.getDisplayOptions();
+    }
+
+    public ActionMode startActionMode(ActionMode.Callback callback) {
+        if (mActionMode != null) {
+            mActionMode.finish();
+        }
+
+        // Don't wait for the close context mode animation to finish.
+        if (mClosingContext) {
+            mAnimatorView.clearAnimation();
+            mHandler.removeCallbacks(mCloseContext);
+            mCloseContext.run();
+        }
+
+        ActionMode mode = new ActionModeImpl(callback);
+        if (callback.onCreateActionMode(mode, mode.getMenu())) {
+            mode.invalidate();
+            mUpperContextView.initForMode(mode);
+            mAnimatorView.setDisplayedChild(CONTEXT_VIEW);
+            if (mLowerContextView != null) {
+                // TODO animate this
+                mLowerContextView.setVisibility(View.VISIBLE);
+            }
+            mActionMode = mode;
+            return mode;
+        }
+        return null;
+    }
+
+    private void configureTab(Tab tab, int position) {
+        final TabImpl tabi = (TabImpl) tab;
+        final boolean isFirstTab = mTabs.isEmpty();
+        final FragmentTransaction trans = mActivity.openFragmentTransaction();
+        final Fragment frag = tabi.getFragment();
+
+        tabi.setPosition(position);
+        mTabs.add(position, tabi);
+
+        if (mTabSwitchMode == TAB_SWITCH_SHOW_HIDE) {
+            if (!frag.isAdded()) {
+                trans.add(mTabContainerViewId, frag);
+            }
+        }
+
+        if (isFirstTab) {
+            if (mTabSwitchMode == TAB_SWITCH_SHOW_HIDE) {
+                trans.show(frag);
+            } else if (mTabSwitchMode == TAB_SWITCH_ADD_REMOVE) {
+                trans.add(mTabContainerViewId, frag);
+            }
+            mSelectedTab = tabi;
+        } else {
+            if (mTabSwitchMode == TAB_SWITCH_SHOW_HIDE) {
+                trans.hide(frag);
+            }
+        }
+        trans.commit();
+    }
+
+    @Override
+    public void addTab(Tab tab) {
+        mActionView.addTab(tab);
+        configureTab(tab, mTabs.size());
+    }
+
+    @Override
+    public void insertTab(Tab tab, int position) {
+        mActionView.insertTab(tab, position);
+        configureTab(tab, position);
+    }
+
+    @Override
+    public Tab newTab() {
+        return new TabImpl();
+    }
+
+    @Override
+    public void removeTab(Tab tab) {
+        removeTabAt(tab.getPosition());
+    }
+
+    @Override
+    public void removeTabAt(int position) {
+        mActionView.removeTabAt(position);
+        mTabs.remove(position);
+
+        final int newTabCount = mTabs.size();
+        for (int i = position; i < newTabCount; i++) {
+            mTabs.get(i).setPosition(i);
+        }
+
+        selectTab(mTabs.isEmpty() ? null : mTabs.get(Math.max(0, position - 1)));
+    }
+
+    @Override
+    public void setTabNavigationMode() {
+        mActionView.setNavigationMode(NAVIGATION_MODE_TABS);
+    }
+
+    @Override
+    public void setTabNavigationMode(int containerViewId) {
+        mTabContainerViewId = containerViewId;
+        setTabNavigationMode();
+    }
+
+    @Override
+    public void selectTab(Tab tab) {
+        if (mSelectedTab == tab) {
+            return;
+        }
+
+        mActionView.setTabSelected(tab != null ? tab.getPosition() : Tab.INVALID_POSITION);
+        final FragmentTransaction trans = mActivity.openFragmentTransaction();
+        if (mSelectedTab != null) {
+            if (mTabSwitchMode == TAB_SWITCH_SHOW_HIDE) {
+                trans.hide(mSelectedTab.getFragment());
+            } else if (mTabSwitchMode == TAB_SWITCH_ADD_REMOVE) {
+                trans.remove(mSelectedTab.getFragment());
+            }
+        }
+        if (tab != null) {
+            if (mTabSwitchMode == TAB_SWITCH_SHOW_HIDE) {
+                trans.show(tab.getFragment());
+            } else if (mTabSwitchMode == TAB_SWITCH_ADD_REMOVE) {
+                trans.add(mTabContainerViewId, tab.getFragment());
+            }
+        }
+        mSelectedTab = (TabImpl) tab;
+        trans.commit();
+    }
+
+    /**
+     * @hide 
+     */
+    public class ActionModeImpl extends ActionMode implements MenuBuilder.Callback {
+        private ActionMode.Callback mCallback;
+        private MenuBuilder mMenu;
+        private WeakReference<View> mCustomView;
+        
+        public ActionModeImpl(ActionMode.Callback callback) {
+            mCallback = callback;
+            mMenu = new MenuBuilder(mActionView.getContext());
+            mMenu.setCallback(this);
+        }
+
+        @Override
+        public MenuInflater getMenuInflater() {
+            return new MenuInflater(mActivity);
+        }
+
+        @Override
+        public Menu getMenu() {
+            return mMenu;
+        }
+
+        @Override
+        public void finish() {
+            if (mActionMode != this) {
+                // Not the active action mode - no-op
+                return;
+            }
+
+            mCallback.onDestroyActionMode(this);
+            mAnimatorView.setDisplayedChild(NORMAL_VIEW);
+
+            // Clear out the context mode views after the animation finishes
+            mClosingContext = true;
+            mHandler.postDelayed(mCloseContext, mAnimatorView.getOutAnimation().getDuration());
+
+            if (mLowerContextView != null && mLowerContextView.getVisibility() != View.GONE) {
+                // TODO Animate this
+                mLowerContextView.setVisibility(View.GONE);
+            }
+            mActionMode = null;
+        }
+
+        @Override
+        public void invalidate() {
+            if (mCallback.onPrepareActionMode(this, mMenu)) {
+                // Refresh content in both context views
+            }
+        }
+
+        @Override
+        public void setCustomView(View view) {
+            mUpperContextView.setCustomView(view);
+            mCustomView = new WeakReference<View>(view);
+        }
+
+        @Override
+        public void setSubtitle(CharSequence subtitle) {
+            mUpperContextView.setSubtitle(subtitle);
+        }
+
+        @Override
+        public void setTitle(CharSequence title) {
+            mUpperContextView.setTitle(title);
+        }
+
+        @Override
+        public void setTitle(int resId) {
+            setTitle(mActivity.getString(resId));
+        }
+
+        @Override
+        public void setSubtitle(int resId) {
+            setSubtitle(mActivity.getString(resId));
+        }
+
+        @Override
+        public CharSequence getTitle() {
+            return mUpperContextView.getTitle();
+        }
+
+        @Override
+        public CharSequence getSubtitle() {
+            return mUpperContextView.getSubtitle();
+        }
+        
+        @Override
+        public View getCustomView() {
+            return mCustomView != null ? mCustomView.get() : null;
+        }
+
+        public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) {
+            return mCallback.onActionItemClicked(this, item);
+        }
+
+        public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
+        }
+
+        public boolean onSubMenuSelected(SubMenuBuilder subMenu) {
+            if (!subMenu.hasVisibleItems()) {
+                return true;
+            }
+
+            new MenuPopupHelper(mActivity, subMenu).show();
+            return true;
+        }
+
+        public void onCloseSubMenu(SubMenuBuilder menu) {
+        }
+
+        public void onMenuModeChange(MenuBuilder menu) {
+        }
+    }
+
+    /**
+     * @hide
+     */
+    public class TabImpl extends ActionBar.Tab {
+        private Fragment mFragment;
+        private Drawable mIcon;
+        private CharSequence mText;
+        private int mPosition;
+
+        @Override
+        public Fragment getFragment() {
+            return mFragment;
+        }
+
+        @Override
+        public Drawable getIcon() {
+            return mIcon;
+        }
+
+        @Override
+        public int getPosition() {
+            return mPosition;
+        }
+
+        public void setPosition(int position) {
+            mPosition = position;
+        }
+
+        @Override
+        public CharSequence getText() {
+            return mText;
+        }
+
+        @Override
+        public void setFragment(Fragment fragment) {
+            mFragment = fragment;
+        }
+
+        @Override
+        public void setIcon(Drawable icon) {
+            mIcon = icon;
+        }
+
+        @Override
+        public void setText(CharSequence text) {
+            mText = text;
+        }
+
+        @Override
+        public void select() {
+            selectTab(this);
+        }
+    }
+}
diff --git a/core/java/com/android/internal/appwidget/IAppWidgetHost.aidl b/core/java/com/android/internal/appwidget/IAppWidgetHost.aidl
index 2ed4773..f0920d1 100644
--- a/core/java/com/android/internal/appwidget/IAppWidgetHost.aidl
+++ b/core/java/com/android/internal/appwidget/IAppWidgetHost.aidl
@@ -24,5 +24,6 @@
 oneway interface IAppWidgetHost {
     void updateAppWidget(int appWidgetId, in RemoteViews views);
     void providerChanged(int appWidgetId, in AppWidgetProviderInfo info);
+    void viewDataChanged(int appWidgetId, in RemoteViews views, int viewId);
 }
 
diff --git a/core/java/com/android/internal/appwidget/IAppWidgetService.aidl b/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
index 496aa1a..af75d5b 100644
--- a/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
+++ b/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
@@ -41,6 +41,7 @@
     //
     void updateAppWidgetIds(in int[] appWidgetIds, in RemoteViews views);
     void updateAppWidgetProvider(in ComponentName provider, in RemoteViews views);
+    void notifyAppWidgetViewDataChanged(in int[] appWidgetIds, in RemoteViews views, int viewId);
     List<AppWidgetProviderInfo> getInstalledProviders();
     AppWidgetProviderInfo getAppWidgetInfo(int appWidgetId);
     void bindAppWidgetId(int appWidgetId, in ComponentName provider);
diff --git a/core/java/com/android/internal/database/SortCursor.java b/core/java/com/android/internal/database/SortCursor.java
index 99410bc..0025512 100644
--- a/core/java/com/android/internal/database/SortCursor.java
+++ b/core/java/com/android/internal/database/SortCursor.java
@@ -182,24 +182,6 @@
     }
 
     @Override
-    public boolean deleteRow()
-    {
-        return mCursor.deleteRow();
-    }
-
-    @Override
-    public boolean commitUpdates() {
-        int length = mCursors.length;
-        for (int i = 0 ; i < length ; i++) {
-            if (mCursors[i] != null) {
-                mCursors[i].commitUpdates();
-            }
-        }
-        onChange(true);
-        return true;
-    }
-
-    @Override
     public String getString(int column)
     {
         return mCursor.getString(column);
@@ -236,6 +218,11 @@
     }
 
     @Override
+    public int getType(int column) {
+        return mCursor.getType(column);
+    }
+
+    @Override
     public boolean isNull(int column)
     {
         return mCursor.isNull(column);
diff --git a/core/java/com/android/internal/os/SamplingProfilerIntegration.java b/core/java/com/android/internal/os/SamplingProfilerIntegration.java
index 127fb23..38362c1 100644
--- a/core/java/com/android/internal/os/SamplingProfilerIntegration.java
+++ b/core/java/com/android/internal/os/SamplingProfilerIntegration.java
@@ -16,14 +16,15 @@
 
 package com.android.internal.os;
 
+import android.content.pm.PackageInfo;
 import dalvik.system.SamplingProfiler;
 
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
-import java.io.FileNotFoundException;
 import java.util.concurrent.Executor;
 import java.util.concurrent.Executors;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 import android.util.Log;
 import android.os.*;
@@ -35,15 +36,27 @@
 
     private static final String TAG = "SamplingProfilerIntegration";
 
+    public static final String SNAPSHOT_DIR = "/data/snapshots";
+
     private static final boolean enabled;
     private static final Executor snapshotWriter;
+    private static final int samplingProfilerHz;
+    
+    /** Whether or not we've created the snapshots dir. */
+    private static boolean dirMade = false;
+
+    /** Whether or not a snapshot is being persisted. */
+    private static final AtomicBoolean pending = new AtomicBoolean(false);
+
     static {
-        enabled = "1".equals(SystemProperties.get("persist.sampling_profiler"));
-        if (enabled) {
+        samplingProfilerHz = SystemProperties.getInt("persist.sys.profiler_hz", 0);
+        if (samplingProfilerHz > 0) {
             snapshotWriter = Executors.newSingleThreadExecutor();
-            Log.i(TAG, "Profiler is enabled.");
+            enabled = true;
+            Log.i(TAG, "Profiler is enabled. Sampling Profiler Hz: " + samplingProfilerHz);
         } else {
             snapshotWriter = null;
+            enabled = false;
             Log.i(TAG, "Profiler is disabled.");
         }
     }
@@ -60,46 +73,45 @@
      */
     public static void start() {
         if (!enabled) return;
-        SamplingProfiler.getInstance().start(10);
+        SamplingProfiler.getInstance().start(samplingProfilerHz);
     }
 
-    /** Whether or not we've created the snapshots dir. */
-    static boolean dirMade = false;
-
-    /** Whether or not a snapshot is being persisted. */
-    static volatile boolean pending;
-
     /**
-     * Writes a snapshot to the SD card if profiling is enabled.
+     * Writes a snapshot if profiling is enabled.
      */
-    public static void writeSnapshot(final String name) {
+    public static void writeSnapshot(final String processName, final PackageInfo packageInfo) {
         if (!enabled) return;
 
         /*
-         * If we're already writing a snapshot, don't bother enqueing another
+         * If we're already writing a snapshot, don't bother enqueueing another
          * request right now. This will reduce the number of individual
          * snapshots and in turn the total amount of memory consumed (one big
          * snapshot is smaller than N subset snapshots).
          */
-        if (!pending) {
-            pending = true;
+        if (pending.compareAndSet(false, true)) {
             snapshotWriter.execute(new Runnable() {
                 public void run() {
-                    String dir =
-                        Environment.getExternalStorageDirectory().getPath() + "/snapshots";
                     if (!dirMade) {
-                        new File(dir).mkdirs();
-                        if (new File(dir).isDirectory()) {
+                        File dir = new File(SNAPSHOT_DIR);
+                        dir.mkdirs();
+                        // the directory needs to be writable to anybody
+                        dir.setWritable(true, false);
+                        // the directory needs to be executable to anybody
+                        // don't know why yet, but mode 723 would work, while
+                        // mode 722 throws FileNotFoundExecption at line 151
+                        dir.setExecutable(true, false);
+                        if (new File(SNAPSHOT_DIR).isDirectory()) {
                             dirMade = true;
                         } else {
-                            Log.w(TAG, "Creation of " + dir + " failed.");
+                            Log.w(TAG, "Creation of " + SNAPSHOT_DIR + " failed.");
+                            pending.set(false);
                             return;
                         }
                     }
                     try {
-                        writeSnapshot(dir, name);
+                        writeSnapshot(SNAPSHOT_DIR, processName, packageInfo);
                     } finally {
-                        pending = false;
+                        pending.set(false);
                     }
                 }
             });
@@ -111,13 +123,13 @@
      */
     public static void writeZygoteSnapshot() {
         if (!enabled) return;
-
-        String dir = "/data/zygote/snapshots";
-        new File(dir).mkdirs();
-        writeSnapshot(dir, "zygote");
+        writeSnapshot("zygote", null);
     }
 
-    private static void writeSnapshot(String dir, String name) {
+    /**
+     * pass in PackageInfo to retrieve various values for snapshot header
+     */
+    private static void writeSnapshot(String dir, String processName, PackageInfo packageInfo) {
         byte[] snapshot = SamplingProfiler.getInstance().snapshot();
         if (snapshot == null) {
             return;
@@ -129,39 +141,54 @@
          * we capture two snapshots in rapid succession.
          */
         long start = System.currentTimeMillis();
-        String path = dir + "/" + name.replace(':', '.') + "-" +
-                + System.currentTimeMillis() + ".snapshot";
+        String name = processName.replaceAll(":", ".");
+        String path = dir + "/" + name + "-" +System.currentTimeMillis() + ".snapshot";
+        FileOutputStream out = null;
         try {
-            // Try to open the file a few times. The SD card may not be mounted.
-            FileOutputStream out;
-            int count = 0;
-            while (true) {
-                try {
-                    out = new FileOutputStream(path);
-                    break;
-                } catch (FileNotFoundException e) {
-                    if (++count > 3) {
-                        Log.e(TAG, "Could not open " + path + ".");
-                        return;
-                    }
-
-                    // Sleep for a bit and then try again.
-                    try {
-                        Thread.sleep(2500);
-                    } catch (InterruptedException e1) { /* ignore */ }
-                }
-            }
-
-            try {
-                out.write(snapshot);
-            } finally {
-                out.close();
-            }
-            long elapsed = System.currentTimeMillis() - start;
-            Log.i(TAG, "Wrote snapshot for " + name
-                    + " in " + elapsed + "ms.");
+            out = new FileOutputStream(path);
+            generateSnapshotHeader(name, packageInfo, out);
+            out.write(snapshot);
         } catch (IOException e) {
             Log.e(TAG, "Error writing snapshot.", e);
+        } finally {
+            try {
+                if(out != null) {
+                    out.close();
+                }
+            } catch (IOException ex) {
+                // let it go.
+            }
         }
+        // set file readable to the world so that SamplingProfilerService
+        // can put it to dropbox
+        new File(path).setReadable(true, false);
+
+        long elapsed = System.currentTimeMillis() - start;
+        Log.i(TAG, "Wrote snapshot for " + name + " in " + elapsed + "ms.");
+    }
+
+    /**
+     * generate header for snapshots, with the following format (like http header):
+     *
+     * Version: <version number of profiler>\n
+     * Process: <process name>\n
+     * Package: <package name, if exists>\n
+     * Package-Version: <version number of the package, if exists>\n
+     * Build: <fingerprint>\n
+     * \n
+     * <the actual snapshot content begins here...>
+     */
+    private static void generateSnapshotHeader(String processName, PackageInfo packageInfo,
+            FileOutputStream out) throws IOException {
+        // profiler version
+        out.write("Version: 1\n".getBytes());
+        out.write(("Process: " + processName + "\n").getBytes());
+        if(packageInfo != null) {
+            out.write(("Package: " + packageInfo.packageName + "\n").getBytes());
+            out.write(("Package-Version: " + packageInfo.versionCode + "\n").getBytes());
+        }
+        out.write(("Build: " + Build.FINGERPRINT + "\n").getBytes());
+        // single blank line means the end of snapshot header.
+        out.write("\n".getBytes());
     }
 }
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index 9fcd3f5..f8a77a1 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -523,7 +523,7 @@
         String args[] = {
             "--setuid=1000",
             "--setgid=1000",
-            "--setgroups=1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,3001,3002,3003",
+            "--setgroups=1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1018,3001,3002,3003",
             "--capabilities=130104352,130104352",
             "--runtime-init",
             "--nice-name=system_server",
diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
index 852630d..2307669 100644
--- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
@@ -40,4 +40,5 @@
     void onNotificationError(String pkg, String tag, int id,
             int uid, int initialPid, String message);
     void onClearAllNotifications();
+    void onNotificationClear(String pkg, String tag, int id);
 }
diff --git a/core/java/com/android/internal/util/HierarchicalStateMachine.java b/core/java/com/android/internal/util/HierarchicalStateMachine.java
index c599d68..7138b5c 100644
--- a/core/java/com/android/internal/util/HierarchicalStateMachine.java
+++ b/core/java/com/android/internal/util/HierarchicalStateMachine.java
@@ -1197,6 +1197,35 @@
     }
 
     /**
+     * Get a message and set Message.target = this,
+     * what, arg1 and arg2
+     *
+     * @param what  is assigned to Message.what
+     * @param arg1  is assigned to Message.arg1
+     * @param arg2  is assigned to Message.arg2
+     * @return  A Message object from the global pool.
+     */
+    public final Message obtainMessage(int what, int arg1, int arg2)
+    {
+        return Message.obtain(mHsmHandler, what, arg1, arg2);
+    }
+
+    /**
+     * Get a message and set Message.target = this,
+     * what, arg1, arg2 and obj
+     *
+     * @param what  is assigned to Message.what
+     * @param arg1  is assigned to Message.arg1
+     * @param arg2  is assigned to Message.arg2
+     * @param obj is assigned to Message.obj
+     * @return  A Message object from the global pool.
+     */
+    public final Message obtainMessage(int what, int arg1, int arg2, Object obj)
+    {
+        return Message.obtain(mHsmHandler, what, arg1, arg2, obj);
+    }
+
+    /**
      * Enqueue a message to this state machine.
      */
     public final void sendMessage(int what) {
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/view/StandaloneActionMode.java b/core/java/com/android/internal/view/StandaloneActionMode.java
new file mode 100644
index 0000000..e6d6ba0
--- /dev/null
+++ b/core/java/com/android/internal/view/StandaloneActionMode.java
@@ -0,0 +1,139 @@
+/*
+ * 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.internal.view;
+
+import com.android.internal.view.menu.MenuBuilder;
+import com.android.internal.view.menu.MenuPopupHelper;
+import com.android.internal.view.menu.SubMenuBuilder;
+import com.android.internal.widget.ActionBarContextView;
+
+import android.content.Context;
+import android.view.ActionMode;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+
+import java.lang.ref.WeakReference;
+
+public class StandaloneActionMode extends ActionMode implements MenuBuilder.Callback {
+    private Context mContext;
+    private ActionBarContextView mContextView;
+    private ActionMode.Callback mCallback;
+    private WeakReference<View> mCustomView;
+    private boolean mFinished;
+
+    private MenuBuilder mMenu;
+
+    public StandaloneActionMode(Context context, ActionBarContextView view,
+            ActionMode.Callback callback) {
+        mContext = context;
+        mContextView = view;
+        mCallback = callback;
+
+        mMenu = new MenuBuilder(context);
+        mMenu.setCallback(this);
+    }
+
+    @Override
+    public void setTitle(CharSequence title) {
+        mContextView.setTitle(title);
+    }
+
+    @Override
+    public void setSubtitle(CharSequence subtitle) {
+        mContextView.setSubtitle(subtitle);
+    }
+
+    @Override
+    public void setTitle(int resId) {
+        setTitle(mContext.getString(resId));
+    }
+
+    @Override
+    public void setSubtitle(int resId) {
+        setSubtitle(mContext.getString(resId));
+    }
+
+    @Override
+    public void setCustomView(View view) {
+        mContextView.setCustomView(view);
+        mCustomView = view != null ? new WeakReference<View>(view) : null;
+    }
+
+    @Override
+    public void invalidate() {
+        mCallback.onPrepareActionMode(this, mMenu);
+    }
+
+    @Override
+    public void finish() {
+        if (mFinished) {
+            return;
+        }
+        mFinished = true;
+
+        mCallback.onDestroyActionMode(this);
+        mContextView.setVisibility(View.GONE);
+    }
+
+    @Override
+    public Menu getMenu() {
+        return mMenu;
+    }
+
+    @Override
+    public CharSequence getTitle() {
+        return mContextView.getTitle();
+    }
+
+    @Override
+    public CharSequence getSubtitle() {
+        return mContextView.getSubtitle();
+    }
+
+    @Override
+    public View getCustomView() {
+        return mCustomView != null ? mCustomView.get() : null;
+    }
+
+    @Override
+    public MenuInflater getMenuInflater() {
+        return new MenuInflater(mContext);
+    }
+
+    public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) {
+        return mCallback.onActionItemClicked(this, item);
+    }
+
+    public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
+    }
+
+    public boolean onSubMenuSelected(SubMenuBuilder subMenu) {
+        if (!subMenu.hasVisibleItems()) {
+            return true;
+        }
+
+        new MenuPopupHelper(mContext, subMenu).show();
+        return true;
+    }
+
+    public void onCloseSubMenu(SubMenuBuilder menu) {
+    }
+
+    public void onMenuModeChange(MenuBuilder menu) {
+    }
+}
diff --git a/core/java/com/android/internal/view/menu/ActionMenu.java b/core/java/com/android/internal/view/menu/ActionMenu.java
new file mode 100644
index 0000000..3d44ebc
--- /dev/null
+++ b/core/java/com/android/internal/view/menu/ActionMenu.java
@@ -0,0 +1,263 @@
+/*
+ * 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.internal.view.menu;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.view.KeyEvent;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.SubMenu;
+
+/**
+ * @hide
+ */
+public class ActionMenu implements Menu {
+    private Context mContext;
+    
+    private boolean mIsQwerty;
+    
+    private ArrayList<ActionMenuItem> mItems;
+    
+    public ActionMenu(Context context) {
+        mContext = context;
+        mItems = new ArrayList<ActionMenuItem>();
+    }
+    
+    public Context getContext() {
+        return mContext;
+    }
+
+    public MenuItem add(CharSequence title) {
+        return add(0, 0, 0, title);
+    }
+
+    public MenuItem add(int titleRes) {
+        return add(0, 0, 0, titleRes);
+    }
+
+    public MenuItem add(int groupId, int itemId, int order, int titleRes) {
+        return add(groupId, itemId, order, mContext.getResources().getString(titleRes));
+    }
+    
+    public MenuItem add(int groupId, int itemId, int order, CharSequence title) {
+        ActionMenuItem item = new ActionMenuItem(getContext(),
+                groupId, itemId, 0, order, title);
+        mItems.add(order, item);
+        return item;
+    }
+
+    public int addIntentOptions(int groupId, int itemId, int order,
+            ComponentName caller, Intent[] specifics, Intent intent, int flags,
+            MenuItem[] outSpecificItems) {
+        PackageManager pm = mContext.getPackageManager();
+        final List<ResolveInfo> lri =
+                pm.queryIntentActivityOptions(caller, specifics, intent, 0);
+        final int N = lri != null ? lri.size() : 0;
+
+        if ((flags & FLAG_APPEND_TO_GROUP) == 0) {
+            removeGroup(groupId);
+        }
+
+        for (int i=0; i<N; i++) {
+            final ResolveInfo ri = lri.get(i);
+            Intent rintent = new Intent(
+                ri.specificIndex < 0 ? intent : specifics[ri.specificIndex]);
+            rintent.setComponent(new ComponentName(
+                    ri.activityInfo.applicationInfo.packageName,
+                    ri.activityInfo.name));
+            final MenuItem item = add(groupId, itemId, order, ri.loadLabel(pm))
+                    .setIcon(ri.loadIcon(pm))
+                    .setIntent(rintent);
+            if (outSpecificItems != null && ri.specificIndex >= 0) {
+                outSpecificItems[ri.specificIndex] = item;
+            }
+        }
+
+        return N;
+    }
+
+    public SubMenu addSubMenu(CharSequence title) {
+        // TODO Implement submenus
+        return null;
+    }
+
+    public SubMenu addSubMenu(int titleRes) {
+        // TODO Implement submenus
+        return null;
+    }
+
+    public SubMenu addSubMenu(int groupId, int itemId, int order,
+            CharSequence title) {
+        // TODO Implement submenus
+        return null;
+    }
+
+    public SubMenu addSubMenu(int groupId, int itemId, int order, int titleRes) {
+        // TODO Implement submenus
+        return null;
+    }
+
+    public void clear() {
+        mItems.clear();
+    }
+
+    public void close() {
+    }
+    
+    private int findItemIndex(int id) {
+        final ArrayList<ActionMenuItem> items = mItems;
+        final int itemCount = items.size();
+        for (int i = 0; i < itemCount; i++) {
+            if (items.get(i).getItemId() == id) {
+                return i;
+            }
+        }
+        
+        return -1;
+    }
+
+    public MenuItem findItem(int id) {
+        return mItems.get(findItemIndex(id));
+    }
+
+    public MenuItem getItem(int index) {
+        return mItems.get(index);
+    }
+
+    public boolean hasVisibleItems() {
+        final ArrayList<ActionMenuItem> items = mItems;
+        final int itemCount = items.size();
+        
+        for (int i = 0; i < itemCount; i++) {
+            if (items.get(i).isVisible()) {
+                return true;
+            }
+        }
+        
+        return false;
+    }
+    
+    private ActionMenuItem findItemWithShortcut(int keyCode, KeyEvent event) {
+        // TODO Make this smarter.
+        final boolean qwerty = mIsQwerty;
+        final ArrayList<ActionMenuItem> items = mItems;
+        final int itemCount = items.size();
+        
+        for (int i = 0; i < itemCount; i++) {
+            ActionMenuItem item = items.get(i);
+            final char shortcut = qwerty ? item.getAlphabeticShortcut() :
+                    item.getNumericShortcut();
+            if (keyCode == shortcut) {
+                return item;
+            }
+        }
+        return null;
+    }
+
+    public boolean isShortcutKey(int keyCode, KeyEvent event) {
+        return findItemWithShortcut(keyCode, event) != null;
+    }
+
+    public boolean performIdentifierAction(int id, int flags) {
+        final int index = findItemIndex(id);
+        if (index < 0) {
+            return false;
+        }
+
+        return mItems.get(index).invoke();
+    }
+
+    public boolean performShortcut(int keyCode, KeyEvent event, int flags) {
+        ActionMenuItem item = findItemWithShortcut(keyCode, event);
+        if (item == null) {
+            return false;
+        }
+        
+        return item.invoke();
+    }
+
+    public void removeGroup(int groupId) {
+        final ArrayList<ActionMenuItem> items = mItems;
+        int itemCount = items.size();
+        int i = 0;
+        while (i < itemCount) {
+            if (items.get(i).getGroupId() == groupId) {
+                items.remove(i);
+                itemCount--;
+            } else {
+                i++;
+            }
+        }
+    }
+
+    public void removeItem(int id) {
+        mItems.remove(findItemIndex(id));
+    }
+
+    public void setGroupCheckable(int group, boolean checkable,
+            boolean exclusive) {
+        final ArrayList<ActionMenuItem> items = mItems;
+        final int itemCount = items.size();
+        
+        for (int i = 0; i < itemCount; i++) {
+            ActionMenuItem item = items.get(i);
+            if (item.getGroupId() == group) {
+                item.setCheckable(checkable);
+                item.setExclusiveCheckable(exclusive);
+            }
+        }
+    }
+
+    public void setGroupEnabled(int group, boolean enabled) {
+        final ArrayList<ActionMenuItem> items = mItems;
+        final int itemCount = items.size();
+        
+        for (int i = 0; i < itemCount; i++) {
+            ActionMenuItem item = items.get(i);
+            if (item.getGroupId() == group) {
+                item.setEnabled(enabled);
+            }
+        }
+    }
+
+    public void setGroupVisible(int group, boolean visible) {
+        final ArrayList<ActionMenuItem> items = mItems;
+        final int itemCount = items.size();
+        
+        for (int i = 0; i < itemCount; i++) {
+            ActionMenuItem item = items.get(i);
+            if (item.getGroupId() == group) {
+                item.setVisible(visible);
+            }
+        }
+    }
+
+    public void setQwertyMode(boolean isQwerty) {
+        mIsQwerty = isQwerty;
+    }
+
+    public int size() {
+        return mItems.size();
+    }
+}
diff --git a/core/java/com/android/internal/view/menu/ActionMenuItem.java b/core/java/com/android/internal/view/menu/ActionMenuItem.java
new file mode 100644
index 0000000..035875a
--- /dev/null
+++ b/core/java/com/android/internal/view/menu/ActionMenuItem.java
@@ -0,0 +1,225 @@
+/*
+ * 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.internal.view.menu;
+
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.drawable.Drawable;
+import android.view.MenuItem;
+import android.view.SubMenu;
+import android.view.ContextMenu.ContextMenuInfo;
+
+/**
+ * @hide
+ */
+public class ActionMenuItem implements MenuItem {
+    private final int mId;
+    private final int mGroup;
+    private final int mCategoryOrder;
+    private final int mOrdering;
+    
+    private CharSequence mTitle;
+    private CharSequence mTitleCondensed;
+    private Intent mIntent;
+    private char mShortcutNumericChar;
+    private char mShortcutAlphabeticChar;
+    
+    private Drawable mIconDrawable;
+    private int mIconResId = NO_ICON;
+    
+    private Context mContext;
+    
+    private MenuItem.OnMenuItemClickListener mClickListener;
+    
+    private static final int NO_ICON = 0;
+    
+    private int mFlags = ENABLED;
+    private static final int CHECKABLE      = 0x00000001;
+    private static final int CHECKED        = 0x00000002;
+    private static final int EXCLUSIVE      = 0x00000004;
+    private static final int HIDDEN         = 0x00000008;
+    private static final int ENABLED        = 0x00000010;
+    
+    public ActionMenuItem(Context context, int group, int id, int categoryOrder, int ordering,
+            CharSequence title) {
+        mContext = context;
+        mId = id;
+        mGroup = group;
+        mCategoryOrder = categoryOrder;
+        mOrdering = ordering;
+        mTitle = title;
+    }
+    
+    public char getAlphabeticShortcut() {
+        return mShortcutAlphabeticChar;
+    }
+
+    public int getGroupId() {
+        return mGroup;
+    }
+
+    public Drawable getIcon() {
+        return mIconDrawable;
+    }
+
+    public Intent getIntent() {
+        return mIntent;
+    }
+
+    public int getItemId() {
+        return mId;
+    }
+
+    public ContextMenuInfo getMenuInfo() {
+        return null;
+    }
+
+    public char getNumericShortcut() {
+        return mShortcutNumericChar;
+    }
+
+    public int getOrder() {
+        return mOrdering;
+    }
+
+    public SubMenu getSubMenu() {
+        return null;
+    }
+
+    public CharSequence getTitle() {
+        return mTitle;
+    }
+
+    public CharSequence getTitleCondensed() {
+        return mTitleCondensed;
+    }
+
+    public boolean hasSubMenu() {
+        return false;
+    }
+
+    public boolean isCheckable() {
+        return (mFlags & CHECKABLE) != 0; 
+    }
+
+    public boolean isChecked() {
+        return (mFlags & CHECKED) != 0;
+    }
+
+    public boolean isEnabled() {
+        return (mFlags & ENABLED) != 0;
+    }
+
+    public boolean isVisible() {
+        return (mFlags & HIDDEN) == 0;
+    }
+
+    public MenuItem setAlphabeticShortcut(char alphaChar) {
+        mShortcutAlphabeticChar = alphaChar;
+        return this;
+    }
+
+    public MenuItem setCheckable(boolean checkable) {
+        mFlags = (mFlags & ~CHECKABLE) | (checkable ? CHECKABLE : 0);
+        return this;
+    }
+    
+    public ActionMenuItem setExclusiveCheckable(boolean exclusive) {
+        mFlags = (mFlags & ~EXCLUSIVE) | (exclusive ? EXCLUSIVE : 0);
+        return this;
+    }
+
+    public MenuItem setChecked(boolean checked) {
+        mFlags = (mFlags & ~CHECKED) | (checked ? CHECKED : 0);
+        return this;
+    }
+
+    public MenuItem setEnabled(boolean enabled) {
+        mFlags = (mFlags & ~ENABLED) | (enabled ? ENABLED : 0);
+        return this;
+    }
+
+    public MenuItem setIcon(Drawable icon) {
+        mIconDrawable = icon;
+        mIconResId = NO_ICON;
+        return this;
+    }
+
+    public MenuItem setIcon(int iconRes) {
+        mIconResId = iconRes;
+        mIconDrawable = mContext.getResources().getDrawable(iconRes);
+        return this;
+    }
+
+    public MenuItem setIntent(Intent intent) {
+        mIntent = intent;
+        return this;
+    }
+
+    public MenuItem setNumericShortcut(char numericChar) {
+        mShortcutNumericChar = numericChar;
+        return this;
+    }
+
+    public MenuItem setOnMenuItemClickListener(OnMenuItemClickListener menuItemClickListener) {
+        mClickListener = menuItemClickListener;
+        return this;
+    }
+
+    public MenuItem setShortcut(char numericChar, char alphaChar) {
+        mShortcutNumericChar = numericChar;
+        mShortcutAlphabeticChar = alphaChar;
+        return this;
+    }
+
+    public MenuItem setTitle(CharSequence title) {
+        mTitle = title;
+        return this;
+    }
+
+    public MenuItem setTitle(int title) {
+        mTitle = mContext.getResources().getString(title);
+        return this;
+    }
+
+    public MenuItem setTitleCondensed(CharSequence title) {
+        mTitleCondensed = title;
+        return this;
+    }
+
+    public MenuItem setVisible(boolean visible) {
+        mFlags = (mFlags & HIDDEN) | (visible ? 0 : HIDDEN);
+        return this;
+    }
+
+    public boolean invoke() {
+        if (mClickListener != null && mClickListener.onMenuItemClick(this)) {
+            return true;
+        }
+        
+        if (mIntent != null) {
+            mContext.startActivity(mIntent);
+            return true;
+        }
+        
+        return false;
+    }
+    
+    public void setShowAsAction(int show) {
+        // Do nothing. ActionMenuItems always show as action buttons.
+    }
+}
diff --git a/core/java/com/android/internal/view/menu/ActionMenuItemView.java b/core/java/com/android/internal/view/menu/ActionMenuItemView.java
new file mode 100644
index 0000000..a221faf
--- /dev/null
+++ b/core/java/com/android/internal/view/menu/ActionMenuItemView.java
@@ -0,0 +1,129 @@
+/*
+ * 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.internal.view.menu;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.Button;
+import android.widget.FrameLayout;
+import android.widget.ImageButton;
+
+/**
+ * @hide
+ */
+public class ActionMenuItemView extends FrameLayout
+        implements MenuView.ItemView, View.OnClickListener {
+    private static final String TAG = "ActionMenuItemView";
+
+    private MenuItemImpl mItemData;
+    private CharSequence mTitle;
+    private MenuBuilder.ItemInvoker mItemInvoker;
+
+    private ImageButton mImageButton;
+    private Button mTextButton;
+
+    public ActionMenuItemView(Context context) {
+        this(context, null);
+    }
+
+    public ActionMenuItemView(Context context, AttributeSet attrs) {
+        this(context, attrs, com.android.internal.R.attr.actionButtonStyle);
+    }
+
+    public ActionMenuItemView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    @Override
+    public void onFinishInflate() {
+        mImageButton = (ImageButton) findViewById(com.android.internal.R.id.imageButton);
+        mTextButton = (Button) findViewById(com.android.internal.R.id.textButton);
+        mImageButton.setOnClickListener(this);
+        mTextButton.setOnClickListener(this);
+    }
+
+    public MenuItemImpl getItemData() {
+        return mItemData;
+    }
+
+    public void initialize(MenuItemImpl itemData, int menuType) {
+        mItemData = itemData;
+
+        setClickable(true);
+        setFocusable(true);
+        setIcon(itemData.getIcon());
+        setTitle(itemData.getTitle()); // Title only takes effect if there is no icon
+        setId(itemData.getItemId());
+
+        setVisibility(itemData.isVisible() ? View.VISIBLE : View.GONE);
+        setEnabled(itemData.isEnabled());
+    }
+
+    public void onClick(View v) {
+        if (mItemInvoker != null) {
+            mItemInvoker.invokeItem(mItemData);
+        }
+    }
+
+    public void setItemInvoker(MenuBuilder.ItemInvoker invoker) {
+        mItemInvoker = invoker;
+    }
+
+    public boolean prefersCondensedTitle() {
+        return false;
+    }
+
+    public void setCheckable(boolean checkable) {
+        // TODO Support checkable action items
+    }
+
+    public void setChecked(boolean checked) {
+        // TODO Support checkable action items
+    }
+
+    public void setIcon(Drawable icon) {
+        mImageButton.setImageDrawable(icon);
+        if (icon != null) {
+            mImageButton.setVisibility(VISIBLE);
+            mTextButton.setVisibility(GONE);
+        } else {
+            mImageButton.setVisibility(GONE);
+        }
+    }
+
+    public void setShortcut(boolean showShortcut, char shortcutKey) {
+        // Action buttons don't show text for shortcut keys.
+    }
+
+    public void setTitle(CharSequence title) {
+        mTitle = title;
+
+        // populate accessibility description with title
+        setContentDescription(title);
+
+        if (mImageButton.getDrawable() == null) {
+            mTextButton.setText(mTitle);
+            mTextButton.setVisibility(VISIBLE);
+        }
+    }
+
+    public boolean showsIcon() {
+        return true;
+    }
+}
diff --git a/core/java/com/android/internal/view/menu/ActionMenuView.java b/core/java/com/android/internal/view/menu/ActionMenuView.java
new file mode 100644
index 0000000..e281536
--- /dev/null
+++ b/core/java/com/android/internal/view/menu/ActionMenuView.java
@@ -0,0 +1,181 @@
+/*
+ * 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.internal.view.menu;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+import android.view.ViewGroup;
+import android.widget.ImageButton;
+import android.widget.LinearLayout;
+
+import java.util.ArrayList;
+
+/**
+ * @hide
+ */
+public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvoker, MenuView {
+    private static final String TAG = "ActionMenuView";
+    
+    private MenuBuilder mMenu;
+
+    private int mItemPadding;
+    private int mItemMargin;
+    private int mMaxItems;
+    private boolean mReserveOverflow;
+    private OverflowMenuButton mOverflowButton;
+    
+    public ActionMenuView(Context context) {
+        this(context, null);
+    }
+    
+    public ActionMenuView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        
+        TypedArray a = context.obtainStyledAttributes(attrs,
+                com.android.internal.R.styleable.Theme);
+        mItemPadding = a.getDimensionPixelOffset(
+                com.android.internal.R.styleable.Theme_actionButtonPadding, 0);
+        mItemMargin = mItemPadding / 2;
+        a.recycle();
+        
+        final Resources res = getResources();
+        final int size = res.getDimensionPixelSize(com.android.internal.R.dimen.action_icon_size);
+        final int spaceAvailable = res.getDisplayMetrics().widthPixels / 2;
+        final int itemSpace = size + mItemPadding;
+        
+        mMaxItems = spaceAvailable / (itemSpace > 0 ? itemSpace : 1);
+
+        // TODO There has to be a better way to indicate that we don't have a hard menu key.
+        final int screen = res.getConfiguration().screenLayout;
+        mReserveOverflow = (screen & Configuration.SCREENLAYOUT_SIZE_MASK) ==
+                Configuration.SCREENLAYOUT_SIZE_XLARGE;
+    }
+
+    public boolean isOverflowReserved() {
+        return mReserveOverflow;
+    }
+    
+    public void setOverflowReserved(boolean reserveOverflow) {
+        mReserveOverflow = reserveOverflow;
+    }
+
+    @Override
+    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
+        if (p instanceof LayoutParams) {
+            LayoutParams lp = (LayoutParams) p;
+            return lp.leftMargin == mItemMargin && lp.rightMargin == mItemMargin &&
+                    lp.width == LayoutParams.WRAP_CONTENT && lp.height == LayoutParams.WRAP_CONTENT;
+        }
+        return false;
+    }
+    
+    @Override
+    protected LayoutParams generateDefaultLayoutParams() {
+        LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT,
+                LayoutParams.WRAP_CONTENT);
+        params.leftMargin = mItemMargin;
+        params.rightMargin = mItemMargin;
+        return params;
+    }
+    
+    @Override
+    protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
+        return generateDefaultLayoutParams();
+    }
+    
+    public int getItemMargin() {
+        return mItemMargin;
+    }
+
+    public boolean invokeItem(MenuItemImpl item) {
+        return mMenu.performItemAction(item, 0);
+    }
+
+    public int getWindowAnimations() {
+        return 0;
+    }
+
+    public void initialize(MenuBuilder menu, int menuType) {
+        menu.setMaxActionItems(mMaxItems);
+        mMenu = menu;
+        updateChildren(true);
+    }
+
+    public void updateChildren(boolean cleared) {
+        final boolean reserveOverflow = mReserveOverflow;
+        removeAllViews();
+        
+        final ArrayList<MenuItemImpl> itemsToShow = mMenu.getActionItems(reserveOverflow);
+        final int itemCount = itemsToShow.size();
+        
+        for (int i = 0; i < itemCount; i++) {
+            final MenuItemImpl itemData = itemsToShow.get(i);
+            addItemView((ActionMenuItemView) itemData.getItemView(MenuBuilder.TYPE_ACTION_BUTTON,
+                    this));
+        }
+
+        if (reserveOverflow) {
+            if (mMenu.getNonActionItems(true).size() > 0) {
+                OverflowMenuButton button = new OverflowMenuButton(mContext);
+                addView(button);
+                mOverflowButton = button;
+            } else {
+                mOverflowButton = null;
+            }
+        }
+    }
+
+    public boolean showOverflowMenu() {
+        if (mOverflowButton != null) {
+            MenuPopupHelper popup = new MenuPopupHelper(getContext(), mMenu, mOverflowButton, true);
+            popup.show();
+            return true;
+        }
+        return false;
+    }
+
+    private void addItemView(ActionMenuItemView view) {
+        view.setItemInvoker(this);
+        addView(view);
+    }
+
+    private class OverflowMenuButton extends ImageButton {
+        public OverflowMenuButton(Context context) {
+            super(context, null, com.android.internal.R.attr.actionButtonStyle);
+
+            final Resources res = context.getResources();
+            setClickable(true);
+            setFocusable(true);
+            // TODO setTitle() to a localized string for accessibility
+            setImageDrawable(res.getDrawable(com.android.internal.R.drawable.ic_menu_more));
+            setVisibility(VISIBLE);
+            setEnabled(true);
+        }
+
+        @Override
+        public boolean performClick() {
+            if (super.performClick()) {
+                return true;
+            }
+
+            showOverflowMenu();
+            return true;
+        }
+    }
+}
diff --git a/core/java/com/android/internal/view/menu/IconMenuView.java b/core/java/com/android/internal/view/menu/IconMenuView.java
index beb57ba..178dcde 100644
--- a/core/java/com/android/internal/view/menu/IconMenuView.java
+++ b/core/java/com/android/internal/view/menu/IconMenuView.java
@@ -337,7 +337,9 @@
         // This method does a clear refresh of children
         removeAllViews();
         
-        final ArrayList<MenuItemImpl> itemsToShow = mMenu.getVisibleItems();
+        // IconMenuView never wants content sorted for an overflow action button, since
+        // it is never used in the presence of an overflow button.
+        final ArrayList<MenuItemImpl> itemsToShow = mMenu.getNonActionItems(false);
         final int numItems = itemsToShow.size();
         final int numItemsThatCanFit = mMaxItems;
         // Minimum of the num that can fit and the num that we have
diff --git a/core/java/com/android/internal/view/menu/MenuBuilder.java b/core/java/com/android/internal/view/menu/MenuBuilder.java
index 228d5d0..215d809 100644
--- a/core/java/com/android/internal/view/menu/MenuBuilder.java
+++ b/core/java/com/android/internal/view/menu/MenuBuilder.java
@@ -27,16 +27,17 @@
 import android.graphics.drawable.Drawable;
 import android.os.Bundle;
 import android.os.Parcelable;
+import android.util.Log;
 import android.util.SparseArray;
 import android.view.ContextThemeWrapper;
 import android.view.KeyCharacterMap;
 import android.view.KeyEvent;
+import android.view.LayoutInflater;
 import android.view.Menu;
 import android.view.MenuItem;
 import android.view.SubMenu;
 import android.view.View;
 import android.view.ViewGroup;
-import android.view.LayoutInflater;
 import android.view.ContextMenu.ContextMenuInfo;
 import android.widget.AdapterView;
 import android.widget.BaseAdapter;
@@ -54,7 +55,7 @@
     private static final String LOGTAG = "MenuBuilder";
     
     /** The number of different menu types */
-    public static final int NUM_TYPES = 3;
+    public static final int NUM_TYPES = 5;
     /** The menu type that represents the icon menu view */
     public static final int TYPE_ICON = 0;
     /** The menu type that represents the expanded menu view */
@@ -65,14 +66,24 @@
      * have an ItemView.
      */
     public static final int TYPE_DIALOG = 2;
+    /**
+     * The menu type that represents a button in the application's action bar.
+     */
+    public static final int TYPE_ACTION_BUTTON = 3;
+    /**
+     * The menu type that represents a menu popup.
+     */
+    public static final int TYPE_POPUP = 4;
 
     private static final String VIEWS_TAG = "android:views";
-    
+
     // Order must be the same order as the TYPE_*
     static final int THEME_RES_FOR_TYPE[] = new int[] {
         com.android.internal.R.style.Theme_IconMenu,
         com.android.internal.R.style.Theme_ExpandedMenu,
         0,
+        0,
+        0,
     };
     
     // Order must be the same order as the TYPE_*
@@ -80,6 +91,8 @@
         com.android.internal.R.layout.icon_menu_layout,
         com.android.internal.R.layout.expanded_menu_layout,
         0,
+        com.android.internal.R.layout.action_menu_layout,
+        0,
     };
 
     // Order must be the same order as the TYPE_*
@@ -87,6 +100,8 @@
         com.android.internal.R.layout.icon_menu_item_layout,
         com.android.internal.R.layout.list_menu_item_layout,
         com.android.internal.R.layout.list_menu_item_layout,
+        com.android.internal.R.layout.action_menu_item_layout,
+        com.android.internal.R.layout.list_menu_item_layout,
     };
 
     private static final int[]  sCategoryToOrder = new int[] {
@@ -130,6 +145,30 @@
      * fetched from {@link #getVisibleItems()}
      */ 
     private boolean mIsVisibleItemsStale;
+    
+    /**
+     * Contains only the items that should appear in the Action Bar, if present.
+     */
+    private ArrayList<MenuItemImpl> mActionItems;
+    /**
+     * Contains items that should NOT appear in the Action Bar, if present.
+     */
+    private ArrayList<MenuItemImpl> mNonActionItems;
+    /**
+     * The number of visible action buttons permitted in this menu
+     */
+    private int mMaxActionItems;
+    /**
+     * Whether or not the items (or any one item's action state) has changed since it was
+     * last fetched.
+     */
+    private boolean mIsActionItemsStale;
+
+    /**
+     * Whether the process of granting space as action items should reserve a space for
+     * an overflow option in the action list.
+     */
+    private boolean mReserveActionOverflow;
 
     /**
      * Current use case is Context Menus: As Views populate the context menu, each one has
@@ -281,6 +320,10 @@
         mVisibleItems = new ArrayList<MenuItemImpl>();
         mIsVisibleItemsStale = true;
         
+        mActionItems = new ArrayList<MenuItemImpl>();
+        mNonActionItems = new ArrayList<MenuItemImpl>();
+        mIsActionItemsStale = true;
+        
         mShortcutsVisible =
                 (mResources.getConfiguration().keyboard != Configuration.KEYBOARD_NOKEYS);
     }
@@ -633,6 +676,11 @@
         return mItems.get(index);
     }
 
+    public MenuItem getOverflowItem(int index) {
+        flagActionItems(true);
+        return mNonActionItems.get(index);
+    }
+
     public boolean isShortcutKey(int keyCode, KeyEvent event) {
         return findItemWithShortcutForKey(keyCode, event) != null;
     }
@@ -900,6 +948,7 @@
     private void onItemsChanged(boolean cleared) {
         if (!mPreventDispatchingItemsChanged) {
             if (mIsVisibleItemsStale == false) mIsVisibleItemsStale = true;
+            if (mIsActionItemsStale == false) mIsActionItemsStale = true;
             
             MenuType[] menuTypes = mMenuTypes;
             for (int i = 0; i < NUM_TYPES; i++) {
@@ -920,6 +969,15 @@
         onItemsChanged(false);
     }
     
+    /**
+     * Called by {@link MenuItemImpl} when its action request status is changed.
+     * @param item The item that has gone through a change in action request status.
+     */
+    void onItemActionRequestChanged(MenuItemImpl item) {
+        // Notify of items being changed
+        onItemsChanged(false);
+    }
+    
     ArrayList<MenuItemImpl> getVisibleItems() {
         if (!mIsVisibleItemsStale) return mVisibleItems;
         
@@ -934,9 +992,83 @@
         }
         
         mIsVisibleItemsStale = false;
+        mIsActionItemsStale = true;
         
         return mVisibleItems;
     }
+    
+    private void flagActionItems(boolean reserveActionOverflow) {
+        if (reserveActionOverflow != mReserveActionOverflow) {
+            mReserveActionOverflow = reserveActionOverflow;
+            mIsActionItemsStale = true;
+        }
+
+        if (!mIsActionItemsStale) {
+            return;
+        }
+
+        final ArrayList<MenuItemImpl> visibleItems = getVisibleItems();
+        final int itemsSize = visibleItems.size();
+        int maxActions = mMaxActionItems;
+
+        int requiredItems = 0;
+        int requestedItems = 0;
+        boolean hasOverflow = false;
+        for (int i = 0; i < itemsSize; i++) {
+            MenuItemImpl item = visibleItems.get(i);
+            if (item.requiresActionButton()) {
+                requiredItems++;
+            } else if (item.requestsActionButton()) {
+                requestedItems++;
+            } else {
+                hasOverflow = true;
+            }
+        }
+
+        // Reserve a spot for the overflow item if needed.
+        if (reserveActionOverflow &&
+                (hasOverflow || requiredItems + requestedItems > maxActions)) {
+            maxActions--;
+        }
+        maxActions -= requiredItems;
+
+        // Flag as many more requested items as will fit.
+        for (int i = 0; i < itemsSize; i++) {
+            MenuItemImpl item = visibleItems.get(i);
+            if (item.requestsActionButton()) {
+                item.setIsActionButton(maxActions > 0);
+                maxActions--;
+            }
+        }
+
+        mActionItems.clear();
+        mNonActionItems.clear();
+        for (int i = 0; i < itemsSize; i++) {
+            MenuItemImpl item = visibleItems.get(i);
+            if (item.isActionButton()) {
+                mActionItems.add(item);
+            } else {
+                mNonActionItems.add(item);
+            }
+        }
+
+        mIsActionItemsStale = false;
+    }
+    
+    ArrayList<MenuItemImpl> getActionItems(boolean reserveActionOverflow) {
+        flagActionItems(reserveActionOverflow);
+        return mActionItems;
+    }
+    
+    ArrayList<MenuItemImpl> getNonActionItems(boolean reserveActionOverflow) {
+        flagActionItems(reserveActionOverflow);
+        return mNonActionItems;
+    }
+    
+    void setMaxActionItems(int maxActionItems) {
+        mMaxActionItems = maxActionItems;
+        mIsActionItemsStale = true;
+    }
 
     public void clearHeader() {
         mHeaderIcon = null;
@@ -1078,6 +1210,16 @@
         return new MenuAdapter(menuType);
     }
 
+    /**
+     * Gets an adapter for providing overflow (non-action) items and their views.
+     *
+     * @param menuType The type of menu to get an adapter for.
+     * @return A {@link MenuAdapter} for this menu with the given menu type.
+     */
+    public MenuAdapter getOverflowMenuAdapter(int menuType) {
+        return new OverflowMenuAdapter(menuType);
+    }
+
     void setOptionalIconsVisible(boolean visible) {
         mOptionalIconsVisible = visible;
     }
@@ -1155,8 +1297,42 @@
         }
 
         public View getView(int position, View convertView, ViewGroup parent) {
-            return ((MenuItemImpl) getItem(position)).getItemView(mMenuType, parent);
+            if (convertView != null) {
+                MenuView.ItemView itemView = (MenuView.ItemView) convertView;
+                itemView.getItemData().setItemView(mMenuType, null);
+
+                MenuItemImpl item = (MenuItemImpl) getItem(position);
+                itemView.initialize(item, mMenuType);
+                item.setItemView(mMenuType, itemView);
+                return convertView;
+            } else {
+                MenuItemImpl item = (MenuItemImpl) getItem(position);
+                item.setItemView(mMenuType, null);
+                return item.getItemView(mMenuType, parent);
+            }
+        }
+    }
+
+    /**
+     * An adapter that allows an {@link AdapterView} to use this {@link MenuBuilder} as a data
+     * source for overflow menu items that do not fit in the list of action items.
+     */
+    private class OverflowMenuAdapter extends MenuAdapter {
+        private ArrayList<MenuItemImpl> mOverflowItems;
+
+        public OverflowMenuAdapter(int menuType) {
+            super(menuType);
+            mOverflowItems = getNonActionItems(true);
         }
 
+        @Override
+        public MenuItemImpl getItem(int position) {
+            return mOverflowItems.get(position);
+        }
+
+        @Override
+        public int getCount() {
+            return mOverflowItems.size();
+        }
     }
 }
diff --git a/core/java/com/android/internal/view/menu/MenuItemImpl.java b/core/java/com/android/internal/view/menu/MenuItemImpl.java
index 9b58205..fecbd77 100644
--- a/core/java/com/android/internal/view/menu/MenuItemImpl.java
+++ b/core/java/com/android/internal/view/menu/MenuItemImpl.java
@@ -74,6 +74,9 @@
     private static final int EXCLUSIVE      = 0x00000004;
     private static final int HIDDEN         = 0x00000008;
     private static final int ENABLED        = 0x00000010;
+    private static final int IS_ACTION      = 0x00000020;
+
+    private int mShowAsAction = SHOW_AS_ACTION_NEVER;
 
     /** Used for the icon resource ID if this item does not have an icon */
     static final int NO_ICON = 0;
@@ -580,6 +583,10 @@
         return (View) mItemViews[menuType].get();
     }
 
+    void setItemView(int menuType, ItemView view) {
+        mItemViews[menuType] = new WeakReference<ItemView>(view);
+    }
+
     /**
      * Create and initializes a menu item view that implements {@link MenuView.ItemView}.
      * @param menuType The type of menu to get a View for (must be one of
@@ -628,6 +635,34 @@
      * @return Whether the given menu type should show icons for menu items.
      */
     public boolean shouldShowIcon(int menuType) {
-        return menuType == MenuBuilder.TYPE_ICON || mMenu.getOptionalIconsVisible();
+        return menuType == MenuBuilder.TYPE_ICON ||
+                menuType == MenuBuilder.TYPE_ACTION_BUTTON ||
+                menuType == MenuBuilder.TYPE_POPUP ||
+                mMenu.getOptionalIconsVisible();
+    }
+    
+    public boolean isActionButton() {
+        return (mFlags & IS_ACTION) == IS_ACTION || requiresActionButton();
+    }
+    
+    public boolean requestsActionButton() {
+        return mShowAsAction == SHOW_AS_ACTION_IF_ROOM;
+    }
+    
+    public boolean requiresActionButton() {
+        return mShowAsAction == SHOW_AS_ACTION_ALWAYS;
+    }
+
+    public void setIsActionButton(boolean isActionButton) {
+        if (isActionButton) {
+            mFlags |= IS_ACTION;
+        } else {
+            mFlags &= ~IS_ACTION;
+        }
+    }
+
+    public void setShowAsAction(int actionEnum) {
+        mShowAsAction = actionEnum;
+        mMenu.onItemActionRequestChanged(this);
     }
 }
diff --git a/core/java/com/android/internal/view/menu/MenuPopupHelper.java b/core/java/com/android/internal/view/menu/MenuPopupHelper.java
new file mode 100644
index 0000000..ce0ec04
--- /dev/null
+++ b/core/java/com/android/internal/view/menu/MenuPopupHelper.java
@@ -0,0 +1,138 @@
+/*
+ * 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.internal.view.menu;
+
+import com.android.internal.view.menu.MenuBuilder.MenuAdapter;
+
+import android.content.Context;
+import android.util.DisplayMetrics;
+import android.view.KeyEvent;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.View.MeasureSpec;
+import android.widget.AdapterView;
+import android.widget.ListPopupWindow;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * @hide
+ */
+public class MenuPopupHelper implements AdapterView.OnItemClickListener, View.OnKeyListener {
+    private static final String TAG = "MenuPopupHelper";
+
+    private Context mContext;
+    private ListPopupWindow mPopup;
+    private MenuBuilder mMenu;
+    private int mPopupMaxWidth;
+    private WeakReference<View> mAnchorView;
+    private boolean mOverflowOnly;
+
+    public MenuPopupHelper(Context context, MenuBuilder menu) {
+        this(context, menu, null, false);
+    }
+
+    public MenuPopupHelper(Context context, MenuBuilder menu, View anchorView) {
+        this(context, menu, anchorView, false);
+    }
+
+    public MenuPopupHelper(Context context, MenuBuilder menu,
+            View anchorView, boolean overflowOnly) {
+        mContext = context;
+        mMenu = menu;
+        mOverflowOnly = overflowOnly;
+
+        final DisplayMetrics metrics = context.getResources().getDisplayMetrics();
+        mPopupMaxWidth = metrics.widthPixels / 2;
+
+        if (anchorView != null) {
+            mAnchorView = new WeakReference<View>(anchorView);
+        }
+    }
+
+    public void show() {
+        // TODO Use a style from the theme here
+        mPopup = new ListPopupWindow(mContext, null, 0,
+                com.android.internal.R.style.Widget_Spinner);
+        mPopup.setOnItemClickListener(this);
+
+        final MenuAdapter adapter = mOverflowOnly ?
+                mMenu.getOverflowMenuAdapter(MenuBuilder.TYPE_POPUP) :
+                mMenu.getMenuAdapter(MenuBuilder.TYPE_POPUP);
+        mPopup.setAdapter(adapter);
+        mPopup.setModal(true);
+
+        if (mAnchorView != null) {
+            mPopup.setAnchorView(mAnchorView.get());
+        } else if (mMenu instanceof SubMenuBuilder) {
+            SubMenuBuilder subMenu = (SubMenuBuilder) mMenu;
+            final MenuItemImpl itemImpl = (MenuItemImpl) subMenu.getItem();
+            mPopup.setAnchorView(itemImpl.getItemView(MenuBuilder.TYPE_ACTION_BUTTON, null));
+        } else {
+            throw new IllegalStateException("MenuPopupHelper cannot be used without an anchor");
+        }
+
+        mPopup.setContentWidth(Math.min(measureContentWidth(adapter), mPopupMaxWidth));
+        mPopup.show();
+        mPopup.getListView().setOnKeyListener(this);
+    }
+
+    public void dismiss() {
+        mPopup.dismiss();
+        mPopup = null;
+    }
+
+    public boolean isShowing() {
+        return mPopup != null && mPopup.isShowing();
+    }
+
+    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+        MenuItem item = null;
+        if (mOverflowOnly) {
+            item = mMenu.getOverflowItem(position);
+        } else {
+            item = mMenu.getItem(position);
+        }
+        mMenu.performItemAction(item, 0);
+        mPopup.dismiss();
+    }
+
+    public boolean onKey(View v, int keyCode, KeyEvent event) {
+        if (event.getAction() == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_MENU) {
+            dismiss();
+            return true;
+        }
+        return false;
+    }
+
+    private int measureContentWidth(MenuAdapter adapter) {
+        // Menus don't tend to be long, so this is more sane than it looks.
+        int width = 0;
+        View itemView = null;
+        final int widthMeasureSpec =
+            MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+        final int heightMeasureSpec =
+            MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+        final int count = adapter.getCount();
+        for (int i = 0; i < count; i++) {
+            itemView = adapter.getView(i, itemView, null);
+            itemView.measure(widthMeasureSpec, heightMeasureSpec);
+            width = Math.max(width, itemView.getMeasuredWidth());
+        }
+        return width;
+    }
+}
diff --git a/core/java/com/android/internal/widget/ActionBarContextView.java b/core/java/com/android/internal/widget/ActionBarContextView.java
new file mode 100644
index 0000000..5518b3e
--- /dev/null
+++ b/core/java/com/android/internal/widget/ActionBarContextView.java
@@ -0,0 +1,287 @@
+/*
+ * 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.internal.widget;
+
+import com.android.internal.R;
+import com.android.internal.view.menu.ActionMenuView;
+import com.android.internal.view.menu.MenuBuilder;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.ActionMode;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageButton;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+/**
+ * @hide
+ */
+public class ActionBarContextView extends ViewGroup {
+    // TODO: This must be defined in the default theme
+    private static final int CONTENT_HEIGHT_DIP = 50;
+    
+    private int mItemPadding;
+    private int mItemMargin;
+    private int mActionSpacing;
+    private int mContentHeight;
+    
+    private CharSequence mTitle;
+    private CharSequence mSubtitle;
+    
+    private ImageButton mCloseButton;
+    private View mCustomView;
+    private LinearLayout mTitleLayout;
+    private TextView mTitleView;
+    private TextView mSubtitleView;
+    private Drawable mCloseDrawable;
+    private ActionMenuView mMenuView;
+    
+    public ActionBarContextView(Context context) {
+        this(context, null, 0);
+    }
+    
+    public ActionBarContextView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+    
+    public ActionBarContextView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        
+        TypedArray a = context.obtainStyledAttributes(attrs,
+                com.android.internal.R.styleable.Theme);
+        mItemPadding = a.getDimensionPixelOffset(
+                com.android.internal.R.styleable.Theme_actionButtonPadding, 0);
+        setBackgroundDrawable(a.getDrawable(
+                com.android.internal.R.styleable.Theme_actionModeBackground));
+        mCloseDrawable = a.getDrawable(
+                com.android.internal.R.styleable.Theme_actionModeCloseDrawable);
+        mItemMargin = mItemPadding / 2;
+
+        mContentHeight =
+                (int) (CONTENT_HEIGHT_DIP * getResources().getDisplayMetrics().density + 0.5f);
+        a.recycle();
+    }
+    
+    public void setCustomView(View view) {
+        if (mCustomView != null) {
+            removeView(mCustomView);
+        }
+        mCustomView = view;
+        if (mTitleLayout != null) {
+            removeView(mTitleLayout);
+            mTitleLayout = null;
+        }
+        if (view != null) {
+            addView(view);
+        }
+        requestLayout();
+    }
+
+    public void setTitle(CharSequence title) {
+        mTitle = title;
+        initTitle();
+    }
+
+    public void setSubtitle(CharSequence subtitle) {
+        mSubtitle = subtitle;
+        initTitle();
+    }
+
+    public CharSequence getTitle() {
+        return mTitle;
+    }
+
+    public CharSequence getSubtitle() {
+        return mSubtitle;
+    }
+
+    private void initTitle() {
+        if (mTitleLayout == null) {
+            LayoutInflater inflater = LayoutInflater.from(getContext());
+            mTitleLayout = (LinearLayout) inflater.inflate(R.layout.action_bar_title_item, null);
+            mTitleView = (TextView) mTitleLayout.findViewById(R.id.action_bar_title);
+            mSubtitleView = (TextView) mTitleLayout.findViewById(R.id.action_bar_subtitle);
+            if (mTitle != null) {
+                mTitleView.setText(mTitle);
+            }
+            if (mSubtitle != null) {
+                mSubtitleView.setText(mSubtitle);
+                mSubtitleView.setVisibility(VISIBLE);
+            }
+            addView(mTitleLayout);
+        } else {
+            mTitleView.setText(mTitle);
+            mSubtitleView.setText(mSubtitle);
+            mSubtitleView.setVisibility(mSubtitle != null ? VISIBLE : GONE);
+            if (mTitleLayout.getParent() == null) {
+                addView(mTitleLayout);
+            }
+        }
+    }
+
+    public void initForMode(final ActionMode mode) {
+        if (mCloseButton == null) {
+            mCloseButton = new ImageButton(getContext());
+            mCloseButton.setImageDrawable(mCloseDrawable);
+            mCloseButton.setBackgroundDrawable(null);
+        }
+        mCloseButton.setOnClickListener(new OnClickListener() {
+            public void onClick(View v) {
+                mode.finish();
+            }
+        });
+        addView(mCloseButton);
+
+        final MenuBuilder menu = (MenuBuilder) mode.getMenu();
+        mMenuView = (ActionMenuView) menu.getMenuView(MenuBuilder.TYPE_ACTION_BUTTON, this);
+        mMenuView.setOverflowReserved(true);
+        mMenuView.updateChildren(false);
+        addView(mMenuView);
+    }
+
+    public void closeMode() {
+        removeAllViews();
+        mCustomView = null;
+        mMenuView = null;
+    }
+
+    @Override
+    protected LayoutParams generateDefaultLayoutParams() {
+        // Used by custom views if they don't supply layout params. Everything else
+        // added to an ActionBarContextView should have them already.
+        return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+        if (widthMode != MeasureSpec.EXACTLY) {
+            throw new IllegalStateException(getClass().getSimpleName() + " can only be used " +
+                    "with android:layout_width=\"match_parent\" (or fill_parent)");
+        }
+
+        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+        if (heightMode != MeasureSpec.AT_MOST) {
+            throw new IllegalStateException(getClass().getSimpleName() + " can only be used " +
+                    "with android:layout_height=\"wrap_content\"");
+        }
+        
+        final int contentWidth = MeasureSpec.getSize(widthMeasureSpec);
+        final int itemMargin = mItemPadding;
+
+        int availableWidth = contentWidth - getPaddingLeft() - getPaddingRight();
+        final int height = mContentHeight - getPaddingTop() - getPaddingBottom();
+        final int childSpecHeight = MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST);
+        
+        if (mCloseButton != null) {
+            availableWidth = measureChildView(mCloseButton, availableWidth,
+                    childSpecHeight, itemMargin);
+        }
+
+        if (mTitleLayout != null && mCustomView == null) {
+            availableWidth = measureChildView(mTitleLayout, availableWidth,
+                    childSpecHeight, itemMargin);
+        }
+
+        final int childCount = getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            final View child = getChildAt(i);
+            if (child == mCloseButton || child == mTitleLayout || child == mCustomView) {
+                continue;
+            }
+            
+            availableWidth = measureChildView(child, availableWidth, childSpecHeight, itemMargin);
+        }
+
+        if (mCustomView != null) {
+            LayoutParams lp = mCustomView.getLayoutParams();
+            final int customWidthMode = lp.width != LayoutParams.WRAP_CONTENT ?
+                    MeasureSpec.EXACTLY : MeasureSpec.AT_MOST;
+            final int customWidth = lp.width >= 0 ?
+                    Math.min(lp.width, availableWidth) : availableWidth;
+            final int customHeightMode = lp.height != LayoutParams.WRAP_CONTENT ?
+                    MeasureSpec.EXACTLY : MeasureSpec.AT_MOST;
+            final int customHeight = lp.height >= 0 ?
+                    Math.min(lp.height, height) : height;
+            mCustomView.measure(MeasureSpec.makeMeasureSpec(customWidth, customWidthMode),
+                    MeasureSpec.makeMeasureSpec(customHeight, customHeightMode));
+        }
+
+        setMeasuredDimension(contentWidth, mContentHeight);
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        int x = getPaddingLeft();
+        final int y = getPaddingTop();
+        final int contentHeight = b - t - getPaddingTop() - getPaddingBottom();
+        final int itemMargin = mItemPadding;
+        
+        if (mCloseButton != null && mCloseButton.getVisibility() != GONE) {
+            x += positionChild(mCloseButton, x, y, contentHeight);
+        }
+        
+        if (mTitleLayout != null && mCustomView == null) {
+            x += positionChild(mTitleLayout, x, y, contentHeight) + itemMargin;
+        }
+        
+        if (mCustomView != null) {
+            x += positionChild(mCustomView, x, y, contentHeight) + itemMargin;
+        }
+        
+        x = r - l - getPaddingRight();
+
+        if (mMenuView != null) {
+            x -= positionChildInverse(mMenuView, x + mActionSpacing, y, contentHeight)
+                    - mActionSpacing;
+        }
+    }
+
+    private int measureChildView(View child, int availableWidth, int childSpecHeight, int spacing) {
+        child.measure(MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST),
+                childSpecHeight);
+
+        availableWidth -= child.getMeasuredWidth();
+        availableWidth -= spacing;
+
+        return availableWidth;
+    }
+    
+    private int positionChild(View child, int x, int y, int contentHeight) {
+        int childWidth = child.getMeasuredWidth();
+        int childHeight = child.getMeasuredHeight();
+        int childTop = y + (contentHeight - childHeight) / 2;
+
+        child.layout(x, childTop, x + childWidth, childTop + childHeight);
+
+        return childWidth;
+    }
+    
+    private int positionChildInverse(View child, int x, int y, int contentHeight) {
+        int childWidth = child.getMeasuredWidth();
+        int childHeight = child.getMeasuredHeight();
+        int childTop = y + (contentHeight - childHeight) / 2;
+
+        child.layout(x - childWidth, childTop, x, childTop + childHeight);
+
+        return childWidth;
+    }
+}
diff --git a/core/java/com/android/internal/widget/ActionBarView.java b/core/java/com/android/internal/widget/ActionBarView.java
new file mode 100644
index 0000000..d703a2f
--- /dev/null
+++ b/core/java/com/android/internal/widget/ActionBarView.java
@@ -0,0 +1,677 @@
+/*
+ * 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.internal.widget;
+
+import com.android.internal.R;
+import com.android.internal.view.menu.ActionMenuItem;
+import com.android.internal.view.menu.ActionMenuView;
+import com.android.internal.view.menu.MenuBuilder;
+
+import android.app.ActionBar;
+import android.app.Activity;
+import android.app.ActionBar.NavigationCallback;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.res.TypedArray;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffColorFilter;
+import android.graphics.drawable.Drawable;
+import android.text.TextUtils.TruncateAt;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.Spinner;
+import android.widget.SpinnerAdapter;
+import android.widget.TextView;
+
+/**
+ * @hide
+ */
+public class ActionBarView extends ViewGroup {
+    private static final String TAG = "ActionBarView";
+    
+    // TODO: This must be defined in the default theme
+    private static final int CONTENT_HEIGHT_DIP = 50;
+    private static final int CONTENT_PADDING_DIP = 3;
+    private static final int CONTENT_SPACING_DIP = 6;
+    private static final int CONTENT_ACTION_SPACING_DIP = 12;
+    
+    /**
+     * Display options applied by default
+     */
+    public static final int DISPLAY_DEFAULT = 0;
+
+    /**
+     * Display options that require re-layout as opposed to a simple invalidate
+     */
+    private static final int DISPLAY_RELAYOUT_MASK =
+            ActionBar.DISPLAY_HIDE_HOME |
+            ActionBar.DISPLAY_USE_LOGO;
+    
+    private final int mContentHeight;
+
+    private int mNavigationMode;
+    private int mDisplayOptions;
+    private int mSpacing;
+    private int mActionSpacing;
+    private CharSequence mTitle;
+    private CharSequence mSubtitle;
+    private Drawable mIcon;
+    private Drawable mLogo;
+
+    private ImageView mIconView;
+    private ImageView mLogoView;
+    private LinearLayout mTitleLayout;
+    private TextView mTitleView;
+    private TextView mSubtitleView;
+    private Spinner mSpinner;
+    private LinearLayout mTabLayout;
+    private View mCustomNavView;
+    
+    private boolean mShowMenu;
+    private boolean mUserTitle;
+
+    private MenuBuilder mOptionsMenu;
+    private ActionMenuView mMenuView;
+    
+    private ActionMenuItem mLogoNavItem;
+    
+    private NavigationCallback mCallback;
+
+    private final AdapterView.OnItemSelectedListener mNavItemSelectedListener =
+            new AdapterView.OnItemSelectedListener() {
+        public void onItemSelected(AdapterView parent, View view, int position, long id) {
+            if (mCallback != null) {
+                mCallback.onNavigationItemSelected(position, id);
+            }
+        }
+        public void onNothingSelected(AdapterView parent) {
+            // Do nothing
+        }
+    };
+
+    private OnClickListener mHomeClickListener = null;
+
+    public ActionBarView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        final DisplayMetrics metrics = context.getResources().getDisplayMetrics();
+        mContentHeight = (int) (CONTENT_HEIGHT_DIP * metrics.density + 0.5f);
+
+        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ActionBar);
+
+        final int colorFilter = a.getColor(R.styleable.ActionBar_colorFilter, 0);
+
+        if (colorFilter != 0) {
+            final Drawable d = getBackground();
+            d.setDither(true);
+            d.setColorFilter(new PorterDuffColorFilter(colorFilter, PorterDuff.Mode.OVERLAY));
+        }
+
+        ApplicationInfo info = context.getApplicationInfo();
+        PackageManager pm = context.getPackageManager();
+        mNavigationMode = a.getInt(R.styleable.ActionBar_navigationMode,
+                ActionBar.NAVIGATION_MODE_STANDARD);
+        mTitle = a.getText(R.styleable.ActionBar_title);
+        mSubtitle = a.getText(R.styleable.ActionBar_subtitle);
+        mDisplayOptions = a.getInt(R.styleable.ActionBar_displayOptions, DISPLAY_DEFAULT);
+        
+        mLogo = a.getDrawable(R.styleable.ActionBar_logo);
+        if (mLogo == null) {
+            mLogo = info.loadLogo(pm);
+        }
+        mIcon = a.getDrawable(R.styleable.ActionBar_icon);
+        if (mIcon == null) {
+            mIcon = info.loadIcon(pm);
+        }
+        
+        Drawable background = a.getDrawable(R.styleable.ActionBar_background);
+        if (background != null) {
+            setBackgroundDrawable(background);
+        }
+        
+        final int customNavId = a.getResourceId(R.styleable.ActionBar_customNavigationLayout, 0);
+        if (customNavId != 0) {
+            LayoutInflater inflater = LayoutInflater.from(context);
+            mCustomNavView = (View) inflater.inflate(customNavId, null);
+            mNavigationMode = ActionBar.NAVIGATION_MODE_CUSTOM;
+        }
+
+        a.recycle();
+
+        // TODO: Set this in the theme
+        int padding = (int) (CONTENT_PADDING_DIP * metrics.density + 0.5f);
+        setPadding(padding, padding, padding, padding);
+
+        mSpacing = (int) (CONTENT_SPACING_DIP * metrics.density + 0.5f);
+        mActionSpacing = (int) (CONTENT_ACTION_SPACING_DIP * metrics.density + 0.5f);
+        
+        if (mLogo != null || mIcon != null || mTitle != null) {
+            mLogoNavItem = new ActionMenuItem(context, 0, android.R.id.home, 0, 0, mTitle);
+            mHomeClickListener = new OnClickListener() {
+                public void onClick(View v) {
+                    Context context = getContext();
+                    if (context instanceof Activity) {
+                        Activity activity = (Activity) context;
+                        activity.onOptionsItemSelected(mLogoNavItem);
+                    }
+                }
+            };
+        }
+    }
+
+    public void setCallback(NavigationCallback callback) {
+        mCallback = callback;
+    }
+
+    public void setMenu(Menu menu) {
+        MenuBuilder builder = (MenuBuilder) menu;
+        mOptionsMenu = builder;
+        if (mMenuView != null) {
+            removeView(mMenuView);
+        }
+        final ActionMenuView menuView = (ActionMenuView) builder.getMenuView(
+                MenuBuilder.TYPE_ACTION_BUTTON, null);
+        mActionSpacing = menuView.getItemMargin();
+        final LayoutParams layoutParams = new LayoutParams(LayoutParams.WRAP_CONTENT,
+                LayoutParams.MATCH_PARENT);
+        menuView.setLayoutParams(layoutParams);
+        addView(menuView);
+        mMenuView = menuView;
+    }
+
+    public boolean showOverflowMenu() {
+        if (mMenuView != null) {
+            return mMenuView.showOverflowMenu();
+        }
+        return false;
+    }
+
+    public boolean isOverflowReserved() {
+        return mMenuView != null && mMenuView.isOverflowReserved();
+    }
+
+    public void setCustomNavigationView(View view) {
+        mCustomNavView = view;
+        if (view != null) {
+            setNavigationMode(ActionBar.NAVIGATION_MODE_CUSTOM);
+        }
+    }
+
+    public CharSequence getTitle() {
+        return mTitle;
+    }
+
+    /**
+     * Set the action bar title. This will always replace or override window titles.
+     * @param title Title to set
+     *
+     * @see #setWindowTitle(CharSequence)
+     */
+    public void setTitle(CharSequence title) {
+        mUserTitle = true;
+        setTitleImpl(title);
+    }
+
+    /**
+     * Set the window title. A window title will always be replaced or overridden by a user title.
+     * @param title Title to set
+     *
+     * @see #setTitle(CharSequence)
+     */
+    public void setWindowTitle(CharSequence title) {
+        if (!mUserTitle) {
+            setTitleImpl(title);
+        }
+    }
+
+    private void setTitleImpl(CharSequence title) {
+        mTitle = title;
+        if (mTitleView != null) {
+            mTitleView.setText(title);
+        }
+        if (mLogoNavItem != null) {
+            mLogoNavItem.setTitle(title);
+        }
+    }
+
+    public CharSequence getSubtitle() {
+        return mSubtitle;
+    }
+
+    public void setSubtitle(CharSequence subtitle) {
+        mSubtitle = subtitle;
+        if (mSubtitleView != null) {
+            mSubtitleView.setText(subtitle);
+        }
+    }
+
+    public void setDisplayOptions(int options) {
+        final int flagsChanged = options ^ mDisplayOptions;
+        mDisplayOptions = options;
+        if ((flagsChanged & DISPLAY_RELAYOUT_MASK) != 0) {
+            final int vis = (options & ActionBar.DISPLAY_HIDE_HOME) != 0 ? GONE : VISIBLE;
+            if (mLogoView != null) {
+                mLogoView.setVisibility(vis);
+            }
+            if (mIconView != null) {
+                mIconView.setVisibility(vis);
+            }
+            
+            requestLayout();
+        } else {
+            invalidate();
+        }
+    }
+
+    public void setNavigationMode(int mode) {
+        final int oldMode = mNavigationMode;
+        if (mode != oldMode) {
+            switch (oldMode) {
+            case ActionBar.NAVIGATION_MODE_STANDARD:
+                if (mTitleLayout != null) {
+                    removeView(mTitleLayout);
+                    mTitleLayout = null;
+                    mTitleView = null;
+                    mSubtitleView = null;
+                }
+                break;
+            case ActionBar.NAVIGATION_MODE_DROPDOWN_LIST:
+                if (mSpinner != null) {
+                    removeView(mSpinner);
+                    mSpinner = null;
+                }
+                break;
+            case ActionBar.NAVIGATION_MODE_CUSTOM:
+                if (mCustomNavView != null) {
+                    removeView(mCustomNavView);
+                    mCustomNavView = null;
+                }
+                break;
+            case ActionBar.NAVIGATION_MODE_TABS:
+                if (mTabLayout != null) {
+                    removeView(mTabLayout);
+                    mTabLayout = null;
+                }
+            }
+            
+            switch (mode) {
+            case ActionBar.NAVIGATION_MODE_STANDARD:
+                initTitle();
+                break;
+            case ActionBar.NAVIGATION_MODE_DROPDOWN_LIST:
+                mSpinner = new Spinner(mContext, null,
+                        com.android.internal.R.attr.dropDownSpinnerStyle);
+                mSpinner.setOnItemSelectedListener(mNavItemSelectedListener);
+                addView(mSpinner);
+                break;
+            case ActionBar.NAVIGATION_MODE_CUSTOM:
+                addView(mCustomNavView);
+                break;
+            case ActionBar.NAVIGATION_MODE_TABS:
+                mTabLayout = new LinearLayout(getContext());
+                addView(mTabLayout);
+                break;
+            }
+            mNavigationMode = mode;
+            requestLayout();
+        }
+    }
+    
+    public void setDropdownAdapter(SpinnerAdapter adapter) {
+        mSpinner.setAdapter(adapter);
+    }
+
+    public void setDropdownSelectedPosition(int position) {
+        mSpinner.setSelection(position);
+    }
+
+    public int getDropdownSelectedPosition() {
+        return mSpinner.getSelectedItemPosition();
+    }
+
+    public View getCustomNavigationView() {
+        return mCustomNavView;
+    }
+    
+    public int getNavigationMode() {
+        return mNavigationMode;
+    }
+    
+    public int getDisplayOptions() {
+        return mDisplayOptions;
+    }
+
+    private TabView createTabView(ActionBar.Tab tab) {
+        final TabView tabView = new TabView(getContext(), tab);
+        tabView.setFocusable(true);
+        tabView.setOnClickListener(new TabClickListener());
+        return tabView;
+    }
+
+    public void addTab(ActionBar.Tab tab) {
+        final boolean isFirst = mTabLayout.getChildCount() == 0;
+        final TabView tabView = createTabView(tab);
+        mTabLayout.addView(tabView);
+        if (isFirst) {
+            tabView.setSelected(true);
+        }
+    }
+
+    public void insertTab(ActionBar.Tab tab, int position) {
+        final boolean isFirst = mTabLayout.getChildCount() == 0;
+        final TabView tabView = createTabView(tab);
+        mTabLayout.addView(tabView, position);
+        if (isFirst) {
+            tabView.setSelected(true);
+        }
+    }
+
+    public void removeTabAt(int position) {
+        mTabLayout.removeViewAt(position);
+    }
+
+    @Override
+    protected LayoutParams generateDefaultLayoutParams() {
+        // Used by custom nav views if they don't supply layout params. Everything else
+        // added to an ActionBarView should have them already.
+        return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+
+        if ((mDisplayOptions & ActionBar.DISPLAY_HIDE_HOME) == 0) {
+            if (mLogo != null && (mDisplayOptions & ActionBar.DISPLAY_USE_LOGO) != 0) {
+                mLogoView = new ImageView(getContext());
+                mLogoView.setAdjustViewBounds(true);
+                mLogoView.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,
+                        LayoutParams.MATCH_PARENT));
+                mLogoView.setImageDrawable(mLogo);
+                mLogoView.setClickable(true);
+                mLogoView.setFocusable(true);
+                mLogoView.setOnClickListener(mHomeClickListener);
+                addView(mLogoView);
+            } else if (mIcon != null) {
+                mIconView = new ImageView(getContext());
+                mIconView.setAdjustViewBounds(true);
+                mIconView.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,
+                        LayoutParams.MATCH_PARENT));
+                mIconView.setImageDrawable(mIcon);
+                mIconView.setClickable(true);
+                mIconView.setFocusable(true);
+                mIconView.setOnClickListener(mHomeClickListener);
+                addView(mIconView);
+            }
+        }
+
+        switch (mNavigationMode) {
+        case ActionBar.NAVIGATION_MODE_STANDARD:
+            if (mLogoView == null) {
+                initTitle();
+            }
+            break;
+            
+        case ActionBar.NAVIGATION_MODE_DROPDOWN_LIST:
+            throw new UnsupportedOperationException(
+                    "Inflating dropdown list navigation isn't supported yet!");
+            
+        case ActionBar.NAVIGATION_MODE_TABS:
+            throw new UnsupportedOperationException(
+                    "Inflating tab navigation isn't supported yet!");
+            
+        case ActionBar.NAVIGATION_MODE_CUSTOM:
+            if (mCustomNavView != null) {
+                addView(mCustomNavView);
+            }
+            break;
+        }
+    }
+    
+    private void initTitle() {
+        LayoutInflater inflater = LayoutInflater.from(getContext());
+        mTitleLayout = (LinearLayout) inflater.inflate(R.layout.action_bar_title_item, null);
+        mTitleView = (TextView) mTitleLayout.findViewById(R.id.action_bar_title);
+        mSubtitleView = (TextView) mTitleLayout.findViewById(R.id.action_bar_subtitle);
+        if (mTitle != null) {
+            mTitleView.setText(mTitle);
+        }
+        if (mSubtitle != null) {
+            mSubtitleView.setText(mSubtitle);
+            mSubtitleView.setVisibility(VISIBLE);
+        }
+        addView(mTitleLayout);
+    }
+
+    public void setTabSelected(int position) {
+        final int tabCount = mTabLayout.getChildCount();
+        for (int i = 0; i < tabCount; i++) {
+            final View child = mTabLayout.getChildAt(i);
+            child.setSelected(i == position);
+        }
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+        if (widthMode != MeasureSpec.EXACTLY) {
+            throw new IllegalStateException(getClass().getSimpleName() + " can only be used " +
+                    "with android:layout_width=\"match_parent\" (or fill_parent)");
+        }
+        
+        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+        if (heightMode != MeasureSpec.AT_MOST) {
+            throw new IllegalStateException(getClass().getSimpleName() + " can only be used " +
+                    "with android:layout_height=\"wrap_content\"");
+        }
+
+        int contentWidth = MeasureSpec.getSize(widthMeasureSpec);
+        
+        int availableWidth = contentWidth - getPaddingLeft() - getPaddingRight();
+        final int height = mContentHeight - getPaddingTop() - getPaddingBottom();
+        final int childSpecHeight = MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST);
+
+        if (mLogoView != null && mLogoView.getVisibility() != GONE) {
+            availableWidth = measureChildView(mLogoView, availableWidth, childSpecHeight, mSpacing);
+        }
+        if (mIconView != null && mIconView.getVisibility() != GONE) {
+            availableWidth = measureChildView(mIconView, availableWidth, childSpecHeight, mSpacing);
+        }
+        
+        if (mMenuView != null) {
+            availableWidth = measureChildView(mMenuView, availableWidth,
+                    childSpecHeight, 0);
+        }
+        
+        switch (mNavigationMode) {
+        case ActionBar.NAVIGATION_MODE_STANDARD:
+            if (mTitleLayout != null) {
+                measureChildView(mTitleLayout, availableWidth, childSpecHeight, mSpacing);
+            }
+            break;
+        case ActionBar.NAVIGATION_MODE_DROPDOWN_LIST:
+            if (mSpinner != null) {
+                mSpinner.measure(
+                        MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST),
+                        MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
+            }
+            break;
+        case ActionBar.NAVIGATION_MODE_CUSTOM:
+            if (mCustomNavView != null) {
+                LayoutParams lp = mCustomNavView.getLayoutParams();
+                final int customNavWidthMode = lp.width != LayoutParams.WRAP_CONTENT ?
+                        MeasureSpec.EXACTLY : MeasureSpec.AT_MOST;
+                final int customNavWidth = lp.width >= 0 ?
+                        Math.min(lp.width, availableWidth) : availableWidth;
+                final int customNavHeightMode = lp.height != LayoutParams.WRAP_CONTENT ?
+                        MeasureSpec.EXACTLY : MeasureSpec.AT_MOST;
+                final int customNavHeight = lp.height >= 0 ?
+                        Math.min(lp.height, height) : height;
+                mCustomNavView.measure(
+                        MeasureSpec.makeMeasureSpec(customNavWidth, customNavWidthMode),
+                        MeasureSpec.makeMeasureSpec(customNavHeight, customNavHeightMode));
+            }
+            break;
+        case ActionBar.NAVIGATION_MODE_TABS:
+            if (mTabLayout != null) {
+                mTabLayout.measure(
+                        MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST),
+                        MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
+            }
+            break;
+        }
+
+        setMeasuredDimension(contentWidth, mContentHeight);
+    }
+
+    private int measureChildView(View child, int availableWidth, int childSpecHeight, int spacing) {
+        child.measure(MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST),
+                childSpecHeight);
+
+        availableWidth -= child.getMeasuredWidth();
+        availableWidth -= spacing;
+
+        return availableWidth;
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        int x = getPaddingLeft();
+        final int y = getPaddingTop();
+        final int contentHeight = b - t - getPaddingTop() - getPaddingBottom();
+
+        if (mLogoView != null && mLogoView.getVisibility() != GONE) {
+            x += positionChild(mLogoView, x, y, contentHeight) + mSpacing;
+        }
+        if (mIconView != null && mIconView.getVisibility() != GONE) {
+            x += positionChild(mIconView, x, y, contentHeight) + mSpacing;
+        }
+        
+        switch (mNavigationMode) {
+        case ActionBar.NAVIGATION_MODE_STANDARD:
+            if (mTitleLayout != null) {
+                x += positionChild(mTitleLayout, x, y, contentHeight) + mSpacing;
+            }
+            break;
+        case ActionBar.NAVIGATION_MODE_DROPDOWN_LIST:
+            if (mSpinner != null) {
+                x += positionChild(mSpinner, x, y, contentHeight) + mSpacing;
+            }
+            break;
+        case ActionBar.NAVIGATION_MODE_CUSTOM:
+            if (mCustomNavView != null) {
+                x += positionChild(mCustomNavView, x, y, contentHeight) + mSpacing;
+            }
+            break;
+        case ActionBar.NAVIGATION_MODE_TABS:
+            if (mTabLayout != null) {
+                x += positionChild(mTabLayout, x, y, contentHeight) + mSpacing;
+            }
+        }
+
+        x = r - l - getPaddingRight();
+
+        if (mMenuView != null) {
+            x -= positionChildInverse(mMenuView, x + mActionSpacing, y, contentHeight)
+                    - mActionSpacing;
+        }
+    }
+
+    private int positionChild(View child, int x, int y, int contentHeight) {
+        int childWidth = child.getMeasuredWidth();
+        int childHeight = child.getMeasuredHeight();
+        int childTop = y + (contentHeight - childHeight) / 2;
+
+        child.layout(x, childTop, x + childWidth, childTop + childHeight);
+
+        return childWidth;
+    }
+    
+    private int positionChildInverse(View child, int x, int y, int contentHeight) {
+        int childWidth = child.getMeasuredWidth();
+        int childHeight = child.getMeasuredHeight();
+        int childTop = y + (contentHeight - childHeight) / 2;
+
+        child.layout(x - childWidth, childTop, x, childTop + childHeight);
+
+        return childWidth;
+    }
+
+    private static class TabView extends LinearLayout {
+        private ActionBar.Tab mTab;
+
+        public TabView(Context context, ActionBar.Tab tab) {
+            super(context);
+            mTab = tab;
+
+            // TODO Style tabs based on the theme
+
+            final Drawable icon = tab.getIcon();
+            final CharSequence text = tab.getText();
+
+            if (icon != null) {
+                ImageView iconView = new ImageView(context);
+                iconView.setImageDrawable(icon);
+                LayoutParams lp = new LayoutParams(LayoutParams.WRAP_CONTENT,
+                        LayoutParams.WRAP_CONTENT);
+                lp.gravity = Gravity.CENTER_VERTICAL;
+                iconView.setLayoutParams(lp);
+                addView(iconView);
+            }
+
+            if (text != null) {
+                TextView textView = new TextView(context);
+                textView.setText(text);
+                textView.setSingleLine();
+                textView.setEllipsize(TruncateAt.END);
+                LayoutParams lp = new LayoutParams(LayoutParams.WRAP_CONTENT,
+                        LayoutParams.WRAP_CONTENT);
+                lp.gravity = Gravity.CENTER_VERTICAL;
+                textView.setLayoutParams(lp);
+                addView(textView);
+            }
+
+            setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,
+                    LayoutParams.MATCH_PARENT, 1));
+        }
+
+        public ActionBar.Tab getTab() {
+            return mTab;
+        }
+    }
+
+    private class TabClickListener implements OnClickListener {
+        public void onClick(View view) {
+            TabView tabView = (TabView) view;
+            tabView.getTab().select();
+            final int tabCount = mTabLayout.getChildCount();
+            for (int i = 0; i < tabCount; i++) {
+                final View child = mTabLayout.getChildAt(i);
+                child.setSelected(child == view);
+            }
+        }
+    }
+}
diff --git a/core/java/com/android/internal/widget/ContactHeaderWidget.java b/core/java/com/android/internal/widget/ContactHeaderWidget.java
deleted file mode 100644
index f421466..0000000
--- a/core/java/com/android/internal/widget/ContactHeaderWidget.java
+++ /dev/null
@@ -1,661 +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 com.android.internal.widget;
-
-import com.android.internal.R;
-
-import android.Manifest;
-import android.content.AsyncQueryHandler;
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.res.Resources;
-import android.content.res.Resources.NotFoundException;
-import android.database.Cursor;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.net.Uri;
-import android.os.SystemClock;
-import android.provider.ContactsContract.Contacts;
-import android.provider.ContactsContract.Data;
-import android.provider.ContactsContract.PhoneLookup;
-import android.provider.ContactsContract.RawContacts;
-import android.provider.ContactsContract.StatusUpdates;
-import android.provider.ContactsContract.CommonDataKinds.Email;
-import android.provider.ContactsContract.CommonDataKinds.Photo;
-import android.text.TextUtils;
-import android.text.format.DateUtils;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.CheckBox;
-import android.widget.QuickContactBadge;
-import android.widget.FrameLayout;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-/**
- * 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.
- * <p>
- * The parent must request the {@link Manifest.permission#READ_CONTACTS}
- * permission to access contact data.
- */
-public class ContactHeaderWidget extends FrameLayout implements View.OnClickListener {
-
-    private static final String TAG = "ContactHeaderWidget";
-
-    private TextView mDisplayNameView;
-    private View mAggregateBadge;
-    private TextView mPhoneticNameView;
-    private CheckBox mStarredView;
-    private QuickContactBadge mPhotoView;
-    private ImageView mPresenceView;
-    private TextView mStatusView;
-    private TextView mStatusAttributionView;
-    private int mNoPhotoResource;
-    private QueryHandler mQueryHandler;
-
-    protected Uri mContactUri;
-
-    protected String[] mExcludeMimes = null;
-
-    protected ContentResolver mContentResolver;
-
-    /**
-     * Interface for callbacks invoked when the user interacts with a header.
-     */
-    public interface ContactHeaderListener {
-        public void onPhotoClick(View view);
-        public void onDisplayNameClick(View view);
-    }
-
-    private ContactHeaderListener mListener;
-
-
-    private interface ContactQuery {
-        //Projection used for the summary info in the header.
-        String[] COLUMNS = new String[] {
-            Contacts._ID,
-            Contacts.LOOKUP_KEY,
-            Contacts.PHOTO_ID,
-            Contacts.DISPLAY_NAME,
-            Contacts.PHONETIC_NAME,
-            Contacts.STARRED,
-            Contacts.CONTACT_PRESENCE,
-            Contacts.CONTACT_STATUS,
-            Contacts.CONTACT_STATUS_TIMESTAMP,
-            Contacts.CONTACT_STATUS_RES_PACKAGE,
-            Contacts.CONTACT_STATUS_LABEL,
-        };
-        int _ID = 0;
-        int LOOKUP_KEY = 1;
-        int PHOTO_ID = 2;
-        int DISPLAY_NAME = 3;
-        int PHONETIC_NAME = 4;
-        //TODO: We need to figure out how we're going to get the phonetic name.
-        //static final int HEADER_PHONETIC_NAME_COLUMN_INDEX
-        int STARRED = 5;
-        int CONTACT_PRESENCE_STATUS = 6;
-        int CONTACT_STATUS = 7;
-        int CONTACT_STATUS_TIMESTAMP = 8;
-        int CONTACT_STATUS_RES_PACKAGE = 9;
-        int CONTACT_STATUS_LABEL = 10;
-    }
-
-    private interface PhotoQuery {
-        String[] COLUMNS = new String[] {
-            Photo.PHOTO
-        };
-
-        int PHOTO = 0;
-    }
-
-    //Projection used for looking up contact id from phone number
-    protected static final String[] PHONE_LOOKUP_PROJECTION = new String[] {
-        PhoneLookup._ID,
-        PhoneLookup.LOOKUP_KEY,
-    };
-    protected static final int PHONE_LOOKUP_CONTACT_ID_COLUMN_INDEX = 0;
-    protected static final int PHONE_LOOKUP_CONTACT_LOOKUP_KEY_COLUMN_INDEX = 1;
-
-    //Projection used for looking up contact id from email address
-    protected static final String[] EMAIL_LOOKUP_PROJECTION = new String[] {
-        RawContacts.CONTACT_ID,
-        Contacts.LOOKUP_KEY,
-    };
-    protected static final int EMAIL_LOOKUP_CONTACT_ID_COLUMN_INDEX = 0;
-    protected static final int EMAIL_LOOKUP_CONTACT_LOOKUP_KEY_COLUMN_INDEX = 1;
-
-    protected static final String[] CONTACT_LOOKUP_PROJECTION = new String[] {
-        Contacts._ID,
-    };
-    protected static final int CONTACT_LOOKUP_ID_COLUMN_INDEX = 0;
-
-    private static final int TOKEN_CONTACT_INFO = 0;
-    private static final int TOKEN_PHONE_LOOKUP = 1;
-    private static final int TOKEN_EMAIL_LOOKUP = 2;
-    private static final int TOKEN_PHOTO_QUERY = 3;
-
-    public ContactHeaderWidget(Context context) {
-        this(context, null);
-    }
-
-    public ContactHeaderWidget(Context context, AttributeSet attrs) {
-        this(context, attrs, 0);
-    }
-
-    public ContactHeaderWidget(Context context, AttributeSet attrs, int defStyle) {
-        super(context, attrs, defStyle);
-
-        mContentResolver = mContext.getContentResolver();
-
-        LayoutInflater inflater =
-            (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
-        inflater.inflate(R.layout.contact_header, this);
-
-        mDisplayNameView = (TextView) findViewById(R.id.name);
-        mAggregateBadge = findViewById(R.id.aggregate_badge);
-
-        mPhoneticNameView = (TextView) findViewById(R.id.phonetic_name);
-
-        mStarredView = (CheckBox)findViewById(R.id.star);
-        mStarredView.setOnClickListener(this);
-
-        mPhotoView = (QuickContactBadge) findViewById(R.id.photo);
-
-        mPresenceView = (ImageView) findViewById(R.id.presence);
-
-        mStatusView = (TextView)findViewById(R.id.status);
-        mStatusAttributionView = (TextView)findViewById(R.id.status_date);
-
-        // Set the photo with a random "no contact" image
-        long now = SystemClock.elapsedRealtime();
-        int num = (int) now & 0xf;
-        if (num < 9) {
-            // Leaning in from right, common
-            mNoPhotoResource = R.drawable.ic_contact_picture;
-        } else if (num < 14) {
-            // Leaning in from left uncommon
-            mNoPhotoResource = R.drawable.ic_contact_picture_2;
-        } else {
-            // Coming in from the top, rare
-            mNoPhotoResource = R.drawable.ic_contact_picture_3;
-        }
-
-        resetAsyncQueryHandler();
-    }
-
-    public void enableClickListeners() {
-        mDisplayNameView.setOnClickListener(this);
-        mPhotoView.setOnClickListener(this);
-    }
-
-    /**
-     * Set the given {@link ContactHeaderListener} to handle header events.
-     */
-    public void setContactHeaderListener(ContactHeaderListener listener) {
-        mListener = listener;
-    }
-
-    private void performPhotoClick() {
-        if (mListener != null) {
-            mListener.onPhotoClick(mPhotoView);
-        }
-    }
-
-    private void performDisplayNameClick() {
-        if (mListener != null) {
-            mListener.onDisplayNameClick(mDisplayNameView);
-        }
-    }
-
-    private class QueryHandler extends AsyncQueryHandler {
-
-        public QueryHandler(ContentResolver cr) {
-            super(cr);
-        }
-
-        @Override
-        protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
-            try{
-                if (this != mQueryHandler) {
-                    Log.d(TAG, "onQueryComplete: discard result, the query handler is reset!");
-                    return;
-                }
-
-                switch (token) {
-                    case TOKEN_PHOTO_QUERY: {
-                        //Set the photo
-                        Bitmap photoBitmap = null;
-                        if (cursor != null && cursor.moveToFirst()
-                                && !cursor.isNull(PhotoQuery.PHOTO)) {
-                            byte[] photoData = cursor.getBlob(PhotoQuery.PHOTO);
-                            photoBitmap = BitmapFactory.decodeByteArray(photoData, 0,
-                                    photoData.length, null);
-                        }
-
-                        if (photoBitmap == null) {
-                            photoBitmap = loadPlaceholderPhoto(null);
-                        }
-                        mPhotoView.setImageBitmap(photoBitmap);
-                        if (cookie != null && cookie instanceof Uri) {
-                            mPhotoView.assignContactUri((Uri) cookie);
-                        }
-                        invalidate();
-                        break;
-                    }
-                    case TOKEN_CONTACT_INFO: {
-                        if (cursor != null && cursor.moveToFirst()) {
-                            bindContactInfo(cursor);
-                            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 */);
-                            }
-                        } else {
-                            // shouldn't really happen
-                            setDisplayName(null, null);
-                            setSocialSnippet(null);
-                            setPhoto(loadPlaceholderPhoto(null));
-                        }
-                        break;
-                    }
-                    case TOKEN_PHONE_LOOKUP: {
-                        if (cursor != null && cursor.moveToFirst()) {
-                            long contactId = cursor.getLong(PHONE_LOOKUP_CONTACT_ID_COLUMN_INDEX);
-                            String lookupKey = cursor.getString(
-                                    PHONE_LOOKUP_CONTACT_LOOKUP_KEY_COLUMN_INDEX);
-                            bindFromContactUriInternal(Contacts.getLookupUri(contactId, lookupKey),
-                                    false /* don't reset query handler */);
-                        } else {
-                            String phoneNumber = (String) cookie;
-                            setDisplayName(phoneNumber, null);
-                            setSocialSnippet(null);
-                            setPhoto(loadPlaceholderPhoto(null));
-                            mPhotoView.assignContactFromPhone(phoneNumber, true);
-                        }
-                        break;
-                    }
-                    case TOKEN_EMAIL_LOOKUP: {
-                        if (cursor != null && cursor.moveToFirst()) {
-                            long contactId = cursor.getLong(EMAIL_LOOKUP_CONTACT_ID_COLUMN_INDEX);
-                            String lookupKey = cursor.getString(
-                                    EMAIL_LOOKUP_CONTACT_LOOKUP_KEY_COLUMN_INDEX);
-                            bindFromContactUriInternal(Contacts.getLookupUri(contactId, lookupKey),
-                                    false /* don't reset query handler */);
-                        } else {
-                            String emailAddress = (String) cookie;
-                            setDisplayName(emailAddress, null);
-                            setSocialSnippet(null);
-                            setPhoto(loadPlaceholderPhoto(null));
-                            mPhotoView.assignContactFromEmail(emailAddress, true);
-                        }
-                        break;
-                    }
-                }
-            } finally {
-                if (cursor != null) {
-                    cursor.close();
-                }
-            }
-        }
-    }
-
-    /**
-     * Turn on/off showing of the aggregate badge element.
-     */
-    public void showAggregateBadge(boolean showBagde) {
-        mAggregateBadge.setVisibility(showBagde ? View.VISIBLE : View.GONE);
-    }
-
-    /**
-     * Turn on/off showing of the star element.
-     */
-    public void showStar(boolean showStar) {
-        mStarredView.setVisibility(showStar ? View.VISIBLE : View.GONE);
-    }
-
-    /**
-     * Manually set the starred state of this header widget. This doesn't change
-     * the underlying {@link Contacts} value, only the UI state.
-     */
-    public void setStared(boolean starred) {
-        mStarredView.setChecked(starred);
-    }
-
-    /**
-     * Manually set the presence.
-     */
-    public void setPresence(int presence) {
-        mPresenceView.setImageResource(StatusUpdates.getPresenceIconResourceId(presence));
-    }
-
-    /**
-     * Manually set the contact uri
-     */
-    public void setContactUri(Uri uri) {
-        setContactUri(uri, true);
-    }
-
-    /**
-     * Manually set the contact uri
-     */
-    public void setContactUri(Uri uri, boolean sendToFastrack) {
-        mContactUri = uri;
-        if (sendToFastrack) {
-            mPhotoView.assignContactUri(uri);
-        }
-    }
-
-    /**
-     * Manually set the photo to display in the header. This doesn't change the
-     * underlying {@link Contacts}, only the UI state.
-     */
-    public void setPhoto(Bitmap bitmap) {
-        mPhotoView.setImageBitmap(bitmap);
-    }
-
-    /**
-     * 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.
-     */
-    public void setDisplayName(CharSequence displayName, CharSequence phoneticName) {
-        mDisplayNameView.setText(displayName);
-        if (!TextUtils.isEmpty(phoneticName)) {
-            mPhoneticNameView.setText(phoneticName);
-            mPhoneticNameView.setVisibility(View.VISIBLE);
-        } else {
-            mPhoneticNameView.setVisibility(View.GONE);
-        }
-    }
-
-    /**
-     * Manually set the social snippet text to display in the header.
-     */
-    public void setSocialSnippet(CharSequence snippet) {
-        if (snippet == null) {
-            mStatusView.setVisibility(View.GONE);
-            mStatusAttributionView.setVisibility(View.GONE);
-        } else {
-            mStatusView.setText(snippet);
-            mStatusView.setVisibility(View.VISIBLE);
-        }
-    }
-
-    /**
-     * 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.
-     */
-    public void setExcludeMimes(String[] excludeMimes) {
-        mExcludeMimes = excludeMimes;
-        mPhotoView.setExcludeMimes(excludeMimes);
-    }
-
-    /**
-     * Convenience method for binding all available data from an existing
-     * contact.
-     *
-     * @param contactLookupUri a {Contacts.CONTENT_LOOKUP_URI} style URI.
-     */
-    public void bindFromContactLookupUri(Uri contactLookupUri) {
-        bindFromContactUriInternal(contactLookupUri, true /* reset query handler */);
-    }
-
-    /**
-     * Convenience method for binding all available data from an existing
-     * contact.
-     *
-     * @param contactUri a {Contacts.CONTENT_URI} style URI.
-     * @param resetQueryHandler whether to use a new AsyncQueryHandler or not.
-     */
-    private void bindFromContactUriInternal(Uri contactUri, boolean resetQueryHandler) {
-        mContactUri = contactUri;
-        startContactQuery(contactUri, resetQueryHandler);
-    }
-
-    /**
-     * Convenience method for binding all available data from an existing
-     * contact.
-     *
-     * @param emailAddress The email address used to do a reverse lookup in
-     * the contacts database. If more than one contact contains this email
-     * address, one of them will be chosen to bind to.
-     */
-    public void bindFromEmail(String emailAddress) {
-        resetAsyncQueryHandler();
-
-        mQueryHandler.startQuery(TOKEN_EMAIL_LOOKUP, emailAddress,
-                Uri.withAppendedPath(Email.CONTENT_LOOKUP_URI, Uri.encode(emailAddress)),
-                EMAIL_LOOKUP_PROJECTION, null, null, null);
-    }
-
-    /**
-     * Convenience method for binding all available data from an existing
-     * contact.
-     *
-     * @param number The phone number used to do a reverse lookup in
-     * the contacts database. If more than one contact contains this phone
-     * number, one of them will be chosen to bind to.
-     */
-    public void bindFromPhoneNumber(String number) {
-        resetAsyncQueryHandler();
-
-        mQueryHandler.startQuery(TOKEN_PHONE_LOOKUP, number,
-                Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(number)),
-                PHONE_LOOKUP_PROJECTION, null, null, null);
-    }
-
-    /**
-     * startContactQuery
-     *
-     * internal method to query contact by Uri.
-     *
-     * @param contactUri the contact uri
-     * @param resetQueryHandler whether to use a new AsyncQueryHandler or not
-     */
-    private void startContactQuery(Uri contactUri, boolean resetQueryHandler) {
-        if (resetQueryHandler) {
-            resetAsyncQueryHandler();
-        }
-
-        mQueryHandler.startQuery(TOKEN_CONTACT_INFO, contactUri, contactUri, ContactQuery.COLUMNS,
-                null, null, null);
-    }
-
-    /**
-     * startPhotoQuery
-     *
-     * internal method to query contact photo by photo id and uri.
-     *
-     * @param photoId the photo id.
-     * @param lookupKey the lookup uri.
-     * @param resetQueryHandler whether to use a new AsyncQueryHandler or not.
-     */
-    protected void startPhotoQuery(long photoId, Uri lookupKey, boolean resetQueryHandler) {
-        if (resetQueryHandler) {
-            resetAsyncQueryHandler();
-        }
-
-        mQueryHandler.startQuery(TOKEN_PHOTO_QUERY, lookupKey,
-                ContentUris.withAppendedId(Data.CONTENT_URI, photoId), PhotoQuery.COLUMNS,
-                null, null, null);
-    }
-
-    /**
-     * Method to force this widget to forget everything it knows about the contact.
-     * We need to stop any existing async queries for phone, email, contact, and photos.
-     */
-    public void wipeClean() {
-        resetAsyncQueryHandler();
-
-        setDisplayName(null, null);
-        setPhoto(loadPlaceholderPhoto(null));
-        setSocialSnippet(null);
-        setPresence(0);
-        mContactUri = null;
-        mExcludeMimes = null;
-    }
-
-
-    private void resetAsyncQueryHandler() {
-        // the api AsyncQueryHandler.cancelOperation() doesn't really work. Since we really
-        // need the old async queries to be cancelled, let's do it the hard way.
-        mQueryHandler = new QueryHandler(mContentResolver);
-    }
-
-    /**
-     * Bind the contact details provided by the given {@link Cursor}.
-     */
-    protected void bindContactInfo(Cursor c) {
-        final String displayName = c.getString(ContactQuery.DISPLAY_NAME);
-        final String phoneticName = c.getString(ContactQuery.PHONETIC_NAME);
-        this.setDisplayName(displayName, phoneticName);
-
-        final boolean starred = c.getInt(ContactQuery.STARRED) != 0;
-        mStarredView.setChecked(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);
-        } else {
-            mPresenceView.setVisibility(View.GONE);
-        }
-
-        //Set the status update
-        String status = c.getString(ContactQuery.CONTACT_STATUS);
-        if (!TextUtils.isEmpty(status)) {
-            mStatusView.setText(status);
-            mStatusView.setVisibility(View.VISIBLE);
-
-            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);
-        }
-    }
-
-    public void onClick(View view) {
-        switch (view.getId()) {
-            case R.id.star: {
-                // Toggle "starred" state
-                // Make sure there is a contact
-                if (mContactUri != null) {
-                    final ContentValues values = new ContentValues(1);
-                    values.put(Contacts.STARRED, mStarredView.isChecked());
-                    mContentResolver.update(mContactUri, values, null, null);
-                }
-                break;
-            }
-            case R.id.photo: {
-                performPhotoClick();
-                break;
-            }
-            case R.id.name: {
-                performDisplayNameClick();
-                break;
-            }
-        }
-    }
-
-    private Bitmap loadPlaceholderPhoto(BitmapFactory.Options options) {
-        if (mNoPhotoResource == 0) {
-            return null;
-        }
-        return BitmapFactory.decodeResource(mContext.getResources(),
-                mNoPhotoResource, options);
-    }
-}
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/java/com/android/internal/widget/EditStyledText.java b/core/java/com/android/internal/widget/EditStyledText.java
deleted file mode 100644
index 82197c0..0000000
--- a/core/java/com/android/internal/widget/EditStyledText.java
+++ /dev/null
@@ -1,1663 +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 com.android.internal.widget;
-
-import java.io.InputStream;
-import java.util.ArrayList;
-
-import android.app.AlertDialog.Builder;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.Canvas;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.ShapeDrawable;
-import android.graphics.drawable.shapes.RectShape;
-import android.net.Uri;
-import android.os.Bundle;
-import android.text.Editable;
-import android.text.Html;
-import android.text.Layout;
-import android.text.Spannable;
-import android.text.Spanned;
-import android.text.method.ArrowKeyMovementMethod;
-import android.text.style.AbsoluteSizeSpan;
-import android.text.style.AlignmentSpan;
-import android.text.style.CharacterStyle;
-import android.text.style.ForegroundColorSpan;
-import android.text.style.ImageSpan;
-import android.text.style.ParagraphStyle;
-import android.text.style.QuoteSpan;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.KeyEvent;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.inputmethod.InputMethodManager;
-import android.widget.EditText;
-import android.widget.TextView;
-
-/**
- * EditStyledText extends EditText for managing the flow and status to edit
- * the styled text. This manages the states and flows of editing, supports
- * inserting image, import/export HTML.
- */
-public class EditStyledText extends EditText {
-
-    private static final String LOG_TAG = "EditStyledText";
-    private static final boolean DBG = false;
-
-    /**
-     * The modes of editing actions.
-     */
-    /** The mode that no editing action is done. */
-    public static final int MODE_NOTHING = 0;
-    /** The mode of copy. */
-    public static final int MODE_COPY = 1;
-    /** The mode of paste. */
-    public static final int MODE_PASTE = 2;
-    /** The mode of changing size. */
-    public static final int MODE_SIZE = 3;
-    /** The mode of changing color. */
-    public static final int MODE_COLOR = 4;
-    /** The mode of selection. */
-    public static final int MODE_SELECT = 5;
-    /** The mode of changing alignment. */
-    public static final int MODE_ALIGN = 6;
-    /** The mode of changing cut. */
-    public static final int MODE_CUT = 7;
-
-    /**
-     * The state of selection.
-     */
-    /** The state that selection isn't started. */
-    public static final int STATE_SELECT_OFF = 0;
-    /** The state that selection is started. */
-    public static final int STATE_SELECT_ON = 1;
-    /** The state that selection is done, but not fixed. */
-    public static final int STATE_SELECTED = 2;
-    /** The state that selection is done and not fixed. */
-    public static final int STATE_SELECT_FIX = 3;
-
-    /**
-     * The help message strings.
-     */
-    public static final int HINT_MSG_NULL = 0;
-    public static final int HINT_MSG_COPY_BUF_BLANK = 1;
-    public static final int HINT_MSG_SELECT_START = 2;
-    public static final int HINT_MSG_SELECT_END = 3;
-    public static final int HINT_MSG_PUSH_COMPETE = 4;
-
-    
-    /**
-     * The help message strings.
-     */
-    public static final int DEFAULT_BACKGROUND_COLOR = 0x00FFFFFF;
-
-    /**
-     * EditStyledTextInterface provides functions for notifying messages to
-     * calling class.
-     */
-    public interface EditStyledTextNotifier {
-        public void notifyHintMsg(int msgId);
-        public void notifyStateChanged(int mode, int state);
-    }
-
-    private EditStyledTextNotifier mESTInterface;
-
-    /**
-     * EditStyledTextEditorManager manages the flow and status of each
-     * function for editing styled text.
-     */
-    private EditorManager mManager;
-    private StyledTextConverter mConverter;
-    private StyledTextDialog mDialog;
-    private Drawable mDefaultBackground;
-    private int mBackgroundColor;
-
-    /**
-     * EditStyledText extends EditText for managing flow of each editing
-     * action.
-     */
-    public EditStyledText(Context context, AttributeSet attrs, int defStyle) {
-        super(context, attrs, defStyle);
-        init();
-    }
-
-    public EditStyledText(Context context, AttributeSet attrs) {
-        super(context, attrs);
-        init();
-    }
-
-    public EditStyledText(Context context) {
-        super(context);
-        init();
-    }
-
-    /**
-     * Set Notifier.
-     */
-    public void setNotifier(EditStyledTextNotifier estInterface) {
-        mESTInterface = estInterface;
-    }
-
-    /**
-     * Set Builder for AlertDialog.
-     * 
-     * @param builder
-     *            Builder for opening Alert Dialog.
-     */
-    public void setBuilder(Builder builder) {
-        mDialog.setBuilder(builder);
-    }
-
-    /**
-     * Set Parameters for ColorAlertDialog.
-     * 
-     * @param colortitle
-     *            Title for Alert Dialog.
-     * @param colornames
-     *            List of name of selecting color.
-     * @param colorints
-     *            List of int of color.
-     */
-    public void setColorAlertParams(CharSequence colortitle,
-            CharSequence[] colornames, CharSequence[] colorints) {
-        mDialog.setColorAlertParams(colortitle, colornames, colorints);
-    }
-
-    /**
-     * Set Parameters for SizeAlertDialog.
-     * 
-     * @param sizetitle
-     *            Title for Alert Dialog.
-     * @param sizenames
-     *            List of name of selecting size.
-     * @param sizedisplayints
-     *            List of int of size displayed in TextView.
-     * @param sizesendints
-     *            List of int of size exported to HTML.
-     */
-    public void setSizeAlertParams(CharSequence sizetitle,
-            CharSequence[] sizenames, CharSequence[] sizedisplayints,
-            CharSequence[] sizesendints) {
-        mDialog.setSizeAlertParams(sizetitle, sizenames, sizedisplayints,
-                sizesendints);
-    }
-
-    public void setAlignAlertParams(CharSequence aligntitle,
-            CharSequence[] alignnames) {
-        mDialog.setAlignAlertParams(aligntitle, alignnames);
-    }
-
-    @Override
-    public boolean onTouchEvent(MotionEvent event) {
-        if (mManager.isSoftKeyBlocked() &&
-                event.getAction() == MotionEvent.ACTION_UP) {
-            cancelLongPress();
-        }
-        final boolean superResult = super.onTouchEvent(event);
-        if (event.getAction() == MotionEvent.ACTION_UP) {
-            if (DBG) {
-                Log.d(LOG_TAG, "--- onTouchEvent");
-            }
-            mManager.onCursorMoved();
-        }
-        return superResult;
-    }
-
-    /**
-     * Start editing. This function have to be called before other editing
-     * actions.
-     */
-    public void onStartEdit() {
-        mManager.onStartEdit();
-    }
-
-    /**
-     * End editing.
-     */
-    public void onEndEdit() {
-        mManager.onEndEdit();
-    }
-
-    /**
-     * Start "Copy" action.
-     */
-    public void onStartCopy() {
-        mManager.onStartCopy();
-    }
-
-    /**
-     * Start "Cut" action.
-     */
-    public void onStartCut() {
-        mManager.onStartCut();
-    }
-
-    /**
-     * Start "Paste" action.
-     */
-    public void onStartPaste() {
-        mManager.onStartPaste();
-    }
-
-    /**
-     * Start changing "Size" action.
-     */
-    public void onStartSize() {
-        mManager.onStartSize();
-    }
-
-    /**
-     * Start changing "Color" action.
-     */
-    public void onStartColor() {
-        mManager.onStartColor();
-    }
-
-    /**
-     * Start changing "BackgroundColor" action.
-     */
-    public void onStartBackgroundColor() {
-        mManager.onStartBackgroundColor();
-    }
-
-    /**
-     * Start changing "Alignment" action.
-     */
-    public void onStartAlign() {
-        mManager.onStartAlign();
-    }
-
-    /**
-     * Start "Select" action.
-     */
-    public void onStartSelect() {
-        mManager.onStartSelect();
-    }
-
-    /**
-     * Start "SelectAll" action.
-     */
-    public void onStartSelectAll() {
-        mManager.onStartSelectAll();
-    }
-
-    /**
-     * Fix Selected Item.
-     */
-    public void onFixSelectedItem() {
-        mManager.onFixSelectedItem();
-    }
-
-    /**
-     * InsertImage to TextView by using URI
-     * 
-     * @param uri
-     *            URI of the iamge inserted to TextView.
-     */
-    public void onInsertImage(Uri uri) {
-        mManager.onInsertImage(uri);
-    }
-
-    /**
-     * InsertImage to TextView by using resource ID
-     * 
-     * @param resId
-     *            Resource ID of the iamge inserted to TextView.
-     */
-    public void onInsertImage(int resId) {
-        mManager.onInsertImage(resId);
-    }
-
-    public void onInsertHorizontalLine() {
-        mManager.onInsertHorizontalLine();
-    }
-
-    public void onClearStyles() {
-        mManager.onClearStyles();
-    }
-    /**
-     * Set Size of the Item.
-     * 
-     * @param size
-     *            The size of the Item.
-     */
-    public void setItemSize(int size) {
-        mManager.setItemSize(size);
-    }
-
-    /**
-     * Set Color of the Item.
-     * 
-     * @param color
-     *            The color of the Item.
-     */
-    public void setItemColor(int color) {
-        mManager.setItemColor(color);
-    }
-
-    /**
-     * Set Alignment of the Item.
-     * 
-     * @param color
-     *            The color of the Item.
-     */
-    public void setAlignment(Layout.Alignment align) {
-        mManager.setAlignment(align);
-    }
-
-    /**
-     * Set Background color of View.
-     * 
-     * @param color
-     *            The background color of view.
-     */
-    @Override
-    public void setBackgroundColor(int color) {
-        super.setBackgroundColor(color);
-        mBackgroundColor = color;
-    }
-
-    /**
-     * Set html to EditStyledText.
-     * 
-     * @param html
-     *            The html to be set.
-     */
-    public void setHtml(String html) {
-        mConverter.SetHtml(html);
-    }
-    /**
-     * Check whether editing is started or not.
-     * 
-     * @return Whether editing is started or not.
-     */
-    public boolean isEditting() {
-        return mManager.isEditting();
-    }
-
-    /**
-     * Check whether styled text or not.
-     * 
-     * @return Whether styled text or not.
-     */
-    public boolean isStyledText() {
-        return mManager.isStyledText();
-    }
-    /**
-     * Check whether SoftKey is Blocked or not.
-     * 
-     * @return whether SoftKey is Blocked or not.
-     */
-    public boolean isSoftKeyBlocked() {
-        return mManager.isSoftKeyBlocked();
-    }
-
-    /**
-     * Get the mode of the action.
-     * 
-     * @return The mode of the action.
-     */
-    public int getEditMode() {
-        return mManager.getEditMode();
-    }
-
-    /**
-     * Get the state of the selection.
-     * 
-     * @return The state of the selection.
-     */
-    public int getSelectState() {
-        return mManager.getSelectState();
-    }
-
-    @Override
-    public Bundle getInputExtras(boolean create) {
-        if (DBG) {
-            Log.d(LOG_TAG, "---getInputExtras");
-        }
-        Bundle bundle = super.getInputExtras(create);
-        if (bundle != null) {
-            bundle = new Bundle();
-        }
-        bundle.putBoolean("allowEmoji", true);
-        return bundle;
-    }
-
-    /**
-     * Get the state of the selection.
-     * 
-     * @return The state of the selection.
-     */
-    public String getHtml() {
-        return mConverter.getHtml();
-    }
-
-    /**
-     * Get the state of the selection.
-     * 
-     * @param uris
-     *            The array of used uris.
-     * @return The state of the selection.
-     */
-    public String getHtml(ArrayList<Uri> uris) {
-        mConverter.getUriArray(uris, getText());
-        return mConverter.getHtml();
-    }
-
-    /**
-     * Get Background color of View.
-     * 
-     * @return The background color of View.
-     */
-    public int getBackgroundColor() {
-        return mBackgroundColor;
-    }
-
-    /**
-     * Get Foreground color of View.
-     * 
-     * @return The background color of View.
-     */
-    public int getForeGroundColor(int pos) {
-        if (DBG) {
-            Log.d(LOG_TAG, "---getForeGroundColor: " + pos);
-        }
-        if (pos < 0 || pos > getText().length()) {
-            Log.e(LOG_TAG, "---getForeGroundColor: Illigal position.");
-            return DEFAULT_BACKGROUND_COLOR;
-        } else {
-            ForegroundColorSpan[] spans =
-                getText().getSpans(pos, pos, ForegroundColorSpan.class);
-            if (spans.length > 0) {
-                return spans[0].getForegroundColor();
-            } else {
-                return DEFAULT_BACKGROUND_COLOR;
-            }
-        }
-    }
-
-    /**
-     * Initialize members.
-     */
-    private void init() {
-        if (DBG) {
-            Log.d(LOG_TAG, "--- init");
-        }
-        requestFocus();
-        mDefaultBackground = getBackground();
-        mBackgroundColor = DEFAULT_BACKGROUND_COLOR;
-        mManager = new EditorManager(this);
-        mConverter = new StyledTextConverter(this);
-        mDialog = new StyledTextDialog(this);
-        setMovementMethod(new StyledTextArrowKeyMethod(mManager));
-        mManager.blockSoftKey();
-        mManager.unblockSoftKey();
-    }
-
-    /**
-     * Show Foreground Color Selecting Dialog.
-     */
-    private void onShowForegroundColorAlert() {
-        mDialog.onShowForegroundColorAlertDialog();
-    }
-
-    /**
-     * Show Background Color Selecting Dialog.
-     */
-    private void onShowBackgroundColorAlert() {
-        mDialog.onShowBackgroundColorAlertDialog();
-    }
-
-    /**
-     * Show Size Selecting Dialog.
-     */
-    private void onShowSizeAlert() {
-        mDialog.onShowSizeAlertDialog();
-    }
-
-    /**
-     * Show Alignment Selecting Dialog.
-     */
-    private void onShowAlignAlert() {
-        mDialog.onShowAlignAlertDialog();
-    }
-
-    /**
-     * Notify hint messages what action is expected to calling class.
-     * 
-     * @param msgId
-     *            Id of the hint message.
-     */
-    private void setHintMessage(int msgId) {
-        if (mESTInterface != null) {
-            mESTInterface.notifyHintMsg(msgId);
-        }
-    }
-
-    /**
-     * Notify the event that the mode and state are changed.
-     * 
-     * @param mode
-     *            Mode of the editing action.
-     * @param state
-     *            Mode of the selection state.
-     */
-    private void notifyStateChanged(int mode, int state) {
-        if (mESTInterface != null) {
-            mESTInterface.notifyStateChanged(mode, state);
-        }
-    }
-
-    /**
-     * EditorManager manages the flow and status of editing actions.
-     */
-    private class EditorManager {
-        private boolean mEditFlag = false;
-        private boolean mSoftKeyBlockFlag = false;
-        private int mMode = 0;
-        private int mState = 0;
-        private int mCurStart = 0;
-        private int mCurEnd = 0;
-        private EditStyledText mEST;
-
-        EditorManager(EditStyledText est) {
-            mEST = est;
-        }
-
-        public void onStartEdit() {
-            if (DBG) {
-                Log.d(LOG_TAG, "--- onStartEdit");
-            }
-            Log.d(LOG_TAG, "--- onstartedit:");
-            handleResetEdit();
-            mEST.notifyStateChanged(mMode, mState);
-        }
-
-        public void onEndEdit() {
-            if (DBG) {
-                Log.d(LOG_TAG, "--- onEndEdit");
-            }
-            handleCancel();
-            mEST.notifyStateChanged(mMode, mState);
-        }
-
-        public void onStartCopy() {
-            if (DBG) {
-                Log.d(LOG_TAG, "--- onStartCopy");
-            }
-            handleCopy();
-            mEST.notifyStateChanged(mMode, mState);
-        }
-
-        public void onStartCut() {
-            if (DBG) {
-                Log.d(LOG_TAG, "--- onStartCut");
-            }
-            handleCut();
-            mEST.notifyStateChanged(mMode, mState);
-        }
-
-        public void onStartPaste() {
-            if (DBG) {
-                Log.d(LOG_TAG, "--- onStartPaste");
-            }
-            handlePaste();
-            mEST.notifyStateChanged(mMode, mState);
-        }
-
-        public void onStartSize() {
-            if (DBG) {
-                Log.d(LOG_TAG, "--- onStartSize");
-            }
-            handleSize();
-            mEST.notifyStateChanged(mMode, mState);
-        }
-
-        public void onStartAlign() {
-            if (DBG) {
-                Log.d(LOG_TAG, "--- onStartAlignRight");
-            }
-            handleAlign();
-            mEST.notifyStateChanged(mMode, mState);
-        }
-
-        public void onStartColor() {
-            if (DBG) {
-                Log.d(LOG_TAG, "--- onClickColor");
-            }
-            handleColor();
-            mEST.notifyStateChanged(mMode, mState);
-        }
-
-        public void onStartBackgroundColor() {
-            if (DBG) {
-                Log.d(LOG_TAG, "--- onClickColor");
-            }
-            mEST.onShowBackgroundColorAlert();
-            mEST.notifyStateChanged(mMode, mState);
-        }
-
-        public void onStartSelect() {
-            if (DBG) {
-                Log.d(LOG_TAG, "--- onClickSelect");
-            }
-            mMode = MODE_SELECT;
-            if (mState == STATE_SELECT_OFF) {
-                handleSelect();
-            } else {
-                unsetSelect();
-                handleSelect();
-            }
-            mEST.notifyStateChanged(mMode, mState);
-        }
-
-        public void onCursorMoved() {
-            if (DBG) {
-                Log.d(LOG_TAG, "--- onClickView");
-            }
-            if (mState == STATE_SELECT_ON || mState == STATE_SELECTED) {
-                handleSelect();
-                mEST.notifyStateChanged(mMode, mState);
-            }
-        }
-
-        public void onStartSelectAll() {
-            if (DBG) {
-                Log.d(LOG_TAG, "--- onClickSelectAll");
-            }
-            handleSelectAll();
-            mEST.notifyStateChanged(mMode, mState);
-        }
-
-        public void onFixSelectedItem() {
-            if (DBG) {
-                Log.d(LOG_TAG, "--- onClickComplete");
-            }
-            handleComplete();
-            mEST.notifyStateChanged(mMode, mState);
-        }
-
-        public void onInsertImage(Uri uri) {
-            if (DBG) {
-                Log.d(LOG_TAG, "--- onInsertImage by URI: " + uri.getPath()
-                        + "," + uri.toString());
-            }
-            insertImageSpan(new ImageSpan(mEST.getContext(), uri));
-            mEST.notifyStateChanged(mMode, mState);
-        }
-
-        public void onInsertImage(int resID) {
-            if (DBG) {
-                Log.d(LOG_TAG, "--- onInsertImage by resID");
-            }
-            insertImageSpan(new ImageSpan(mEST.getContext(), resID));
-            mEST.notifyStateChanged(mMode, mState);
-        }
-
-        public void onInsertHorizontalLine() {
-            if (DBG) {
-                Log.d(LOG_TAG, "--- onInsertHorizontalLine:");
-            }
-            insertImageSpan(new HorizontalLineSpan(0xFF000000, mEST));
-            mEST.notifyStateChanged(mMode, mState);
-        }
-
-        public void onClearStyles() {
-            if (DBG) {
-                Log.d(LOG_TAG, "--- onClearStyles");
-            }
-            Editable txt = mEST.getText();
-            int len = txt.length();
-            Object[] styles = txt.getSpans(0, len, Object.class);
-            for (Object style : styles) {
-                if (style instanceof ParagraphStyle ||
-                        style instanceof QuoteSpan ||
-                        style instanceof CharacterStyle) {
-                    if (style instanceof ImageSpan) {
-                        int start = txt.getSpanStart(style);
-                        int end = txt.getSpanEnd(style);
-                        txt.replace(start, end, "");
-                    }
-                    txt.removeSpan(style);
-                }
-            }
-            mEST.setBackgroundDrawable(mEST.mDefaultBackground);
-            mEST.mBackgroundColor = DEFAULT_BACKGROUND_COLOR;
-        }
-
-        public void setItemSize(int size) {
-            if (DBG) {
-                Log.d(LOG_TAG, "--- onClickSizeItem");
-            }
-            if (mState == STATE_SELECTED || mState == STATE_SELECT_FIX) {
-                changeSizeSelectedText(size);
-                handleResetEdit();
-            }
-        }
-
-        public void setItemColor(int color) {
-            if (DBG) {
-                Log.d(LOG_TAG, "--- onClickColorItem");
-            }
-            if (mState == STATE_SELECTED || mState == STATE_SELECT_FIX) {
-                changeColorSelectedText(color);
-                handleResetEdit();
-            }
-        }
-
-        public void setAlignment(Layout.Alignment align) {
-            if (DBG) {
-                Log.d(LOG_TAG, "--- onClickColorItem");
-            }
-            if (mState == STATE_SELECTED || mState == STATE_SELECT_FIX) {
-                changeAlign(align);
-                handleResetEdit();
-            }
-        }
-
-        public boolean isEditting() {
-            return mEditFlag;
-        }
-
-        /* If the style of the span is added, add check case for that style */
-        public boolean isStyledText() {
-            Editable txt = mEST.getText();
-            int len = txt.length();
-            if (txt.getSpans(0, len -1, ParagraphStyle.class).length > 0 ||
-                    txt.getSpans(0, len -1, QuoteSpan.class).length > 0 ||
-                    txt.getSpans(0, len -1, CharacterStyle.class).length > 0 ||
-                    mEST.mBackgroundColor != DEFAULT_BACKGROUND_COLOR) {
-                return true;
-            }
-            return false;
-        }
-
-        public boolean isSoftKeyBlocked() {
-            return mSoftKeyBlockFlag;
-        }
-
-        public int getEditMode() {
-            return mMode;
-        }
-
-        public int getSelectState() {
-            return mState;
-        }
-
-        public int getSelectionStart() {
-            return mCurStart;
-        }
-
-        public int getSelectionEnd() {
-            return mCurEnd;
-        }
-
-        private void doNextHandle() {
-            if (DBG) {
-                Log.d(LOG_TAG, "--- doNextHandle: " + mMode + "," + mState);
-            }
-            switch (mMode) {
-            case MODE_COPY:
-                handleCopy();
-                break;
-            case MODE_CUT:
-                handleCut();
-                break;
-            case MODE_PASTE:
-                handlePaste();
-                break;
-            case MODE_SIZE:
-                handleSize();
-                break;
-            case MODE_COLOR:
-                handleColor();
-                break;
-            case MODE_ALIGN:
-                handleAlign();
-                break;
-            default:
-                break;
-            }
-        }
-
-        private void handleCancel() {
-            if (DBG) {
-                Log.d(LOG_TAG, "--- handleCancel");
-            }
-            mMode = MODE_NOTHING;
-            mState = STATE_SELECT_OFF;
-            mEditFlag = false;
-            Log.d(LOG_TAG, "--- handleCancel:" + mEST.getInputType());
-            unblockSoftKey();
-            unsetSelect();
-        }
-
-        private void handleComplete() {
-            if (DBG) {
-                Log.d(LOG_TAG, "--- handleComplete");
-            }
-            if (!mEditFlag) {
-                return;
-            }
-            if (mState == STATE_SELECTED) {
-                mState = STATE_SELECT_FIX;
-            }
-            doNextHandle();
-        }
-
-        private void handleTextViewFunc(int mode, int id) {
-            if (DBG) {
-                Log.d(LOG_TAG, "--- handleTextView: " + mMode + "," + mState +
-                        "," + id);
-            }
-            if (!mEditFlag) {
-                return;
-            }
-            if (mMode == MODE_NOTHING || mMode == MODE_SELECT) {
-                mMode = mode;
-                if (mState == STATE_SELECTED) {
-                    mState = STATE_SELECT_FIX;
-                    handleTextViewFunc(mode, id);
-                } else {
-                    handleSelect();
-                }
-            } else if (mMode != mode) {
-                handleCancel();
-                mMode = mode;
-                handleTextViewFunc(mode, id);
-            } else if (mState == STATE_SELECT_FIX) {
-                mEST.onTextContextMenuItem(id);
-                handleResetEdit();
-            }
-        }
-
-        private void handleCopy() {
-            if (DBG) {
-                Log.d(LOG_TAG, "--- handleCopy: " + mMode + "," + mState);
-            }
-            handleTextViewFunc(MODE_COPY, android.R.id.copy);
-        }
-
-        private void handleCut() {
-            if (DBG) {
-                Log.d(LOG_TAG, "--- handleCopy: " + mMode + "," + mState);
-            }
-            handleTextViewFunc(MODE_CUT, android.R.id.cut);
-        }
-
-        private void handlePaste() {
-            if (DBG) {
-                Log.d(LOG_TAG, "--- handlePaste");
-            }
-            if (!mEditFlag) {
-                return;
-            }
-            mEST.onTextContextMenuItem(android.R.id.paste);
-        }
-
-        private void handleSetSpan(int mode) {
-            if (DBG) {
-                Log.d(LOG_TAG, "--- handleSetSpan:" + mEditFlag + ","
-                        + mState + ',' + mMode);
-            }
-            if (!mEditFlag) {
-                Log.e(LOG_TAG, "--- handleSetSpan: Editing is not started.");
-                return;
-            }
-            if (mMode == MODE_NOTHING || mMode == MODE_SELECT) {
-                mMode = mode;
-                if (mState == STATE_SELECTED) {
-                    mState = STATE_SELECT_FIX;
-                    handleSetSpan(mode);
-                } else {
-                    handleSelect();
-                }
-            } else if (mMode != mode) {
-                handleCancel();
-                mMode = mode;
-                handleSetSpan(mode);
-            } else {
-                if (mState == STATE_SELECT_FIX) {
-                    mEST.setHintMessage(HINT_MSG_NULL);
-                    switch (mode) {
-                    case MODE_COLOR:
-                        mEST.onShowForegroundColorAlert();
-                        break;
-                    case MODE_SIZE:
-                        mEST.onShowSizeAlert();
-                        break;
-                    case MODE_ALIGN:
-                        mEST.onShowAlignAlert();
-                        break;
-                    default:
-                        Log.e(LOG_TAG, "--- handleSetSpan: invalid mode.");
-                        break;
-                    }
-                } else {
-                    Log.d(LOG_TAG, "--- handleSetSpan: do nothing.");
-                }
-            }
-        }
-
-        private void handleSize() {
-            handleSetSpan(MODE_SIZE);
-        }
-
-        private void handleColor() {
-            handleSetSpan(MODE_COLOR);
-        }
-
-        private void handleAlign() {
-            handleSetSpan(MODE_ALIGN);
-        }
-
-        private void handleSelect() {
-            if (DBG) {
-                Log.d(LOG_TAG, "--- handleSelect:" + mEditFlag + "," + mState);
-            }
-            if (!mEditFlag) {
-                return;
-            }
-            if (mState == STATE_SELECT_OFF) {
-                if (isTextSelected()) {
-                    Log.e(LOG_TAG, "Selection is off, but selected");
-                }
-                setSelectStartPos();
-                blockSoftKey();
-                mEST.setHintMessage(HINT_MSG_SELECT_END);
-            } else if (mState == STATE_SELECT_ON) {
-                if (isTextSelected()) {
-                    Log.e(LOG_TAG, "Selection now start, but selected");
-                }
-                setSelectedEndPos();
-                mEST.setHintMessage(HINT_MSG_PUSH_COMPETE);
-                doNextHandle();
-            } else if (mState == STATE_SELECTED) {
-                if (!isTextSelected()) {
-                    Log.e(LOG_TAG, "Selection is done, but not selected");
-                }
-                setSelectedEndPos();
-                doNextHandle();
-            }
-        }
-
-        private void handleSelectAll() {
-            if (DBG) {
-                Log.d(LOG_TAG, "--- handleSelectAll");
-            }
-            if (!mEditFlag) {
-                return;
-            }
-            mEST.selectAll();
-            mState = STATE_SELECTED;
-        }
-
-        private void handleResetEdit() {
-            if (DBG) {
-                Log.d(LOG_TAG, "Reset Editor");
-            }
-            blockSoftKey();
-            handleCancel();
-            mEditFlag = true;
-            mEST.setHintMessage(HINT_MSG_SELECT_START);
-        }
-
-        private void setSelection() {
-            if (DBG) {
-                Log.d(LOG_TAG, "--- onSelect:" + mCurStart + "," + mCurEnd);
-            }
-            if (mCurStart >= 0 && mCurStart <= mEST.getText().length()
-                    && mCurEnd >= 0 && mCurEnd <= mEST.getText().length()) {
-                if (mCurStart < mCurEnd) {
-                    mEST.setSelection(mCurStart, mCurEnd);
-                } else {
-                    mEST.setSelection(mCurEnd, mCurStart);
-                }
-                mState = STATE_SELECTED;
-            } else {
-                Log.e(LOG_TAG,
-                        "Select is on, but cursor positions are illigal.:"
-                                + mEST.getText().length() + "," + mCurStart
-                                + "," + mCurEnd);
-            }
-        }
-
-        private void unsetSelect() {
-            if (DBG) {
-                Log.d(LOG_TAG, "--- offSelect");
-            }
-            int currpos = mEST.getSelectionStart();
-            mEST.setSelection(currpos, currpos);
-            mState = STATE_SELECT_OFF;
-        }
-
-        private void setSelectStartPos() {
-            if (DBG) {
-                Log.d(LOG_TAG, "--- setSelectStartPos");
-            }
-            mCurStart = mEST.getSelectionStart();
-            mState = STATE_SELECT_ON;
-        }
-
-        private void setSelectedEndPos() {
-            if (DBG) {
-                Log.d(LOG_TAG, "--- setSelectEndPos:");
-            }
-            if (mEST.getSelectionStart() == mCurStart) {
-                setSelectedEndPos(mEST.getSelectionEnd());
-            } else {
-                setSelectedEndPos(mEST.getSelectionStart());
-            }
-        }
-
-        public void setSelectedEndPos(int pos) {
-            if (DBG) {
-                Log.d(LOG_TAG, "--- setSelectedEndPos:");
-            }
-            mCurEnd = pos;
-            setSelection();
-        }
-
-        private boolean isTextSelected() {
-            if (DBG) {
-                Log.d(LOG_TAG, "--- isTextSelected:" + mCurStart + ","
-                        + mCurEnd);
-            }
-            return (mCurStart != mCurEnd)
-                    && (mState == STATE_SELECTED ||
-                            mState == STATE_SELECT_FIX);
-        }
-
-        private void setStyledTextSpan(Object span, int start, int end) {
-            if (DBG) {
-                Log.d(LOG_TAG, "--- setStyledTextSpan:" + mMode + ","
-                        + start + "," + end);
-            }
-            if (start < end) {
-                mEST.getText().setSpan(span, start, end,
-                        Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
-            } else {
-                mEST.getText().setSpan(span, end, start,
-                        Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
-            }
-        }
-
-        private void changeSizeSelectedText(int size) {
-            if (DBG) {
-                Log.d(LOG_TAG, "--- changeSize:" + size);
-            }
-            setStyledTextSpan(new AbsoluteSizeSpan(size),
-                mCurStart, mCurEnd);
-        }
-
-        private void changeColorSelectedText(int color) {
-            if (DBG) {
-                Log.d(LOG_TAG, "--- changeColor:" + color);
-            }
-            setStyledTextSpan(new ForegroundColorSpan(color),
-                mCurStart, mCurEnd);
-        }
-
-        private void changeAlign(Layout.Alignment align) {
-            if (DBG) {
-                Log.d(LOG_TAG, "--- changeAlign:" + align);
-            }
-            setStyledTextSpan(new AlignmentSpan.Standard(align),
-                    findLineStart(mEST.getText(), mCurStart),
-                    findLineEnd(mEST.getText(), mCurEnd));
-        }
-
-        private int findLineStart(Editable text, int current) {
-            if (DBG) {
-                Log.d(LOG_TAG, "--- findLineStart: curr:" + current +
-                        ", length:" + text.length());
-            }
-            int pos = current;
-            for (; pos > 0; pos--) {
-                if (text.charAt(pos - 1) == '\n') {
-                    break;
-                }
-            }
-            return pos;
-        }
-
-        private void insertImageSpan(ImageSpan span) {
-            if (DBG) {
-                Log.d(LOG_TAG, "--- insertImageSpan");
-            }
-            if (span != null) {
-                Log.d(LOG_TAG, "--- insertimagespan:" + span.getDrawable().getIntrinsicHeight() + "," + span.getDrawable().getIntrinsicWidth());
-                Log.d(LOG_TAG, "--- insertimagespan:" + span.getDrawable().getClass());
-                int curpos = mEST.getSelectionStart();
-                mEST.getText().insert(curpos, "\uFFFC");
-                mEST.getText().setSpan(span, curpos, curpos + 1,
-                        Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
-                mEST.notifyStateChanged(mMode, mState);
-            } else {
-                Log.e(LOG_TAG, "--- insertImageSpan: null span was inserted");
-            }
-        }
-
-        private int findLineEnd(Editable text, int current) {
-            if (DBG) {
-                Log.d(LOG_TAG, "--- findLineEnd: curr:" + current +
-                        ", length:" + text.length());
-            }
-            int pos = current;
-            for (; pos < text.length(); pos++) {
-                if (pos > 0 && text.charAt(pos - 1) == '\n') {
-                    break;
-                }
-            }
-            return pos;
-        }
-
-        private void blockSoftKey() {
-            if (DBG) {
-                Log.d(LOG_TAG, "--- blockSoftKey:");
-            }
-            InputMethodManager imm = (InputMethodManager) mEST.getContext().
-            getSystemService(Context.INPUT_METHOD_SERVICE);
-            imm.hideSoftInputFromWindow(mEST.getWindowToken(), 0);
-            mEST.setOnClickListener(
-                    new OnClickListener() {
-                        public void onClick(View v) {
-                            Log.d(LOG_TAG, "--- ontrackballclick:");
-                            onFixSelectedItem();
-                        }
-            });
-            mSoftKeyBlockFlag = true;
-        }
-
-        private void unblockSoftKey() {
-            if (DBG) {
-                Log.d(LOG_TAG, "--- unblockSoftKey:");
-            }
-            mEST.setOnClickListener(null);
-            mSoftKeyBlockFlag = false;
-        }
-    }
-
-    private class StyledTextConverter {
-        private EditStyledText mEST;
-
-        public StyledTextConverter(EditStyledText est) {
-            mEST = est;
-        }
-
-        public String getHtml() {
-            String htmlBody = Html.toHtml(mEST.getText());
-            if (DBG) {
-                Log.d(LOG_TAG, "--- getConvertedBody:" + htmlBody);
-            }
-            return htmlBody;
-        }
-
-        public void getUriArray(ArrayList<Uri> uris, Editable text) {
-            uris.clear();
-            if (DBG) {
-                Log.d(LOG_TAG, "--- getUriArray:");
-            }
-            int len = text.length();
-            int next;
-            for (int i = 0; i < text.length(); i = next) {
-                next = text.nextSpanTransition(i, len, ImageSpan.class);
-                ImageSpan[] images = text.getSpans(i, next, ImageSpan.class);
-                for (int j = 0; j < images.length; j++) {
-                    if (DBG) {
-                        Log.d(LOG_TAG, "--- getUriArray: foundArray" +
-                                ((ImageSpan) images[j]).getSource());
-                    }
-                    uris.add(Uri.parse(
-                            ((ImageSpan) images[j]).getSource()));
-                }
-            }
-        }
-
-        public void SetHtml (String html) {
-            final Spanned spanned = Html.fromHtml(html, new Html.ImageGetter() {
-                public Drawable getDrawable(String src) {
-                    Log.d(LOG_TAG, "--- sethtml: src="+src);
-                    if (src.startsWith("content://")) {
-                        Uri uri = Uri.parse(src);
-                        try {
-                            InputStream is = mEST.getContext().getContentResolver().openInputStream(uri);
-                            Bitmap bitmap = BitmapFactory.decodeStream(is);
-                            Drawable drawable = new BitmapDrawable(
-                                    getContext().getResources(), bitmap);
-                            drawable.setBounds(0, 0,
-                                    drawable.getIntrinsicWidth(),
-                                    drawable.getIntrinsicHeight());
-                            is.close();
-                            return drawable;
-                        } catch (Exception e) {
-                            Log.e(LOG_TAG, "--- set html: Failed to loaded content " + uri, e);
-                            return null;
-                        }
-                    }
-                    Log.d(LOG_TAG, "  unknown src="+src);
-                    return null;
-                }
-            }, null);
-            mEST.setText(spanned);
-        }
-    }
-
-    private class StyledTextDialog {
-        Builder mBuilder;
-        CharSequence mColorTitle;
-        CharSequence mSizeTitle;
-        CharSequence mAlignTitle;
-        CharSequence[] mColorNames;
-        CharSequence[] mColorInts;
-        CharSequence[] mSizeNames;
-        CharSequence[] mSizeDisplayInts;
-        CharSequence[] mSizeSendInts;
-        CharSequence[] mAlignNames;
-        EditStyledText mEST;
-
-        public StyledTextDialog(EditStyledText est) {
-            mEST = est;
-        }
-
-        public void setBuilder(Builder builder) {
-            mBuilder = builder;
-        }
-
-        public void setColorAlertParams(CharSequence colortitle,
-                CharSequence[] colornames, CharSequence[] colorints) {
-            mColorTitle = colortitle;
-            mColorNames = colornames;
-            mColorInts = colorints;
-        }
-
-        public void setSizeAlertParams(CharSequence sizetitle,
-                CharSequence[] sizenames, CharSequence[] sizedisplayints,
-                CharSequence[] sizesendints) {
-            mSizeTitle = sizetitle;
-            mSizeNames = sizenames;
-            mSizeDisplayInts = sizedisplayints;
-            mSizeSendInts = sizesendints;
-        }
-
-        public void setAlignAlertParams(CharSequence aligntitle,
-                CharSequence[] alignnames) {
-            mAlignTitle = aligntitle;
-            mAlignNames = alignnames;
-        }
-
-        private boolean checkColorAlertParams() {
-            if (DBG) {
-                Log.d(LOG_TAG, "--- checkParams");
-            }
-            if (mBuilder == null) {
-                Log.e(LOG_TAG, "--- builder is null.");
-                return false;
-            } else if (mColorTitle == null || mColorNames == null
-                    || mColorInts == null) {
-                Log.e(LOG_TAG, "--- color alert params are null.");
-                return false;
-            } else if (mColorNames.length != mColorInts.length) {
-                Log.e(LOG_TAG, "--- the length of color alert params are "
-                        + "different.");
-                return false;
-            }
-            return true;
-        }
-
-        private boolean checkSizeAlertParams() {
-            if (DBG) {
-                Log.d(LOG_TAG, "--- checkParams");
-            }
-            if (mBuilder == null) {
-                Log.e(LOG_TAG, "--- builder is null.");
-                return false;
-            } else if (mSizeTitle == null || mSizeNames == null
-                    || mSizeDisplayInts == null || mSizeSendInts == null) {
-                Log.e(LOG_TAG, "--- size alert params are null.");
-                return false;
-            } else if (mSizeNames.length != mSizeDisplayInts.length
-                    && mSizeSendInts.length != mSizeDisplayInts.length) {
-                Log.e(LOG_TAG, "--- the length of size alert params are "
-                        + "different.");
-                return false;
-            }
-            return true;
-        }
-
-        private boolean checkAlignAlertParams() {
-            if (DBG) {
-                Log.d(LOG_TAG, "--- checkAlignAlertParams");
-            }
-            if (mBuilder == null) {
-                Log.e(LOG_TAG, "--- builder is null.");
-                return false;
-            } else if (mAlignTitle == null) {
-                Log.e(LOG_TAG, "--- align alert params are null.");
-                return false;
-            }
-            return true;
-        }
-
-        private void onShowForegroundColorAlertDialog() {
-            if (DBG) {
-                Log.d(LOG_TAG, "--- onShowForegroundColorAlertDialog");
-            }
-            if (!checkColorAlertParams()) {
-                return;
-            }
-            mBuilder.setTitle(mColorTitle);
-            mBuilder.setIcon(0);
-            mBuilder.
-            setItems(mColorNames,
-                    new DialogInterface.OnClickListener() {
-                public void onClick(DialogInterface dialog, int which) {
-                    Log.d("EETVM", "mBuilder.onclick:" + which);
-                    int color = Integer.parseInt(
-                            (String) mColorInts[which], 16) - 0x01000000;
-                    mEST.setItemColor(color);
-                }
-            });
-            mBuilder.show();
-        }
-
-        private void onShowBackgroundColorAlertDialog() {
-            if (DBG) {
-                Log.d(LOG_TAG, "--- onShowBackgroundColorAlertDialog");
-            }
-            if (!checkColorAlertParams()) {
-                return;
-            }
-            mBuilder.setTitle(mColorTitle);
-            mBuilder.setIcon(0);
-            mBuilder.
-            setItems(mColorNames,
-                    new DialogInterface.OnClickListener() {
-                public void onClick(DialogInterface dialog, int which) {
-                    Log.d("EETVM", "mBuilder.onclick:" + which);
-                    int color = Integer.parseInt(
-                            (String) mColorInts[which], 16) - 0x01000000;
-                    mEST.setBackgroundColor(color);
-                }
-            });
-            mBuilder.show();
-        }
-
-        private void onShowSizeAlertDialog() {
-            if (DBG) {
-                Log.d(LOG_TAG, "--- onShowSizeAlertDialog");
-            }
-            if (!checkSizeAlertParams()) {
-                return;
-            }
-            mBuilder.setTitle(mSizeTitle);
-            mBuilder.setIcon(0);
-            mBuilder.
-            setItems(mSizeNames,
-                    new DialogInterface.OnClickListener() {
-                public void onClick(DialogInterface dialog, int which) {
-                    Log.d(LOG_TAG, "mBuilder.onclick:" + which);
-                    int size = Integer
-                    .parseInt((String) mSizeDisplayInts[which]);
-                    mEST.setItemSize(size);
-                }
-            });
-            mBuilder.show();
-        }
-
-        private void onShowAlignAlertDialog() {
-            if (DBG) {
-                Log.d(LOG_TAG, "--- onShowAlignAlertDialog");
-            }
-            if (!checkAlignAlertParams()) {
-                return;
-            }
-            mBuilder.setTitle(mAlignTitle);
-            mBuilder.setIcon(0);
-            mBuilder.
-            setItems(mAlignNames,
-                    new DialogInterface.OnClickListener() {
-                public void onClick(DialogInterface dialog, int which) {
-                    Log.d(LOG_TAG, "mBuilder.onclick:" + which);
-                    Layout.Alignment align = Layout.Alignment.ALIGN_NORMAL;
-                    switch (which) {
-                    case 0:
-                        align = Layout.Alignment.ALIGN_NORMAL;
-                        break;
-                    case 1:
-                        align = Layout.Alignment.ALIGN_CENTER;
-                        break;
-                    case 2:
-                        align = Layout.Alignment.ALIGN_OPPOSITE;
-                        break;
-                    default:
-                        break;
-                    }
-                    mEST.setAlignment(align);
-                }
-            });
-            mBuilder.show();
-        }
-    }
-
-    private class StyledTextArrowKeyMethod extends ArrowKeyMovementMethod {
-        EditorManager mManager;
-        StyledTextArrowKeyMethod(EditorManager manager) {
-            super();
-            mManager = manager;
-        }
-
-        @Override
-        public boolean onKeyDown(TextView widget, Spannable buffer,
-                int keyCode, KeyEvent event) {
-            if (!mManager.isSoftKeyBlocked()) {
-                return super.onKeyDown(widget, buffer, keyCode, event);
-            }
-            if (executeDown(widget, buffer, keyCode)) {
-                return true;
-            }
-            return false;
-        }
-
-        private int getEndPos(TextView widget) {
-            int end;
-            if (widget.getSelectionStart() == mManager.getSelectionStart()) {
-                end = widget.getSelectionEnd();
-            } else {
-                end = widget.getSelectionStart();
-            }
-            return end;
-        }
-
-        private boolean up(TextView widget, Spannable buffer) {
-            if (DBG) {
-                Log.d(LOG_TAG, "--- up:");
-            }
-            Layout layout = widget.getLayout();
-            int end = getEndPos(widget);
-            int line = layout.getLineForOffset(end);
-            if (line > 0) {
-                int to;
-                if (layout.getParagraphDirection(line) ==
-                    layout.getParagraphDirection(line - 1)) {
-                    float h = layout.getPrimaryHorizontal(end);
-                    to = layout.getOffsetForHorizontal(line - 1, h);
-                } else {
-                    to = layout.getLineStart(line - 1);
-                }
-                mManager.setSelectedEndPos(to);
-                mManager.onCursorMoved();
-                return true;
-            }
-            return false;
-        }
-
-        private boolean down(TextView widget, Spannable buffer) {
-            if (DBG) {
-                Log.d(LOG_TAG, "--- down:");
-            }
-            Layout layout = widget.getLayout();
-            int end = getEndPos(widget);
-            int line = layout.getLineForOffset(end);
-            if (line < layout.getLineCount() - 1) {
-                int to;
-                if (layout.getParagraphDirection(line) ==
-                    layout.getParagraphDirection(line + 1)) {
-                    float h = layout.getPrimaryHorizontal(end);
-                    to = layout.getOffsetForHorizontal(line + 1, h);
-                } else {
-                    to = layout.getLineStart(line + 1);
-                }
-                mManager.setSelectedEndPos(to);
-                mManager.onCursorMoved();
-                return true;
-            }
-            return false;
-        }
-
-        private boolean left(TextView widget, Spannable buffer) {
-            if (DBG) {
-                Log.d(LOG_TAG, "--- left:");
-            }
-            Layout layout = widget.getLayout();
-            int to = layout.getOffsetToLeftOf(getEndPos(widget));
-            mManager.setSelectedEndPos(to);
-            mManager.onCursorMoved();
-            return true;
-        }
-
-        private boolean right(TextView widget, Spannable buffer) {
-            if (DBG) {
-                Log.d(LOG_TAG, "--- right:");
-            }
-            Layout layout = widget.getLayout();
-            int to = layout.getOffsetToRightOf(getEndPos(widget));
-            mManager.setSelectedEndPos(to);
-            mManager.onCursorMoved();
-            return true;
-        }
-
-        private boolean executeDown(TextView widget, Spannable buffer,
-                int keyCode) {
-            if (DBG) {
-                Log.d(LOG_TAG, "--- executeDown: " + keyCode);
-            }
-            boolean handled = false;
-
-            switch (keyCode) {
-            case KeyEvent.KEYCODE_DPAD_UP:
-                handled |= up(widget, buffer);
-                break;
-            case KeyEvent.KEYCODE_DPAD_DOWN:
-                handled |= down(widget, buffer);
-                break;
-            case KeyEvent.KEYCODE_DPAD_LEFT:
-                handled |= left(widget, buffer);
-                break;
-            case KeyEvent.KEYCODE_DPAD_RIGHT:
-                handled |= right(widget, buffer);
-                break;
-                case KeyEvent.KEYCODE_DPAD_CENTER:
-                    mManager.onFixSelectedItem();
-                    handled = true;
-                    break;
-            }
-            return handled;
-        }
-    }
-
-    public class HorizontalLineSpan extends ImageSpan {
-        public HorizontalLineSpan(int color, View view) {
-            super(new HorizontalLineDrawable(color, view));
-        }
-    }
-    public class HorizontalLineDrawable extends ShapeDrawable {
-        private View mView;
-        public HorizontalLineDrawable(int color, View view) {
-            super(new RectShape());
-            mView = view;
-            renewColor(color);
-            renewBounds(view);
-        }
-        @Override
-        public void draw(Canvas canvas) {
-            if (DBG) {
-                Log.d(LOG_TAG, "--- draw:");
-            }
-            renewColor();
-            renewBounds(mView);
-            super.draw(canvas);
-        }
-
-        private void renewBounds(View view) {
-            if (DBG) {
-                int width = mView.getBackground().getBounds().width();
-                int height = mView.getBackground().getBounds().height();
-                Log.d(LOG_TAG, "--- renewBounds:" + width + "," + height);
-                Log.d(LOG_TAG, "--- renewBounds:" + mView.getClass());
-            }
-            int width = mView.getWidth();
-            if (width > 20) {
-                width -= 20;
-            }
-            setBounds(0, 0, width, 2);
-        }
-        private void renewColor(int color) {
-            if (DBG) {
-                Log.d(LOG_TAG, "--- renewColor:" + color);
-            }
-            getPaint().setColor(color);
-        }
-        private void renewColor() {
-            if (DBG) {
-                Log.d(LOG_TAG, "--- renewColor:");
-            }
-            if (mView instanceof View) {
-                ImageSpan parent = getParentSpan();
-                Editable text = ((EditStyledText)mView).getText();
-                int start = text.getSpanStart(parent);
-                ForegroundColorSpan[] spans = text.getSpans(start, start, ForegroundColorSpan.class);
-                if (spans.length > 0) {
-                    renewColor(spans[spans.length - 1].getForegroundColor());
-                }
-            }
-        }
-        private ImageSpan getParentSpan() {
-            if (DBG) {
-                Log.d(LOG_TAG, "--- getParentSpan:");
-            }
-            if (mView instanceof EditStyledText) {
-                Editable text = ((EditStyledText)mView).getText();
-                ImageSpan[] images = text.getSpans(0, text.length(), ImageSpan.class);
-                if (images.length > 0) {
-                    for (ImageSpan image: images) {
-                        if (image.getDrawable() == this) {
-                            return image;
-                        }
-                    }
-                }
-            }
-            Log.e(LOG_TAG, "---renewBounds: Couldn't find");
-            return null;
-        }
-    }
-}
diff --git a/core/java/com/android/internal/widget/IRemoteViewsFactory.aidl b/core/java/com/android/internal/widget/IRemoteViewsFactory.aidl
new file mode 100644
index 0000000..7851347
--- /dev/null
+++ b/core/java/com/android/internal/widget/IRemoteViewsFactory.aidl
@@ -0,0 +1,30 @@
+/*
+ * 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.
+ */
+
+package com.android.internal.widget;
+
+import android.widget.RemoteViews;
+
+/** {@hide} */
+interface IRemoteViewsFactory {
+    int getCount();
+    RemoteViews getViewAt(int position);
+    RemoteViews getLoadingView();
+    int getViewTypeCount();
+    long getItemId(int position);
+    boolean hasStableIds();
+}
+
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index dbbd286..0b62a67 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -92,6 +92,8 @@
     public final static String PASSWORD_TYPE_KEY = "lockscreen.password_type";
     private final static String LOCK_PASSWORD_SALT_KEY = "lockscreen.password_salt";
 
+    private final static String PASSWORD_HISTORY_KEY = "lockscreen.passwordhistory";
+
     private final Context mContext;
     private final ContentResolver mContentResolver;
     private DevicePolicyManager mDevicePolicyManager;
@@ -138,6 +140,33 @@
         return getDevicePolicyManager().getPasswordQuality(null);
     }
 
+    public int getRequestedPasswordHistoryLength() {
+        return getDevicePolicyManager().getPasswordHistoryLength(null);
+    }
+
+    public int getRequestedPasswordMinimumLetters() {
+        return getDevicePolicyManager().getPasswordMinimumLetters(null);
+    }
+
+    public int getRequestedPasswordMinimumUpperCase() {
+        return getDevicePolicyManager().getPasswordMinimumUpperCase(null);
+    }
+
+    public int getRequestedPasswordMinimumLowerCase() {
+        return getDevicePolicyManager().getPasswordMinimumLowerCase(null);
+    }
+
+    public int getRequestedPasswordMinimumNumeric() {
+        return getDevicePolicyManager().getPasswordMinimumNumeric(null);
+    }
+
+    public int getRequestedPasswordMinimumSymbols() {
+        return getDevicePolicyManager().getPasswordMinimumSymbols(null);
+    }
+
+    public int getRequestedPasswordMinimumNonLetter() {
+        return getDevicePolicyManager().getPasswordMinimumNonLetter(null);
+    }
     /**
      * Returns the actual password mode, as set by keyguard after updating the password.
      *
@@ -202,8 +231,36 @@
     }
 
     /**
-     * Checks to see if the given file exists and contains any data. Returns true if it does,
-     * false otherwise.
+     * Check to see if a password matches any of the passwords stored in the
+     * password history.
+     *
+     * @param password The password to check.
+     * @return Whether the password matches any in the history.
+     */
+    public boolean checkPasswordHistory(String password) {
+        String passwordHashString = new String(passwordToHash(password));
+        String passwordHistory = getString(PASSWORD_HISTORY_KEY);
+        if (passwordHistory == null) {
+            return false;
+        }
+        // Password History may be too long...
+        int passwordHashLength = passwordHashString.length();
+        int passwordHistoryLength = getRequestedPasswordHistoryLength();
+        if(passwordHistoryLength == 0) {
+            return false;
+        }
+        int neededPasswordHistoryLength = passwordHashLength * passwordHistoryLength
+                + passwordHistoryLength - 1;
+        if (passwordHistory.length() > neededPasswordHistoryLength) {
+            passwordHistory = passwordHistory.substring(0, neededPasswordHistoryLength);
+        }
+        return passwordHistory.contains(passwordHashString);
+    }
+
+    /**
+     * Checks to see if the given file exists and contains any data. Returns
+     * true if it does, false otherwise.
+     *
      * @param filename
      * @return true if file exists and is non-empty.
      */
@@ -274,6 +331,11 @@
                     activePasswordQuality = DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC;
                 }
                 break;
+            case DevicePolicyManager.PASSWORD_QUALITY_COMPLEX:
+                if (isLockPasswordEnabled()) {
+                    activePasswordQuality = DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
+                }
+                break;
         }
         return activePasswordQuality;
     }
@@ -282,8 +344,6 @@
      * Clear any lock pattern or password.
      */
     public void clearLock() {
-        getDevicePolicyManager().setActivePasswordState(
-                DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, 0);
         saveLockPassword(null, DevicePolicyManager.PASSWORD_QUALITY_SOMETHING);
         setLockPatternEnabled(false);
         saveLockPattern(null);
@@ -296,7 +356,7 @@
      */
     public void saveLockPattern(List<LockPatternView.Cell> pattern) {
         // Compute the hash
-        final byte[] hash  = LockPatternUtils.patternToHash(pattern);
+        final byte[] hash = LockPatternUtils.patternToHash(pattern);
         try {
             // Write the hash to file
             RandomAccessFile raf = new RandomAccessFile(sLockPatternFilename, "rw");
@@ -311,14 +371,15 @@
             if (pattern != null) {
                 setBoolean(PATTERN_EVER_CHOSEN_KEY, true);
                 setLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_SOMETHING);
-                dpm.setActivePasswordState(
-                        DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, pattern.size());
+                dpm.setActivePasswordState(DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, pattern
+                        .size(), 0, 0, 0, 0, 0, 0);
             } else {
-                dpm.setActivePasswordState(
-                        DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, 0);
+                dpm.setActivePasswordState(DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, 0, 0,
+                        0, 0, 0, 0, 0);
             }
         } catch (FileNotFoundException fnfe) {
-            // Cant do much, unless we want to fail over to using the settings provider
+            // Cant do much, unless we want to fail over to using the settings
+            // provider
             Log.e(TAG, "Unable to save lock pattern to " + sLockPatternFilename);
         } catch (IOException ioe) {
             // Cant do much
@@ -376,17 +437,59 @@
             DevicePolicyManager dpm = getDevicePolicyManager();
             if (password != null) {
                 int computedQuality = computePasswordQuality(password);
-                setLong(PASSWORD_TYPE_KEY, computedQuality);
+                setLong(PASSWORD_TYPE_KEY, Math.max(quality, computedQuality));
                 if (computedQuality != DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) {
-                    dpm.setActivePasswordState(computedQuality, password.length());
+                    int letters = 0;
+                    int uppercase = 0;
+                    int lowercase = 0;
+                    int numbers = 0;
+                    int symbols = 0;
+                    int nonletter = 0;
+                    for (int i = 0; i < password.length(); i++) {
+                        char c = password.charAt(i);
+                        if (c >= 'A' && c <= 'Z') {
+                            letters++;
+                            uppercase++;
+                        } else if (c >= 'a' && c <= 'z') {
+                            letters++;
+                            lowercase++;
+                        } else if (c >= '0' && c <= '9') {
+                            numbers++;
+                            nonletter++;
+                        } else {
+                            symbols++;
+                            nonletter++;
+                        }
+                    }
+                    dpm.setActivePasswordState(Math.max(quality, computedQuality), password
+                            .length(), letters, uppercase, lowercase, numbers, symbols, nonletter);
                 } else {
                     // The password is not anything.
                     dpm.setActivePasswordState(
-                            DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, 0);
+                            DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, 0, 0, 0, 0, 0, 0, 0);
                 }
+                // Add the password to the password history. We assume all
+                // password
+                // hashes have the same length for simplicity of implementation.
+                String passwordHistory = getString(PASSWORD_HISTORY_KEY);
+                if (passwordHistory == null) {
+                    passwordHistory = new String();
+                }
+                int passwordHistoryLength = getRequestedPasswordHistoryLength();
+                if (passwordHistoryLength == 0) {
+                    passwordHistory = "";
+                } else {
+                    passwordHistory = new String(hash) + "," + passwordHistory;
+                    // Cut it to contain passwordHistoryLength hashes
+                    // and passwordHistoryLength -1 commas.
+                    passwordHistory = passwordHistory.substring(0, Math.min(hash.length
+                            * passwordHistoryLength + passwordHistoryLength - 1, passwordHistory
+                            .length()));
+                }
+                setString(PASSWORD_HISTORY_KEY, passwordHistory);
             } else {
                 dpm.setActivePasswordState(
-                        DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, 0);
+                        DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, 0, 0, 0, 0, 0, 0, 0);
             }
         } catch (FileNotFoundException fnfe) {
             // Cant do much, unless we want to fail over to using the settings provider
@@ -526,7 +629,8 @@
         return savedPasswordExists() &&
                 (mode == DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC
                         || mode == DevicePolicyManager.PASSWORD_QUALITY_NUMERIC
-                        || mode == DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC);
+                        || mode == DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC
+                        || mode == DevicePolicyManager.PASSWORD_QUALITY_COMPLEX);
     }
 
     /**
@@ -650,12 +754,21 @@
         android.provider.Settings.Secure.putLong(mContentResolver, secureSettingKey, value);
     }
 
+    private String getString(String secureSettingKey) {
+        return android.provider.Settings.Secure.getString(mContentResolver, secureSettingKey);
+    }
+
+    private void setString(String secureSettingKey, String value) {
+        android.provider.Settings.Secure.putString(mContentResolver, secureSettingKey, value);
+    }
+
     public boolean isSecure() {
         long mode = getKeyguardStoredPasswordQuality();
         final boolean isPattern = mode == DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
         final boolean isPassword = mode == DevicePolicyManager.PASSWORD_QUALITY_NUMERIC
                 || mode == DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC
-                || mode == DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC;
+                || mode == DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC
+                || mode == DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
         final boolean secure = isPattern && isLockPatternEnabled() && savedPatternExists()
                 || isPassword && savedPasswordExists();
         return secure;
diff --git a/core/java/com/android/internal/widget/SlidingTab.java b/core/java/com/android/internal/widget/SlidingTab.java
index 9152729..3218ba8 100644
--- a/core/java/com/android/internal/widget/SlidingTab.java
+++ b/core/java/com/android/internal/widget/SlidingTab.java
@@ -17,10 +17,8 @@
 package com.android.internal.widget;
 
 import android.content.Context;
-import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
-import android.graphics.Canvas;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.os.Vibrator;
@@ -38,6 +36,7 @@
 import android.widget.ImageView;
 import android.widget.TextView;
 import android.widget.ImageView.ScaleType;
+
 import com.android.internal.R;
 
 /**
@@ -69,21 +68,21 @@
     private int mGrabbedState = OnTriggerListener.NO_HANDLE;
     private boolean mTriggered = false;
     private Vibrator mVibrator;
-    private float mDensity; // used to scale dimensions for bitmaps.
+    private final float mDensity; // used to scale dimensions for bitmaps.
 
     /**
      * Either {@link #HORIZONTAL} or {@link #VERTICAL}.
      */
-    private int mOrientation;
+    private final int mOrientation;
 
-    private Slider mLeftSlider;
-    private Slider mRightSlider;
+    private final Slider mLeftSlider;
+    private final Slider mRightSlider;
     private Slider mCurrentSlider;
     private boolean mTracking;
     private float mThreshold;
     private Slider mOtherSlider;
     private boolean mAnimating;
-    private Rect mTmpRect;
+    private final Rect mTmpRect;
 
     /**
      * Listener used to reset the view when the current animation completes.
@@ -608,14 +607,7 @@
 
                 case MotionEvent.ACTION_UP:
                 case MotionEvent.ACTION_CANCEL:
-                    mTracking = false;
-                    mTriggered = false;
-                    mOtherSlider.show(true);
-                    mCurrentSlider.reset(false);
-                    mCurrentSlider.hideTarget();
-                    mCurrentSlider = null;
-                    mOtherSlider = null;
-                    setGrabbedState(OnTriggerListener.NO_HANDLE);
+                    cancelGrab();
                     break;
             }
         }
@@ -623,6 +615,17 @@
         return mTracking || super.onTouchEvent(event);
     }
 
+    private void cancelGrab() {
+        mTracking = false;
+        mTriggered = false;
+        mOtherSlider.show(true);
+        mCurrentSlider.reset(false);
+        mCurrentSlider.hideTarget();
+        mCurrentSlider = null;
+        mOtherSlider = null;
+        setGrabbedState(OnTriggerListener.NO_HANDLE);
+    }
+
     void startAnimating(final boolean holdAfter) {
         mAnimating = true;
         final Animation trans1;
@@ -832,6 +835,17 @@
         }
     }
 
+    @Override
+    protected void onVisibilityChanged(View changedView, int visibility) {
+        super.onVisibilityChanged(changedView, visibility);
+        // When visibility changes and the user has a tab selected, unselect it and
+        // make sure their callback gets called.
+        if (changedView == this && visibility != VISIBLE
+                && mGrabbedState != OnTriggerListener.NO_HANDLE) {
+            cancelGrab();
+        }
+    }
+
     /**
      * Sets the current grabbed state, and dispatches a grabbed state change
      * event to our listener.
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index 5c37c7c..77c77f9 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -24,7 +24,6 @@
 LOCAL_SRC_FILES:= \
 	ActivityManager.cpp \
 	AndroidRuntime.cpp \
-	CursorWindow.cpp \
 	Time.cpp \
 	com_google_android_gles_jni_EGLImpl.cpp \
 	com_google_android_gles_jni_GLImpl.cpp.arm \
@@ -48,6 +47,8 @@
 	android_view_InputChannel.cpp \
 	android_view_InputQueue.cpp \
 	android_view_KeyEvent.cpp \
+	android_view_HardwareRenderer.cpp \
+	android_view_GLES20Canvas.cpp \
 	android_view_MotionEvent.cpp \
 	android_text_AndroidCharacter.cpp \
 	android_text_AndroidBidi.cpp \
@@ -104,6 +105,7 @@
 	android/graphics/Rasterizer.cpp \
 	android/graphics/Region.cpp \
 	android/graphics/Shader.cpp \
+	android/graphics/TextLayout.cpp \
 	android/graphics/Typeface.cpp \
 	android/graphics/Xfermode.cpp \
 	android/graphics/YuvToJpegEncoder.cpp \
@@ -122,7 +124,6 @@
 	android_bluetooth_common.cpp \
 	android_bluetooth_BluetoothAudioGateway.cpp \
 	android_bluetooth_BluetoothSocket.cpp \
-	android_bluetooth_ScoSocket.cpp \
 	android_server_BluetoothService.cpp \
 	android_server_BluetoothEventLoop.cpp \
 	android_server_BluetoothA2dpService.cpp \
@@ -140,6 +141,7 @@
 LOCAL_C_INCLUDES += \
 	$(JNI_H_INCLUDE) \
 	$(LOCAL_PATH)/android/graphics \
+	$(LOCAL_PATH)/../../libs/hwui \
 	$(call include-path-for, bluedroid) \
 	$(call include-path-for, libhardware)/hardware \
 	$(call include-path-for, libhardware_legacy)/hardware_legacy \
@@ -168,6 +170,7 @@
 	libbinder \
 	libnetutils \
 	libui \
+	libhwui \
 	libgui \
 	libsurfaceflinger_client \
 	libcamera_client \
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 7fe56a7..b3b33fd 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -114,6 +114,8 @@
 extern int register_android_graphics_PixelFormat(JNIEnv* env);
 extern int register_com_android_internal_graphics_NativeUtils(JNIEnv *env);
 extern int register_android_view_Display(JNIEnv* env);
+extern int register_android_view_GLES20Canvas(JNIEnv* env);
+extern int register_android_view_HardwareRenderer(JNIEnv* env);
 extern int register_android_view_Surface(JNIEnv* env);
 extern int register_android_view_ViewRoot(JNIEnv* env);
 extern int register_android_database_CursorWindow(JNIEnv* env);
@@ -150,7 +152,6 @@
 extern int register_android_bluetooth_HeadsetBase(JNIEnv* env);
 extern int register_android_bluetooth_BluetoothAudioGateway(JNIEnv* env);
 extern int register_android_bluetooth_BluetoothSocket(JNIEnv *env);
-extern int register_android_bluetooth_ScoSocket(JNIEnv *env);
 extern int register_android_server_BluetoothService(JNIEnv* env);
 extern int register_android_server_BluetoothEventLoop(JNIEnv *env);
 extern int register_android_server_BluetoothA2dpService(JNIEnv* env);
@@ -1251,6 +1252,8 @@
     REG_JNI(register_android_nio_utils),
     REG_JNI(register_android_graphics_PixelFormat),
     REG_JNI(register_android_graphics_Graphics),
+    REG_JNI(register_android_view_GLES20Canvas),
+    REG_JNI(register_android_view_HardwareRenderer),
     REG_JNI(register_android_view_Surface),
     REG_JNI(register_android_view_ViewRoot),
     REG_JNI(register_com_google_android_gles_jni_EGLImpl),
@@ -1321,7 +1324,6 @@
     REG_JNI(register_android_bluetooth_HeadsetBase),
     REG_JNI(register_android_bluetooth_BluetoothAudioGateway),
     REG_JNI(register_android_bluetooth_BluetoothSocket),
-    REG_JNI(register_android_bluetooth_ScoSocket),
     REG_JNI(register_android_server_BluetoothService),
     REG_JNI(register_android_server_BluetoothEventLoop),
     REG_JNI(register_android_server_BluetoothA2dpService),
diff --git a/core/jni/CursorWindow.cpp b/core/jni/CursorWindow.cpp
deleted file mode 100644
index 7877921..0000000
--- a/core/jni/CursorWindow.cpp
+++ /dev/null
@@ -1,413 +0,0 @@
-/*
- * Copyright (C) 2006-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.
- */
-
-#undef LOG_TAG
-#define LOG_TAG "CursorWindow"
-
-#include <utils/Log.h>
-#include <binder/MemoryHeapBase.h>
-#include <binder/MemoryBase.h>
-
-#include <assert.h>
-#include <string.h>
-#include <stdlib.h>
-
-#include <jni.h>
-#include <JNIHelp.h>
-
-#include "CursorWindow.h"
-
-
-namespace android {
-
-CursorWindow::CursorWindow(size_t maxSize) :
-    mMaxSize(maxSize)
-{
-}
-
-bool CursorWindow::setMemory(const sp<IMemory>& memory)
-{
-    mMemory = memory;
-    mData = (uint8_t *) memory->pointer();
-    if (mData == NULL) {
-        return false;
-    }
-    mHeader = (window_header_t *) mData;
-
-    // Make the window read-only
-    ssize_t size = memory->size();
-    mSize = size;
-    mMaxSize = size;
-    mFreeOffset = size;
-LOG_WINDOW("Created CursorWindow from existing IMemory: mFreeOffset = %d, numRows = %d, numColumns = %d, mSize = %d, mMaxSize = %d, mData = %p", mFreeOffset, mHeader->numRows, mHeader->numColumns, mSize, mMaxSize, mData);
-    return true;
-}
-
-bool CursorWindow::initBuffer(bool localOnly)
-{
-    //TODO Use a non-memory dealer mmap region for localOnly
-
-    sp<MemoryHeapBase> heap;
-    heap = new MemoryHeapBase(mMaxSize, 0, "CursorWindow");
-    if (heap != NULL) {
-        mMemory = new MemoryBase(heap, 0, mMaxSize);
-        if (mMemory != NULL) {
-            mData = (uint8_t *) mMemory->pointer();
-            if (mData) {
-                mHeader = (window_header_t *) mData;
-                mSize = mMaxSize;
-
-                // Put the window into a clean state
-                clear();
-            LOG_WINDOW("Created CursorWindow with new MemoryDealer: mFreeOffset = %d, mSize = %d, mMaxSize = %d, mData = %p", mFreeOffset, mSize, mMaxSize, mData);
-                return true;                
-            }
-        } 
-        LOGE("CursorWindow heap allocation failed");
-        return false;
-    } else {
-        LOGE("failed to create the CursorWindow heap");
-        return false;
-    }
-}
-
-CursorWindow::~CursorWindow()
-{
-    // Everything that matters is a smart pointer
-}
-
-void CursorWindow::clear()
-{
-    mHeader->numRows = 0;
-    mHeader->numColumns = 0;
-    mFreeOffset = sizeof(window_header_t) + ROW_SLOT_CHUNK_SIZE;
-    // Mark the first chunk's next 'pointer' as null
-    *((uint32_t *)(mData + mFreeOffset - sizeof(uint32_t))) = 0;
-}
-
-int32_t CursorWindow::freeSpace()
-{
-    int32_t freeSpace = mSize - mFreeOffset;
-    if (freeSpace < 0) {
-        freeSpace = 0;
-    }
-    return freeSpace;
-}
-
-field_slot_t * CursorWindow::allocRow()
-{
-    // Fill in the row slot
-    row_slot_t * rowSlot = allocRowSlot();
-    if (rowSlot == NULL) {
-        return NULL;
-    }
-
-    // Allocate the slots for the field directory
-    size_t fieldDirSize = mHeader->numColumns * sizeof(field_slot_t);
-    uint32_t fieldDirOffset = alloc(fieldDirSize);
-    if (!fieldDirOffset) {
-        mHeader->numRows--;
-        LOGE("The row failed, so back out the new row accounting from allocRowSlot %d", mHeader->numRows);
-        return NULL;
-    }
-    field_slot_t * fieldDir = (field_slot_t *)offsetToPtr(fieldDirOffset);
-    memset(fieldDir, 0x0, fieldDirSize);
-
-LOG_WINDOW("Allocated row %u, rowSlot is at offset %u, fieldDir is %d bytes at offset %u\n", (mHeader->numRows - 1), ((uint8_t *)rowSlot) - mData, fieldDirSize, fieldDirOffset);
-    rowSlot->offset = fieldDirOffset;
-
-    return fieldDir;
-}
-
-uint32_t CursorWindow::alloc(size_t requestedSize, bool aligned)
-{
-    int32_t size;
-    uint32_t padding;
-    if (aligned) {
-        // 4 byte alignment
-        padding = 4 - (mFreeOffset & 0x3);
-    } else {
-        padding = 0;
-    }
-
-    size = requestedSize + padding;
-
-    if (size > freeSpace()) {
-        LOGE("need to grow: mSize = %d, size = %d, freeSpace() = %d, numRows = %d", mSize, size, freeSpace(), mHeader->numRows);
-        // Only grow the window if the first row doesn't fit
-        if (mHeader->numRows > 1) {
-LOGE("not growing since there are already %d row(s), max size %d", mHeader->numRows, mMaxSize);
-            return 0;
-        }
-
-        // Find a new size that will fit the allocation
-        int allocated = mSize - freeSpace();
-        int newSize = mSize + WINDOW_ALLOCATION_SIZE;
-        while (size > (newSize - allocated)) {
-            newSize += WINDOW_ALLOCATION_SIZE;
-            if (newSize > mMaxSize) {
-                LOGE("Attempting to grow window beyond max size (%d)", mMaxSize);
-                return 0;
-            }
-        }
-LOG_WINDOW("found size %d", newSize);
-        mSize = newSize;
-    }
-
-    uint32_t offset = mFreeOffset + padding;
-    mFreeOffset += size;
-    return offset;
-}
-
-row_slot_t * CursorWindow::getRowSlot(int row)
-{
-    LOG_WINDOW("enter getRowSlot current row num %d, this row %d", mHeader->numRows, row);
-    int chunkNum = row / ROW_SLOT_CHUNK_NUM_ROWS;
-    int chunkPos = row % ROW_SLOT_CHUNK_NUM_ROWS;
-    int chunkPtrOffset = sizeof(window_header_t) + ROW_SLOT_CHUNK_SIZE - sizeof(uint32_t);
-    uint8_t * rowChunk = mData + sizeof(window_header_t);
-    for (int i = 0; i < chunkNum; i++) {
-        rowChunk = offsetToPtr(*((uint32_t *)(mData + chunkPtrOffset)));
-        chunkPtrOffset = rowChunk - mData + (ROW_SLOT_CHUNK_NUM_ROWS * sizeof(row_slot_t));
-    }
-    return (row_slot_t *)(rowChunk + (chunkPos * sizeof(row_slot_t)));
-    LOG_WINDOW("exit getRowSlot current row num %d, this row %d", mHeader->numRows, row);    
-}
-
-row_slot_t * CursorWindow::allocRowSlot()
-{
-    int chunkNum = mHeader->numRows / ROW_SLOT_CHUNK_NUM_ROWS;
-    int chunkPos = mHeader->numRows % ROW_SLOT_CHUNK_NUM_ROWS;
-    int chunkPtrOffset = sizeof(window_header_t) + ROW_SLOT_CHUNK_SIZE - sizeof(uint32_t);
-    uint8_t * rowChunk = mData + sizeof(window_header_t);
-LOG_WINDOW("Allocating row slot, mHeader->numRows is %d, chunkNum is %d, chunkPos is %d", mHeader->numRows, chunkNum, chunkPos);
-    for (int i = 0; i < chunkNum; i++) {
-        uint32_t nextChunkOffset = *((uint32_t *)(mData + chunkPtrOffset));
-LOG_WINDOW("nextChunkOffset is %d", nextChunkOffset);
-        if (nextChunkOffset == 0) {
-            // Allocate a new row chunk
-            nextChunkOffset = alloc(ROW_SLOT_CHUNK_SIZE, true);
-            if (nextChunkOffset == 0) {
-                return NULL;
-            }
-            rowChunk = offsetToPtr(nextChunkOffset);
-LOG_WINDOW("allocated new chunk at %d, rowChunk = %p", nextChunkOffset, rowChunk);
-            *((uint32_t *)(mData + chunkPtrOffset)) = rowChunk - mData;
-            // Mark the new chunk's next 'pointer' as null
-            *((uint32_t *)(rowChunk + ROW_SLOT_CHUNK_SIZE - sizeof(uint32_t))) = 0;
-        } else {
-LOG_WINDOW("follwing 'pointer' to next chunk, offset of next pointer is %d", chunkPtrOffset);
-            rowChunk = offsetToPtr(nextChunkOffset);
-            chunkPtrOffset = rowChunk - mData + (ROW_SLOT_CHUNK_NUM_ROWS * sizeof(row_slot_t));
-        }
-    }
-    mHeader->numRows++;
-
-    return (row_slot_t *)(rowChunk + (chunkPos * sizeof(row_slot_t)));
-}
-
-field_slot_t * CursorWindow::getFieldSlotWithCheck(int row, int column)
-{
-  if (row < 0 || row >= mHeader->numRows || column < 0 || column >= mHeader->numColumns) {
-      LOGE("Bad request for field slot %d,%d. numRows = %d, numColumns = %d", row, column, mHeader->numRows, mHeader->numColumns);
-      return NULL;
-  }        
-  row_slot_t * rowSlot = getRowSlot(row);
-  if (!rowSlot) {
-      LOGE("Failed to find rowSlot for row %d", row);
-      return NULL;
-  }
-  if (rowSlot->offset == 0 || rowSlot->offset >= mSize) {
-      LOGE("Invalid rowSlot, offset = %d", rowSlot->offset);
-      return NULL;
-  }  
-  int fieldDirOffset = rowSlot->offset;
-  return ((field_slot_t *)offsetToPtr(fieldDirOffset)) + column;  
-}
-
-uint32_t CursorWindow::read_field_slot(int row, int column, field_slot_t * slotOut)
-{
-    if (row < 0 || row >= mHeader->numRows || column < 0 || column >= mHeader->numColumns) {
-        LOGE("Bad request for field slot %d,%d. numRows = %d, numColumns = %d", row, column, mHeader->numRows, mHeader->numColumns);
-        return -1;
-    }        
-    row_slot_t * rowSlot = getRowSlot(row);
-    if (!rowSlot) {
-        LOGE("Failed to find rowSlot for row %d", row);
-        return -1;
-    }
-    if (rowSlot->offset == 0 || rowSlot->offset >= mSize) {
-        LOGE("Invalid rowSlot, offset = %d", rowSlot->offset);
-        return -1;
-    }
-LOG_WINDOW("Found field directory for %d,%d at rowSlot %d, offset %d", row, column, (uint8_t *)rowSlot - mData, rowSlot->offset);
-    field_slot_t * fieldDir = (field_slot_t *)offsetToPtr(rowSlot->offset);
-LOG_WINDOW("Read field_slot_t %d,%d: offset = %d, size = %d, type = %d", row, column, fieldDir[column].data.buffer.offset, fieldDir[column].data.buffer.size, fieldDir[column].type);
-
-    // Copy the data to the out param
-    slotOut->data.buffer.offset = fieldDir[column].data.buffer.offset;
-    slotOut->data.buffer.size = fieldDir[column].data.buffer.size;
-    slotOut->type = fieldDir[column].type;
-    return 0;
-}
-
-void CursorWindow::copyIn(uint32_t offset, uint8_t const * data, size_t size)
-{
-    assert(offset + size <= mSize);    
-    memcpy(mData + offset, data, size);
-}
-
-void CursorWindow::copyIn(uint32_t offset, int64_t data)
-{
-    assert(offset + sizeof(int64_t) <= mSize);
-    memcpy(mData + offset, (uint8_t *)&data, sizeof(int64_t));
-}
-
-void CursorWindow::copyIn(uint32_t offset, double data)
-{
-    assert(offset + sizeof(double) <= mSize);
-    memcpy(mData + offset, (uint8_t *)&data, sizeof(double));
-}
-
-void CursorWindow::copyOut(uint32_t offset, uint8_t * data, size_t size)
-{
-    assert(offset + size <= mSize);
-    memcpy(data, mData + offset, size);
-}
-
-int64_t CursorWindow::copyOutLong(uint32_t offset)
-{
-    int64_t value;
-    assert(offset + sizeof(int64_t) <= mSize);
-    memcpy(&value, mData + offset, sizeof(int64_t));
-    return value;
-}
-
-double CursorWindow::copyOutDouble(uint32_t offset)
-{
-    double value;
-    assert(offset + sizeof(double) <= mSize);
-    memcpy(&value, mData + offset, sizeof(double));
-    return value;
-}
-
-bool CursorWindow::putLong(unsigned int row, unsigned int col, int64_t value)
-{
-    field_slot_t * fieldSlot = getFieldSlotWithCheck(row, col);
-    if (!fieldSlot) {
-        return false;
-    }
-
-#if WINDOW_STORAGE_INLINE_NUMERICS
-    fieldSlot->data.l = value;
-#else
-    int offset = alloc(sizeof(int64_t));
-    if (!offset) {
-        return false;
-    }
-
-    copyIn(offset, value);
-
-    fieldSlot->data.buffer.offset = offset;
-    fieldSlot->data.buffer.size = sizeof(int64_t);
-#endif
-    fieldSlot->type = FIELD_TYPE_INTEGER;
-    return true;
-}
-
-bool CursorWindow::putDouble(unsigned int row, unsigned int col, double value)
-{
-    field_slot_t * fieldSlot = getFieldSlotWithCheck(row, col);
-    if (!fieldSlot) {
-        return false;
-    }
-
-#if WINDOW_STORAGE_INLINE_NUMERICS
-    fieldSlot->data.d = value;
-#else
-    int offset = alloc(sizeof(int64_t));
-    if (!offset) {
-        return false;
-    }
-
-    copyIn(offset, value);
-
-    fieldSlot->data.buffer.offset = offset;
-    fieldSlot->data.buffer.size = sizeof(double);
-#endif
-    fieldSlot->type = FIELD_TYPE_FLOAT;
-    return true;
-}
-
-bool CursorWindow::putNull(unsigned int row, unsigned int col)
-{
-    field_slot_t * fieldSlot = getFieldSlotWithCheck(row, col);
-    if (!fieldSlot) {
-        return false;
-    }
-
-    fieldSlot->type = FIELD_TYPE_NULL;
-    fieldSlot->data.buffer.offset = 0;
-    fieldSlot->data.buffer.size = 0;
-    return true;
-}
-
-bool CursorWindow::getLong(unsigned int row, unsigned int col, int64_t * valueOut)
-{
-    field_slot_t * fieldSlot = getFieldSlotWithCheck(row, col);
-    if (!fieldSlot || fieldSlot->type != FIELD_TYPE_INTEGER) {
-        return false;
-    }
-    
-#if WINDOW_STORAGE_INLINE_NUMERICS
-    *valueOut = fieldSlot->data.l;
-#else
-    *valueOut = copyOutLong(fieldSlot->data.buffer.offset);
-#endif
-    return true;
-}
-
-bool CursorWindow::getDouble(unsigned int row, unsigned int col, double * valueOut)
-{
-    field_slot_t * fieldSlot = getFieldSlotWithCheck(row, col);
-    if (!fieldSlot || fieldSlot->type != FIELD_TYPE_FLOAT) {
-        return false;
-    }
-
-#if WINDOW_STORAGE_INLINE_NUMERICS
-    *valueOut = fieldSlot->data.d;
-#else
-    *valueOut = copyOutDouble(fieldSlot->data.buffer.offset);
-#endif
-    return true;
-}
-
-bool CursorWindow::getNull(unsigned int row, unsigned int col, bool * valueOut)
-{
-    field_slot_t * fieldSlot = getFieldSlotWithCheck(row, col);
-    if (!fieldSlot) {
-        return false;
-    }
-    
-    if (fieldSlot->type != FIELD_TYPE_NULL) {
-        *valueOut = false;
-    } else {
-        *valueOut = true;
-    }
-    return true;
-}
-
-}; // namespace android
diff --git a/core/jni/CursorWindow.h b/core/jni/CursorWindow.h
deleted file mode 100644
index 3fcb560..0000000
--- a/core/jni/CursorWindow.h
+++ /dev/null
@@ -1,202 +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.
- */
-
-#ifndef _ANDROID__DATABASE_WINDOW_H
-#define _ANDROID__DATABASE_WINDOW_H
-
-#include <cutils/log.h>
-#include <stddef.h>
-#include <stdint.h>
-
-#include <binder/IMemory.h>
-#include <utils/RefBase.h>
-
-#include <jni.h>
-
-#define DEFAULT_WINDOW_SIZE 4096
-#define MAX_WINDOW_SIZE (1024 * 1024)
-#define WINDOW_ALLOCATION_SIZE 4096
-
-#define ROW_SLOT_CHUNK_NUM_ROWS 16
-
-// Row slots are allocated in chunks of ROW_SLOT_CHUNK_NUM_ROWS,
-// with an offset after the rows that points to the next chunk
-#define ROW_SLOT_CHUNK_SIZE ((ROW_SLOT_CHUNK_NUM_ROWS * sizeof(row_slot_t)) + sizeof(uint32_t))
-
-
-#if LOG_NDEBUG
-
-#define IF_LOG_WINDOW() if (false)
-#define LOG_WINDOW(...)
-
-#else
-
-#define IF_LOG_WINDOW() IF_LOG(LOG_DEBUG, "CursorWindow")
-#define LOG_WINDOW(...) LOG(LOG_DEBUG, "CursorWindow", __VA_ARGS__)
-
-#endif
-
-
-// When defined to true strings are stored as UTF8, otherwise they're UTF16
-#define WINDOW_STORAGE_UTF8 1
-
-// When defined to true numberic values are stored inline in the field_slot_t, otherwise they're allocated in the window
-#define WINDOW_STORAGE_INLINE_NUMERICS 1
-
-namespace android {
-
-typedef struct
-{
-    uint32_t numRows;
-    uint32_t numColumns;
-} window_header_t;
-
-typedef struct
-{
-    uint32_t offset;
-} row_slot_t;
-
-typedef struct
-{
-    uint8_t type;
-    union {
-        double d;
-        int64_t l;
-        struct {
-            uint32_t offset;
-            uint32_t size;
-        } buffer;
-    } data;
-} __attribute__((packed)) field_slot_t;
-
-#define FIELD_TYPE_INTEGER 1
-#define FIELD_TYPE_FLOAT 2
-#define FIELD_TYPE_STRING 3
-#define FIELD_TYPE_BLOB 4
-#define FIELD_TYPE_NULL 5
-
-/**
- * This class stores a set of rows from a database in a buffer. The begining of the
- * window has first chunk of row_slot_ts, which are offsets to the row directory, followed by
- * an offset to the next chunk in a linked-list of additional chunk of row_slot_ts in case
- * the pre-allocated chunk isn't big enough to refer to all rows. Each row directory has a
- * field_slot_t per column, which has the size, offset, and type of the data for that field.
- * Note that the data types come from sqlite3.h.
- */
-class CursorWindow
-{
-public:
-                        CursorWindow(size_t maxSize);
-                        CursorWindow(){}
-    bool                setMemory(const sp<IMemory>&);
-                        ~CursorWindow();
-
-    bool                initBuffer(bool localOnly);
-    sp<IMemory>         getMemory() {return mMemory;}
-
-    size_t              size() {return mSize;}
-    uint8_t *           data() {return mData;}
-    uint32_t            getNumRows() {return mHeader->numRows;}
-    uint32_t            getNumColumns() {return mHeader->numColumns;}
-    void                freeLastRow() {
-                            if (mHeader->numRows > 0) {
-                                mHeader->numRows--;
-                            }
-                        }
-    bool                setNumColumns(uint32_t numColumns)
-                            {
-                                uint32_t cur = mHeader->numColumns;
-                                if (cur > 0 && cur != numColumns) {
-                                    LOGE("Trying to go from %d columns to %d", cur, numColumns);
-                                    return false;
-                                }
-                                mHeader->numColumns = numColumns;
-                                return true;
-                            }
-
-    int32_t             freeSpace();
-
-    void                clear();
-
-                        /**
-                         * Allocate a row slot and its directory. The returned
-                         * pointer points to the begining of the row's directory
-                         * or NULL if there wasn't room. The directory is
-                         * initialied with NULL entries for each field.
-                         */
-    field_slot_t *      allocRow();
-
-                        /**
-                         * Allocate a portion of the window. Returns the offset
-                         * of the allocation, or 0 if there isn't enough space.
-                         * If aligned is true, the allocation gets 4 byte alignment.
-                         */
-    uint32_t            alloc(size_t size, bool aligned = false);
-
-    uint32_t            read_field_slot(int row, int column, field_slot_t * slot);
-
-                        /**
-                         * Copy data into the window at the given offset.
-                         */
-    void                copyIn(uint32_t offset, uint8_t const * data, size_t size);
-    void                copyIn(uint32_t offset, int64_t data);
-    void                copyIn(uint32_t offset, double data);
-
-    void                copyOut(uint32_t offset, uint8_t * data, size_t size);
-    int64_t             copyOutLong(uint32_t offset);
-    double              copyOutDouble(uint32_t offset);
-
-    bool                putLong(unsigned int row, unsigned int col, int64_t value);
-    bool                putDouble(unsigned int row, unsigned int col, double value);
-    bool                putNull(unsigned int row, unsigned int col);
-
-    bool                getLong(unsigned int row, unsigned int col, int64_t * valueOut);
-    bool                getDouble(unsigned int row, unsigned int col, double * valueOut);
-    bool                getNull(unsigned int row, unsigned int col, bool * valueOut);
-
-    uint8_t *           offsetToPtr(uint32_t offset) {return mData + offset;}
-
-    row_slot_t *        allocRowSlot();
-
-    row_slot_t *        getRowSlot(int row);
-    
-                        /**
-                         * return NULL if Failed to find rowSlot or
-                         * Invalid rowSlot
-                         */
-    field_slot_t *      getFieldSlotWithCheck(int row, int column);
-    field_slot_t *      getFieldSlot(int row, int column)
-                            {
-                                int fieldDirOffset = getRowSlot(row)->offset;
-                                return ((field_slot_t *)offsetToPtr(fieldDirOffset)) + column;
-                            }
-
-private:
-    uint8_t * mData;
-    size_t mSize;
-    size_t mMaxSize;
-    window_header_t * mHeader;
-    sp<IMemory> mMemory;
-
-    /**
-     * Offset of the lowest unused data byte in the array.
-     */
-    uint32_t mFreeOffset;
-};
-
-}; // namespace android
-
-#endif
diff --git a/core/jni/android/graphics/Bitmap.cpp b/core/jni/android/graphics/Bitmap.cpp
index 88bbafd..b062264 100644
--- a/core/jni/android/graphics/Bitmap.cpp
+++ b/core/jni/android/graphics/Bitmap.cpp
@@ -313,6 +313,10 @@
     return bitmap->config();

 }

 

+static int Bitmap_getGenerationId(JNIEnv* env, jobject, SkBitmap* bitmap) {

+    return bitmap->getGenerationID();

+}

+

 static jboolean Bitmap_hasAlpha(JNIEnv* env, jobject, SkBitmap* bitmap) {

     return !bitmap->isOpaque();

 }

@@ -606,6 +610,7 @@
         (void*)Bitmap_writeToParcel },

     {   "nativeExtractAlpha",       "(II[I)Landroid/graphics/Bitmap;",

         (void*)Bitmap_extractAlpha },

+    {   "nativeGenerationId",       "(I)I", (void*)Bitmap_getGenerationId },

     {   "nativeGetPixel",           "(III)I", (void*)Bitmap_getPixel },

     {   "nativeGetPixels",          "(I[IIIIIII)V", (void*)Bitmap_getPixels },

     {   "nativeSetPixel",           "(IIII)V", (void*)Bitmap_setPixel },

diff --git a/core/jni/android/graphics/Canvas.cpp b/core/jni/android/graphics/Canvas.cpp
index e1e9536..558f5ff 100644
--- a/core/jni/android/graphics/Canvas.cpp
+++ b/core/jni/android/graphics/Canvas.cpp
@@ -30,6 +30,13 @@
 #include "SkBoundaryPatch.h"
 #include "SkMeshUtils.h"
 
+#include "TextLayout.h"
+
+#include "unicode/ubidi.h"
+#include "unicode/ushape.h"
+
+#include <utils/Log.h>
+
 #define TIME_DRAWx
 
 static uint32_t get_thread_msec() {
@@ -743,45 +750,49 @@
         canvas->drawVertices(mode, ptCount, verts, texs, colors, NULL,
                              indices, indexCount, *paint);
     }
-    
-    static void drawText___CIIFFPaint(JNIEnv* env, jobject, SkCanvas* canvas,
+
+
+    static void drawText___CIIFFIPaint(JNIEnv* env, jobject, SkCanvas* canvas,
                                       jcharArray text, int index, int count,
-                                      jfloat x, jfloat y, SkPaint* paint) {
+                                      jfloat x, jfloat y, int flags, SkPaint* paint) {
         jchar* textArray = env->GetCharArrayElements(text, NULL);
-        jsize textCount = env->GetArrayLength(text);
-        SkScalar x_ = SkFloatToScalar(x);
-        SkScalar y_ = SkFloatToScalar(y);
-        canvas->drawText(textArray + index, count << 1, x_, y_, *paint);
-        env->ReleaseCharArrayElements(text, textArray, 0);
+        TextLayout::drawText(paint, textArray + index, count, flags, x, y, canvas);
+        env->ReleaseCharArrayElements(text, textArray, JNI_ABORT);
     }
- 
-    static void drawText__StringIIFFPaint(JNIEnv* env, jobject,
-                            SkCanvas* canvas, jstring text, int start, int end,
-                                          jfloat x, jfloat y, SkPaint* paint) {
-        const void* text_ = env->GetStringChars(text, NULL);
-        SkScalar x_ = SkFloatToScalar(x);
-        SkScalar y_ = SkFloatToScalar(y);
-        canvas->drawText((const uint16_t*)text_ + start, (end - start) << 1,
-                         x_, y_, *paint);
-        env->ReleaseStringChars(text, (const jchar*) text_);
+
+    static void drawText__StringIIFFIPaint(JNIEnv* env, jobject,
+                                          SkCanvas* canvas, jstring text,
+                                          int start, int end,
+                                          jfloat x, jfloat y, int flags, SkPaint* paint) {
+        const jchar* textArray = env->GetStringChars(text, NULL);
+        TextLayout::drawText(paint, textArray + start, end - start, flags, x, y, canvas);
+        env->ReleaseStringChars(text, textArray);
     }
-    
-    static void drawString(JNIEnv* env, jobject canvas, jstring text,
-                           jfloat x, jfloat y, jobject paint) {
-        NPE_CHECK_RETURN_VOID(env, canvas);
-        NPE_CHECK_RETURN_VOID(env, paint);
-        NPE_CHECK_RETURN_VOID(env, text);
-        size_t count = env->GetStringLength(text);
-        if (0 == count) {
-            return;
-        }
-        const jchar* text_ = env->GetStringChars(text, NULL);
-        SkCanvas* c = GraphicsJNI::getNativeCanvas(env, canvas);
-        c->drawText(text_, count << 1, SkFloatToScalar(x), SkFloatToScalar(y),
-                    *GraphicsJNI::getNativePaint(env, paint));
-        env->ReleaseStringChars(text, text_);
+
+    static void drawTextRun___CIIIIFFIPaint(
+        JNIEnv* env, jobject, SkCanvas* canvas, jcharArray text, int index,
+        int count, int contextIndex, int contextCount,
+        jfloat x, jfloat y, int dirFlags, SkPaint* paint) {
+
+        jchar* chars = env->GetCharArrayElements(text, NULL);
+        TextLayout::drawTextRun(paint, chars + contextIndex, index - contextIndex,
+                                count, contextCount, dirFlags, x, y, canvas);
+        env->ReleaseCharArrayElements(text, chars, JNI_ABORT);
     }
-    
+
+    static void drawTextRun__StringIIIIFFIPaint(
+        JNIEnv* env, jobject obj, SkCanvas* canvas, jstring text, jint start,
+        jint end, jint contextStart, jint contextEnd,
+        jfloat x, jfloat y, jint dirFlags, SkPaint* paint) {
+
+        jint count = end - start;
+        jint contextCount = contextEnd - contextStart;
+        const jchar* chars = env->GetStringChars(text, NULL);
+        TextLayout::drawTextRun(paint, chars + contextStart, start - contextStart,
+                                count, contextCount, dirFlags, x, y, canvas);
+        env->ReleaseStringChars(text, chars);
+    }
+
     static void drawPosText___CII_FPaint(JNIEnv* env, jobject, SkCanvas* canvas,
                                          jcharArray text, int index, int count,
                                          jfloatArray pos, SkPaint* paint) {
@@ -804,10 +815,11 @@
         }
         delete[] posPtr;
     }
- 
+
     static void drawPosText__String_FPaint(JNIEnv* env, jobject,
                                            SkCanvas* canvas, jstring text,
-                                           jfloatArray pos, SkPaint* paint) {
+                                           jfloatArray pos,
+                                           SkPaint* paint) {
         const void* text_ = text ? env->GetStringChars(text, NULL) : NULL;
         int byteLength = text ? env->GetStringLength(text) : 0;
         float* posArray = pos ? env->GetFloatArrayElements(pos, NULL) : NULL;
@@ -827,27 +839,27 @@
         }
         delete[] posPtr;
     }
- 
+
     static void drawTextOnPath___CIIPathFFPaint(JNIEnv* env, jobject,
-                        SkCanvas* canvas, jcharArray text, int index, int count,
-                SkPath* path, jfloat hOffset, jfloat vOffset, SkPaint* paint) {
+            SkCanvas* canvas, jcharArray text, int index, int count,
+            SkPath* path, jfloat hOffset, jfloat vOffset, jint bidiFlags, SkPaint* paint) {
 
         jchar* textArray = env->GetCharArrayElements(text, NULL);
-        canvas->drawTextOnPathHV(textArray + index, count << 1, *path,
-                    SkFloatToScalar(hOffset), SkFloatToScalar(vOffset), *paint);
+        TextLayout::drawTextOnPath(paint, textArray, count, bidiFlags, hOffset, vOffset,
+                                   path, canvas);
         env->ReleaseCharArrayElements(text, textArray, 0);
     }
- 
+
     static void drawTextOnPath__StringPathFFPaint(JNIEnv* env, jobject,
-                            SkCanvas* canvas, jstring text, SkPath* path,
-                            jfloat hOffset, jfloat vOffset, SkPaint* paint) {
+            SkCanvas* canvas, jstring text, SkPath* path,
+            jfloat hOffset, jfloat vOffset, jint bidiFlags, SkPaint* paint) {
         const jchar* text_ = env->GetStringChars(text, NULL);
-        int byteLength = env->GetStringLength(text) << 1;
-        canvas->drawTextOnPathHV(text_, byteLength, *path,
-                    SkFloatToScalar(hOffset), SkFloatToScalar(vOffset), *paint);
+        int count = env->GetStringLength(text);
+        TextLayout::drawTextOnPath(paint, text_, count, bidiFlags, hOffset, vOffset,
+                                   path, canvas);
         env->ReleaseStringChars(text, text_);
     }
- 
+
     static bool getClipBounds(JNIEnv* env, jobject, SkCanvas* canvas,
                               jobject bounds) {
         SkRect   r;
@@ -940,26 +952,27 @@
         (void*) SkCanvasGlue::drawBitmapRR},
     {"native_drawBitmap", "(I[IIIFFIIZI)V",
     (void*)SkCanvasGlue::drawBitmapArray},
-    
     {"nativeDrawBitmapMatrix", "(IIII)V",
         (void*)SkCanvasGlue::drawBitmapMatrix},
     {"nativeDrawBitmapMesh", "(IIII[FI[III)V",
         (void*)SkCanvasGlue::drawBitmapMesh},
     {"nativeDrawVertices", "(III[FI[FI[II[SIII)V",
         (void*)SkCanvasGlue::drawVertices},
-    {"native_drawText","(I[CIIFFI)V",
-        (void*) SkCanvasGlue::drawText___CIIFFPaint},
-    {"native_drawText","(ILjava/lang/String;IIFFI)V",
-        (void*) SkCanvasGlue::drawText__StringIIFFPaint},
-    {"drawText","(Ljava/lang/String;FFLandroid/graphics/Paint;)V",
-        (void*) SkCanvasGlue::drawString},
+    {"native_drawText","(I[CIIFFII)V",
+        (void*) SkCanvasGlue::drawText___CIIFFIPaint},
+    {"native_drawText","(ILjava/lang/String;IIFFII)V",
+        (void*) SkCanvasGlue::drawText__StringIIFFIPaint},
+    {"native_drawTextRun","(I[CIIIIFFII)V",
+        (void*) SkCanvasGlue::drawTextRun___CIIIIFFIPaint},
+    {"native_drawTextRun","(ILjava/lang/String;IIIIFFII)V",
+        (void*) SkCanvasGlue::drawTextRun__StringIIIIFFIPaint},
     {"native_drawPosText","(I[CII[FI)V",
         (void*) SkCanvasGlue::drawPosText___CII_FPaint},
     {"native_drawPosText","(ILjava/lang/String;[FI)V",
         (void*) SkCanvasGlue::drawPosText__String_FPaint},
-    {"native_drawTextOnPath","(I[CIIIFFI)V",
+    {"native_drawTextOnPath","(I[CIIIFFII)V",
         (void*) SkCanvasGlue::drawTextOnPath___CIIPathFFPaint},
-    {"native_drawTextOnPath","(ILjava/lang/String;IFFI)V",
+    {"native_drawTextOnPath","(ILjava/lang/String;IFFII)V",
         (void*) SkCanvasGlue::drawTextOnPath__StringPathFFPaint},
     {"native_drawPicture", "(II)V", (void*) SkCanvasGlue::drawPicture},
 
diff --git a/core/jni/android/graphics/ColorFilter.cpp b/core/jni/android/graphics/ColorFilter.cpp
index ebfb209..848234f 100644
--- a/core/jni/android/graphics/ColorFilter.cpp
+++ b/core/jni/android/graphics/ColorFilter.cpp
@@ -23,28 +23,38 @@
 #include "SkColorMatrixFilter.h"
 #include "SkPorterDuff.h"
 
+#include <SkiaColorFilter.h>
+
 namespace android {
 
+using namespace uirenderer;
+
 class SkColorFilterGlue {
 public:
-
-    static void finalizer(JNIEnv* env, jobject clazz, SkColorFilter* obj) {
+    static void finalizer(JNIEnv* env, jobject clazz, SkColorFilter* obj, SkiaColorFilter* f) {
+        delete f;
         obj->safeUnref();
     }
 
-    static SkColorFilter* CreatePorterDuffFilter(JNIEnv* env, jobject,
-                            jint srcColor, SkPorterDuff::Mode mode) {
-        return SkColorFilter::CreateModeFilter(srcColor,
-                                           SkPorterDuff::ToXfermodeMode(mode));
+    static SkColorFilter* CreatePorterDuffFilter(JNIEnv* env, jobject, jint srcColor,
+            SkPorterDuff::Mode mode) {
+        return SkColorFilter::CreateModeFilter(srcColor, SkPorterDuff::ToXfermodeMode(mode));
     }
  
-    static SkColorFilter* CreateLightingFilter(JNIEnv* env, jobject,
-                                               jint mul, jint add) {
+    static SkiaColorFilter* glCreatePorterDuffFilter(JNIEnv* env, jobject, jint srcColor,
+            SkPorterDuff::Mode mode) {
+        return new SkiaBlendFilter(srcColor, SkPorterDuff::ToXfermodeMode(mode));
+    }
+
+    static SkColorFilter* CreateLightingFilter(JNIEnv* env, jobject, jint mul, jint add) {
         return SkColorFilter::CreateLightingFilter(mul, add);
     }
     
-    static SkColorFilter* CreateColorMatrixFilter(JNIEnv* env, jobject,
-                                                  jfloatArray jarray) {
+    static SkiaColorFilter* glCreateLightingFilter(JNIEnv* env, jobject, jint mul, jint add) {
+        return new SkiaLightingFilter(mul, add);
+    }
+
+    static SkColorFilter* CreateColorMatrixFilter(JNIEnv* env, jobject, jfloatArray jarray) {
         AutoJavaFloatArray autoArray(env, jarray, 20);
         const float* src = autoArray.ptr();
 
@@ -58,26 +68,44 @@
         return new SkColorMatrixFilter(src);
 #endif
     }
- 
+
+    static SkiaColorFilter* glCreateColorMatrixFilter(JNIEnv* env, jobject, jfloatArray jarray) {
+        AutoJavaFloatArray autoArray(env, jarray, 20);
+        const float* src = autoArray.ptr();
+
+        float* colorMatrix = new float[16];
+        memcpy(colorMatrix, src, 4 * sizeof(float));
+        memcpy(&colorMatrix[4], &src[5], 4 * sizeof(float));
+        memcpy(&colorMatrix[8], &src[10], 4 * sizeof(float));
+        memcpy(&colorMatrix[12], &src[15], 4 * sizeof(float));
+
+        float* colorVector = new float[4];
+        colorVector[0] = src[4];
+        colorVector[1] = src[9];
+        colorVector[2] = src[14];
+        colorVector[3] = src[19];
+
+        return new SkiaColorMatrixFilter(colorMatrix, colorVector);
+    }
 };
 
 static JNINativeMethod colorfilter_methods[] = {
-    {"finalizer", "(I)V", (void*) SkColorFilterGlue::finalizer}
+    {"finalizer", "(II)V", (void*) SkColorFilterGlue::finalizer}
 };
 
 static JNINativeMethod porterduff_methods[] = {
-    {"native_CreatePorterDuffFilter","(II)I",
-        (void*) SkColorFilterGlue::CreatePorterDuffFilter}
+    { "native_CreatePorterDuffFilter", "(II)I", (void*) SkColorFilterGlue::CreatePorterDuffFilter   },
+    { "nCreatePorterDuffFilter",       "(II)I", (void*) SkColorFilterGlue::glCreatePorterDuffFilter }
 };
 
 static JNINativeMethod lighting_methods[] = {
-    {"native_CreateLightingFilter","(II)I",
-        (void*) SkColorFilterGlue::CreateLightingFilter}
+    { "native_CreateLightingFilter", "(II)I", (void*) SkColorFilterGlue::CreateLightingFilter   },
+    { "nCreateLightingFilter",       "(II)I", (void*) SkColorFilterGlue::glCreateLightingFilter },
 };
 
 static JNINativeMethod colormatrix_methods[] = {
-    {"nativeColorMatrixFilter","([F)I",
-        (void*) SkColorFilterGlue::CreateColorMatrixFilter}
+    { "nativeColorMatrixFilter", "([F)I", (void*) SkColorFilterGlue::CreateColorMatrixFilter   },
+    { "nColorMatrixFilter",      "([F)I", (void*) SkColorFilterGlue::glCreateColorMatrixFilter }
 };
 
 #define REG(env, name, array) \
@@ -85,7 +113,6 @@
                                                     SK_ARRAY_COUNT(array));  \
     if (result < 0) return result
 
-
 int register_android_graphics_ColorFilter(JNIEnv* env) {
     int result;
     
diff --git a/core/jni/android/graphics/Paint.cpp b/core/jni/android/graphics/Paint.cpp
index 780badc..e4d4850 100644
--- a/core/jni/android/graphics/Paint.cpp
+++ b/core/jni/android/graphics/Paint.cpp
@@ -31,6 +31,11 @@
 #include "SkShader.h"
 #include "SkTypeface.h"
 #include "SkXfermode.h"
+#include "unicode/ushape.h"
+#include "TextLayout.h"
+
+// temporary for debugging
+#include <utils/Log.h>
 
 namespace android {
 
@@ -57,6 +62,9 @@
 
 class SkPaintGlue {
 public:
+    enum MoveOpt {
+        AFTER, AT_OR_AFTER, BEFORE, AT_OR_BEFORE, AT
+    };
 
     static void finalizer(JNIEnv* env, jobject clazz, SkPaint* obj) {
         delete obj;
@@ -395,20 +403,161 @@
         env->ReleaseStringChars(text, textArray);
         return count;
     }
-    
-    static void getTextPath___CIIFFPath(JNIEnv* env, jobject clazz, SkPaint* paint, jcharArray text, int index, int count, jfloat x, jfloat y, SkPath* path) {
-        const jchar* textArray = env->GetCharArrayElements(text, NULL);
-        paint->getTextPath(textArray + index, count << 1, SkFloatToScalar(x), SkFloatToScalar(y), path);
-        env->ReleaseCharArrayElements(text, const_cast<jchar*>(textArray),
-                                      JNI_ABORT);
+
+    static jfloat doTextRunAdvances(JNIEnv *env, SkPaint *paint, const jchar *text,
+                                    jint start, jint count, jint contextCount, jint flags,
+                                    jfloatArray advances, jint advancesIndex) {
+        jfloat advancesArray[count];
+        jfloat totalAdvance;
+
+        TextLayout::getTextRunAdvances(paint, text, start, count, contextCount, flags,
+                                       advancesArray, totalAdvance);
+
+        if (advances != NULL) {
+            env->SetFloatArrayRegion(advances, advancesIndex, count, advancesArray);
+        }
+        return totalAdvance;
     }
- 
-    static void getTextPath__StringIIFFPath(JNIEnv* env, jobject clazz, SkPaint* paint, jstring text, int start, int end, jfloat x, jfloat y, SkPath* path) {
+
+    static float getTextRunAdvances___CIIIII_FI(JNIEnv* env, jobject clazz, SkPaint* paint,
+            jcharArray text, jint index, jint count, jint contextIndex, jint contextCount,
+            jint flags, jfloatArray advances, jint advancesIndex) {
+        jchar* textArray = env->GetCharArrayElements(text, NULL);
+        jfloat result = doTextRunAdvances(env, paint, textArray + contextIndex,
+            index - contextIndex, count, contextCount, flags, advances, advancesIndex);
+        env->ReleaseCharArrayElements(text, textArray, JNI_ABORT);
+        return result;
+    }
+
+    static float getTextRunAdvances__StringIIIII_FI(JNIEnv* env, jobject clazz, SkPaint* paint,
+            jstring text, jint start, jint end, jint contextStart, jint contextEnd, jint flags,
+            jfloatArray advances, jint advancesIndex) {
         const jchar* textArray = env->GetStringChars(text, NULL);
-        paint->getTextPath(textArray + start, (end - start) << 1, SkFloatToScalar(x), SkFloatToScalar(y), path);
+        jfloat result = doTextRunAdvances(env, paint, textArray + contextStart,
+            start - contextStart, end - start, contextEnd - contextStart, flags, advances,
+            advancesIndex);
+        env->ReleaseStringChars(text, textArray);
+        return result;
+    }
+
+    static jint doTextRunCursor(JNIEnv *env, SkPaint* paint, const jchar *text, jint start,
+            jint count, jint flags, jint offset, jint opt) {
+        SkScalar scalarArray[count];
+        jchar buffer[count];
+
+        // this is where we'd call harfbuzz
+        // for now we just use ushape.c and widths returned from skia
+
+        int widths;
+        if (flags & 0x1) { // rtl, call arabic shaping in case
+            UErrorCode status = U_ZERO_ERROR;
+            // Use fixed length since we need to keep start and count valid
+            u_shapeArabic(text + start, count, buffer, count,
+                    U_SHAPE_LENGTH_FIXED_SPACES_NEAR | U_SHAPE_TEXT_DIRECTION_LOGICAL |
+                    U_SHAPE_LETTERS_SHAPE | U_SHAPE_X_LAMALEF_SUB_ALTERNATE, &status);
+            // we shouldn't fail unless there's an out of memory condition,
+            // in which case we're hosed anyway
+            for (int i = 0; i < count; ++i) {
+              if (buffer[i] == 0xffff) {
+                buffer[i] = 0x200b; // zero-width-space for skia
+              }
+            }
+            widths = paint->getTextWidths(buffer, count << 1, scalarArray);
+        } else {
+            widths = paint->getTextWidths(text + start, count << 1, scalarArray);
+        }
+
+        if (widths < count) {
+            // Skia operates on code points, not code units, so surrogate pairs return only one
+            // value. Expand the result so we have one value per UTF-16 code unit.
+
+            // Note, skia's getTextWidth gets confused if it encounters a surrogate pair,
+            // leaving the remaining widths zero.  Not nice.
+            const jchar *chars = text + start;
+            for (int i = count, p = widths - 1; --i > p;) {
+                if (chars[i] >= 0xdc00 && chars[i] < 0xe000 &&
+                        chars[i-1] >= 0xd800 && chars[i-1] < 0xdc00) {
+                    scalarArray[i] = 0;
+                } else {
+                  scalarArray[i] = scalarArray[--p];
+                }
+            }
+        }
+
+        jint pos = offset - start;
+        switch (opt) {
+        case AFTER:
+          if (pos < count) {
+            pos += 1;
+          }
+          // fall through
+        case AT_OR_AFTER:
+          while (pos < count && scalarArray[pos] == 0) {
+            ++pos;
+          }
+          break;
+        case BEFORE:
+          if (pos > 0) {
+            --pos;
+          }
+          // fall through
+        case AT_OR_BEFORE:
+          while (pos > 0 && scalarArray[pos] == 0) {
+            --pos;
+          }
+          break;
+        case AT:
+        default:
+          if (scalarArray[pos] == 0) {
+            pos = -1;
+          }
+          break;
+        }
+
+        if (pos != -1) {
+          pos += start;
+        }
+
+        return pos;
+    }
+
+    static jint getTextRunCursor___C(JNIEnv* env, jobject clazz, SkPaint* paint, jcharArray text,
+            jint contextStart, jint contextCount, jint flags, jint offset, jint cursorOpt) {
+        jchar* textArray = env->GetCharArrayElements(text, NULL);
+        jint result = doTextRunCursor(env, paint, textArray, contextStart, contextCount, flags,
+                offset, cursorOpt);
+        env->ReleaseCharArrayElements(text, textArray, JNI_ABORT);
+        return result;
+    }
+
+    static jint getTextRunCursor__String(JNIEnv* env, jobject clazz, SkPaint* paint, jstring text,
+            jint contextStart, jint contextEnd, jint flags, jint offset, jint cursorOpt) {
+        const jchar* textArray = env->GetStringChars(text, NULL);
+        jint result = doTextRunCursor(env, paint, textArray, contextStart,
+                contextEnd - contextStart, flags, offset, cursorOpt);
+        env->ReleaseStringChars(text, textArray);
+        return result;
+    }
+
+    static void getTextPath(JNIEnv* env, SkPaint* paint, const jchar* text, jint count,
+                            jint bidiFlags, jfloat x, jfloat y, SkPath *path) {
+        TextLayout::getTextPath(paint, text, count, bidiFlags, x, y, path);
+    }
+
+    static void getTextPath___C(JNIEnv* env, jobject clazz, SkPaint* paint, jint bidiFlags,
+            jcharArray text, int index, int count, jfloat x, jfloat y, SkPath* path) {
+        const jchar* textArray = env->GetCharArrayElements(text, NULL);
+        getTextPath(env, paint, textArray + index, count, bidiFlags, x, y, path);
+        env->ReleaseCharArrayElements(text, const_cast<jchar*>(textArray), JNI_ABORT);
+    }
+
+    static void getTextPath__String(JNIEnv* env, jobject clazz, SkPaint* paint, jint bidiFlags,
+            jstring text, int start, int end, jfloat x, jfloat y, SkPath* path) {
+        const jchar* textArray = env->GetStringChars(text, NULL);
+        getTextPath(env, paint, textArray + start, end - start, bidiFlags, x, y, path);
         env->ReleaseStringChars(text, textArray);
     }
- 
+
     static void setShadowLayer(JNIEnv* env, jobject jpaint, jfloat radius,
                                jfloat dx, jfloat dy, int color) {
         NPE_CHECK_RETURN_VOID(env, jpaint);
@@ -576,8 +725,15 @@
     {"native_breakText","(Ljava/lang/String;ZF[F)I", (void*) SkPaintGlue::breakTextS},
     {"native_getTextWidths","(I[CII[F)I", (void*) SkPaintGlue::getTextWidths___CII_F},
     {"native_getTextWidths","(ILjava/lang/String;II[F)I", (void*) SkPaintGlue::getTextWidths__StringII_F},
-    {"native_getTextPath","(I[CIIFFI)V", (void*) SkPaintGlue::getTextPath___CIIFFPath},
-    {"native_getTextPath","(ILjava/lang/String;IIFFI)V", (void*) SkPaintGlue::getTextPath__StringIIFFPath},
+    {"native_getTextRunAdvances","(I[CIIIII[FI)F", (void*)
+        SkPaintGlue::getTextRunAdvances___CIIIII_FI},
+    {"native_getTextRunAdvances","(ILjava/lang/String;IIIII[FI)F",
+        (void*) SkPaintGlue::getTextRunAdvances__StringIIIII_FI},
+    {"native_getTextRunCursor", "(I[CIIIII)I", (void*) SkPaintGlue::getTextRunCursor___C},
+    {"native_getTextRunCursor", "(ILjava/lang/String;IIIII)I",
+        (void*) SkPaintGlue::getTextRunCursor__String},
+    {"native_getTextPath","(II[CIIFFI)V", (void*) SkPaintGlue::getTextPath___C},
+    {"native_getTextPath","(IILjava/lang/String;IIFFI)V", (void*) SkPaintGlue::getTextPath__String},
     {"nativeGetStringBounds", "(ILjava/lang/String;IILandroid/graphics/Rect;)V",
                                         (void*) SkPaintGlue::getStringBounds },
     {"nativeGetCharArrayBounds", "(I[CIILandroid/graphics/Rect;)V",
diff --git a/core/jni/android/graphics/Shader.cpp b/core/jni/android/graphics/Shader.cpp
index b09c62b..34b4ab5 100644
--- a/core/jni/android/graphics/Shader.cpp
+++ b/core/jni/android/graphics/Shader.cpp
@@ -8,6 +8,15 @@
 #include "SkTemplates.h"
 #include "SkXfermode.h"
 
+#include <SkiaShader.h>
+
+using namespace android::uirenderer;
+
+static struct {
+    jclass clazz;
+    jfieldID shader;
+} gShaderClassInfo;
+
 static void ThrowIAE_IfNull(JNIEnv* env, void* ptr) {
     if (NULL == ptr) {
         doThrowIAE(env);
@@ -41,8 +50,9 @@
 
 ///////////////////////////////////////////////////////////////////////////////////////////////
 
-static void Shader_destructor(JNIEnv* env, jobject, SkShader* shader)
+static void Shader_destructor(JNIEnv* env, jobject o, SkShader* shader, SkiaShader* skiaShader)
 {
+    delete skiaShader;
     shader->safeUnref();
 }
 
@@ -51,7 +61,8 @@
     return shader ? shader->getLocalMatrix(matrix) : false;
 }
  
-static void Shader_setLocalMatrix(JNIEnv* env, jobject, SkShader* shader, const SkMatrix* matrix)
+static void Shader_setLocalMatrix(JNIEnv* env, jobject o, SkShader* shader, SkiaShader* skiaShader,
+        const SkMatrix* matrix)
 {
     if (shader) {
         if (NULL == matrix) {
@@ -60,24 +71,34 @@
         else {
             shader->setLocalMatrix(*matrix);
         }
+        skiaShader->setMatrix(const_cast<SkMatrix*>(matrix));
     }
 }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////
 
-static SkShader* BitmapShader_constructor(JNIEnv* env, jobject, const SkBitmap* bitmap,
+static SkShader* BitmapShader_constructor(JNIEnv* env, jobject o, const SkBitmap* bitmap,
                                           int tileModeX, int tileModeY)
 {
     SkShader* s = SkShader::CreateBitmapShader(*bitmap,
                                         (SkShader::TileMode)tileModeX,
                                         (SkShader::TileMode)tileModeY);
+
     ThrowIAE_IfNull(env, s);
     return s;
 }
+
+static SkiaShader* BitmapShader_postConstructor(JNIEnv* env, jobject o, SkShader* shader,
+        SkBitmap* bitmap, int tileModeX, int tileModeY) {
+    SkiaShader* skiaShader = new SkiaBitmapShader(bitmap, shader,
+            static_cast<SkShader::TileMode>(tileModeX), static_cast<SkShader::TileMode>(tileModeY),
+            NULL, (shader->getFlags() & SkShader::kOpaqueAlpha_Flag) == 0);
+    return skiaShader;
+}
     
 ///////////////////////////////////////////////////////////////////////////////////////////////
 
-static SkShader* LinearGradient_create1(JNIEnv* env, jobject,
+static SkShader* LinearGradient_create1(JNIEnv* env, jobject o,
                                         float x0, float y0, float x1, float y1,
                                         jintArray colorArray, jfloatArray posArray, int tileMode)
 {
@@ -85,31 +106,86 @@
     pts[0].set(SkFloatToScalar(x0), SkFloatToScalar(y0));
     pts[1].set(SkFloatToScalar(x1), SkFloatToScalar(y1));
 
-    size_t      count = env->GetArrayLength(colorArray);
+    size_t count = env->GetArrayLength(colorArray);
     const jint* colorValues = env->GetIntArrayElements(colorArray, NULL);
 
     SkAutoSTMalloc<8, SkScalar> storage(posArray ? count : 0);
     SkScalar*                   pos = NULL;
-    
+
     if (posArray) {
         AutoJavaFloatArray autoPos(env, posArray, count);
         const float* posValues = autoPos.ptr();
         pos = (SkScalar*)storage.get();
-        for (size_t i = 0; i < count; i++)
+        for (size_t i = 0; i < count; i++) {
             pos[i] = SkFloatToScalar(posValues[i]);
+        }
     }
-
+    
     SkShader* shader = SkGradientShader::CreateLinear(pts,
                                 reinterpret_cast<const SkColor*>(colorValues),
                                 pos, count,
                                 static_cast<SkShader::TileMode>(tileMode));
-    env->ReleaseIntArrayElements(colorArray, const_cast<jint*>(colorValues),
-                                 JNI_ABORT);
+
+    env->ReleaseIntArrayElements(colorArray, const_cast<jint*>(colorValues), JNI_ABORT);
     ThrowIAE_IfNull(env, shader);
     return shader;
 }
 
-static SkShader* LinearGradient_create2(JNIEnv* env, jobject,
+static SkiaShader* LinearGradient_postCreate1(JNIEnv* env, jobject o, SkShader* shader,
+        float x0, float y0, float x1, float y1, jintArray colorArray,
+        jfloatArray posArray, int tileMode) {
+
+    size_t count = env->GetArrayLength(colorArray);
+    const jint* colorValues = env->GetIntArrayElements(colorArray, NULL);
+
+    jfloat* storedBounds = new jfloat[4];
+    storedBounds[0] = x0; storedBounds[1] = y0;
+    storedBounds[2] = x1; storedBounds[3] = y1;
+    jfloat* storedPositions = new jfloat[count];
+    uint32_t* storedColors = new uint32_t[count];
+    memcpy(storedColors, colorValues, count);
+
+    if (posArray) {
+        AutoJavaFloatArray autoPos(env, posArray, count);
+        const float* posValues = autoPos.ptr();
+        for (size_t i = 0; i < count; i++) {
+            storedPositions[i] = posValues[i];
+        }
+    } else {
+        storedPositions[0] = 0.0f;
+        storedPositions[1] = 1.0f;
+    }
+
+    SkiaShader* skiaShader = new SkiaLinearGradientShader(storedBounds, storedColors,
+            storedPositions, count, shader, static_cast<SkShader::TileMode>(tileMode), NULL,
+            (shader->getFlags() & SkShader::kOpaqueAlpha_Flag) == 0);
+
+    env->ReleaseIntArrayElements(colorArray, const_cast<jint*>(colorValues), JNI_ABORT);
+    return skiaShader;
+}
+
+static SkiaShader* LinearGradient_postCreate2(JNIEnv* env, jobject o, SkShader* shader,
+        float x0, float y0, float x1, float y1, int color0, int color1, int tileMode) {
+    float* storedBounds = new float[4];
+    storedBounds[0] = x0; storedBounds[1] = y0;
+    storedBounds[2] = x1; storedBounds[3] = y1;
+
+    float* storedPositions = new float[2];
+    storedPositions[0] = 0.0f;
+    storedPositions[1] = 1.0f;
+
+    uint32_t* storedColors = new uint32_t[2];
+    storedColors[0] = color0;
+    storedColors[1] = color1;
+
+    SkiaShader* skiaShader = new SkiaLinearGradientShader(storedBounds, storedColors,
+            storedPositions, 2, shader, static_cast<SkShader::TileMode>(tileMode), NULL,
+            (shader->getFlags() & SkShader::kOpaqueAlpha_Flag) == 0);
+
+    return skiaShader;
+}
+
+static SkShader* LinearGradient_create2(JNIEnv* env, jobject o,
                                         float x0, float y0, float x1, float y1,
                                         int color0, int color1, int tileMode)
 {
@@ -120,8 +196,9 @@
     SkColor colors[2];
     colors[0] = color0;
     colors[1] = color1;
-
+    
     SkShader* s = SkGradientShader::CreateLinear(pts, colors, NULL, 2, (SkShader::TileMode)tileMode);
+
     ThrowIAE_IfNull(env, s);
     return s;
 }
@@ -222,18 +299,38 @@
 
 ///////////////////////////////////////////////////////////////////////////////////////////////
 
-static SkShader* ComposeShader_create1(JNIEnv* env, jobject,
-                                       SkShader* shaderA, SkShader* shaderB, SkXfermode* mode)
+static SkShader* ComposeShader_create1(JNIEnv* env, jobject o,
+        SkShader* shaderA, SkShader* shaderB, SkXfermode* mode)
 {
     return new SkComposeShader(shaderA, shaderB, mode);
 }
 
-static SkShader* ComposeShader_create2(JNIEnv* env, jobject,
-                                       SkShader* shaderA, SkShader* shaderB, SkPorterDuff::Mode mode)
+static SkShader* ComposeShader_create2(JNIEnv* env, jobject o,
+        SkShader* shaderA, SkShader* shaderB, SkPorterDuff::Mode porterDuffMode)
 {
-    SkAutoUnref au(SkPorterDuff::CreateXfermode(mode));
+    SkAutoUnref au(SkPorterDuff::CreateXfermode(porterDuffMode));
+    SkXfermode* mode = (SkXfermode*) au.get();
+    return new SkComposeShader(shaderA, shaderB, mode);
+}
 
-    return new SkComposeShader(shaderA, shaderB, (SkXfermode*)au.get());
+static SkiaShader* ComposeShader_postCreate2(JNIEnv* env, jobject o, SkShader* shader,
+        SkiaShader* shaderA, SkiaShader* shaderB, SkPorterDuff::Mode porterDuffMode) {
+    SkAutoUnref au(SkPorterDuff::CreateXfermode(porterDuffMode));
+    SkXfermode* mode = (SkXfermode*) au.get();
+    SkXfermode::Mode skiaMode;
+    if (!SkXfermode::IsMode(mode, &skiaMode)) {
+        skiaMode = SkXfermode::kSrcOver_Mode;
+    }
+    return new SkiaComposeShader(shaderA, shaderB, skiaMode, shader);
+}
+
+static SkiaShader* ComposeShader_postCreate1(JNIEnv* env, jobject o, SkShader* shader,
+        SkiaShader* shaderA, SkiaShader* shaderB, SkXfermode* mode) {
+    SkXfermode::Mode skiaMode;
+    if (!SkXfermode::IsMode(mode, &skiaMode)) {
+        skiaMode = SkXfermode::kSrcOver_Mode;
+    }
+    return new SkiaComposeShader(shaderA, shaderB, skiaMode, shader);
 }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////
@@ -244,18 +341,21 @@
 };
 
 static JNINativeMethod gShaderMethods[] = {
-    { "nativeDestructor",        "(I)V",     (void*)Shader_destructor        },
+    { "nativeDestructor",        "(II)V",    (void*)Shader_destructor        },
     { "nativeGetLocalMatrix",    "(II)Z",    (void*)Shader_getLocalMatrix    },
-    { "nativeSetLocalMatrix",    "(II)V",    (void*)Shader_setLocalMatrix    }
+    { "nativeSetLocalMatrix",    "(III)V",   (void*)Shader_setLocalMatrix    }
 };
 
 static JNINativeMethod gBitmapShaderMethods[] = {
-    { "nativeCreate",   "(III)I",  (void*)BitmapShader_constructor }
+    { "nativeCreate",     "(III)I",  (void*)BitmapShader_constructor },
+    { "nativePostCreate", "(IIII)I", (void*)BitmapShader_postConstructor }
 };
 
 static JNINativeMethod gLinearGradientMethods[] = {
-    { "nativeCreate1",  "(FFFF[I[FI)I", (void*)LinearGradient_create1   },
-    { "nativeCreate2",  "(FFFFIII)I",   (void*)LinearGradient_create2   }
+    { "nativeCreate1",     "(FFFF[I[FI)I",  (void*)LinearGradient_create1     },
+    { "nativeCreate2",     "(FFFFIII)I",    (void*)LinearGradient_create2     },
+    { "nativePostCreate1", "(IFFFF[I[FI)I", (void*)LinearGradient_postCreate1 },
+    { "nativePostCreate2", "(IFFFFIII)I",   (void*)LinearGradient_postCreate2 }
 };
 
 static JNINativeMethod gRadialGradientMethods[] = {
@@ -269,8 +369,10 @@
 };
 
 static JNINativeMethod gComposeShaderMethods[] = {
-    {"nativeCreate1",  "(III)I",    (void*)ComposeShader_create1 },
-    {"nativeCreate2",  "(III)I",    (void*)ComposeShader_create2 }
+    {"nativeCreate1",      "(III)I",   (void*)ComposeShader_create1     },
+    {"nativeCreate2",      "(III)I",   (void*)ComposeShader_create2     },
+    {"nativePostCreate1",  "(IIII)I",  (void*)ComposeShader_postCreate1 },
+    {"nativePostCreate2",  "(IIII)I",  (void*)ComposeShader_postCreate2 }
 };
 
 #include <android_runtime/AndroidRuntime.h>
diff --git a/core/jni/android/graphics/TextLayout.cpp b/core/jni/android/graphics/TextLayout.cpp
new file mode 100644
index 0000000..716d960
--- /dev/null
+++ b/core/jni/android/graphics/TextLayout.cpp
@@ -0,0 +1,335 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "TextLayout.h"
+
+#include <android_runtime/AndroidRuntime.h>
+
+#include "SkTemplates.h"
+#include "unicode/ubidi.h"
+#include "unicode/ushape.h"
+#include <utils/Log.h>
+
+
+namespace android {
+// Returns true if we might need layout.  If bidiFlags force LTR, assume no layout, if
+// bidiFlags indicate there probably is RTL, assume we do, otherwise scan the text
+// looking for a character >= the first RTL character in unicode and assume we do if
+// we find one.
+bool TextLayout::needsLayout(const jchar* text, jint len, jint bidiFlags) {
+    if (bidiFlags == kBidi_Force_LTR) {
+        return false;
+    }
+    if ((bidiFlags == kBidi_RTL) || (bidiFlags == kBidi_Default_RTL) ||
+            bidiFlags == kBidi_Force_RTL) {
+        return true;
+    }
+    for (int i = 0; i < len; ++i) {
+        if (text[i] >= 0x0590) {
+            return true;
+        }
+    }
+    return false;
+}
+
+/**
+ * Character-based Arabic shaping.
+ *
+ * We'll use harfbuzz and glyph-based shaping instead once we're set up for it.
+ *
+ * @context the text context
+ * @start the start of the text to render
+ * @count the length of the text to render, start + count  must be <= contextCount
+ * @contextCount the length of the context
+ * @shaped where to put the shaped text, must have capacity for count uchars
+ * @return the length of the shaped text, or -1 if error
+ */
+int TextLayout::shapeRtlText(const jchar* context, jsize start, jsize count, jsize contextCount,
+                        jchar* shaped, UErrorCode &status) {
+    jchar buffer[contextCount];
+
+    // Use fixed length since we need to keep start and count valid
+    u_shapeArabic(context, contextCount, buffer, contextCount,
+                   U_SHAPE_LENGTH_FIXED_SPACES_NEAR |
+                   U_SHAPE_TEXT_DIRECTION_LOGICAL | U_SHAPE_LETTERS_SHAPE |
+                   U_SHAPE_X_LAMALEF_SUB_ALTERNATE, &status);
+
+    if (U_SUCCESS(status)) {
+        // trim out 0xffff following ligatures, if any
+        int end = 0;
+        for (int i = start, e = start + count; i < e; ++i) {
+            if (buffer[i] != 0xffff) {
+                buffer[end++] = buffer[i];
+            }
+        }
+        count = end;
+        // LOG(LOG_INFO, "CSRTL", "start %d count %d ccount %d\n", start, count, contextCount);
+        ubidi_writeReverse(buffer, count, shaped, count, UBIDI_DO_MIRRORING | UBIDI_OUTPUT_REVERSE
+                           | UBIDI_KEEP_BASE_COMBINING, &status);
+        if (U_SUCCESS(status)) {
+            return count;
+        }
+    }
+
+    return -1;
+}
+
+/**
+ * Basic character-based layout supporting rtl and arabic shaping.
+ * Runs bidi on the text and generates a reordered, shaped line in buffer, returning
+ * the length.
+ * @text the text
+ * @len the length of the text in uchars
+ * @dir receives the resolved paragraph direction
+ * @buffer the buffer to receive the reordered, shaped line.  Must have capacity of
+ * at least len jchars.
+ * @flags line bidi flags
+ * @return the length of the reordered, shaped line, or -1 if error
+ */
+jint TextLayout::layoutLine(const jchar* text, jint len, jint flags, int &dir, jchar* buffer,
+        UErrorCode &status) {
+    static const int RTL_OPTS = UBIDI_DO_MIRRORING | UBIDI_KEEP_BASE_COMBINING |
+            UBIDI_REMOVE_BIDI_CONTROLS | UBIDI_OUTPUT_REVERSE;
+
+    UBiDiLevel bidiReq = 0;
+    switch (flags) {
+    case kBidi_LTR: bidiReq = 0; break; // no ICU constant, canonical LTR level
+    case kBidi_RTL: bidiReq = 1; break; // no ICU constant, canonical RTL level
+    case kBidi_Default_LTR: bidiReq = UBIDI_DEFAULT_LTR; break;
+    case kBidi_Default_RTL: bidiReq = UBIDI_DEFAULT_RTL; break;
+    case kBidi_Force_LTR: memcpy(buffer, text, len * sizeof(jchar)); return len;
+    case kBidi_Force_RTL: return shapeRtlText(text, 0, len, len, buffer, status);
+    }
+
+    int32_t result = -1;
+
+    UBiDi* bidi = ubidi_open();
+    if (bidi) {
+        ubidi_setPara(bidi, text, len, bidiReq, NULL, &status);
+        if (U_SUCCESS(status)) {
+            dir = ubidi_getParaLevel(bidi) & 0x1; // 0 if ltr, 1 if rtl
+
+            int rc = ubidi_countRuns(bidi, &status);
+            if (U_SUCCESS(status)) {
+                // LOG(LOG_INFO, "LAYOUT", "para bidiReq=%d dir=%d rc=%d\n", bidiReq, dir, rc);
+
+                int32_t slen = 0;
+                for (int i = 0; i < rc; ++i) {
+                    int32_t start;
+                    int32_t length;
+                    UBiDiDirection runDir = ubidi_getVisualRun(bidi, i, &start, &length);
+
+                    if (runDir == UBIDI_RTL) {
+                        slen += shapeRtlText(text + start, 0, length, length, buffer + slen, status);
+                    } else {
+                        memcpy(buffer + slen, text + start, length * sizeof(jchar));
+                        slen += length;
+                    }
+                }
+                if (U_SUCCESS(status)) {
+                    result = slen;
+                }
+            }
+        }
+        ubidi_close(bidi);
+    }
+
+    return result;
+}
+
+bool TextLayout::prepareText(SkPaint *paint, const jchar* text, jsize len, jint bidiFlags,
+        const jchar** outText, int32_t* outBytes, jchar** outBuffer) {
+    const jchar *workText = text;
+    jchar *buffer = NULL;
+    int dir = kDirection_LTR;
+    if (needsLayout(text, len, bidiFlags)) {
+        buffer =(jchar *) malloc(len * sizeof(jchar));
+        if (!buffer) {
+            return false;
+        }
+        UErrorCode status = U_ZERO_ERROR;
+        len = layoutLine(text, len, bidiFlags, dir, buffer, status); // might change len, dir
+        if (!U_SUCCESS(status)) {
+            LOG(LOG_WARN, "LAYOUT", "drawText error %d\n", status);
+            free(buffer);
+            return false; // can't render
+        }
+
+        workText = buffer; // use the shaped text
+    }
+
+    bool trimLeft = false;
+    bool trimRight = false;
+
+    SkPaint::Align horiz = paint->getTextAlign();
+    switch (horiz) {
+        case SkPaint::kLeft_Align: trimLeft = dir & kDirection_Mask; break;
+        case SkPaint::kCenter_Align: trimLeft = trimRight = true; break;
+        case SkPaint::kRight_Align: trimRight = !(dir & kDirection_Mask);
+        default: break;
+    }
+    const jchar* workLimit = workText + len;
+
+    if (trimLeft) {
+        while (workText < workLimit && *workText == ' ') {
+            ++workText;
+        }
+    }
+    if (trimRight) {
+        while (workLimit > workText && *(workLimit - 1) == ' ') {
+            --workLimit;
+        }
+    }
+
+    *outBytes = (workLimit - workText) << 1;
+    *outText = workText;
+    *outBuffer = buffer;
+
+    return true;
+}
+
+// Draws or gets the path of a paragraph of text on a single line, running bidi and shaping.
+// This will draw if canvas is not null, otherwise path must be non-null and it will create
+// a path representing the text that would have been drawn.
+void TextLayout::handleText(SkPaint *paint, const jchar* text, jsize len,
+                            jint bidiFlags, jfloat x, jfloat y,SkCanvas *canvas, SkPath *path) {
+    const jchar *workText;
+    jchar *buffer = NULL;
+    int32_t workBytes;
+    if (prepareText(paint, text, len, bidiFlags, &workText, &workBytes, &buffer)) {
+        SkScalar x_ = SkFloatToScalar(x);
+        SkScalar y_ = SkFloatToScalar(y);
+        if (canvas) {
+            canvas->drawText(workText, workBytes, x_, y_, *paint);
+        } else {
+            paint->getTextPath(workText, workBytes, x_, y_, path);
+        }
+        free(buffer);
+    }
+}
+
+void TextLayout::drawTextRun(SkPaint* paint, const jchar* chars,
+                             jint start, jint count, jint contextCount,
+                             int dirFlags, jfloat x, jfloat y, SkCanvas* canvas) {
+
+     SkScalar x_ = SkFloatToScalar(x);
+     SkScalar y_ = SkFloatToScalar(y);
+
+     uint8_t rtl = dirFlags & 0x1;
+     if (rtl) {
+         SkAutoSTMalloc<80, jchar> buffer(contextCount);
+         UErrorCode status = U_ZERO_ERROR;
+         count = shapeRtlText(chars, start, count, contextCount, buffer.get(), status);
+         if (U_SUCCESS(status)) {
+             canvas->drawText(buffer.get(), count << 1, x_, y_, *paint);
+         } else {
+             LOG(LOG_WARN, "LAYOUT", "drawTextRun error %d\n", status);
+         }
+     } else {
+         canvas->drawText(chars + start, count << 1, x_, y_, *paint);
+     }
+ }
+
+void TextLayout::getTextRunAdvances(SkPaint *paint, const jchar *chars, jint start,
+                                    jint count, jint contextCount, jint dirFlags,
+                                    jfloat *resultAdvances, jfloat &resultTotalAdvance) {
+    jchar buffer[contextCount];
+
+    SkScalar* scalarArray = (SkScalar *)resultAdvances;
+    resultTotalAdvance = 0;
+
+    // this is where we'd call harfbuzz
+    // for now we just use ushape.c
+
+    int widths;
+    const jchar* text;
+    if (dirFlags & 0x1) { // rtl, call arabic shaping in case
+        UErrorCode status = U_ZERO_ERROR;
+        // Use fixed length since we need to keep start and count valid
+        u_shapeArabic(chars, contextCount, buffer, contextCount,
+                      U_SHAPE_LENGTH_FIXED_SPACES_NEAR |
+                      U_SHAPE_TEXT_DIRECTION_LOGICAL | U_SHAPE_LETTERS_SHAPE |
+                      U_SHAPE_X_LAMALEF_SUB_ALTERNATE, &status);
+        // we shouldn't fail unless there's an out of memory condition,
+        // in which case we're hosed anyway
+        for (int i = start, e = i + count; i < e; ++i) {
+          if (buffer[i] == 0xffff) {
+            buffer[i] = 0x200b; // zero-width-space for skia
+          }
+        }
+        text = buffer + start;
+        widths = paint->getTextWidths(text, count << 1, scalarArray);
+    } else {
+        text = chars + start;
+        widths = paint->getTextWidths(text, count << 1, scalarArray);
+    }
+
+    if (widths < count) {
+        // Skia operates on code points, not code units, so surrogate pairs return only
+        // one value. Expand the result so we have one value per UTF-16 code unit.
+
+        // Note, skia's getTextWidth gets confused if it encounters a surrogate pair,
+        // leaving the remaining widths zero.  Not nice.
+        for (int i = 0, p = 0; i < widths; ++i) {
+            resultTotalAdvance += resultAdvances[p++] = SkScalarToFloat(scalarArray[i]);
+            if (p < count && text[p] >= 0xdc00 && text[p] < 0xe000 &&
+                    text[p-1] >= 0xd800 && text[p-1] < 0xdc00) {
+                resultAdvances[p++] = 0;
+            }
+        }
+    } else {
+        for (int i = 0; i < count; i++) {
+            resultTotalAdvance += resultAdvances[i] = SkScalarToFloat(scalarArray[i]);
+        }
+    }
+}
+
+
+// Draws a paragraph of text on a single line, running bidi and shaping
+void TextLayout::drawText(SkPaint* paint, const jchar* text, jsize len,
+                          int bidiFlags, jfloat x, jfloat y, SkCanvas* canvas) {
+
+    handleText(paint, text, len, bidiFlags, x, y, canvas, NULL);
+}
+
+void TextLayout::getTextPath(SkPaint *paint, const jchar *text, jsize len,
+                             jint bidiFlags, jfloat x, jfloat y, SkPath *path) {
+    handleText(paint, text, len, bidiFlags, x, y, NULL, path);
+}
+
+
+void TextLayout::drawTextOnPath(SkPaint* paint, const jchar* text, int count,
+                                int bidiFlags, jfloat hOffset, jfloat vOffset,
+                                SkPath* path, SkCanvas* canvas) {
+
+    SkScalar h_ = SkFloatToScalar(hOffset);
+    SkScalar v_ = SkFloatToScalar(vOffset);
+
+    if (!needsLayout(text, count, bidiFlags)) {
+        canvas->drawTextOnPathHV(text, count << 1, *path, h_, v_, *paint);
+        return;
+    }
+
+    SkAutoSTMalloc<80, jchar> buffer(count);
+    int dir = kDirection_LTR;
+    UErrorCode status = U_ZERO_ERROR;
+    count = layoutLine(text, count, bidiFlags, dir, buffer.get(), status);
+    if (U_SUCCESS(status)) {
+        canvas->drawTextOnPathHV(buffer.get(), count << 1, *path, h_, v_, *paint);
+    }
+}
+
+}
diff --git a/core/jni/android/graphics/TextLayout.h b/core/jni/android/graphics/TextLayout.h
new file mode 100644
index 0000000..3d05e18
--- /dev/null
+++ b/core/jni/android/graphics/TextLayout.h
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "jni.h"
+
+#include "SkCanvas.h"
+#include "SkPaint.h"
+#include "unicode/utypes.h"
+
+namespace android {
+
+class TextLayout {
+public:
+
+    enum {
+        kDirection_LTR = 0,
+        kDirection_RTL = 1,
+
+        kDirection_Mask = 0x1
+    };
+
+    enum {
+        kBidi_LTR = 0,
+        kBidi_RTL = 1,
+        kBidi_Default_LTR = 2,
+        kBidi_Default_RTL = 3,
+        kBidi_Force_LTR = 4,
+        kBidi_Force_RTL = 5,
+
+        kBidi_Mask = 0x7
+    };
+
+    /*
+     * Draws a unidirectional run of text.
+     */
+    static void drawTextRun(SkPaint* paint, const jchar* chars,
+                            jint start, jint count, jint contextCount,
+                            int dirFlags, jfloat x, jfloat y, SkCanvas* canvas);
+
+    static void getTextRunAdvances(SkPaint *paint, const jchar *chars, jint start,
+                                   jint count, jint contextCount, jint dirFlags,
+                                   jfloat *resultAdvances, jfloat &resultTotalAdvance);
+
+    static void drawText(SkPaint* paint, const jchar* text, jsize len,
+                         jint bidiFlags, jfloat x, jfloat y, SkCanvas* canvas);
+
+    static void getTextPath(SkPaint *paint, const jchar *text, jsize len,
+                            jint bidiFlags, jfloat x, jfloat y, SkPath *path);
+
+    static void drawTextOnPath(SkPaint* paint, const jchar* text, jsize len,
+                               int bidiFlags, jfloat hOffset, jfloat vOffset,
+                               SkPath* path, SkCanvas* canvas);
+                               
+   static bool prepareText(SkPaint *paint, const jchar* text, jsize len, jint bidiFlags,
+        const jchar** outText, int32_t* outBytes, jchar** outBuffer);
+
+private:
+    static bool needsLayout(const jchar* text, jint len, jint bidiFlags);
+    static int shapeRtlText(const jchar* context, jsize start, jsize count, jsize contextCount,
+                            jchar* shaped, UErrorCode &status);
+    static jint layoutLine(const jchar* text, jint len, jint flags, int &dir, jchar* buffer,
+                           UErrorCode &status);
+    static void handleText(SkPaint *paint, const jchar* text, jsize len,
+                           int bidiFlags, jfloat x, jfloat y,SkCanvas *canvas, SkPath *path);
+};
+
+}
diff --git a/core/jni/android/graphics/Typeface.cpp b/core/jni/android/graphics/Typeface.cpp
index 7c7bfeb..1fe72e6 100644
--- a/core/jni/android/graphics/Typeface.cpp
+++ b/core/jni/android/graphics/Typeface.cpp
@@ -130,7 +130,13 @@
         return NULL;
     }
     
-    return SkTypeface::CreateFromStream(new AssetStream(asset, true));
+    SkStream* stream = new AssetStream(asset, true);
+    SkTypeface* face = SkTypeface::CreateFromStream(stream);
+    // SkTypeFace::CreateFromStream calls ref() on the stream, so we
+    // need to unref it here or it won't be freed later on
+    stream->unref();
+
+    return face;
 }
 
 static SkTypeface* Typeface_createFromFile(JNIEnv* env, jobject, jstring jpath) {
diff --git a/core/jni/android_bluetooth_ScoSocket.cpp b/core/jni/android_bluetooth_ScoSocket.cpp
deleted file mode 100644
index 94e4409..0000000
--- a/core/jni/android_bluetooth_ScoSocket.cpp
+++ /dev/null
@@ -1,689 +0,0 @@
-/*
-** Copyright 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.
-*/
-
-#define LOG_TAG "bluetooth_ScoSocket.cpp"
-
-#include "android_bluetooth_common.h"
-#include "android_runtime/AndroidRuntime.h"
-#include "JNIHelp.h"
-#include "jni.h"
-#include "utils/Log.h"
-#include "utils/misc.h"
-
-#include <stdio.h>
-#include <string.h>
-#include <stdlib.h>
-#include <errno.h>
-#include <unistd.h>
-#include <pthread.h>
-#include <sys/socket.h>
-#include <sys/types.h>
-#include <sys/uio.h>
-#include <sys/poll.h>
-
-#ifdef HAVE_BLUETOOTH
-#include <bluetooth/bluetooth.h>
-#include <bluetooth/sco.h>
-#include <bluetooth/hci.h>
-
-#define MAX_LINE 255
-
-/*
- * Defines the module strings used in the blacklist file.
- * These are used by consumers of the blacklist file to see if the line is
- * used by that module.
- */
-#define SCO_BLACKLIST_MODULE_NAME "scoSocket"
-
-
-/* Define the type strings used in the blacklist file. */
-#define BLACKLIST_BY_NAME "name"
-#define BLACKLIST_BY_PARTIAL_NAME "partial_name"
-#define BLACKLIST_BY_OUI "vendor_oui"
-
-#endif
-
-/* Ideally, blocking I/O on a SCO socket would return when another thread
- * calls close(). However it does not right now, in fact close() on a SCO
- * socket has strange behavior (returns a bogus value) when other threads
- * are performing blocking I/O on that socket. So, to workaround, we always
- * call close() from the same thread that does blocking I/O. This requires the
- * use of a socketpair to signal the blocking I/O to abort.
- *
- * Unfortunately I don't know a way to abort connect() yet, but at least this
- * times out after the BT page timeout (10 seconds currently), so the thread
- * will die eventually. The fact that the thread can outlive
- * the Java object forces us to use a mutex in destoryNative().
- *
- * The JNI API is entirely async.
- *
- * Also note this class deals only with SCO connections, not with data
- * transmission.
- */
-namespace android {
-#ifdef HAVE_BLUETOOTH
-
-static JavaVM *jvm;
-static jfieldID field_mNativeData;
-static jmethodID method_onAccepted;
-static jmethodID method_onConnected;
-static jmethodID method_onClosed;
-
-struct thread_data_t;
-static void *work_thread(void *arg);
-static int connect_work(const char *address, uint16_t sco_pkt_type);
-static int accept_work(int signal_sk);
-static void wait_for_close(int sk, int signal_sk);
-static void closeNative(JNIEnv *env, jobject object);
-
-static void parseBlacklist(void);
-static uint16_t getScoType(char *address, const char *name);
-
-#define COMPARE_STRING(key, s) (!strncmp(key, s, strlen(s)))
-
-/* Blacklist data */
-typedef struct scoBlacklist {
-    int fieldType;
-    char *value;
-    uint16_t scoType;
-    struct scoBlacklist *next;
-} scoBlacklist_t;
-
-#define BL_TYPE_NAME 1   // Field type is name string
-
-static scoBlacklist_t *blacklist = NULL;
-
-/* shared native data - protected by mutex */
-typedef struct {
-    pthread_mutex_t mutex;
-    int signal_sk;        // socket to signal blocked I/O to unblock
-    jobject object;       // JNI global ref to the Java object
-    thread_data_t *thread_data;  // pointer to thread local data
-                                 // max 1 thread per sco socket
-} native_data_t;
-
-/* thread local data */
-struct thread_data_t {
-    native_data_t *nat;
-    bool is_accept;        // accept (listening) or connect (outgoing) thread
-    int signal_sk;         // socket for thread to listen for unblock signal
-    char address[BTADDR_SIZE];  // BT addres as string
-    uint16_t sco_pkt_type;   // SCO packet types supported
-};
-
-static inline native_data_t * get_native_data(JNIEnv *env, jobject object) {
-    return (native_data_t *)(env->GetIntField(object, field_mNativeData));
-}
-
-static uint16_t str2scoType (char *key) {
-    LOGV("%s: key = %s", __FUNCTION__, key);
-    if (COMPARE_STRING(key, "ESCO_HV1"))
-        return ESCO_HV1;
-    if (COMPARE_STRING(key, "ESCO_HV2"))
-        return ESCO_HV2;
-    if (COMPARE_STRING(key, "ESCO_HV3"))
-        return ESCO_HV3;
-    if (COMPARE_STRING(key, "ESCO_EV3"))
-        return ESCO_EV3;
-    if (COMPARE_STRING(key, "ESCO_EV4"))
-        return ESCO_EV4;
-    if (COMPARE_STRING(key, "ESCO_EV5"))
-        return ESCO_EV5;
-    if (COMPARE_STRING(key, "ESCO_2EV3"))
-        return ESCO_2EV3;
-    if (COMPARE_STRING(key, "ESCO_3EV3"))
-        return ESCO_3EV3;
-    if (COMPARE_STRING(key, "ESCO_2EV5"))
-        return ESCO_2EV5;
-    if (COMPARE_STRING(key, "ESCO_3EV5"))
-        return ESCO_3EV5;
-    if (COMPARE_STRING(key, "SCO_ESCO_MASK"))
-        return SCO_ESCO_MASK;
-    if (COMPARE_STRING(key, "EDR_ESCO_MASK"))
-        return EDR_ESCO_MASK;
-    if (COMPARE_STRING(key, "ALL_ESCO_MASK"))
-        return ALL_ESCO_MASK;
-    LOGE("Unknown SCO Type (%s) skipping",key);
-    return 0;
-}
-
-static void parseBlacklist(void) {
-    const char *filename = "/etc/bluetooth/blacklist.conf";
-    char line[MAX_LINE];
-    scoBlacklist_t *list = NULL;
-    scoBlacklist_t *newelem;
-
-    LOGV(__FUNCTION__);
-
-    /* Open file */
-    FILE *fp = fopen(filename, "r");
-    if(!fp) {
-        LOGE("Error(%s)opening blacklist file", strerror(errno));
-        return;
-    }
-
-    while (fgets(line, MAX_LINE, fp) != NULL) {
-        if ((COMPARE_STRING(line, "//")) || (!strcmp(line, "")))
-            continue;
-        char *module = strtok(line,":");
-        if (COMPARE_STRING(module, SCO_BLACKLIST_MODULE_NAME)) {
-            newelem = (scoBlacklist_t *)calloc(1, sizeof(scoBlacklist_t));
-            if (newelem == NULL) {
-                LOGE("%s: out of memory!", __FUNCTION__);
-                return;
-            }
-            // parse line
-            char *type = strtok(NULL, ",");
-            char *valueList = strtok(NULL, ",");
-            char *paramList = strtok(NULL, ",");
-            if (COMPARE_STRING(type, BLACKLIST_BY_NAME)) {
-                // Extract Name from Value list
-                newelem->fieldType = BL_TYPE_NAME;
-                newelem->value = (char *)calloc(1, strlen(valueList));
-                if (newelem->value == NULL) {
-                    LOGE("%s: out of memory!", __FUNCTION__);
-                    continue;
-                }
-                valueList++;  // Skip open quote
-                strncpy(newelem->value, valueList, strlen(valueList) - 1);
-
-                // Get Sco Settings from Parameters
-                char *param = strtok(paramList, ";");
-                uint16_t scoTypes = 0;
-                while (param != NULL) {
-                    uint16_t sco;
-                    if (param[0] == '-') {
-                        param++;
-                        sco = str2scoType(param);
-                        if (sco != 0)
-                            scoTypes &= ~sco;
-                    } else if (param[0] == '+') {
-                        param++;
-                        sco = str2scoType(param);
-                        if (sco != 0)
-                            scoTypes |= sco;
-                    } else if (param[0] == '=') {
-                        param++;
-                        sco = str2scoType(param);
-                        if (sco != 0)
-                            scoTypes = sco;
-                    } else {
-                        LOGE("Invalid SCO type must be =, + or -");
-                    }
-                    param = strtok(NULL, ";");
-                }
-                newelem->scoType = scoTypes;
-            } else {
-                LOGE("Unknown SCO type entry in Blacklist file");
-                continue;
-            }
-            if (list) {
-                list->next = newelem;
-                list = newelem;
-            } else {
-                blacklist = list = newelem;
-            }
-            LOGI("Entry name = %s ScoTypes = 0x%x", newelem->value,
-                 newelem->scoType);
-        }
-    }
-    fclose(fp);
-    return;
-}
-static uint16_t getScoType(char *address, const char *name) {
-    uint16_t ret = 0;
-    scoBlacklist_t *list = blacklist;
-
-    while (list != NULL) {
-        if (list->fieldType == BL_TYPE_NAME) {
-            if (COMPARE_STRING(name, list->value)) {
-                ret = list->scoType;
-                break;
-            }
-        }
-        list = list->next;
-    }
-    LOGI("%s %s - 0x%x",  __FUNCTION__, name, ret);
-    return ret;
-}
-#endif
-
-static void classInitNative(JNIEnv* env, jclass clazz) {
-    LOGV(__FUNCTION__);
-#ifdef HAVE_BLUETOOTH
-    if (env->GetJavaVM(&jvm) < 0) {
-        LOGE("Could not get handle to the VM");
-    }
-    field_mNativeData = get_field(env, clazz, "mNativeData", "I");
-    method_onAccepted = env->GetMethodID(clazz, "onAccepted", "(I)V");
-    method_onConnected = env->GetMethodID(clazz, "onConnected", "(I)V");
-    method_onClosed = env->GetMethodID(clazz, "onClosed", "()V");
-
-    /* Read the blacklist file in here */
-    parseBlacklist();
-#endif
-}
-
-/* Returns false if a serious error occured */
-static jboolean initNative(JNIEnv* env, jobject object) {
-    LOGV(__FUNCTION__);
-#ifdef HAVE_BLUETOOTH
-
-    native_data_t *nat = (native_data_t *) calloc(1, sizeof(native_data_t));
-    if (nat == NULL) {
-        LOGE("%s: out of memory!", __FUNCTION__);
-        return JNI_FALSE;
-    }
-
-    pthread_mutex_init(&nat->mutex, NULL);
-    env->SetIntField(object, field_mNativeData, (jint)nat);
-    nat->signal_sk = -1;
-    nat->object = NULL;
-    nat->thread_data = NULL;
-
-#endif
-    return JNI_TRUE;
-}
-
-static void destroyNative(JNIEnv* env, jobject object) {
-    LOGV(__FUNCTION__);
-#ifdef HAVE_BLUETOOTH
-    native_data_t *nat = get_native_data(env, object);
-    
-    closeNative(env, object);
-    
-    pthread_mutex_lock(&nat->mutex);
-    if (nat->thread_data != NULL) {
-        nat->thread_data->nat = NULL;
-    }
-    pthread_mutex_unlock(&nat->mutex);
-    pthread_mutex_destroy(&nat->mutex);
-
-    free(nat);
-#endif
-}
-
-static jboolean acceptNative(JNIEnv *env, jobject object) {
-    LOGV(__FUNCTION__);
-#ifdef HAVE_BLUETOOTH
-    native_data_t *nat = get_native_data(env, object);
-    int signal_sks[2];
-    pthread_t thread;
-    struct thread_data_t *data = NULL;
-
-    pthread_mutex_lock(&nat->mutex);
-    if (nat->signal_sk != -1) {
-        pthread_mutex_unlock(&nat->mutex);
-        return JNI_FALSE;
-    }
-
-    // setup socketpair to pass messages between threads
-    if (socketpair(AF_UNIX, SOCK_STREAM, 0, signal_sks) < 0) {
-        LOGE("%s: socketpair() failed: %s", __FUNCTION__, strerror(errno));
-        pthread_mutex_unlock(&nat->mutex);
-        return JNI_FALSE;
-    }
-    nat->signal_sk = signal_sks[0];
-    nat->object = env->NewGlobalRef(object);
-
-    data = (thread_data_t *)calloc(1, sizeof(thread_data_t));
-    if (data == NULL) {
-        LOGE("%s: out of memory", __FUNCTION__);
-        pthread_mutex_unlock(&nat->mutex);
-        return JNI_FALSE;
-    }
-    nat->thread_data = data;
-    pthread_mutex_unlock(&nat->mutex);
-
-    data->signal_sk = signal_sks[1];
-    data->nat = nat;
-    data->is_accept = true;
-
-    if (pthread_create(&thread, NULL, &work_thread, (void *)data) < 0) {
-        LOGE("%s: pthread_create() failed: %s", __FUNCTION__, strerror(errno));
-        return JNI_FALSE;
-    }
-    return JNI_TRUE;
-
-#endif
-    return JNI_FALSE;
-}
-
-static jboolean connectNative(JNIEnv *env, jobject object, jstring address,
-        jstring name) {
-
-    LOGV(__FUNCTION__);
-#ifdef HAVE_BLUETOOTH
-    native_data_t *nat = get_native_data(env, object);
-    int signal_sks[2];
-    pthread_t thread;
-    struct thread_data_t *data;
-    const char *c_address;
-    const char *c_name;
-
-    pthread_mutex_lock(&nat->mutex);
-    if (nat->signal_sk != -1) {
-        pthread_mutex_unlock(&nat->mutex);
-        return JNI_FALSE;
-    }
-
-    // setup socketpair to pass messages between threads
-    if (socketpair(AF_UNIX, SOCK_STREAM, 0, signal_sks) < 0) {
-        LOGE("%s: socketpair() failed: %s\n", __FUNCTION__, strerror(errno));
-        pthread_mutex_unlock(&nat->mutex);
-        return JNI_FALSE;
-    }
-    nat->signal_sk = signal_sks[0];
-    nat->object = env->NewGlobalRef(object);
-
-    data = (thread_data_t *)calloc(1, sizeof(thread_data_t));
-    if (data == NULL) {
-        LOGE("%s: out of memory", __FUNCTION__);
-        pthread_mutex_unlock(&nat->mutex);
-        return JNI_FALSE;
-    }
-    pthread_mutex_unlock(&nat->mutex);
-
-    data->signal_sk = signal_sks[1];
-    data->nat = nat;
-    c_address = env->GetStringUTFChars(address, NULL);
-    strlcpy(data->address, c_address, BTADDR_SIZE);
-    env->ReleaseStringUTFChars(address, c_address);
-    data->is_accept = false;
-
-    if (name == NULL) {
-        LOGE("%s: Null pointer passed in for device name", __FUNCTION__);
-        data->sco_pkt_type = 0;
-    } else {
-        c_name = env->GetStringUTFChars(name, NULL);
-        /* See if this device is in the black list */
-        data->sco_pkt_type = getScoType(data->address, c_name);
-        env->ReleaseStringUTFChars(name, c_name);
-    }
-    if (pthread_create(&thread, NULL, &work_thread, (void *)data) < 0) {
-        LOGE("%s: pthread_create() failed: %s", __FUNCTION__, strerror(errno));
-        return JNI_FALSE;
-    }
-    return JNI_TRUE;
-
-#endif
-    return JNI_FALSE;
-}
-
-static void closeNative(JNIEnv *env, jobject object) {
-    LOGV(__FUNCTION__);
-#ifdef HAVE_BLUETOOTH
-    native_data_t *nat = get_native_data(env, object);
-    int signal_sk;
-
-    pthread_mutex_lock(&nat->mutex);
-    signal_sk = nat->signal_sk;
-    nat->signal_sk = -1;
-    env->DeleteGlobalRef(nat->object);
-    nat->object = NULL;
-    pthread_mutex_unlock(&nat->mutex);
-
-    if (signal_sk >= 0) {
-        LOGV("%s: signal_sk = %d", __FUNCTION__, signal_sk);
-        unsigned char dummy;
-        write(signal_sk, &dummy, sizeof(dummy));
-        close(signal_sk);
-    }
-#endif
-}
-
-#ifdef HAVE_BLUETOOTH
-/* thread entry point */
-static void *work_thread(void *arg) {
-    JNIEnv* env;
-    thread_data_t *data = (thread_data_t *)arg;
-    int sk;
-
-    LOGV(__FUNCTION__);
-    if (jvm->AttachCurrentThread(&env, NULL) != JNI_OK) {
-        LOGE("%s: AttachCurrentThread() failed", __FUNCTION__);
-        return NULL;
-    }
-
-    /* connect the SCO socket */
-    if (data->is_accept) {
-        LOGV("SCO OBJECT %p ACCEPT #####", data->nat->object);
-        sk = accept_work(data->signal_sk);
-        LOGV("SCO OBJECT %p END ACCEPT *****", data->nat->object);
-    } else {
-        sk = connect_work(data->address, data->sco_pkt_type);
-    }
-
-    /* callback with connection result */
-    if (data->nat == NULL) {
-        LOGV("%s: object destroyed!", __FUNCTION__);
-        goto done;
-    }
-    pthread_mutex_lock(&data->nat->mutex);
-    if (data->nat->object == NULL) {
-        pthread_mutex_unlock(&data->nat->mutex);
-        LOGV("%s: callback cancelled", __FUNCTION__);
-        goto done;
-    }
-    if (data->is_accept) {
-        env->CallVoidMethod(data->nat->object, method_onAccepted, sk);
-    } else {
-        env->CallVoidMethod(data->nat->object, method_onConnected, sk);
-    }
-    pthread_mutex_unlock(&data->nat->mutex);
-
-    if (sk < 0) {
-        goto done;
-    }
-
-    LOGV("SCO OBJECT %p %d CONNECTED +++ (%s)", data->nat->object, sk,
-         data->is_accept ? "in" : "out");
-
-    /* wait for the socket to close */
-    LOGV("wait_for_close()...");
-    wait_for_close(sk, data->signal_sk);
-    LOGV("wait_for_close() returned");
-
-    /* callback with close result */
-    if (data->nat == NULL) {
-        LOGV("%s: object destroyed!", __FUNCTION__);
-        goto done;
-    }
-    pthread_mutex_lock(&data->nat->mutex);
-    if (data->nat->object == NULL) {
-        LOGV("%s: callback cancelled", __FUNCTION__);
-    } else {
-        env->CallVoidMethod(data->nat->object, method_onClosed);
-    }
-    pthread_mutex_unlock(&data->nat->mutex);
-
-done:
-    if (sk >= 0) {
-        close(sk);
-        LOGV("SCO OBJECT %p %d CLOSED --- (%s)", data->nat->object, sk, data->is_accept ? "in" : "out");
-    }
-    if (data->signal_sk >= 0) {
-        close(data->signal_sk);
-    }
-    LOGV("SCO socket closed");
-
-    if (data->nat != NULL) {
-        pthread_mutex_lock(&data->nat->mutex);
-        env->DeleteGlobalRef(data->nat->object);
-        data->nat->object = NULL;
-        data->nat->thread_data = NULL;
-        pthread_mutex_unlock(&data->nat->mutex);
-    }
-
-    free(data);
-    if (jvm->DetachCurrentThread() != JNI_OK) {
-        LOGE("%s: DetachCurrentThread() failed", __FUNCTION__);
-    }
-
-    LOGV("work_thread() done");
-    return NULL;
-}
-
-static int accept_work(int signal_sk) {
-    LOGV(__FUNCTION__);
-    int sk;
-    int nsk;
-    int addr_sz;
-    int max_fd;
-    fd_set fds;
-    struct sockaddr_sco addr;
-
-    sk = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_SCO);
-    if (sk < 0) {
-        LOGE("%s socket() failed: %s", __FUNCTION__, strerror(errno));
-        return -1;
-    }
-
-    memset(&addr, 0, sizeof(addr));
-    addr.sco_family = AF_BLUETOOTH;
-    memcpy(&addr.sco_bdaddr, BDADDR_ANY, sizeof(bdaddr_t));
-    if (bind(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
-        LOGE("%s bind() failed: %s", __FUNCTION__, strerror(errno));
-        goto error;
-    }
-
-    if (listen(sk, 1)) {
-        LOGE("%s: listen() failed: %s", __FUNCTION__, strerror(errno));
-        goto error;
-    }
-
-    memset(&addr, 0, sizeof(addr));
-    addr_sz = sizeof(addr);
-
-    FD_ZERO(&fds);
-    FD_SET(sk, &fds);
-    FD_SET(signal_sk, &fds);
-
-    max_fd = (sk > signal_sk) ? sk : signal_sk;
-    LOGI("Listening SCO socket...");
-    while (select(max_fd + 1, &fds, NULL, NULL, NULL) < 0) {
-        if (errno != EINTR) {
-            LOGE("%s: select() failed: %s", __FUNCTION__, strerror(errno));
-            goto error;
-        }
-        LOGV("%s: select() EINTR, retrying", __FUNCTION__);
-    }
-    LOGV("select() returned");
-    if (FD_ISSET(signal_sk, &fds)) {
-        // signal to cancel listening
-        LOGV("cancelled listening socket, closing");
-        goto error;
-    }
-    if (!FD_ISSET(sk, &fds)) {
-        LOGE("error: select() returned >= 0 with no fds set");
-        goto error;
-    }
-
-    nsk = accept(sk, (struct sockaddr *)&addr, &addr_sz);
-    if (nsk < 0) {
-        LOGE("%s: accept() failed: %s", __FUNCTION__, strerror(errno));
-        goto error;
-    }
-    LOGI("Connected SCO socket (incoming)");
-    close(sk);  // The listening socket
-
-    return nsk;
-
-error:
-    close(sk);
-
-    return -1;
-}
-
-static int connect_work(const char *address, uint16_t sco_pkt_type) {
-    LOGV(__FUNCTION__);
-    struct sockaddr_sco addr;
-    int sk = -1;
-
-    sk = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_SCO);
-    if (sk < 0) {
-        LOGE("%s: socket() failed: %s", __FUNCTION__, strerror(errno));
-        return -1;
-    }
-
-    /* Bind to local address */
-    memset(&addr, 0, sizeof(addr));
-    addr.sco_family = AF_BLUETOOTH;
-    memcpy(&addr.sco_bdaddr, BDADDR_ANY, sizeof(bdaddr_t));
-    if (bind(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
-        LOGE("%s: bind() failed: %s", __FUNCTION__, strerror(errno));
-        goto error;
-    }
-
-    memset(&addr, 0, sizeof(addr));
-    addr.sco_family = AF_BLUETOOTH;
-    get_bdaddr(address, &addr.sco_bdaddr);
-    addr.sco_pkt_type = sco_pkt_type;
-    LOGI("Connecting to socket");
-    while (connect(sk, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
-        if (errno != EINTR) {
-            LOGE("%s: connect() failed: %s", __FUNCTION__, strerror(errno));
-            goto error;
-        }
-        LOGV("%s: connect() EINTR, retrying", __FUNCTION__);
-    }
-    LOGI("SCO socket connected (outgoing)");
-
-    return sk;
-
-error:
-    if (sk >= 0) close(sk);
-    return -1;
-}
-
-static void wait_for_close(int sk, int signal_sk) {
-    LOGV(__FUNCTION__);
-    pollfd p[2];
-
-    memset(p, 0, 2 * sizeof(pollfd));
-    p[0].fd = sk;
-    p[1].fd = signal_sk;
-    p[1].events = POLLIN | POLLPRI;
-
-    LOGV("poll...");
-
-    while (poll(p, 2, -1) < 0) {  // blocks
-        if (errno != EINTR) {
-            LOGE("%s: poll() failed: %s", __FUNCTION__, strerror(errno));
-            break;
-        }
-        LOGV("%s: poll() EINTR, retrying", __FUNCTION__);
-    }
-
-    LOGV("poll() returned");
-}
-#endif
-
-static JNINativeMethod sMethods[] = {
-    {"classInitNative", "()V", (void*)classInitNative},
-    {"initNative", "()V", (void *)initNative},
-    {"destroyNative", "()V", (void *)destroyNative},
-    {"connectNative", "(Ljava/lang/String;Ljava/lang/String;)Z", (void *)connectNative},
-    {"acceptNative", "()Z", (void *)acceptNative},
-    {"closeNative", "()V", (void *)closeNative},
-};
-
-int register_android_bluetooth_ScoSocket(JNIEnv *env) {
-    return AndroidRuntime::registerNativeMethods(env,
-            "android/bluetooth/ScoSocket", sMethods, NELEM(sMethods));
-}
-
-} /* namespace android */
diff --git a/core/jni/android_bluetooth_common.cpp b/core/jni/android_bluetooth_common.cpp
index 43c3a95..a38d3b2 100644
--- a/core/jni/android_bluetooth_common.cpp
+++ b/core/jni/android_bluetooth_common.cpp
@@ -69,6 +69,10 @@
     {"UUIDs", DBUS_TYPE_ARRAY},
 };
 
+static Properties input_properties[] = {
+    {"Connected", DBUS_TYPE_BOOLEAN},
+};
+
 typedef union {
     char *str_val;
     int int_val;
@@ -699,6 +703,11 @@
                     sizeof(remote_device_properties) / sizeof(Properties));
 }
 
+jobjectArray parse_input_property_change(JNIEnv *env, DBusMessage *msg) {
+    return parse_property_change(env, msg, (Properties *) &input_properties,
+                    sizeof(input_properties) / sizeof(Properties));
+}
+
 jobjectArray parse_adapter_properties(JNIEnv *env, DBusMessageIter *iter) {
     return parse_properties(env, iter, (Properties *) &adapter_properties,
                             sizeof(adapter_properties) / sizeof(Properties));
@@ -709,6 +718,11 @@
                           sizeof(remote_device_properties) / sizeof(Properties));
 }
 
+jobjectArray parse_input_properties(JNIEnv *env, DBusMessageIter *iter) {
+    return parse_properties(env, iter, (Properties *) &input_properties,
+                          sizeof(input_properties) / sizeof(Properties));
+}
+
 int get_bdaddr(const char *str, bdaddr_t *ba) {
     char *d = ((char *)ba) + 5, *endp;
     int i;
diff --git a/core/jni/android_bluetooth_common.h b/core/jni/android_bluetooth_common.h
index 378bb6f..27a00ae 100644
--- a/core/jni/android_bluetooth_common.h
+++ b/core/jni/android_bluetooth_common.h
@@ -162,6 +162,8 @@
 jobjectArray parse_remote_device_properties(JNIEnv *env, DBusMessageIter *iter);
 jobjectArray parse_remote_device_property_change(JNIEnv *env, DBusMessage *msg);
 jobjectArray parse_adapter_property_change(JNIEnv *env, DBusMessage *msg);
+jobjectArray parse_input_properties(JNIEnv *env, DBusMessageIter *iter);
+jobjectArray parse_input_property_change(JNIEnv *env, DBusMessage *msg);
 void append_variant(DBusMessageIter *iter, int type, void *val);
 int get_bdaddr(const char *str, bdaddr_t *ba);
 void get_bdaddr_as_string(const bdaddr_t *ba, char *str);
diff --git a/core/jni/android_database_CursorWindow.cpp b/core/jni/android_database_CursorWindow.cpp
index 91449bc..040dac3 100644
--- a/core/jni/android_database_CursorWindow.cpp
+++ b/core/jni/android_database_CursorWindow.cpp
@@ -29,7 +29,7 @@
 #include <string.h>
 #include <unistd.h>
 
-#include "CursorWindow.h"
+#include "binder/CursorWindow.h"
 #include "sqlite3_exception.h"
 #include "android_util_Binder.h"
 
@@ -225,70 +225,6 @@
     return NULL;
 }
 
-static jboolean isBlob_native(JNIEnv* env, jobject object, jint row, jint column)
-{
-    int32_t err;
-    CursorWindow * window = GET_WINDOW(env, object);
-LOG_WINDOW("Checking if column is a blob or null for %d,%d from %p", row, column, window);
-
-    field_slot_t field;
-    err = window->read_field_slot(row, column, &field);
-    if (err != 0) {
-        throwExceptionWithRowCol(env, row, column);
-        return NULL;
-    }
-
-    return field.type == FIELD_TYPE_BLOB || field.type == FIELD_TYPE_NULL;
-}
-
-static jboolean isString_native(JNIEnv* env, jobject object, jint row, jint column)
-{
-    int32_t err;
-    CursorWindow * window = GET_WINDOW(env, object);
-LOG_WINDOW("Checking if column is a string or null for %d,%d from %p", row, column, window);
-
-    field_slot_t field;
-    err = window->read_field_slot(row, column, &field);
-    if (err != 0) {
-        throwExceptionWithRowCol(env, row, column);
-        return NULL;
-    }
-
-    return field.type == FIELD_TYPE_STRING || field.type == FIELD_TYPE_NULL;
-}
-
-static jboolean isInteger_native(JNIEnv* env, jobject object, jint row, jint column)
-{
-    int32_t err;
-    CursorWindow * window = GET_WINDOW(env, object);
-LOG_WINDOW("Checking if column is an integer for %d,%d from %p", row, column, window);
-
-    field_slot_t field;
-    err = window->read_field_slot(row, column, &field);
-    if (err != 0) {
-        throwExceptionWithRowCol(env, row, column);
-        return NULL;
-    }
-
-    return field.type == FIELD_TYPE_INTEGER;
-}
-
-static jboolean isFloat_native(JNIEnv* env, jobject object, jint row, jint column)
-{
-    int32_t err;
-    CursorWindow * window = GET_WINDOW(env, object);
-LOG_WINDOW("Checking if column is a float for %d,%d from %p", row, column, window);
-
-    field_slot_t field;
-    err = window->read_field_slot(row, column, &field);
-    if (err != 0) {
-        throwExceptionWithRowCol(env, row, column);
-        return NULL;
-    }
-
-    return field.type == FIELD_TYPE_FLOAT;
-}
-
 static jstring getString_native(JNIEnv* env, jobject object, jint row, jint column)
 {
     int32_t err;
@@ -487,10 +423,9 @@
     }
 }
 
-static jboolean isNull_native(JNIEnv* env, jobject object, jint row, jint column)
+bool isNull_native(CursorWindow *window, jint row, jint column)
 {
-    CursorWindow * window = GET_WINDOW(env, object);
-LOG_WINDOW("Checking for NULL at %d,%d from %p", row, column, window);
+    LOG_WINDOW("Checking for NULL at %d,%d from %p", row, column, window);
 
     bool isNull;
     if (window->getNull(row, column, &isNull)) {
@@ -652,6 +587,26 @@
     window->freeLastRow();
 }
 
+static jint getType_native(JNIEnv* env, jobject object, jint row, jint column)
+{
+    int32_t err;
+    CursorWindow * window = GET_WINDOW(env, object);
+    LOG_WINDOW("returning column type affinity for %d,%d from %p", row, column, window);
+
+    if (isNull_native(window, row, column)) {
+      return FIELD_TYPE_NULL;
+    }
+
+    field_slot_t field;
+    err = window->read_field_slot(row, column, &field);
+    if (err != 0) {
+        throwExceptionWithRowCol(env, row, column);
+        return NULL;
+    }
+
+    return field.type;
+}
+
 static JNINativeMethod sMethods[] =
 {
      /* name, signature, funcPtr */
@@ -662,11 +617,9 @@
     {"close_native", "()V", (void *)native_close},
     {"getLong_native", "(II)J", (void *)getLong_native},
     {"getBlob_native", "(II)[B", (void *)getBlob_native},
-    {"isBlob_native", "(II)Z", (void *)isBlob_native},
     {"getString_native", "(II)Ljava/lang/String;", (void *)getString_native},
     {"copyStringToBuffer_native", "(IIILandroid/database/CharArrayBuffer;)[C", (void *)copyStringToBuffer_native},
     {"getDouble_native", "(II)D", (void *)getDouble_native},
-    {"isNull_native", "(II)Z", (void *)isNull_native},
     {"getNumRows_native", "()I", (void *)getNumRows},
     {"setNumColumns_native", "(I)Z", (void *)setNumColumns},
     {"allocRow_native", "()Z", (void *)allocRow},
@@ -676,9 +629,7 @@
     {"putDouble_native", "(DII)Z", (void *)putDouble_native},
     {"freeLastRow_native", "()V", (void *)freeLastRow},
     {"putNull_native", "(II)Z", (void *)putNull_native},
-    {"isString_native", "(II)Z", (void *)isString_native},
-    {"isFloat_native", "(II)Z", (void *)isFloat_native},
-    {"isInteger_native", "(II)Z", (void *)isInteger_native},
+    {"getType_native", "(II)I", (void *)getType_native},
 };
 
 int register_android_database_CursorWindow(JNIEnv * env)
diff --git a/core/jni/android_database_SQLiteCompiledSql.cpp b/core/jni/android_database_SQLiteCompiledSql.cpp
index 8d1c39e..de4c5c8 100644
--- a/core/jni/android_database_SQLiteCompiledSql.cpp
+++ b/core/jni/android_database_SQLiteCompiledSql.cpp
@@ -91,22 +91,11 @@
     compile(env, object, GET_HANDLE(env, object), sqlString);
 }
 
-static void native_finalize(JNIEnv* env, jobject object)
-{
-    int err;
-    sqlite3_stmt * statement = GET_STATEMENT(env, object);
-
-    if (statement != NULL) {
-        sqlite3_finalize(statement);
-        env->SetIntField(object, gStatementField, 0);
-    }
-}
 
 static JNINativeMethod sMethods[] =
 {
      /* name, signature, funcPtr */
     {"native_compile", "(Ljava/lang/String;)V", (void *)native_compile},
-    {"native_finalize", "()V", (void *)native_finalize},
 };
 
 int register_android_database_SQLiteCompiledSql(JNIEnv * env)
diff --git a/core/jni/android_database_SQLiteDatabase.cpp b/core/jni/android_database_SQLiteDatabase.cpp
index 36234a9..4b3f1c0 100644
--- a/core/jni/android_database_SQLiteDatabase.cpp
+++ b/core/jni/android_database_SQLiteDatabase.cpp
@@ -15,7 +15,7 @@
  */
 
 #undef LOG_TAG
-#define LOG_TAG "Database"
+#define LOG_TAG "SqliteDatabaseCpp"
 
 #include <utils/Log.h>
 #include <utils/String8.h>
@@ -62,9 +62,11 @@
 };
 
 static jfieldID offset_db_handle;
+static jmethodID method_custom_function_callback;
+static jclass string_class = NULL;
 
-static char *createStr(const char *path) {
-    int len = strlen(path);
+static char *createStr(const char *path, short extra) {
+    int len = strlen(path) + extra;
     char *str = (char *)malloc(len + 1);
     strncpy(str, path, len);
     str[len] = NULL;
@@ -85,7 +87,7 @@
     }
 
     LOGV("Registering sqlite logging func \n");
-    int err = sqlite3_config(SQLITE_CONFIG_LOG, &sqlLogger, (void *)createStr(path));
+    int err = sqlite3_config(SQLITE_CONFIG_LOG, &sqlLogger, (void *)createStr(path, 0));
     if (err != SQLITE_OK) {
         LOGE("sqlite_config failed error_code = %d. THIS SHOULD NEVER occur.\n", err);
         return;
@@ -176,13 +178,17 @@
     if (handle != NULL) sqlite3_close(handle);
 }
 
-static char *getDatabaseName(JNIEnv* env, sqlite3 * handle, jstring databaseName) {
+static char *getDatabaseName(JNIEnv* env, sqlite3 * handle, jstring databaseName, short connNum) {
     char const *path = env->GetStringUTFChars(databaseName, NULL);
     if (path == NULL) {
         LOGE("Failure in getDatabaseName(). VM ran out of memory?\n");
         return NULL; // VM would have thrown OutOfMemoryError
     }
-    char *dbNameStr = createStr(path);
+    char *dbNameStr = createStr(path, 4);
+    if (connNum > 999) { // TODO: if number of pooled connections > 999, fix this line.
+      connNum = -1;
+    }
+    sprintf(dbNameStr + strlen(path), "|%03d", connNum);
     env->ReleaseStringUTFChars(databaseName, path);
     return dbNameStr;
 }
@@ -192,10 +198,10 @@
 }
 
 /* public native void enableSqlTracing(); */
-static void enableSqlTracing(JNIEnv* env, jobject object, jstring databaseName)
+static void enableSqlTracing(JNIEnv* env, jobject object, jstring databaseName, jshort connType)
 {
     sqlite3 * handle = (sqlite3 *)env->GetIntField(object, offset_db_handle);
-    sqlite3_trace(handle, &sqlTrace, (void *)getDatabaseName(env, handle, databaseName));
+    sqlite3_trace(handle, &sqlTrace, (void *)getDatabaseName(env, handle, databaseName, connType));
 }
 
 static void sqlProfile(void *databaseName, const char *sql, sqlite3_uint64 tm) {
@@ -204,13 +210,13 @@
 }
 
 /* public native void enableSqlProfiling(); */
-static void enableSqlProfiling(JNIEnv* env, jobject object, jstring databaseName)
+static void enableSqlProfiling(JNIEnv* env, jobject object, jstring databaseName, jshort connType)
 {
     sqlite3 * handle = (sqlite3 *)env->GetIntField(object, offset_db_handle);
-    sqlite3_profile(handle, &sqlProfile, (void *)getDatabaseName(env, handle, databaseName));
+    sqlite3_profile(handle, &sqlProfile, (void *)getDatabaseName(env, handle, databaseName,
+            connType));
 }
 
-
 /* public native void close(); */
 static void dbclose(JNIEnv* env, jobject object)
 {
@@ -240,73 +246,6 @@
     }
 }
 
-/* public native void native_execSQL(String sql); */
-static void native_execSQL(JNIEnv* env, jobject object, jstring sqlString)
-{
-    int err;
-    int stepErr;
-    sqlite3_stmt * statement = NULL;
-    sqlite3 * handle = (sqlite3 *)env->GetIntField(object, offset_db_handle);
-    jchar const * sql = env->GetStringChars(sqlString, NULL);
-    jsize sqlLen = env->GetStringLength(sqlString);
-
-    if (sql == NULL || sqlLen == 0) {
-        jniThrowException(env, "java/lang/IllegalArgumentException", "You must supply an SQL string");
-        return;
-    }
-
-    err = sqlite3_prepare16_v2(handle, sql, sqlLen * 2, &statement, NULL);
-
-    env->ReleaseStringChars(sqlString, sql);
-
-    if (err != SQLITE_OK) {
-        char const * sql8 = env->GetStringUTFChars(sqlString, NULL);
-        LOGE("Failure %d (%s) on %p when preparing '%s'.\n", err, sqlite3_errmsg(handle), handle, sql8);
-        throw_sqlite3_exception(env, handle, sql8);
-        env->ReleaseStringUTFChars(sqlString, sql8);
-        return;
-    }
-
-    stepErr = sqlite3_step(statement);
-    err = sqlite3_finalize(statement);
-
-    if (stepErr != SQLITE_DONE) {
-        if (stepErr == SQLITE_ROW) {
-            throw_sqlite3_exception(env, "Queries cannot be performed using execSQL(), use query() instead.");
-        } else {
-            char const * sql8 = env->GetStringUTFChars(sqlString, NULL);
-            LOGE("Failure %d (%s) on %p when executing '%s'\n", err, sqlite3_errmsg(handle), handle, sql8);
-            throw_sqlite3_exception(env, handle, sql8);
-            env->ReleaseStringUTFChars(sqlString, sql8);
-
-        }
-    } else
-#ifndef DB_LOG_STATEMENTS
-    IF_LOGV()
-#endif
-    {
-        char const * sql8 = env->GetStringUTFChars(sqlString, NULL);
-        LOGV("Success on %p when executing '%s'\n", handle, sql8);
-        env->ReleaseStringUTFChars(sqlString, sql8);
-    }
-}
-
-/* native long lastInsertRow(); */
-static jlong lastInsertRow(JNIEnv* env, jobject object)
-{
-    sqlite3 * handle = (sqlite3 *)env->GetIntField(object, offset_db_handle);
-
-    return sqlite3_last_insert_rowid(handle);
-}
-
-/* native int lastChangeCount(); */
-static jint lastChangeCount(JNIEnv* env, jobject object)
-{
-    sqlite3 * handle = (sqlite3 *)env->GetIntField(object, offset_db_handle);
-
-    return sqlite3_changes(handle);
-}
-
 /* native int native_getDbLookaside(); */
 static jint native_getDbLookaside(JNIEnv* env, jobject object)
 {
@@ -443,19 +382,93 @@
     return sqlite3_release_memory(SQLITE_SOFT_HEAP_LIMIT);
 }
 
+static void native_finalize(JNIEnv* env, jobject object, jint statementId)
+{
+    if (statementId > 0) {
+        sqlite3_finalize((sqlite3_stmt *)statementId);
+    }
+}
+
+static void custom_function_callback(sqlite3_context * context, int argc, sqlite3_value ** argv) {
+    JNIEnv* env = AndroidRuntime::getJNIEnv();
+    if (!env) {
+        LOGE("custom_function_callback cannot call into Java on this thread");
+        return;
+    }
+    // get global ref to CustomFunction object from our user data
+    jobject function = (jobject)sqlite3_user_data(context);
+
+    // pack up the arguments into a string array
+    if (!string_class)
+        string_class = (jclass)env->NewGlobalRef(env->FindClass("java/lang/String"));
+    jobjectArray strArray = env->NewObjectArray(argc, string_class, NULL);
+    if (!strArray)
+        goto done;
+    for (int i = 0; i < argc; i++) {
+        char* arg = (char *)sqlite3_value_text(argv[i]);
+        if (!arg) {
+            LOGE("NULL argument in custom_function_callback.  This should not happen.");
+            return;
+        }
+        jobject obj = env->NewStringUTF(arg);
+        if (!obj)
+            goto done;
+        env->SetObjectArrayElement(strArray, i, obj);
+        env->DeleteLocalRef(obj);
+    }
+
+    env->CallVoidMethod(function, method_custom_function_callback, strArray);
+
+done:
+    if (env->ExceptionCheck()) {
+        LOGE("An exception was thrown by custom sqlite3 function.");
+        LOGE_EX(env);
+        env->ExceptionClear();
+    }
+}
+
+static jint native_addCustomFunction(JNIEnv* env, jobject object,
+        jstring name, jint numArgs, jobject function)
+{
+    sqlite3 * handle = (sqlite3 *)env->GetIntField(object, offset_db_handle);
+    char const *nameStr = env->GetStringUTFChars(name, NULL);
+    jobject ref = env->NewGlobalRef(function);
+    LOGD("native_addCustomFunction %s ref: %p", nameStr, ref);
+    int err = sqlite3_create_function(handle, nameStr, numArgs, SQLITE_UTF8,
+            (void *)ref, custom_function_callback, NULL, NULL);
+    env->ReleaseStringUTFChars(name, nameStr);
+
+    if (err == SQLITE_OK)
+        return (int)ref;
+    else {
+        LOGE("sqlite3_create_function returned %d", err);
+        env->DeleteGlobalRef(ref);
+        throw_sqlite3_exception(env, handle);
+        return 0;
+     }
+}
+
+static void native_releaseCustomFunction(JNIEnv* env, jobject object, jint ref)
+{
+    LOGD("native_releaseCustomFunction %d", ref);
+    env->DeleteGlobalRef((jobject)ref);
+}
+
 static JNINativeMethod sMethods[] =
 {
     /* name, signature, funcPtr */
     {"dbopen", "(Ljava/lang/String;I)V", (void *)dbopen},
     {"dbclose", "()V", (void *)dbclose},
-    {"enableSqlTracing", "(Ljava/lang/String;)V", (void *)enableSqlTracing},
-    {"enableSqlProfiling", "(Ljava/lang/String;)V", (void *)enableSqlProfiling},
-    {"native_execSQL", "(Ljava/lang/String;)V", (void *)native_execSQL},
-    {"lastInsertRow", "()J", (void *)lastInsertRow},
-    {"lastChangeCount", "()I", (void *)lastChangeCount},
+    {"enableSqlTracing", "(Ljava/lang/String;S)V", (void *)enableSqlTracing},
+    {"enableSqlProfiling", "(Ljava/lang/String;S)V", (void *)enableSqlProfiling},
     {"native_setLocale", "(Ljava/lang/String;I)V", (void *)native_setLocale},
     {"native_getDbLookaside", "()I", (void *)native_getDbLookaside},
     {"releaseMemory", "()I", (void *)native_releaseMemory},
+    {"native_finalize", "(I)V", (void *)native_finalize},
+    {"native_addCustomFunction",
+                    "(Ljava/lang/String;ILandroid/database/sqlite/SQLiteDatabase$CustomFunction;)I",
+                    (void *)native_addCustomFunction},
+    {"native_releaseCustomFunction", "(I)V", (void *)native_releaseCustomFunction},
 };
 
 int register_android_database_SQLiteDatabase(JNIEnv *env)
@@ -474,7 +487,19 @@
         return -1;
     }
 
-    return AndroidRuntime::registerNativeMethods(env, "android/database/sqlite/SQLiteDatabase", sMethods, NELEM(sMethods));
+    clazz = env->FindClass("android/database/sqlite/SQLiteDatabase$CustomFunction");
+    if (clazz == NULL) {
+        LOGE("Can't find android/database/sqlite/SQLiteDatabase$CustomFunction\n");
+        return -1;
+    }
+    method_custom_function_callback = env->GetMethodID(clazz, "callback", "([Ljava/lang/String;)V");
+    if (method_custom_function_callback == NULL) {
+        LOGE("Can't find method SQLiteDatabase.CustomFunction.callback\n");
+        return -1;
+    }
+
+    return AndroidRuntime::registerNativeMethods(env, "android/database/sqlite/SQLiteDatabase",
+            sMethods, NELEM(sMethods));
 }
 
 /* throw a SQLiteException with a message appropriate for the error in handle */
@@ -523,6 +548,7 @@
             exceptionClass = "android/database/sqlite/SQLiteDiskIOException";
             break;
         case SQLITE_CORRUPT:
+        case SQLITE_NOTADB: // treat "unsupported file format" error as corruption also
             exceptionClass = "android/database/sqlite/SQLiteDatabaseCorruptException";
             break;
         case SQLITE_CONSTRAINT:
@@ -540,6 +566,33 @@
         case SQLITE_MISUSE:
            exceptionClass = "android/database/sqlite/SQLiteMisuseException";
            break;
+        case SQLITE_PERM:
+           exceptionClass = "android/database/sqlite/SQLiteAccessPermException";
+           break;
+        case SQLITE_BUSY:
+           exceptionClass = "android/database/sqlite/SQLiteDatabaseLockedException";
+           break;
+        case SQLITE_LOCKED:
+           exceptionClass = "android/database/sqlite/SQLiteTableLockedException";
+           break;
+        case SQLITE_READONLY:
+           exceptionClass = "android/database/sqlite/SQLiteReadOnlyDatabaseException";
+           break;
+        case SQLITE_CANTOPEN:
+           exceptionClass = "android/database/sqlite/SQLiteCantOpenDatabaseException";
+           break;
+        case SQLITE_TOOBIG:
+           exceptionClass = "android/database/sqlite/SQLiteBlobTooBigException";
+           break;
+        case SQLITE_RANGE:
+           exceptionClass = "android/database/sqlite/SQLiteBindOrColumnIndexOutOfRangeException";
+           break;
+        case SQLITE_NOMEM:
+           exceptionClass = "android/database/sqlite/SQLiteOutOfMemoryException";
+           break;
+        case SQLITE_MISMATCH:
+           exceptionClass = "android/database/sqlite/SQLiteDatatypeMismatchException";
+           break;
         default:
            exceptionClass = "android/database/sqlite/SQLiteException";
            break;
diff --git a/core/jni/android_database_SQLiteQuery.cpp b/core/jni/android_database_SQLiteQuery.cpp
index 44271683..747ee50 100644
--- a/core/jni/android_database_SQLiteQuery.cpp
+++ b/core/jni/android_database_SQLiteQuery.cpp
@@ -29,7 +29,7 @@
 #include <string.h>
 #include <unistd.h>
 
-#include "CursorWindow.h"
+#include "binder/CursorWindow.h"
 #include "sqlite3_exception.h"
 
 
diff --git a/core/jni/android_database_SQLiteStatement.cpp b/core/jni/android_database_SQLiteStatement.cpp
index ff2ed5d..2212d9a 100644
--- a/core/jni/android_database_SQLiteStatement.cpp
+++ b/core/jni/android_database_SQLiteStatement.cpp
@@ -16,7 +16,7 @@
 */
 
 #undef LOG_TAG
-#define LOG_TAG "Cursor"
+#define LOG_TAG "SQLiteStatementCpp"
 
 #include <jni.h>
 #include <JNIHelp.h>
@@ -48,22 +48,40 @@
         (sqlite3 *)env->GetIntField(object, gHandleField)
 
 
-static void native_execute(JNIEnv* env, jobject object)
+static jint native_execute(JNIEnv* env, jobject object)
 {
     int err;
     sqlite3 * handle = GET_HANDLE(env, object);
     sqlite3_stmt * statement = GET_STATEMENT(env, object);
+    int numChanges = -1;
 
     // Execute the statement
     err = sqlite3_step(statement);
 
-    // Throw an exception if an error occured
-    if (err != SQLITE_DONE) {
+    // Throw an exception if an error occurred
+    if (err == SQLITE_ROW) {
+        throw_sqlite3_exception(env,
+                "Queries can be performed using SQLiteDatabase query or rawQuery methods only.");
+    } else if (err != SQLITE_DONE) {
         throw_sqlite3_exception_errcode(env, err, sqlite3_errmsg(handle));
+    } else {
+        numChanges = sqlite3_changes(handle);
     }
 
-    // Reset the statment so it's ready to use again
+    // Reset the statement so it's ready to use again
     sqlite3_reset(statement);
+    return numChanges;
+}
+
+static jlong native_executeInsert(JNIEnv* env, jobject object)
+{
+    sqlite3 * handle = GET_HANDLE(env, object);
+    jint numChanges = native_execute(env, object);
+    if (numChanges > 0) {
+        return sqlite3_last_insert_rowid(handle);
+    } else {
+        return -1;
+    }
 }
 
 static jlong native_1x1_long(JNIEnv* env, jobject object)
@@ -115,11 +133,11 @@
     return value;
 }
 
-
 static JNINativeMethod sMethods[] =
 {
      /* name, signature, funcPtr */
-    {"native_execute", "()V", (void *)native_execute},
+    {"native_execute", "()I", (void *)native_execute},
+    {"native_executeInsert", "()J", (void *)native_executeInsert},
     {"native_1x1_long", "()J", (void *)native_1x1_long},
     {"native_1x1_string", "()Ljava/lang/String;", (void *)native_1x1_string},
 };
diff --git a/core/jni/android_net_NetUtils.cpp b/core/jni/android_net_NetUtils.cpp
index 50df9d3..3cde9d6 100644
--- a/core/jni/android_net_NetUtils.cpp
+++ b/core/jni/android_net_NetUtils.cpp
@@ -22,8 +22,29 @@
 #include <utils/Log.h>
 #include <arpa/inet.h>
 
-#include <netutils/ifc.h>
-#include <netutils/dhcp.h>
+extern "C" {
+int ifc_enable(const char *ifname);
+int ifc_disable(const char *ifname);
+int ifc_add_host_route(const char *ifname, uint32_t addr);
+int ifc_remove_host_routes(const char *ifname);
+int ifc_set_default_route(const char *ifname, uint32_t gateway);
+int ifc_get_default_route(const char *ifname);
+int ifc_remove_default_route(const char *ifname);
+int ifc_reset_connections(const char *ifname);
+int ifc_configure(const char *ifname, in_addr_t ipaddr, in_addr_t netmask, in_addr_t gateway, in_addr_t dns1, in_addr_t dns2);
+
+int dhcp_do_request(const char *ifname,
+                    in_addr_t *ipaddr,
+                    in_addr_t *gateway,
+                    in_addr_t *mask,
+                    in_addr_t *dns1,
+                    in_addr_t *dns2,
+                    in_addr_t *server,
+                    uint32_t  *lease);
+int dhcp_stop(const char *ifname);
+int dhcp_release_lease(const char *ifname);
+char *dhcp_get_errmsg();
+}
 
 #define NETUTILS_PKG_NAME "android/net/NetworkUtils"
 
@@ -201,10 +222,10 @@
 
     { "enableInterface", "(Ljava/lang/String;)I",  (void *)android_net_utils_enableInterface },
     { "disableInterface", "(Ljava/lang/String;)I",  (void *)android_net_utils_disableInterface },
-    { "addHostRoute", "(Ljava/lang/String;I)I",  (void *)android_net_utils_addHostRoute },
+    { "addHostRouteNative", "(Ljava/lang/String;I)I",  (void *)android_net_utils_addHostRoute },
     { "removeHostRoutes", "(Ljava/lang/String;)I",  (void *)android_net_utils_removeHostRoutes },
-    { "setDefaultRoute", "(Ljava/lang/String;I)I",  (void *)android_net_utils_setDefaultRoute },
-    { "getDefaultRoute", "(Ljava/lang/String;)I",  (void *)android_net_utils_getDefaultRoute },
+    { "setDefaultRouteNative", "(Ljava/lang/String;I)I",  (void *)android_net_utils_setDefaultRoute },
+    { "getDefaultRouteNative", "(Ljava/lang/String;)I",  (void *)android_net_utils_getDefaultRoute },
     { "removeDefaultRoute", "(Ljava/lang/String;)I",  (void *)android_net_utils_removeDefaultRoute },
     { "resetConnections", "(Ljava/lang/String;)I",  (void *)android_net_utils_resetConnections },
     { "runDhcp", "(Ljava/lang/String;Landroid/net/DhcpInfo;)Z",  (void *)android_net_utils_runDhcp },
diff --git a/core/jni/android_net_wifi_Wifi.cpp b/core/jni/android_net_wifi_Wifi.cpp
index 3fc0d58..aec537d 100644
--- a/core/jni/android_net_wifi_Wifi.cpp
+++ b/core/jni/android_net_wifi_Wifi.cpp
@@ -30,6 +30,23 @@
 
 static jboolean sScanModeActive = false;
 
+/*
+ * The following remembers the jfieldID's of the fields
+ * of the DhcpInfo Java object, so that we don't have
+ * to look them up every time.
+ */
+static struct fieldIds {
+    jclass dhcpInfoClass;
+    jmethodID constructorId;
+    jfieldID ipaddress;
+    jfieldID gateway;
+    jfieldID netmask;
+    jfieldID dns1;
+    jfieldID dns2;
+    jfieldID serverAddress;
+    jfieldID leaseDuration;
+} dhcpInfoFieldIds;
+
 static int doCommand(const char *cmd, char *replybuf, int replybuflen)
 {
     size_t reply_len = replybuflen - 1;
@@ -81,6 +98,11 @@
     }
 }
 
+static jboolean android_net_wifi_isDriverLoaded(JNIEnv* env, jobject clazz)
+{
+    return (jboolean)(::is_wifi_driver_loaded() == 1);
+}
+
 static jboolean android_net_wifi_loadDriver(JNIEnv* env, jobject clazz)
 {
     return (jboolean)(::wifi_load_driver() == 0);
@@ -476,6 +498,28 @@
     return doBooleanCommand("BLACKLIST clear", "OK");
 }
 
+static jboolean android_net_wifi_doDhcpRequest(JNIEnv* env, jobject clazz, jobject info)
+{
+    jint ipaddr, gateway, mask, dns1, dns2, server, lease;
+    jboolean succeeded = ((jboolean)::do_dhcp_request(&ipaddr, &gateway, &mask,
+                                        &dns1, &dns2, &server, &lease) == 0);
+    if (succeeded && dhcpInfoFieldIds.dhcpInfoClass != NULL) {
+        env->SetIntField(info, dhcpInfoFieldIds.ipaddress, ipaddr);
+        env->SetIntField(info, dhcpInfoFieldIds.gateway, gateway);
+        env->SetIntField(info, dhcpInfoFieldIds.netmask, mask);
+        env->SetIntField(info, dhcpInfoFieldIds.dns1, dns1);
+        env->SetIntField(info, dhcpInfoFieldIds.dns2, dns2);
+        env->SetIntField(info, dhcpInfoFieldIds.serverAddress, server);
+        env->SetIntField(info, dhcpInfoFieldIds.leaseDuration, lease);
+    }
+    return succeeded;
+}
+
+static jstring android_net_wifi_getDhcpError(JNIEnv* env, jobject clazz)
+{
+    return env->NewStringUTF(::get_dhcp_error_string());
+}
+
 // ----------------------------------------------------------------------------
 
 /*
@@ -485,6 +529,7 @@
     /* name, signature, funcPtr */
 
     { "loadDriver", "()Z",  (void *)android_net_wifi_loadDriver },
+    { "isDriverLoaded", "()Z",  (void *)android_net_wifi_isDriverLoaded},
     { "unloadDriver", "()Z",  (void *)android_net_wifi_unloadDriver },
     { "startSupplicant", "()Z",  (void *)android_net_wifi_startSupplicant },
     { "stopSupplicant", "()Z",  (void *)android_net_wifi_stopSupplicant },
@@ -532,6 +577,9 @@
     { "setScanResultHandlingCommand", "(I)Z", (void*) android_net_wifi_setScanResultHandlingCommand },
     { "addToBlacklistCommand", "(Ljava/lang/String;)Z", (void*) android_net_wifi_addToBlacklistCommand },
     { "clearBlacklistCommand", "()Z", (void*) android_net_wifi_clearBlacklistCommand },
+
+    { "doDhcpRequest", "(Landroid/net/DhcpInfo;)Z", (void*) android_net_wifi_doDhcpRequest },
+    { "getDhcpError", "()Ljava/lang/String;", (void*) android_net_wifi_getDhcpError },
 };
 
 int register_android_net_wifi_WifiManager(JNIEnv* env)
@@ -539,6 +587,18 @@
     jclass wifi = env->FindClass(WIFI_PKG_NAME);
     LOG_FATAL_IF(wifi == NULL, "Unable to find class " WIFI_PKG_NAME);
 
+    dhcpInfoFieldIds.dhcpInfoClass = env->FindClass("android/net/DhcpInfo");
+    if (dhcpInfoFieldIds.dhcpInfoClass != NULL) {
+        dhcpInfoFieldIds.constructorId = env->GetMethodID(dhcpInfoFieldIds.dhcpInfoClass, "<init>", "()V");
+        dhcpInfoFieldIds.ipaddress = env->GetFieldID(dhcpInfoFieldIds.dhcpInfoClass, "ipAddress", "I");
+        dhcpInfoFieldIds.gateway = env->GetFieldID(dhcpInfoFieldIds.dhcpInfoClass, "gateway", "I");
+        dhcpInfoFieldIds.netmask = env->GetFieldID(dhcpInfoFieldIds.dhcpInfoClass, "netmask", "I");
+        dhcpInfoFieldIds.dns1 = env->GetFieldID(dhcpInfoFieldIds.dhcpInfoClass, "dns1", "I");
+        dhcpInfoFieldIds.dns2 = env->GetFieldID(dhcpInfoFieldIds.dhcpInfoClass, "dns2", "I");
+        dhcpInfoFieldIds.serverAddress = env->GetFieldID(dhcpInfoFieldIds.dhcpInfoClass, "serverAddress", "I");
+        dhcpInfoFieldIds.leaseDuration = env->GetFieldID(dhcpInfoFieldIds.dhcpInfoClass, "leaseDuration", "I");
+    }
+
     return AndroidRuntime::registerNativeMethods(env,
             WIFI_PKG_NAME, gWifiMethods, NELEM(gWifiMethods));
 }
diff --git a/core/jni/android_os_Debug.cpp b/core/jni/android_os_Debug.cpp
index 3ee404a..4a877d2 100644
--- a/core/jni/android_os_Debug.cpp
+++ b/core/jni/android_os_Debug.cpp
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+#define LOG_TAG "android.os.Debug"
 #include "JNIHelp.h"
 #include "jni.h"
 #include "utils/misc.h"
@@ -24,6 +25,8 @@
 #include <unistd.h>
 #include <time.h>
 #include <sys/time.h>
+#include <errno.h>
+#include <assert.h>
 
 #ifdef HAVE_MALLOC_H
 #include <malloc.h>
@@ -274,6 +277,176 @@
 jint android_os_Debug_getProxyObjectCount(JNIEnv* env, jobject clazz);
 jint android_os_Debug_getDeathObjectCount(JNIEnv* env, jobject clazz);
 
+
+#ifdef HAVE_ANDROID_OS
+/* pulled out of bionic */
+extern "C" void get_malloc_leak_info(uint8_t** info, size_t* overallSize,
+    size_t* infoSize, size_t* totalMemory, size_t* backtraceSize);
+extern "C" void free_malloc_leak_info(uint8_t* info);
+#define SIZE_FLAG_ZYGOTE_CHILD  (1<<31)
+#define BACKTRACE_SIZE          32
+
+/*
+ * This is a qsort() callback.
+ *
+ * See dumpNativeHeap() for comments about the data format and sort order.
+ */
+static int compareHeapRecords(const void* vrec1, const void* vrec2)
+{
+    const size_t* rec1 = (const size_t*) vrec1;
+    const size_t* rec2 = (const size_t*) vrec2;
+    size_t size1 = *rec1;
+    size_t size2 = *rec2;
+
+    if (size1 < size2) {
+        return 1;
+    } else if (size1 > size2) {
+        return -1;
+    }
+
+    intptr_t* bt1 = (intptr_t*)(rec1 + 2);
+    intptr_t* bt2 = (intptr_t*)(rec2 + 2);
+    for (size_t idx = 0; idx < BACKTRACE_SIZE; idx++) {
+        intptr_t addr1 = bt1[idx];
+        intptr_t addr2 = bt2[idx];
+        if (addr1 == addr2) {
+            if (addr1 == 0)
+                break;
+            continue;
+        }
+        if (addr1 < addr2) {
+            return -1;
+        } else if (addr1 > addr2) {
+            return 1;
+        }
+    }
+
+    return 0;
+}
+
+/*
+ * The get_malloc_leak_info() call returns an array of structs that
+ * look like this:
+ *
+ *   size_t size
+ *   size_t allocations
+ *   intptr_t backtrace[32]
+ *
+ * "size" is the size of the allocation, "backtrace" is a fixed-size
+ * array of function pointers, and "allocations" is the number of
+ * allocations with the exact same size and backtrace.
+ *
+ * The entries are sorted by descending total size (i.e. size*allocations)
+ * then allocation count.  For best results with "diff" we'd like to sort
+ * primarily by individual size then stack trace.  Since the entries are
+ * fixed-size, and we're allowed (by the current implementation) to mangle
+ * them, we can do this in place.
+ */
+static void dumpNativeHeap(FILE* fp)
+{
+    uint8_t* info = NULL;
+    size_t overallSize, infoSize, totalMemory, backtraceSize;
+
+    get_malloc_leak_info(&info, &overallSize, &infoSize, &totalMemory,
+        &backtraceSize);
+    if (info == NULL) {
+        fprintf(fp, "Native heap dump not available. To enable, run these"
+                    " commands (requires root):\n");
+        fprintf(fp, "$ adb shell setprop libc.debug.malloc 1\n");
+        fprintf(fp, "$ adb shell stop\n");
+        fprintf(fp, "$ adb shell start\n");
+        return;
+    }
+    assert(infoSize != 0);
+    assert(overallSize % infoSize == 0);
+
+    fprintf(fp, "Android Native Heap Dump v1.0\n\n");
+
+    size_t recordCount = overallSize / infoSize;
+    fprintf(fp, "Total memory: %zu\n", totalMemory);
+    fprintf(fp, "Allocation records: %zd\n", recordCount);
+    if (backtraceSize != BACKTRACE_SIZE) {
+        fprintf(fp, "WARNING: mismatched backtrace sizes (%d vs. %d)\n",
+            backtraceSize, BACKTRACE_SIZE);
+    }
+    fprintf(fp, "\n");
+
+    /* re-sort the entries */
+    qsort(info, recordCount, infoSize, compareHeapRecords);
+
+    /* dump the entries to the file */
+    const uint8_t* ptr = info;
+    for (size_t idx = 0; idx < recordCount; idx++) {
+        size_t size = *(size_t*) ptr;
+        size_t allocations = *(size_t*) (ptr + sizeof(size_t));
+        intptr_t* backtrace = (intptr_t*) (ptr + sizeof(size_t) * 2);
+
+        fprintf(fp, "z %d  sz %8zu  num %4zu  bt",
+                (size & SIZE_FLAG_ZYGOTE_CHILD) != 0,
+                size & ~SIZE_FLAG_ZYGOTE_CHILD,
+                allocations);
+        for (size_t bt = 0; bt < backtraceSize; bt++) {
+            if (backtrace[bt] == 0) {
+                break;
+            } else {
+                fprintf(fp, " %08x", backtrace[bt]);
+            }
+        }
+        fprintf(fp, "\n");
+
+        ptr += infoSize;
+    }
+
+    fprintf(fp, "END\n");
+    free_malloc_leak_info(info);
+}
+#endif /*HAVE_ANDROID_OS*/
+
+/*
+ * Dump the native heap, writing human-readable output to the specified
+ * file descriptor.
+ */
+static void android_os_Debug_dumpNativeHeap(JNIEnv* env, jobject clazz,
+    jobject fileDescriptor)
+{
+    if (fileDescriptor == NULL) {
+        jniThrowNullPointerException(env, NULL);
+        return;
+    }
+    int origFd = jniGetFDFromFileDescriptor(env, fileDescriptor);
+    if (origFd < 0) {
+        jniThrowRuntimeException(env, "Invalid file descriptor");
+        return;
+    }
+
+    /* dup() the descriptor so we don't close the original with fclose() */
+    int fd = dup(origFd);
+    if (fd < 0) {
+        LOGW("dup(%d) failed: %s\n", origFd, strerror(errno));
+        jniThrowRuntimeException(env, "dup() failed");
+        return;
+    }
+
+    FILE* fp = fdopen(fd, "w");
+    if (fp == NULL) {
+        LOGW("fdopen(%d) failed: %s\n", fd, strerror(errno));
+        close(fd);
+        jniThrowRuntimeException(env, "fdopen() failed");
+        return;
+    }
+
+#ifdef HAVE_ANDROID_OS
+    LOGD("Native heap dump starting...\n");
+    dumpNativeHeap(fp);
+    LOGD("Native heap dump complete.\n");
+#else
+    fprintf(fp, "Native heap dump not available on this platform\n");
+#endif
+
+    fclose(fp);
+}
+
+
 /*
  * JNI registration.
  */
@@ -289,6 +462,8 @@
             (void*) android_os_Debug_getDirtyPages },
     { "getMemoryInfo",          "(ILandroid/os/Debug$MemoryInfo;)V",
             (void*) android_os_Debug_getDirtyPagesPid },
+    { "dumpNativeHeap",         "(Ljava/io/FileDescriptor;)V",
+            (void*) android_os_Debug_dumpNativeHeap },
     { "getBinderSentTransactions", "()I",
             (void*) android_os_Debug_getBinderSentTransactions },
     { "getBinderReceivedTransactions", "()I",
@@ -320,4 +495,4 @@
     return jniRegisterNativeMethods(env, "android/os/Debug", gMethods, NELEM(gMethods));
 }
 
-};
+}; // namespace android
diff --git a/core/jni/android_server_BluetoothEventLoop.cpp b/core/jni/android_server_BluetoothEventLoop.cpp
index 01b6711..4dc012c 100644
--- a/core/jni/android_server_BluetoothEventLoop.cpp
+++ b/core/jni/android_server_BluetoothEventLoop.cpp
@@ -64,6 +64,9 @@
 static jmethodID method_onAgentAuthorize;
 static jmethodID method_onAgentCancel;
 
+static jmethodID method_onInputDevicePropertyChanged;
+static jmethodID method_onInputDeviceConnectionResult;
+
 typedef event_loop_native_data_t native_data_t;
 
 #define EVENT_LOOP_REFS 10
@@ -116,6 +119,10 @@
                                                "(Ljava/lang/String;I)V");
     method_onDisplayPasskey = env->GetMethodID(clazz, "onDisplayPasskey",
                                                "(Ljava/lang/String;II)V");
+    method_onInputDevicePropertyChanged = env->GetMethodID(clazz, "onInputDevicePropertyChanged",
+                                                      "(Ljava/lang/String;[Ljava/lang/String;)V");
+    method_onInputDeviceConnectionResult = env->GetMethodID(clazz, "onInputDeviceConnectionResult",
+                                                      "(Ljava/lang/String;Z)V");
 
     field_mNativeData = env->GetFieldID(clazz, "mNativeData", "I");
 #endif
@@ -226,6 +233,20 @@
             return JNI_FALSE;
         }
         dbus_bus_add_match(nat->conn,
+                "type='signal',interface='"BLUEZ_DBUS_BASE_IFC".Input'",
+                &err);
+        if (dbus_error_is_set(&err)) {
+            LOG_AND_FREE_DBUS_ERROR(&err);
+            return JNI_FALSE;
+        }
+        dbus_bus_add_match(nat->conn,
+                "type='signal',interface='"BLUEZ_DBUS_BASE_IFC".Network'",
+                &err);
+        if (dbus_error_is_set(&err)) {
+            LOG_AND_FREE_DBUS_ERROR(&err);
+            return JNI_FALSE;
+        }
+        dbus_bus_add_match(nat->conn,
                 "type='signal',interface='org.bluez.AudioSink'",
                 &err);
         if (dbus_error_is_set(&err)) {
@@ -853,6 +874,22 @@
                             method_onDeviceDisconnectRequested,
                             env->NewStringUTF(remote_device_path));
         goto success;
+    } else if (dbus_message_is_signal(msg,
+                                      "org.bluez.Input",
+                                      "PropertyChanged")) {
+
+        jobjectArray str_array =
+                    parse_input_property_change(env, msg);
+        if (str_array != NULL) {
+            const char *c_path = dbus_message_get_path(msg);
+            env->CallVoidMethod(nat->me,
+                                method_onInputDevicePropertyChanged,
+                                env->NewStringUTF(c_path),
+                                str_array);
+        } else {
+            LOG_AND_FREE_DBUS_ERROR_WITH_MSG(&err, msg);
+        }
+        goto success;
     }
 
     ret = a2dp_event_filter(msg, env);
@@ -1210,6 +1247,32 @@
     env->DeleteLocalRef(addr);
     free(user);
 }
+
+void onInputDeviceConnectionResult(DBusMessage *msg, void *user, void *n) {
+    LOGV(__FUNCTION__);
+
+    native_data_t *nat = (native_data_t *)n;
+    const char *path = (const char *)user;
+    DBusError err;
+    dbus_error_init(&err);
+    JNIEnv *env;
+    nat->vm->GetEnv((void**)&env, nat->envVer);
+
+    bool result = JNI_TRUE;
+    if (dbus_set_error_from_message(&err, msg)) {
+        LOG_AND_FREE_DBUS_ERROR(&err);
+        result = JNI_FALSE;
+    }
+    LOGV("... Device Path = %s, result = %d", path, result);
+    jstring jPath = env->NewStringUTF(path);
+    env->CallVoidMethod(nat->me,
+                        method_onInputDeviceConnectionResult,
+                        jPath,
+                        result);
+    env->DeleteLocalRef(jPath);
+    free(user);
+}
+
 #endif
 
 static JNINativeMethod sMethods[] = {
diff --git a/core/jni/android_server_BluetoothService.cpp b/core/jni/android_server_BluetoothService.cpp
index 4420aca..69a8003 100644
--- a/core/jni/android_server_BluetoothService.cpp
+++ b/core/jni/android_server_BluetoothService.cpp
@@ -16,6 +16,8 @@
 
 #define DBUS_ADAPTER_IFACE BLUEZ_DBUS_BASE_IFC ".Adapter"
 #define DBUS_DEVICE_IFACE BLUEZ_DBUS_BASE_IFC ".Device"
+#define DBUS_INPUT_IFACE BLUEZ_DBUS_BASE_IFC ".Input"
+
 #define LOG_TAG "BluetoothService.cpp"
 
 #include "android_bluetooth_common.h"
@@ -68,6 +70,7 @@
 void onCreatePairedDeviceResult(DBusMessage *msg, void *user, void *nat);
 void onDiscoverServicesResult(DBusMessage *msg, void *user, void *nat);
 void onCreateDeviceResult(DBusMessage *msg, void *user, void *nat);
+void onInputDeviceConnectionResult(DBusMessage *msg, void *user, void *nat);
 
 
 /** Get native data stored in the opaque (Java code maintained) pointer mNativeData
@@ -881,6 +884,61 @@
     return JNI_FALSE;
 }
 
+static jboolean connectInputDeviceNative(JNIEnv *env, jobject object, jstring path) {
+    LOGV(__FUNCTION__);
+#ifdef HAVE_BLUETOOTH
+    native_data_t *nat = get_native_data(env, object);
+    jobject eventLoop = env->GetObjectField(object, field_mEventLoop);
+    struct event_loop_native_data_t *eventLoopNat =
+            get_EventLoop_native_data(env, eventLoop);
+
+    if (nat && eventLoopNat) {
+        const char *c_path = env->GetStringUTFChars(path, NULL);
+
+        int len = env->GetStringLength(path) + 1;
+        char *context_path = (char *)calloc(len, sizeof(char));
+        strlcpy(context_path, c_path, len);  // for callback
+
+        bool ret = dbus_func_args_async(env, nat->conn, -1, onInputDeviceConnectionResult,
+                                        context_path, eventLoopNat, c_path, DBUS_INPUT_IFACE,
+                                        "Connect",
+                                        DBUS_TYPE_INVALID);
+
+        env->ReleaseStringUTFChars(path, c_path);
+        return ret ? JNI_TRUE : JNI_FALSE;
+    }
+#endif
+    return JNI_FALSE;
+}
+
+static jboolean disconnectInputDeviceNative(JNIEnv *env, jobject object,
+                                     jstring path) {
+    LOGV(__FUNCTION__);
+#ifdef HAVE_BLUETOOTH
+    native_data_t *nat = get_native_data(env, object);
+    jobject eventLoop = env->GetObjectField(object, field_mEventLoop);
+    struct event_loop_native_data_t *eventLoopNat =
+            get_EventLoop_native_data(env, eventLoop);
+
+    if (nat && eventLoopNat) {
+        const char *c_path = env->GetStringUTFChars(path, NULL);
+
+        int len = env->GetStringLength(path) + 1;
+        char *context_path = (char *)calloc(len, sizeof(char));
+        strlcpy(context_path, c_path, len);  // for callback
+
+        bool ret = dbus_func_args_async(env, nat->conn, -1, onInputDeviceConnectionResult,
+                                        context_path, eventLoopNat, c_path, DBUS_INPUT_IFACE,
+                                        "Disconnect",
+                                        DBUS_TYPE_INVALID);
+
+        env->ReleaseStringUTFChars(path, c_path);
+        return ret ? JNI_TRUE : JNI_FALSE;
+    }
+#endif
+    return JNI_FALSE;
+}
+
 static JNINativeMethod sMethods[] = {
      /* name, signature, funcPtr */
     {"classInitNative", "()V", (void*)classInitNative},
@@ -926,6 +984,9 @@
     {"addRfcommServiceRecordNative", "(Ljava/lang/String;JJS)I", (void *)addRfcommServiceRecordNative},
     {"removeServiceRecordNative", "(I)Z", (void *)removeServiceRecordNative},
     {"setLinkTimeoutNative", "(Ljava/lang/String;I)Z", (void *)setLinkTimeoutNative},
+    // HID functions
+    {"connectInputDeviceNative", "(Ljava/lang/String;)Z", (void *)connectInputDeviceNative},
+    {"disconnectInputDeviceNative", "(Ljava/lang/String;)Z", (void *)disconnectInputDeviceNative},
 };
 
 int register_android_server_BluetoothService(JNIEnv *env) {
diff --git a/core/jni/android_view_GLES20Canvas.cpp b/core/jni/android_view_GLES20Canvas.cpp
new file mode 100644
index 0000000..142e194
--- /dev/null
+++ b/core/jni/android_view_GLES20Canvas.cpp
@@ -0,0 +1,349 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "OpenGLRenderer"
+
+#include "jni.h"
+#include <nativehelper/JNIHelp.h>
+#include <android_runtime/AndroidRuntime.h>
+#include <utils/ResourceTypes.h>
+
+#include <SkBitmap.h>
+#include <SkCanvas.h>
+#include <SkMatrix.h>
+#include <SkPaint.h>
+#include <SkRegion.h>
+#include <SkScalerContext.h>
+#include <SkXfermode.h>
+
+#include <OpenGLRenderer.h>
+#include <SkiaShader.h>
+#include <SkiaColorFilter.h>
+#include <Rect.h>
+#include <ui/Rect.h>
+
+#include "TextLayout.h"
+
+namespace android {
+
+using namespace uirenderer;
+
+// ----------------------------------------------------------------------------
+// Java APIs
+// ----------------------------------------------------------------------------
+
+static struct {
+    jclass clazz;
+    jmethodID set;
+} gRectClassInfo;
+
+// ----------------------------------------------------------------------------
+// Constructors
+// ----------------------------------------------------------------------------
+
+static OpenGLRenderer* android_view_GLES20Canvas_createRenderer(JNIEnv* env, jobject canvas) {
+    return new OpenGLRenderer;
+}
+
+static void android_view_GLES20Canvas_destroyRenderer(JNIEnv* env, jobject canvas,
+        OpenGLRenderer* renderer) {
+    delete renderer;
+}
+
+// ----------------------------------------------------------------------------
+// Setup
+// ----------------------------------------------------------------------------
+
+static void android_view_GLES20Canvas_setViewport(JNIEnv* env, jobject canvas,
+        OpenGLRenderer* renderer, jint width, jint height) {
+    renderer->setViewport(width, height);
+}
+
+static void android_view_GLES20Canvas_prepare(JNIEnv* env, jobject canvas,
+        OpenGLRenderer* renderer) {
+    renderer->prepare();
+}
+
+// ----------------------------------------------------------------------------
+// State
+// ----------------------------------------------------------------------------
+
+static jint android_view_GLES20Canvas_save(JNIEnv* env, jobject canvas, OpenGLRenderer* renderer,
+        jint flags) {
+    return renderer->save(flags);
+}
+
+static jint android_view_GLES20Canvas_getSaveCount(JNIEnv* env, jobject canvas,
+        OpenGLRenderer* renderer) {
+    return renderer->getSaveCount();
+}
+
+static void android_view_GLES20Canvas_restore(JNIEnv* env, jobject canvas,
+        OpenGLRenderer* renderer) {
+    renderer->restore();
+}
+
+static void android_view_GLES20Canvas_restoreToCount(JNIEnv* env, jobject canvas,
+        OpenGLRenderer* renderer, jint saveCount) {
+    renderer->restoreToCount(saveCount);
+}
+
+// ----------------------------------------------------------------------------
+// Layers
+// ----------------------------------------------------------------------------
+
+static jint android_view_GLES20Canvas_saveLayer(JNIEnv* env, jobject canvas,
+        OpenGLRenderer* renderer, jfloat left, jfloat top, jfloat right, jfloat bottom,
+        SkPaint* paint, jint saveFlags) {
+    return renderer->saveLayer(left, top, right, bottom, paint, saveFlags);
+}
+
+static jint android_view_GLES20Canvas_saveLayerAlpha(JNIEnv* env, jobject canvas,
+        OpenGLRenderer* renderer, jfloat left, jfloat top, jfloat right, jfloat bottom,
+        jint alpha, jint saveFlags) {
+    return renderer->saveLayerAlpha(left, top, right, bottom, alpha, saveFlags);
+}
+
+// ----------------------------------------------------------------------------
+// Clipping
+// ----------------------------------------------------------------------------
+
+static bool android_view_GLES20Canvas_quickReject(JNIEnv* env, jobject canvas,
+        OpenGLRenderer* renderer, jfloat left, jfloat top, jfloat right, jfloat bottom,
+        SkCanvas::EdgeType edge) {
+    return renderer->quickReject(left, top, right, bottom);
+}
+
+static bool android_view_GLES20Canvas_clipRectF(JNIEnv* env, jobject canvas,
+        OpenGLRenderer* renderer, jfloat left, jfloat top, jfloat right, jfloat bottom,
+        SkRegion::Op op) {
+    return renderer->clipRect(left, top, right, bottom, op);
+}
+
+static bool android_view_GLES20Canvas_clipRect(JNIEnv* env, jobject canvas,
+        OpenGLRenderer* renderer, jint left, jint top, jint right, jint bottom,
+        SkRegion::Op op) {
+    return renderer->clipRect(float(left), float(top), float(right), float(bottom), op);
+}
+
+static bool android_view_GLES20Canvas_getClipBounds(JNIEnv* env, jobject canvas,
+        OpenGLRenderer* renderer, jobject rect) {
+    const android::uirenderer::Rect& bounds(renderer->getClipBounds());
+
+    env->CallVoidMethod(rect, gRectClassInfo.set,
+            int(bounds.left), int(bounds.top), int(bounds.right), int(bounds.bottom));
+
+    return !bounds.isEmpty();
+}
+
+// ----------------------------------------------------------------------------
+// Transforms
+// ----------------------------------------------------------------------------
+
+static void android_view_GLES20Canvas_translate(JNIEnv* env, jobject canvas,
+        OpenGLRenderer* renderer, jfloat dx, jfloat dy) {
+    renderer->translate(dx, dy);
+}
+
+static void android_view_GLES20Canvas_rotate(JNIEnv* env, jobject canvas,
+        OpenGLRenderer* renderer, jfloat degrees) {
+    renderer->rotate(degrees);
+}
+
+static void android_view_GLES20Canvas_scale(JNIEnv* env, jobject canvas,
+        OpenGLRenderer* renderer, jfloat sx, jfloat sy) {
+    renderer->scale(sx, sy);
+}
+
+static void android_view_GLES20Canvas_setMatrix(JNIEnv* env, jobject canvas,
+        OpenGLRenderer* renderer, SkMatrix* matrix) {
+    renderer->setMatrix(matrix);
+}
+
+static void android_view_GLES20Canvas_getMatrix(JNIEnv* env, jobject canvas,
+        OpenGLRenderer* renderer, SkMatrix* matrix) {
+    renderer->getMatrix(matrix);
+}
+
+static void android_view_GLES20Canvas_concatMatrix(JNIEnv* env, jobject canvas,
+        OpenGLRenderer* renderer, SkMatrix* matrix) {
+    renderer->concatMatrix(matrix);
+}
+
+// ----------------------------------------------------------------------------
+// Drawing
+// ----------------------------------------------------------------------------
+
+static void android_view_GLES20Canvas_drawBitmap(JNIEnv* env, jobject canvas,
+        OpenGLRenderer* renderer, SkBitmap* bitmap, float left, float top, SkPaint* paint) {
+    renderer->drawBitmap(bitmap, left, top, paint);
+}
+
+static void android_view_GLES20Canvas_drawBitmapRect(JNIEnv* env, jobject canvas,
+        OpenGLRenderer* renderer, SkBitmap* bitmap,
+        float srcLeft, float srcTop, float srcRight, float srcBottom,
+        float dstLeft, float dstTop, float dstRight, float dstBottom, SkPaint* paint) {
+    renderer->drawBitmap(bitmap, srcLeft, srcTop, srcRight, srcBottom,
+            dstLeft, dstTop, dstRight, dstBottom, paint);
+}
+
+static void android_view_GLES20Canvas_drawBitmapMatrix(JNIEnv* env, jobject canvas,
+        OpenGLRenderer* renderer, SkBitmap* bitmap, SkMatrix* matrix, SkPaint* paint) {
+    renderer->drawBitmap(bitmap, matrix, paint);
+}
+
+static void android_view_GLES20Canvas_drawPatch(JNIEnv* env, jobject canvas,
+        OpenGLRenderer* renderer, SkBitmap* bitmap, jbyteArray chunks,
+        float left, float top, float right, float bottom, SkPaint* paint) {
+    jbyte* storage = env->GetByteArrayElements(chunks, NULL);
+    Res_png_9patch* patch = reinterpret_cast<Res_png_9patch*>(storage);
+    Res_png_9patch::deserialize(patch);
+
+    renderer->drawPatch(bitmap, patch, left, top, right, bottom, paint);
+
+    env->ReleaseByteArrayElements(chunks, storage, 0);
+}
+
+static void android_view_GLES20Canvas_drawColor(JNIEnv* env, jobject canvas,
+        OpenGLRenderer* renderer, jint color, SkXfermode::Mode mode) {
+    renderer->drawColor(color, mode);
+}
+
+static void android_view_GLES20Canvas_drawRect(JNIEnv* env, jobject canvas,
+        OpenGLRenderer* renderer, jfloat left, jfloat top, jfloat right, jfloat bottom,
+        SkPaint* paint) {
+    renderer->drawRect(left, top, right, bottom, paint);
+}
+
+// ----------------------------------------------------------------------------
+// Shaders and color filters
+// ----------------------------------------------------------------------------
+
+static void android_view_GLES20Canvas_resetModifiers(JNIEnv* env, jobject canvas,
+        OpenGLRenderer* renderer) {
+    renderer->resetShader();
+    renderer->resetColorFilter();
+}
+
+static void android_view_GLES20Canvas_setupShader(JNIEnv* env, jobject canvas,
+        OpenGLRenderer* renderer, SkiaShader* shader) {
+    renderer->setupShader(shader);
+}
+
+static void android_view_GLES20Canvas_setupColorFilter(JNIEnv* env, jobject canvas,
+        OpenGLRenderer* renderer, SkiaColorFilter* filter) {
+    renderer->setupColorFilter(filter);
+}
+
+// ----------------------------------------------------------------------------
+// Text
+// ----------------------------------------------------------------------------
+
+static void renderText(OpenGLRenderer* renderer, const jchar* text, int count,
+        jfloat x, jfloat y, int flags, SkPaint* paint) {
+    const jchar *workText;
+    jchar* buffer = NULL;
+    int32_t workBytes;
+    if (TextLayout::prepareText(paint, text, count, flags, &workText, &workBytes, &buffer)) {
+        renderer->drawText((const char*) workText, workBytes, count, x, y, paint);
+        free(buffer);
+    }
+}
+
+static void android_view_GLES20Canvas_drawTextArray(JNIEnv* env, jobject canvas,
+        OpenGLRenderer* renderer, jcharArray text, int index, int count,
+        jfloat x, jfloat y, int flags, SkPaint* paint) {
+    jchar* textArray = env->GetCharArrayElements(text, NULL);
+    renderText(renderer, textArray + index, count, x, y, flags, paint);
+    env->ReleaseCharArrayElements(text, textArray, JNI_ABORT);
+}
+
+static void android_view_GLES20Canvas_drawText(JNIEnv* env, jobject canvas,
+        OpenGLRenderer* renderer, jstring text, int start, int end,
+        jfloat x, jfloat y, int flags, SkPaint* paint) {
+    const jchar* textArray = env->GetStringChars(text, NULL);
+    renderText(renderer, textArray + start, end - start, x, y, flags, paint);
+    env->ReleaseStringChars(text, textArray);
+}
+
+// ----------------------------------------------------------------------------
+// JNI Glue
+// ----------------------------------------------------------------------------
+
+const char* const kClassPathName = "android/view/GLES20Canvas";
+
+static JNINativeMethod gMethods[] = {
+    {   "nCreateRenderer",    "()I",             (void*) android_view_GLES20Canvas_createRenderer },
+    {   "nDestroyRenderer",   "(I)V",            (void*) android_view_GLES20Canvas_destroyRenderer },
+    {   "nSetViewport",       "(III)V",          (void*) android_view_GLES20Canvas_setViewport },
+    {   "nPrepare",           "(I)V",            (void*) android_view_GLES20Canvas_prepare },
+
+    {   "nSave",              "(II)I",           (void*) android_view_GLES20Canvas_save },
+    {   "nRestore",           "(I)V",            (void*) android_view_GLES20Canvas_restore },
+    {   "nRestoreToCount",    "(II)V",           (void*) android_view_GLES20Canvas_restoreToCount },
+    {   "nGetSaveCount",      "(I)I",            (void*) android_view_GLES20Canvas_getSaveCount },
+
+    {   "nSaveLayer",         "(IFFFFII)I",      (void*) android_view_GLES20Canvas_saveLayer },
+    {   "nSaveLayerAlpha",    "(IFFFFII)I",      (void*) android_view_GLES20Canvas_saveLayerAlpha },
+
+    {   "nQuickReject",       "(IFFFFI)Z",       (void*) android_view_GLES20Canvas_quickReject },
+    {   "nClipRect",          "(IFFFFI)Z",       (void*) android_view_GLES20Canvas_clipRectF },
+    {   "nClipRect",          "(IIIIII)Z",       (void*) android_view_GLES20Canvas_clipRect },
+
+    {   "nTranslate",         "(IFF)V",          (void*) android_view_GLES20Canvas_translate },
+    {   "nRotate",            "(IF)V",           (void*) android_view_GLES20Canvas_rotate },
+    {   "nScale",             "(IFF)V",          (void*) android_view_GLES20Canvas_scale },
+
+    {   "nSetMatrix",         "(II)V",           (void*) android_view_GLES20Canvas_setMatrix },
+    {   "nGetMatrix",         "(II)V",           (void*) android_view_GLES20Canvas_getMatrix },
+    {   "nConcatMatrix",      "(II)V",           (void*) android_view_GLES20Canvas_concatMatrix },
+
+    {   "nDrawBitmap",        "(IIFFI)V",        (void*) android_view_GLES20Canvas_drawBitmap },
+    {   "nDrawBitmap",        "(IIFFFFFFFFI)V",  (void*) android_view_GLES20Canvas_drawBitmapRect },
+    {   "nDrawBitmap",        "(IIII)V",         (void*) android_view_GLES20Canvas_drawBitmapMatrix },
+    {   "nDrawPatch",         "(II[BFFFFI)V",    (void*) android_view_GLES20Canvas_drawPatch },
+    {   "nDrawColor",         "(III)V",          (void*) android_view_GLES20Canvas_drawColor },
+    {   "nDrawRect",          "(IFFFFI)V",       (void*) android_view_GLES20Canvas_drawRect },
+
+    {   "nResetModifiers",    "(I)V",            (void*) android_view_GLES20Canvas_resetModifiers },
+    {   "nSetupShader",       "(II)V",           (void*) android_view_GLES20Canvas_setupShader },
+    {   "nSetupColorFilter",  "(II)V",           (void*) android_view_GLES20Canvas_setupColorFilter },
+
+    {   "nDrawText",          "(I[CIIFFII)V",    (void*) android_view_GLES20Canvas_drawTextArray },
+    {   "nDrawText",          "(ILjava/lang/String;IIFFII)V",
+            (void*) android_view_GLES20Canvas_drawText },
+
+    {   "nGetClipBounds",     "(ILandroid/graphics/Rect;)Z",
+            (void*) android_view_GLES20Canvas_getClipBounds },
+};
+
+#define FIND_CLASS(var, className) \
+        var = env->FindClass(className); \
+        LOG_FATAL_IF(! var, "Unable to find class " className); \
+        var = jclass(env->NewGlobalRef(var));
+
+#define GET_METHOD_ID(var, clazz, methodName, methodDescriptor) \
+        var = env->GetMethodID(clazz, methodName, methodDescriptor); \
+        LOG_FATAL_IF(! var, "Unable to find method " methodName);
+
+int register_android_view_GLES20Canvas(JNIEnv* env) {
+    FIND_CLASS(gRectClassInfo.clazz, "android/graphics/Rect");
+    GET_METHOD_ID(gRectClassInfo.set, gRectClassInfo.clazz, "set", "(IIII)V");
+
+    return AndroidRuntime::registerNativeMethods(env, kClassPathName, gMethods, NELEM(gMethods));
+}
+
+};
diff --git a/core/jni/android_view_HardwareRenderer.cpp b/core/jni/android_view_HardwareRenderer.cpp
new file mode 100644
index 0000000..6d20c9d
--- /dev/null
+++ b/core/jni/android_view_HardwareRenderer.cpp
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <utils/SkGLCanvas.h>
+
+#include "jni.h"
+#include <nativehelper/JNIHelp.h>
+#include <android_runtime/AndroidRuntime.h>
+#include <utils/misc.h>
+
+// ----------------------------------------------------------------------------
+
+namespace android {
+
+static void android_view_HardwareRenderer_abandonGlCaches(JNIEnv* env, jobject) {
+    SkGLCanvas::AbandonAllTextures();
+}
+
+// ----------------------------------------------------------------------------
+
+const char* const kClassPathName = "android/view/HardwareRenderer";
+
+static JNINativeMethod gMethods[] = {
+    {   "nativeAbandonGlCaches", "()V", (void*)android_view_HardwareRenderer_abandonGlCaches },
+};
+
+int register_android_view_HardwareRenderer(JNIEnv* env) {
+    return AndroidRuntime::registerNativeMethods(env, kClassPathName, gMethods, NELEM(gMethods));
+}
+
+};
diff --git a/core/jni/android_view_ViewRoot.cpp b/core/jni/android_view_ViewRoot.cpp
index 5173bb8..2988ae8 100644
--- a/core/jni/android_view_ViewRoot.cpp
+++ b/core/jni/android_view_ViewRoot.cpp
@@ -76,10 +76,6 @@
     canvas->restore();
 }
 
-static void android_view_ViewRoot_abandonGlCaches(JNIEnv* env, jobject) {
-    SkGLCanvas::AbandonAllTextures();
-}
-
 
 // ----------------------------------------------------------------------------
 
@@ -87,9 +83,7 @@
 
 static JNINativeMethod gMethods[] = {
     {   "nativeShowFPS", "(Landroid/graphics/Canvas;I)V",
-                                        (void*)android_view_ViewRoot_showFPS },
-    {   "nativeAbandonGlCaches", "()V", 
-                                (void*)android_view_ViewRoot_abandonGlCaches }
+                                        (void*)android_view_ViewRoot_showFPS }
 };
 
 int register_android_view_ViewRoot(JNIEnv* env) {
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 82f822f..01ded68 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -83,6 +83,8 @@
     <protected-broadcast android:name="android.hardware.action.USB_CONNECTED" />
     <protected-broadcast android:name="android.hardware.action.USB_DISCONNECTED" />
     <protected-broadcast android:name="android.hardware.action.USB_STATE" />
+    <protected-broadcast android:name="android.hardware.action.USB_CAMERA_ATTACHED" />
+    <protected-broadcast android:name="android.hardware.action.USB_CAMERA_DETACHED" />
 
     <!-- ====================================== -->
     <!-- Permissions for things that cost money -->
@@ -348,6 +350,13 @@
         android:description="@string/permdesc_accountManagerService"
         android:label="@string/permlab_accountManagerService" />
 
+    <!-- Allows an internal user to use privaledged ConnectivityManager
+    APIs.
+        @hide -->
+    <permission android:name="android.permission.CONNECTIVITY_INTERNAL"
+        android:permissionGroup="android.permission-group.NETWORK"
+        android:protectionLevel="signatureOrSystem" />
+
     <!-- ================================== -->
     <!-- Permissions for accessing accounts -->
     <!-- ================================== -->
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/assets/webkit/hyph_en_US.dic b/core/res/assets/webkit/hyph_en_US.dic
new file mode 100644
index 0000000..d91204b
--- /dev/null
+++ b/core/res/assets/webkit/hyph_en_US.dic
@@ -0,0 +1,9784 @@
+ISO8859-1
+LEFTHYPHENMIN 2
+RIGHTHYPHENMIN 3
+.a2ch4
+.ad4der
+.a2d
+.ad1d4
+.a2f1t
+.a2f
+.a4l3t
+.am5at
+.4a1ma
+.an5c
+.a2n
+.2ang4
+.an1i5m
+.an1t4
+.an3te
+.anti5s
+.ant2i
+.a4r5s2
+.2a2r
+.ar4t2ie4
+.ar1ti
+.ar4ty
+.as3c
+.as1p
+.a2s1s
+.aster5
+.a2tom5
+.a1to
+.au1d
+.av4i
+.awn4
+.ba4g
+.ba5na
+.ba2n
+.bas4e
+.ber4
+.be5r1a
+.be3s1m
+.4bes4
+.b4e5s2to
+.bri2
+.but4ti
+.bu4t3t2
+.cam4pe
+.1ca
+.ca4m1p
+.can5c
+.ca2n
+.capa5b
+.ca1pa
+.car5ol
+.c2a2r
+.ca4t
+.ce4la
+.2ch4
+.chill5i
+.ch4il2
+.chil1l
+.1ci2
+.cit5r
+.2c1it
+.co3e2
+.1co
+.co4r
+.cor5n1er
+.corn2e
+.de4moi2
+.d4em
+.de1mo
+.de3o
+.de3r1a
+.de3r1i
+.de1s4c
+.des2
+.dic1t2io5
+.3di2c1t
+.do4t
+.1do
+.du4c
+.1du
+.du4m1b5
+.earth5
+.ear2t
+.e2a2r
+.eas3i
+.2e1b4
+.eer4
+.eg2
+.e2l5d
+.el3em
+.enam3
+.e1na
+.en3g
+.e2n3s2
+.eq5ui5t
+.e1q
+.equ2
+.eq2ui2
+.er4ri
+.er1r4
+.es3
+.4eu3
+.eye5
+.fes3
+.for5mer
+.1fo
+.fo2r
+.for1m
+.for2me
+.1ga2
+.ge2
+.gen3t4
+.1gen
+.ge5o2g
+.1geo
+.1g2i5a
+.gi4b
+.go4r
+.1go
+.hand5i
+.ha2n
+.h4and
+.ha4n5k2
+.he2
+.hero5i2
+.h2ero
+.h1es3
+.he4t3
+.hi3b
+.hi3er
+.h2ie4
+.hon5ey
+.ho2n
+.hon3o
+.hov5
+.id4l
+.2id
+.idol3
+.i1do
+.im3m
+.im5p1i2n
+.i4m1p
+.im2pi
+.in1
+.in3ci
+.2ine2
+.4i4n2k2
+.2i2n3s2
+.ir5r4
+.4ir
+.is4i
+.ju3r
+.la4cy
+.la4m
+.lat5er
+.l4ath5
+.le2
+.leg5e
+.len4
+.lep5
+.lev1
+.l2i4g
+.li1g5a
+.li2n
+.l2i3o
+.l1i4t
+.ma1g5a5
+.1ma
+.mal5o
+.ma1n5a
+.ma2n
+.mar5ti
+.m2a2r
+.me2
+.mer3c
+.me5ter
+.me1te
+.m2is1
+.mis4t5i
+.mon3e
+.1mo
+.mo2n
+.mo3ro
+.mo2r
+.mu5ta
+.1mu
+.mu2ta5b
+.ni4c
+.od2
+.od1d5
+.of5te
+.o2ft
+.or5a1to
+.o1ra
+.or3c
+.or1d
+.or3t
+.os3
+.os4tl
+.4oth3
+.out3
+.ou2
+.ped5al
+.2p2ed
+.p2e2d2a
+.pe5te
+.pe2t
+.pe5tit
+.p2i4e4
+.pio5n4
+.3p2i1o
+.pi2t
+.pre3m
+.pr2
+.ra4c
+.ran4t
+.ra2n
+.ratio5n1a
+.ratio2n4
+.ra1t2io
+.ree2
+.re5mit
+.res2
+.re5stat
+.res2t
+.res1ta
+.r2i4g
+.ri2t5u
+.ro4q
+.ros5t
+.row5d
+.ru4d
+.3s4c2i3e4
+.s1ci
+.5se2l2f5
+.sel1l5
+.se2n
+.se5r2ie4
+.ser1i
+.s2h2
+.si2
+.s3ing4
+.2s1in
+.st4
+.sta5b2l2
+.s1ta
+.s2tab
+.s4y2
+.1ta4
+.te4
+.3ten5a2n
+.te1na
+.th2
+.ti2
+.til4
+.ti1m5o5
+.1tim
+.ting4
+.2t1in
+.t4i4n5k2
+.to1n4a
+.1to
+.to2n
+.to4p
+.top5i
+.to2u5s
+.tou2
+.trib5ut
+.tr4ib
+.u1n1a
+.un3ce
+.under5
+.un1de
+.u2n1e
+.u4n5k2
+.un5o
+.un3u4
+.up3
+.ure3
+.us5a2
+.2us
+.ven4de
+.ve5r1a
+.wil5i
+.wi2
+.wil2
+.ye4
+4ab.
+a5bal
+a5ba2n
+abe2
+ab5erd
+ab2i5a
+ab5i2t5ab
+abi2t
+abi1ta
+ab5lat
+ab2l2
+ab5o5l1iz
+abol2i
+4abr
+ab5rog
+ab3ul
+a4c2a2r
+a1ca
+ac5ard
+ac5aro
+a5ceou2
+ac1er
+a5che4t
+a2ch
+ache2
+4a2ci
+a3c2ie4
+a2c1in
+a3c2io
+ac5rob
+act5if2
+a2c1t
+ac3ul
+ac4um
+a2d
+ad4d1in
+ad1d4
+ad5er.
+2adi
+a3d4i3a
+ad3i1ca
+adi4er
+ad2ie4
+a3d2io
+a3dit
+a5di1u
+ad4le
+ad3ow
+a1do
+ad5ra2n
+a1dr
+ad4su
+a2d1s2
+4a1du
+a3du2c
+ad5um
+ae4r
+aer2i4e4
+aer1i
+a2f
+a4f1f4
+a4gab
+a1ga
+aga4n
+ag5el1l
+a1ge4o
+4ag4eu
+ag1i
+4ag4l2
+ag1n
+a2go
+3a3g4o4g
+ag3o3ni
+ago2n2
+a5guer
+a2gue
+ag5ul
+a4gy
+a3ha
+a3he
+a4h4l4
+a3ho
+ai2
+a5i1a
+a3ic.
+ai5ly
+a4i4n
+ain5in
+a2ini
+a2i1n5o
+ait5en
+a2ite
+a1j
+ak1en
+al5ab
+al3a2d
+a4l2a2r
+4aldi4
+a2ld
+2ale
+al3end
+a4lent2i
+a1len1t
+a5le5o
+al1i
+al4ia.
+al2i1a
+al2i4e4
+al5lev
+al1l
+al2le
+4allic
+all2i
+4a2lm
+a5log.
+a4ly.
+a1ly
+4a2lys4
+5a5lys1t
+5alyt
+3alyz
+4a1ma
+a2m5ab
+am3ag
+ama5ra
+am2a2r
+am5asc
+a4ma3tis
+a4m5a1to
+am5er1a
+am3ic
+am5if
+am5i1ly
+am1in
+am2i4no
+a2mo
+a5mo2n
+amor5i
+amo2r
+amp5en
+a4m1p
+a2n
+an3age
+a1na
+3ana1ly
+a3n2a2r
+an3ar3c
+anar4i
+a3nati
+an2at
+4and
+ande4s2
+an1de
+an3dis1
+an1dl
+an4dow
+an1do
+a5nee
+a3nen
+an5e2st.
+a1nes
+a2nest
+a3n4eu
+2ang
+ang5ie4
+an1gl2
+a4n1ic
+a3nies
+an2ie4
+an3i3f
+an4ime
+an1im
+a5nim1i
+a5n2ine
+an1in
+an3i4o
+a3n2ip
+an3is2h
+an3it
+a3ni1u
+an4kli
+a4nk2
+an1k1l
+5anniz
+a4n1n2
+ano4
+an5ot
+an4oth5
+an2sa2
+a2n1s2
+an4s1co
+ans4c
+an4s1n4
+an2sp
+ans3po
+an4st
+an4su2r
+an1su
+anta2l4
+an1t
+an1ta
+an4t2ie4
+ant2i
+4an1to
+an2tr
+an4tw4
+an3u1a
+an3ul
+a5nur
+4ao
+ap2a2r4
+a1pa
+ap5at
+ap5er3o
+a3ph4er
+4aphi
+a4pilla
+apil1l
+ap5ill2a2r
+ap3i2n
+ap3i1ta
+a3pi2tu
+a2p2l2
+apo4c5
+ap5o1la
+apor5i
+a1p4or
+apos3t
+a1pos
+aps5e4s
+a2p1s2
+ap2se
+a3pu
+aque5
+aqu2
+2a2r
+ar3a2c1t
+a5rade
+ara2d
+ar5adis1
+ar2adi
+ar3al
+a5rame1te
+aram3et
+ar2an4g
+ara2n
+ara3p
+ar4at
+a5ra1t2io
+ar5a1t2iv
+a5rau
+ar5av4
+araw4
+arbal4
+ar1b
+ar4cha2n
+ar1c
+ar3cha
+ar2ch
+ar5d2ine
+ard2i
+ard1in4
+ar4dr
+ar5eas
+a3ree
+ar3en1t
+a5r2e2ss
+ar4fi
+ar1f
+ar4f4l2
+ar1i
+ar5i2al
+ar2i3a
+ar3i2a2n
+a3ri5et
+ar2ie4
+ar4im
+ar5in2at
+ar2i1na
+ar3i1o
+ar2iz
+ar2mi
+ar1m
+ar5o5d
+a5roni
+aro2n
+a3roo2
+ar2p
+ar3q
+arre4
+ar1r4
+ar4sa2
+a4rs2
+ar2s2h
+4as.
+a2s4ab
+asa2
+as3an1t
+asa2n
+ashi4
+as2h
+a5sia.
+as2i1a
+a3si1b
+a3sic
+5a5si4t
+ask3i
+ask2
+as4l2
+a4soc
+a1so
+as5ph
+as4s2h
+a2ss
+as3ten
+as1t4r
+asu1r5a
+a1su
+asu2r
+a2ta
+at3ab2l2
+a2tab
+at5ac
+at3alo
+ata2l
+at5ap
+ate5c
+at5e2ch
+at3e1go
+ateg4
+at3en.
+at3er1a
+ater5n
+a5ter1na
+at3est
+at5ev
+4ath
+ath5em
+ath2e
+a5the2n
+at4ho
+ath5om
+4ati.
+a5t2i1a
+a2t5i5b
+at1ic
+at3if2
+ation5a2r
+a1t2io
+atio2n
+atio1n1a
+at3i1tu
+a4tog
+a1to
+a2tom
+at5om2iz
+a4top
+a4tos2
+a1tr
+at5rop
+at4sk2
+a4t1s2
+at4tag
+a4t3t2
+at1ta
+at5te
+at4th
+a2tu
+at5u1a
+a4t5ue
+at3ul
+at3u1ra
+a2ty
+au4b
+augh3
+au3gu
+au4l2
+aun5d
+au3r
+au5si1b
+a2us
+a4ut5en
+au1th
+a2va
+av3ag4
+a5va2n
+av4e4no
+av3er1a
+av5ern
+av5ery
+av1i
+avi4er
+av2ie4
+av3ig
+av5oc
+a1vor
+3away
+aw3i2
+aw4ly
+aws4
+ax4i5c
+ax3i
+ax4id
+ay5al
+aye4
+ays4
+azi4er
+a2z1i
+az2ie4
+az2z5i
+a4z1z2
+5ba.
+bad5ger
+ba2d
+ba4ge
+bal1a
+ban5dag
+ba2n
+b4and
+ban1d2a
+ban4e
+ban3i
+barbi5
+b2a2r
+bar1b
+bar2i4a
+bar1i
+bas4si
+ba2ss
+1bat
+ba4z
+2b1b
+b2be
+b3ber
+bbi4na
+4b1d
+4be.
+beak4
+bea2t3
+4be2d
+b2e3d2a
+be3de
+b4e3di
+be3gi
+be5gu
+1bel
+be1l2i
+be3lo
+4be5m
+be5n2ig
+be5nu
+4bes4
+be3sp
+b2e5st4r
+3bet
+be1t5iz
+be5tr
+be3tw4
+be3w
+be5y1o4
+2bf
+4b3h
+bi2b
+b2i4d
+3b2ie4
+bi5en
+bi4er
+2b3if
+1bil
+bi3l2iz
+bil1i
+bin2a5r4
+bi1na
+b4in4d
+bi5net
+b2ine
+bi3o2gr
+b2io
+bi5ou2
+bi2t
+3b2i3t2io
+bi1ti
+bi3tr
+3bit5u1a
+bi1tu
+b5i4tz
+b1j
+bk4
+b2l2
+bl4ath5
+b4le.
+blen4
+5ble1sp
+bles2
+b3lis
+b4lo
+blun4t
+4b1m
+4b3n
+bne5g
+3bod
+bod3i
+bo4e
+bol3ic
+bol2i
+bom4bi
+bo4m1b
+bo1n4a
+bo2n
+bon5at
+3boo2
+5bor.
+4b1o1ra
+bor5d
+5bore
+5bori
+5bos4
+b5o1ta
+b4oth5
+bo4to
+boun2d3
+bou2
+4bp
+4brit
+br4oth3
+2b5s2
+bsor4
+b1so
+2bt
+b2t4l
+b4to
+b3tr
+buf4fer1
+bu4f1f
+bu4ga
+bu3l2i
+bu1mi4
+bu4n
+bunt4i
+bun1t
+bu3re
+bus5ie4
+b2us
+buss4e
+bu2ss
+5bust
+4bu1ta
+3bu1t2io
+b4u1t2i
+b5u1to
+b1v
+4b5w
+5by.
+bys4
+1ca
+cab3in
+ca1b2l2
+ca2ch4
+ca5den
+ca2d
+4cag4
+2c5ah
+ca3lat
+cal4la
+cal1l
+cal2l5in4
+call2i
+4calo
+c4an5d
+ca2n
+can4e
+ca4n4ic
+can5is
+can3iz
+can4ty
+can1t
+cany4
+ca5per
+car5om
+c2a2r
+cast5er
+cas5t2ig
+cast2i
+4cas4y
+c4a4th
+4ca1t2iv
+cav5al
+ca2va
+c3c
+ccha5
+c2ch
+c3c2i4a
+c1ci
+ccom1pa5
+c1co
+cco4m1p
+cco2n4
+ccou3t
+ccou2
+2ce.
+4ced.
+4ce1den
+3cei2
+5cel.
+3cel1l
+1cen
+3cenc
+2cen4e
+4ceni
+3cen1t
+3cep
+ce5ram
+cer1a
+4ce1s4a2
+3ces1si
+c2e2ss
+ces5si5b
+ces5t
+cet4
+c5e4ta
+cew4
+2ch
+4ch.
+4ch3ab
+5cha4n1ic
+cha2n
+ch5a5nis
+che2
+cheap3
+4ch4ed
+ch5e5lo
+3chemi
+ch5ene
+che2n
+ch3er.
+ch3e4r1s2
+4ch1in
+5chi2ne.
+ch2ine
+ch5i5n2e2ss
+chi1nes
+5ch2ini
+5ch2io
+3chit
+chi2z
+3cho2
+ch4ti
+1ci
+3c2i1a
+ci2a5b
+ci2a5r
+ci5c
+4cier
+c2ie4
+5c4i2f3ic.
+ci1fi
+4c4i5i4
+ci4la
+3cil1i
+2cim
+2cin
+c4i1na
+3cin2at
+cin3em
+c2ine
+c1ing
+c5ing.
+5c2i1no
+cio2n4
+c2io
+4cipe4
+c2ip
+ci3ph
+4cip4ic
+cip3i
+4cis1ta
+4cis1t2i
+2c1it
+ci1t3iz
+ci1ti
+5ciz
+ck1
+ck3i
+1c4l4
+4cl2a2r
+c5la5ra1t2io
+clar4at
+5clare
+cle4m
+4clic
+clim4
+c1ly4
+c5n
+1co
+co5ag
+c4oa
+coe2
+2cog
+co4gr
+coi4
+co3inc
+col5i
+5colo
+col3o4r
+com5er
+co2me
+co1n4a
+co2n
+c4one
+con3g
+con5t
+co3pa
+cop3ic
+co4p2l2
+4cor1b
+coro3n
+cos4e
+cov1
+cove4
+cow5a
+co2z5e
+co5z1i
+c1q
+cras5t
+cr2as
+5crat.
+5crat1ic
+cre3a2t
+5c2r2ed
+4c3re1ta
+cre4v2
+cri2
+cri5f
+c4rin
+cr2is4
+5cri1ti
+cro4p2l2
+crop5o
+cros4e
+cru4d
+4c3s2
+2c1t
+c2ta4b
+c1ta
+ct5ang
+cta2n
+c5tan1t
+c2te
+c3ter
+c4t4ic1u
+ctim3i
+c1tim
+ctu4r
+c1tu
+c4tw4
+cud5
+c4uf
+c4ui2
+cu5i1ty
+5cul2i
+cul4tis4
+cul1ti
+cu4lt
+3c4ul1tu2
+cu2ma
+c3ume
+cu4mi
+3cun
+cu3pi
+cu5py
+cu2r5a4b
+cu1ra
+cu5r2i3a
+1c2us
+cus1s4i
+cu2ss
+3c4ut
+cu4t2ie4
+c4u1t2i
+4c5u1t2iv
+4cutr
+1cy
+c2ze4
+1d2a
+5da.
+2d3a4b
+da2ch4
+4da2f
+2dag
+da2m2
+d2an3g
+da2n
+dard5
+d2a2r
+dark5
+4dary
+3dat
+4da1t2iv
+4da1to
+5dav4
+dav5e
+5day
+d1b
+d5c
+d1d4
+2de.
+dea2f5
+de4b5i2t
+d2e1b
+de4bo2n
+deca2n4
+de1ca
+de4cil
+de1c2i
+de5com
+de1co
+2d1ed
+4dee.
+de5if
+dei2
+del2i4e4
+del2i
+de4l5i5q
+de5lo
+d4em
+5dem.
+3demic
+dem5ic.
+de5mil
+de4mo2n3s2
+de1mo
+demo2n
+demo2r5
+1den
+de4n2a2r
+de1na
+d4e3no
+denti5f2
+den1t
+dent2i
+de3nu
+de1p
+de3pa
+depi4
+de2pu
+d3e1q
+d4er1h4
+5der3m4
+d5ern5iz
+de4r5s2
+des2
+d2es.
+de1s2c
+de2s5o
+des3t2i
+d2e3st4r
+de4su
+de1t
+de2to
+de1v
+de2v3i4l
+de1vi
+4dey
+4d1f
+d4ga
+d3ge4t
+dg1i
+d2gy
+d1h2
+5di.
+1d4i3a
+dia5b
+d4i4cam
+di1ca
+d4ice
+3di2c1t
+3d2id
+5di3en
+d2ie4
+d1if
+di3ge
+d2ig
+di4la1to
+di1la
+d1in
+1di1na
+3di2ne.
+d2ine
+5d2ini
+di5niz
+1d2io
+dio5g
+di4p2l2
+d2ip
+d4ir2
+di1re
+dir1t5i
+dis1
+5disi
+d4is3t
+d2i1ti
+1d2i1v
+d1j
+d5k2
+4d5la
+3dle.
+3dled
+3dles.
+dles2
+4d3l2e2ss
+2d3lo
+4d5lu
+2d1ly
+d1m
+4d1n4
+1do
+3do.
+do5de
+5doe
+2d5of
+d4og
+do4la
+dol2i4
+do5lo4r
+dom5iz
+do3n2at
+do2n
+do1n1a
+doni4
+doo3d
+doo2
+do4p4p
+d4or
+3dos
+4d5out
+dou2
+do4v
+3dox
+d1p
+1dr
+drag5o2n2
+dra2go
+4dr2ai2
+dre4
+dre2a5r
+5dren
+dr4i4b
+dril4
+dro4p
+4drow
+5drupli
+dru3p2l2
+4dry
+2d1s2
+ds4p
+d4sw2
+d4s4y
+d2th
+1du
+d1u1a
+du2c
+d1u3ca
+duc5er
+4duct.
+du2c1t
+4duc4t1s2
+du5el
+du4g
+d3ul4e
+dum4be
+du4m1b
+du4n
+4dup
+du4pe
+d1v
+d1w
+d2y
+5dyn
+dy4s2e
+dys5p
+e1a4b
+e3a2c1t
+ea2d1
+ead5ie4
+e2adi
+ea4ge
+ea5ger
+ea4l
+eal5er
+e2ale
+eal3ou2
+eam3er
+e5and
+ea2n
+ear3a
+e2a2r
+ear4c
+ear5es
+ear4ic
+ear1i
+ear4il
+ear5k
+ear2t
+eart3e
+ea5sp
+e3a2ss
+east3
+ea2t
+eat5en
+eath3i
+e4ath
+e5at3if2
+e4a3tu
+ea2v
+eav3en
+eav5i
+eav5o
+2e1b
+e4bel.
+e1bel
+e4be2l1s2
+e4ben
+e4bi2t
+e3br
+e4ca2d
+e1ca
+ecan5c
+eca2n
+ec1ca5
+ec3c
+e1ce
+ec5es1sa2
+ec2e2ss
+e1c2i
+e4cib
+ec5ificat
+eci1fi
+ecifi1ca
+ec5i3f2ie4
+ec5i1fy
+e2c3im
+e2c1i4t
+e5c2ite
+e4clam
+e1c4l4
+e4cl2us
+e2col
+e1co
+e4com1m
+e4compe
+eco4m1p
+e4con1c
+eco2n
+e2cor
+ec3o1ra
+eco5ro
+e1cr
+e4crem
+ec4ta2n
+e2c1t
+ec1ta
+ec4te
+e1cu
+e4cul
+ec3u1la
+2e2d2a
+4ed3d4
+e4d1er
+ede4s2
+4edi
+e3d4i3a
+ed3ib
+ed3i1ca
+ed3im
+ed1it
+edi5z
+4e1do
+e4dol
+edo2n2
+e4dri
+e1dr
+e4dul
+e1du
+ed5u1l4o
+ee2c
+e4ed3i
+ee2f
+eel3i
+ee4ly
+ee2m
+ee4na
+ee4p1
+ee2s4
+eest4
+ee4ty
+e5ex
+e1f
+e4f3ere
+efer1
+1e4f1f
+e4fic
+e1fi
+5ef2i1c4i
+efil4
+e3f2i2ne
+e2fin
+ef5i5n2ite
+ef2ini
+efin2it
+3efit
+efor5es
+e1fo
+efo2r
+e4fu4se.
+e3fu
+ef2us
+4egal
+e1ga
+eger4
+eg5ib
+eg4ic
+eg5ing
+e5git5
+eg5n
+e4go.
+e1go
+e4gos
+eg1ul
+e5gur
+5e1gy
+e1h4
+eher4
+ei2
+e5ic
+e2i5d
+e2ig2
+ei5g4l2
+e3i4m1b
+e3in3f
+e1ing
+e5inst
+e2i2n1s2
+eir4d
+e4ir
+e2it3e
+e2i3th
+e5i1ty
+e1j
+e4jud
+ej5udi
+eki4n
+ek1i
+ek4la
+ek1l
+e1la
+e4la.
+e4lac
+e3l4an4d
+ela2n
+e4l5a1t2iv
+e4law
+elax1a4
+e3le2a
+el5ebra
+el2e1b
+ele3br
+5elec
+e4led
+el3e1ga
+e5len
+e4l1er
+e1les2
+e2l2f
+el2i
+e3libe4
+e4l5ic.
+el3i1ca
+e3lier
+el2ie4
+el5i3gib
+el2ig
+el4igi
+e5lim
+e4l3ing
+e3l2io
+e2lis
+el5is2h
+e3l2iv3
+4ella
+el1l
+el4lab
+ell4o4
+e5loc
+el5og
+el3op.
+el2s2h
+e2l1s2
+el4ta
+e4lt
+e5lud
+el5ug
+e4mac
+e1ma
+e4mag
+e5ma2n
+em5a1na
+e4m5b
+e1me
+e2mel
+e4met
+em3i1ca
+em2i4e4
+em5igra
+em2ig4
+emi1gr
+em1in2
+em5ine
+em3i3ni
+e4m2is
+em5is2h
+e5m4i2s1s
+em3iz
+5emniz
+e4m1n
+emo4g
+e1mo
+emo3n2i5o
+emo2n
+em3pi
+e4m1p
+e4mul
+e1mu
+em5u1la
+emu3n2
+e3my
+en5a2mo
+e1na
+e4nan1t
+en2a2n
+ench4er
+en2ch
+enche2
+en3dic
+e5nea
+e5nee
+en3em
+en5ero
+en1er
+en5e1si
+e1nes
+e2n5est
+en3etr
+e3ne4w
+en5i4c3s2
+e5n2ie4
+e5nil
+e3n2i4o
+en3is2h
+en3it
+e5ni1u
+5eniz
+4e4n1n2
+4eno
+e4no4g
+e4nos
+en3ov
+en4sw2
+e2n1s2
+ent5age
+en1t
+en1ta
+4enth1es
+enth2e
+en3u1a
+en5uf
+e3ny.
+4e4n3z
+e5of
+eo2g
+e4oi4
+e3ol
+eop3a2r
+eo2pa
+e1or
+eo3re
+eo5rol
+eos4
+e4ot
+eo4to
+e5out
+eou2
+e5ow
+e2pa
+e3p4ai2
+ep5anc
+epa2n
+e5pel
+e3pen1t
+ep5e5t2i1t2io
+epe2t
+epeti1ti
+ephe4
+e4pli
+e1p2l2
+e1po
+e4prec
+epr2
+ep5re1ca
+e4p2r2ed
+ep3re1h4
+e3pro
+e4prob
+ep4s4h
+e2p1s2
+ep5ti5b
+e2p1t
+e4pu2t
+ep5u1ta
+e1q
+equi3l
+equ2
+eq2ui2
+e4q3ui3s
+er1a
+e2ra4b
+4er4and
+era2n
+er3a2r
+4er4ati.
+2er1b
+er4b2l2
+er3ch
+er1c
+er4che2
+2e2re.
+e3re1a4l
+ere5co
+ere3in
+erei2
+er5el.
+er3e1mo
+er5e1na
+er5ence
+4erene
+er3en1t
+ere4q
+er5e2ss
+er3es2t
+eret4
+er1h4
+er1i
+e1r2i3a4
+5erick1
+e3rien
+er2ie4
+eri4er
+er3in4e
+e1r2i1o
+4erit
+er4i1u
+er2i4v
+e4ri1va
+er3m4
+er4nis4
+4er3n2it
+5erniz
+er3no4
+2ero
+er5ob
+e5r2oc
+ero4r
+er1ou2
+e4r1s2
+er3set
+er2se
+ert3er
+4er2tl
+er3tw4
+4eru
+eru4t
+5erwau
+er1w
+e1s4a2
+e4sa2ge.
+e4sages
+es2c
+e2s1ca
+es5ca2n
+e3scr
+es5cu
+e1s2e
+e2sec
+es5e1cr
+e4s5enc
+e4sert.
+e4ser4t1s2
+e4ser1va
+4es2h
+e3sha
+esh5e2n
+e1si
+e2sic
+e2s2id
+es5i1den
+e4s5ig1n4a
+es2ig
+e2s5im
+e2s4i4n
+esis4te
+e1sis
+e5si4u
+e5skin
+esk2
+esk1i
+es4mi
+e2s1m
+e2sol
+e1so
+es3olu
+e2so2n
+es5o1n1a4
+e1sp
+e2s3per
+es5pi1ra
+esp4ir
+es4pre
+espr2
+2e2ss
+es4si4b
+es1si
+esta2n4
+es1ta
+es3t2ig
+est2i
+es5tim
+4es2to
+e3sto2n
+2est4r
+e5stro
+estruc5
+e2su2r
+e1su
+es5ur1r4
+es4w2
+e2ta4b
+e1ta
+e3ten4d
+e3teo
+ethod3
+et1ic
+e5tide
+et2id
+e2t1in4
+et2i4no
+e5t4ir
+e5t2i1t2io
+eti1ti
+et5i1t2iv
+4e2t1n2
+et5o1n1a
+e1to
+eto2n
+e3tra
+e3tre
+et3ric
+et5rif
+et3rog
+et5ros
+et3u1a
+e1tu
+et5ym
+e1ty
+e4t5z
+4eu
+e5un
+e3up
+eu3ro
+e2us4
+eute4
+euti5l
+e4u1t2i
+eu5tr
+eva2p5
+e1va
+e2vas
+ev5ast
+e5vea
+ev3el1l
+eve4l3o
+e5veng
+even4i
+ev1er
+e5v2er1b
+e1vi
+ev3id
+e2vi4l
+e4v1in
+e3v2i4v
+e5voc
+e5vu
+e1wa
+e4wag
+e5wee
+e3wh
+ewil5
+ewi2
+ew3in4g
+e3wit
+1ex3p
+5ey1c
+5eye.
+eys4
+1fa
+fa3b2l2
+f4ab3r
+fa4ce
+4fag
+fa4i4n4
+fai2
+fal2l5e
+fal1l
+4f4a4ma
+fam5is
+5f2a2r
+far5th
+fa3ta
+fa3th2e
+f4ath
+4fa1to
+fau4lt5
+fau4l2
+4f5b
+4fd
+4fe.
+feas4
+fe4ath3
+fea2t
+f2e4b
+4fe1ca
+5fe2c1t
+2fed
+fe3l2i
+fe4mo
+fen2d
+fen1d5e
+fer1
+5fer1r4
+fev4
+4f1f
+f4fes
+f4f2ie4
+f1fi
+f5f2in.
+f2fin
+f2f5is
+f4f2ly5
+ff4l2
+f2fy
+4fh
+1fi
+f2i3a
+2f3ic.
+4f3ical
+fi1ca
+f3ica2n
+4ficate
+f3i1cen
+fi3cer
+f2i1c4i
+5fi3c2i1a
+5fic2ie4
+4fi4c3s2
+fi3cu
+fi5del
+f2id
+fight5
+f2ig
+fil5i
+fil2l5in4
+fil1l
+fill2i
+4fi1ly
+2fin
+5fi1na
+f4in2d5
+f2i2ne
+f1in3g
+f2i4n4n2
+fis4t2i
+f4l2
+f5l2e2ss
+fles2
+flin4
+flo3re
+f2ly5
+4fm
+4fn
+1fo
+5fo2n
+fon4de
+f2ond
+fon4t
+fo2r
+fo5rat
+fo1ra
+for5ay
+fore5t
+for4i
+for1t5a
+fos5
+4f5p
+fra4t
+f5rea
+fres5c
+fri2
+fril4
+frol5
+2f3s
+2ft
+f4to
+f2ty
+3fu
+fu5el
+4fug
+fu4min
+fu1mi
+fu5ne
+fu3ri
+fusi4
+f2us
+fu2s4s
+4fu1ta
+1fy
+1ga
+ga2f4
+5gal.
+3gal1i
+ga3lo
+2gam
+ga5met
+g5a2mo
+gan5is
+ga2n
+ga3niz
+gani5za1
+4gano4
+gar5n4
+g2a2r
+ga2ss4
+g4ath3
+4ga1t2iv
+4gaz
+g3b
+gd4
+2ge.
+2ged
+geez4
+gel4in
+gel2i
+ge5lis
+ge5l1iz
+4ge1ly
+1gen
+ge4n2at
+ge1na
+g5e5niz
+4g4eno
+4geny
+1geo
+ge3om
+g4ery
+5ge1si
+geth5
+4ge1to
+ge4ty
+ge4v
+4g1g2
+g2ge
+g3ger
+gglu5
+ggl2
+g1go4
+gh3in
+gh5out
+ghou2
+gh4to
+5gi.
+1g2i4a
+gi2a5r
+g1ic
+5gi3c2i1a
+g2i1ci
+g4i1co
+gien5
+g2ie4
+5gies.
+gil4
+g3i1men
+3g4in.
+g4in5ge
+5g4i2n1s2
+5g2io
+3g4ir
+gir4l
+g3is1l2
+gi4u
+5g2iv
+3giz
+gl2
+gla4
+gl2ad5i
+gla2d
+5glas
+1gle
+gli4b
+g3l2ig
+3glo
+glo3r
+g1m
+g4my
+g1n4a
+g4na.
+gne4t4t2
+g1ni
+g2n1in
+g4n2i4o
+g1no
+g4no4n
+1go
+3go.
+gob5
+5goe
+3g4o4g
+go3is
+goi2
+go2n2
+4g3o3n1a
+gon5do5
+g2ond
+go3ni
+5goo2
+go5riz
+gor5ou2
+5gos.
+gov1
+g3p
+1gr
+4gra1d2a
+gra2d
+g4r2ai2
+gra2n2
+5gra4ph.
+g5ra3ph4er
+5graph1ic
+gr4aphi
+4g3ra1phy
+4gray
+gre4n
+4gress.
+gr2e2ss
+4grit
+g4ro
+gruf4
+gs2
+g5ste
+gth3
+gu4a
+3guar2d
+gu2a2r
+2gue
+5gui5t
+g2ui2
+3gun
+3g2us
+4gu4t
+g3w
+1gy
+2g5y3n
+gy5ra
+h3ab4l2
+ha2ch4
+hae4m
+hae4t
+h5agu
+ha3la
+hala3m
+ha4m
+han4ci
+ha2n
+han4cy
+5hand.
+h4and
+h2an4g
+hang5er
+han1g5o
+h5a5niz
+ha4n4k2
+han4te
+han1t
+ha2p3l2
+ha2p5t
+ha3ra2n
+h2a2r
+ha5r2as
+har2d
+hard3e
+har4le4
+har1l
+harp5en
+har2p
+har5ter
+ha2s5s
+haun4
+5haz
+haz3a1
+h1b
+1hea2d1
+3he2a2r
+he4ca2n
+he1ca
+h5ecat
+h4ed
+h4e5do5
+he3l4i
+hel4lis
+hel1l
+hell2i
+hel4ly
+h5elo
+he4m4p
+he2n
+he1na4
+hen5at
+he1o5r
+hep5
+h4er1a
+hera3p
+her4ba
+h2er1b
+here5a
+h3ern
+h5er1ou2
+h2ero
+h3ery
+h1es
+he2s5p
+he4t
+he2t4ed
+h4eu4
+h1f
+h1h
+hi5a2n
+h2i1a
+hi4co
+high5
+h2ig
+h4il2
+himer4
+h4i1na
+hion4e
+h2io
+hio2n
+h2i4p
+hir4l
+h4ir
+hi3ro
+hir4p
+hir4r4
+his3el
+h4ise
+h4i2s4s
+hith5er
+h2ith
+hith2e
+h2i2v
+4hk
+4h1l4
+hla2n4
+h2lo
+hlo3ri
+4h1m
+hmet4
+2h1n
+h5odiz
+h5o2d1s2
+ho4g
+ho1ge4
+hol5a2r
+ho1la
+3hol4e
+ho4ma
+ho2me3
+ho1n4a
+ho2n
+ho5ny
+3hood
+hoo2
+hoo2n4
+hor5at
+ho1ra
+ho5r2is
+hort3e
+ho5ru
+hos4e
+ho5sen
+hos1p
+1ho2us
+hou2
+house3
+hov5el
+4h5p
+4hr4
+hree5
+hro5niz
+hro2n
+hro3po
+4h1s2
+h4s2h
+h4t2a2r
+h1ta
+ht1en
+ht5es
+h4ty
+hu4g
+hu4min
+hu1mi
+hun5ke
+hu4nk2
+hun4t
+hus3t4
+h2us
+hu4t
+h1w
+h4war4t
+hw2a2r
+hy3pe
+hy3ph
+hy2s
+2i1a
+i2al
+iam4
+iam5e1te
+i2a2n
+4ianc
+ian3i
+4ian4t
+ia5pe
+ia2ss4
+i4a1t2iv
+ia4tric
+ia1tr
+i4a2tu
+ibe4
+ib3er1a
+ib5ert
+ib5i1a
+ib3in
+ib5it.
+ibi2t
+ib5ite
+i1b2l2
+ib3li
+i5bo
+i1br
+i2b5ri
+i5bu4n
+4icam
+i1ca
+5icap
+4ic2a2r
+i4car.
+i4cara
+icas5
+i4cay
+iccu4
+ic3c
+4iceo
+4i2ch
+2i1ci
+i5c2id
+ic5i1na
+i2cin
+i2c2ip
+ic3i1pa
+i4c1ly4
+i1c4l4
+i2c5oc
+i1co
+4i1cr
+5icra
+i4cry
+ic4te
+i2c1t
+ic1tu2
+ic4t3u1a
+ic3u1la
+ic4um
+ic5uo
+i3cur
+2id
+i4dai2
+i1d2a
+id5anc
+ida2n
+id5d4
+ide3a4l
+ide4s2
+i2di
+id5i2a2n
+i1d4i3a
+idi4a2r
+i5d2ie4
+i1d3io
+idi5ou2
+id1it
+id5i1u
+i3dle
+i4dom
+i1do
+id3ow
+i4dr
+i2du
+id5uo
+2ie4
+ied4e
+5ie5ga
+ie2ld3
+ie1n5a4
+ien4e
+i5e4n1n2
+i3ent2i
+ien1t
+i1er.
+i3es2c
+i1est
+i3et
+4if.
+if5ero
+ifer1
+iff5en
+i4f1f
+if4fr
+4i2f3ic.
+i1fi
+i3f2ie4
+i3f4l2
+4i2ft
+2ig
+iga5b
+i1ga
+ig3er1a
+ight3i
+4igi
+i3gib
+ig3il4
+ig3in
+ig3it
+i4g4l2
+i2go
+ig3or
+ig5ot
+i5gre
+i1gr
+ig2u5i2
+ig1ur
+i3h
+4i5i4
+i3j
+4ik
+i1la
+il3a4b
+i4l4ade
+ila2d
+i2l5am
+ila5ra
+il2a2r
+i3leg
+il1er
+ilev4
+i2l5f
+il1i
+il3i1a
+il2ib
+il3io
+il4ist
+2il1it
+il2iz
+ill5ab
+il1l
+4i2l1n2
+il3o1q
+il4ty
+i4lt
+il5ur
+il3v
+i4mag
+i1ma
+im3age
+ima5ry
+im2a2r
+iment2a5r
+i1men
+i3men1t
+imen1ta
+4imet
+im1i
+im5i1d4a
+im2id
+imi5le
+i5m2ini
+4imit
+im4ni
+i4m1n
+i3mo2n
+i1mo
+i2mu
+im3u1la
+2in.
+i4n3au
+i1na
+4inav
+incel4
+in3cer
+4ind
+in5dling
+2ine
+i3nee
+in4er4a2r
+in1er
+iner1a
+i5n2e2ss
+i1nes
+4in1ga
+4inge
+in5gen
+4ingi
+in5gling
+ingl2
+4in1go
+4in1gu
+2ini
+i5ni.
+i4n4i1a
+in3i4o
+in1is
+i5ni4te.
+in2it
+in2ite
+5i3n2i1t2io
+ini1ti
+in3i1ty
+4i4nk2
+4i4n1l
+2i4n1n2
+2i1no
+i4no4c
+ino4s
+i4not
+2i2n1s2
+in3se
+insu1r5a
+in1su
+insu2r
+2int.
+in1t
+2in4th
+in1u
+i5n2us
+4iny
+2io
+4io.
+io1ge4
+io2gr
+i1ol
+io4m
+ion3at
+io2n
+io1n1a
+ion4ery
+ion1er
+ion3i
+i2o5ph
+ior3i
+i4os
+i4o5th
+i5oti
+io4to
+i4our
+iou2
+2ip
+ipe4
+iphr2as4
+ip4hr4
+ip3i
+ip4ic
+ip4re4
+ipr2
+ip3ul
+i3qua
+iqu2
+iq5ue1f
+iq3u2id
+iq2ui2
+iq3ui3t
+4ir
+i1ra
+i2ra4b
+i4rac
+ird5e
+ire4de
+i2r2ed
+i4re1f
+i4rel4
+i4res
+ir5gi
+irg2
+ir1i
+iri5de
+ir2id
+ir4is
+iri3tu
+5i5r2iz
+ir4min
+ir1m
+iro4g
+5iron.
+iro2n
+ir5ul
+2is.
+is5ag
+isa2
+is3a2r
+isas5
+2is1c
+is3ch2
+4ise
+is3er
+3i4s3f
+is5ha2n
+is2h
+is3ho2n3
+isho4
+ish5op
+is3i1b
+is2i4d
+i5sis
+is5i1t2iv
+isi1ti
+4is4k2
+isla2n4
+is1l2
+4is4m1s2
+i2s1m
+i2so
+iso5mer
+i3som
+iso2me
+is1p
+is2pi
+is4py
+4i2s1s
+is4sal
+is1sa2
+issen4
+is4s1e4s
+is4ta.
+is1ta
+is1te
+is1t2i
+ist4ly
+is2tl
+4istral
+ist4r
+is1tra
+i2su
+is5us
+4i3ta.
+i1ta
+ita4bi
+i2tab
+i4tag
+4ita5m
+i3ta2n
+i3tat
+2ite
+it3er1a
+i5ter1i
+it4es
+2ith
+i1ti
+4i1t2i1a
+4i2tic
+it3i1ca
+5i5tick1
+i2t3ig
+it5il1l
+i2tim
+2i1t2io
+4itis
+i4ti2s4m
+i2t5o5m
+i1to
+4ito2n
+i4tram
+i1tra
+it5ry
+4i4t3t2
+it3u1at
+i1tu
+itu1a
+i5tud2
+it3ul
+4itz.
+i4tz
+i1u
+2iv
+iv3el1l
+iv3en.
+i4v3er.
+i4vers.
+ive4r1s2
+iv5il.
+i2vil
+iv5io
+iv1it
+i5vore
+iv3o3ro
+i4v3ot
+4i5w
+ix4o
+4iy
+4iz2a2r2
+iza1
+i2z1i4
+5izon1t
+i1zo
+izo2n
+5ja
+jac4q
+ja4p
+1je
+je4r5s2
+4jes4t2ie4
+jest2i
+4jes2ty
+jew3
+jo4p
+5judg
+3ka.
+k3ab
+k5ag
+kais4
+kai2
+kal4
+k1b
+k2ed
+1kee
+ke4g
+ke5l2i
+k3en4d
+k1er
+kes4
+k3e2st.
+ke4ty
+k3f
+kh4
+k1i
+5ki.
+5k2ic
+k4il1l
+kilo5
+k4im
+k4in.
+kin4de
+k4ind
+k5i5n2e2ss
+k2ine
+ki1nes
+kin4g
+k2i4p
+kis4
+k5is2h
+kk4
+k1l
+4k3ley
+4k1ly
+k1m
+k5nes
+1k2no
+ko5r
+kos2h4
+k3ou2
+kro5n
+4k1s2
+k4sc
+ks4l2
+k4s4y
+k5t
+k1w
+lab3ic
+l4abo
+l4a2ci4
+l4ade
+la2d
+la3d2y
+lag4n
+la2m3o
+3l4and
+la2n
+lan4dl
+lan5et
+lan4te
+lan1t
+lar4g2
+l2a2r
+lar3i
+las4e
+la5ta2n
+la2ta
+4latel2i4
+4la1t2iv
+4lav
+la4v4a
+2l1b
+lbin4
+4l1c2
+lce4
+l3ci
+2ld
+l2de
+ld4ere
+ld4er1i
+ldi4
+ld5is1
+l3dr
+l4dri
+le2a
+le4bi
+l2e1b
+le2ft5
+le1f
+5leg.
+5le4g1g2
+le4mat
+le1ma
+lem5at1ic
+4len.
+3lenc
+5le2ne.
+1len1t
+le3ph
+le4pr2
+le2ra5b
+ler1a
+ler4e
+3lerg2
+3l4er1i
+l4ero
+les2
+le5s1co
+les2c
+5lesq
+3l2e2ss
+5less.
+l3e1va
+lev4er.
+lev1er
+lev4er1a
+lev4e4r1s2
+3ley
+4leye
+2lf
+l5fr
+4l1g4
+l5ga
+lg2a2r3
+l4ges
+l1go3
+2l3h
+li4ag
+l2i1a
+li2am4
+liar5iz
+li2a2r
+liar1i
+li4as
+li4a1to
+li5bi
+5lic2io
+l2i1ci
+li4cor
+li1co
+4li4c3s2
+4lict.
+li2c1t
+l4icu
+l3i1cy
+l3i1d2a
+l2id
+lid5er
+3li2di
+lif3er1
+l4i4f1f
+li4f4l2
+5ligate
+l2ig
+li1ga
+3ligh
+li4gra
+li1gr
+3l4ik
+4l4i4l
+lim4b2l2
+li4m1b
+lim3i
+li4mo
+l4i4m4p
+l4i1na
+1l4ine
+lin3ea
+l2in3i
+link5er
+l4i4nk2
+li5og
+l2io
+4l4iq
+lis4p
+l1it
+l2it.
+5lit3i1ca
+li1ti
+l4i2tic
+l5i5ti4c3s2
+liv3er
+l2iv
+l1iz
+4lj
+lka3
+l3kal4
+lka4t
+l1l
+l4law
+l2le
+l5le2a
+l3lec
+l3leg
+l3lel
+l3le4n
+l3le4t
+ll2i
+l2lin4
+l5l4i1na
+ll4o
+lloq2ui5
+llo1q
+lloqu2
+l2l5out
+llou2
+l5low
+2lm
+l5met
+lm3ing
+l4mo2d1
+l1mo
+lmo2n4
+2l1n2
+3lo.
+lob5al
+lo4ci
+4lof
+3log1ic
+l5o1go
+3logu
+lom3er
+lo2me
+5long
+lo2n
+lon4i
+l3o3niz
+lood5
+loo2
+5lo4pe.
+lop3i
+l3o4p1m
+lo1ra4
+lo4ra1to
+lo5r2ie4
+lor5ou2
+5los.
+los5et
+5los5o3phiz
+lo2so
+los4op
+los2oph
+5los5o1phy
+los4t
+lo4ta
+loun5d
+lou2
+2lout
+4lov
+2lp
+lpa5b
+l1pa
+l3pha
+l5phi
+lp5ing
+lpi2n
+l3pit
+l4p2l2
+l5pr2
+4l1r
+2l1s2
+l4sc
+l2se
+l4s2ie4
+4lt
+lt5ag
+l1ta
+ltane5
+lta2n
+l1te
+lten4
+lter1a4
+lth3i
+l5ties.
+lt2ie4
+ltis4
+l1tr
+l1tu2
+ltu1r3a
+lu5a
+lu3br
+lu2ch4
+lu3ci
+lu3en
+luf4
+lu5id
+l2ui2
+lu4ma
+5lu1mi
+l5umn.
+lu4m1n
+5lum3n4i1a
+lu3o
+luo3r
+4lup
+lu2ss4
+l2us
+lus3te
+1lut
+l5ven
+l5vet4
+2l1w
+1ly
+4lya
+4ly1b
+ly5me4
+ly3no
+2lys4
+l5y3s2e
+1ma
+2mab
+ma2ca
+ma5ch2ine
+ma2ch
+ma4ch1in
+ma4c4l4
+mag5in
+mag1i
+5mag1n
+2mah
+ma2id5
+mai2
+4ma2ld
+ma3l2ig
+mal1i
+ma5lin
+mal4l2i
+mal1l
+mal4ty
+ma4lt
+5ma3n4i1a
+ma2n
+man5is
+man3iz
+4map
+ma5ri2ne.
+m2a2r
+mar1i
+mar2in4e
+ma5r2iz
+mar4ly
+mar1l
+mar3v
+ma5sce
+mas4e
+mas1t
+5mate
+m4ath3
+ma3tis
+4mati3za1
+ma1tiz
+4m1b
+m1ba4t5
+m5bil
+m4b3ing
+mb2i4v
+4m5c
+4me.
+2med
+4med.
+5me3d4i3a
+m4edi
+me3d2ie4
+m5e5d2y
+me2g
+mel5o2n
+me4l4t
+me2m
+me1m1o3
+1men
+me1n4a
+men5ac
+men4de
+4mene
+men4i
+me2n1s4
+men1su5
+3men1t
+men4te
+me5o2n
+m5er1sa2
+me4r1s2
+2mes
+3mest2i
+me4ta
+met3a2l
+me1te
+me5thi
+m4etr
+5met3ric
+me5tr2ie4
+me3try
+me4v
+4m1f
+2mh
+5mi.
+m2i3a
+mi1d4a
+m2id
+mid4g
+m2ig4
+3mil3i1a
+mil1i
+m5i5l2ie4
+m4il1l
+mi1n4a
+3m4ind
+m5i3nee
+m2ine
+m4ingl2
+min5gli
+m5ing1ly
+min4t
+m4in1u
+miot4
+m2io
+m2is
+mi4s4er.
+m4ise
+mis3er
+mis5l2
+mis4t2i
+m5i4stry
+mist4r
+4m2ith
+m2iz
+4mk
+4m1l
+m1m
+mma5ry
+m1ma
+mm2a2r
+4m1n
+m1n4a
+m4n1in
+mn4o
+1mo
+4mocr
+5moc5ra1tiz
+mo2d1
+mo4go
+mois2
+moi2
+mo4i5se
+4m2ok
+mo5lest
+moles2
+mo3me
+mon5et
+mo2n
+mon5ge
+mo3n4i3a
+mon4i2s1m
+mon1is
+mon4ist
+mo3niz
+monol4
+mo3ny.
+mo2r
+4mo5ra.
+mo1ra
+mos2
+mo5sey
+mo3sp
+m4oth3
+m5ouf
+mou2
+3mo2us
+mo2v
+4m1p
+mpara5
+m1pa
+mp2a2r
+mpa5rab
+mp4a4r5i
+m3pe2t
+mphas4
+m2pi
+mp2i4a
+mp5ies
+mp2ie4
+m4p1i2n
+m5p4ir
+mp5is
+mpo3ri
+m1p4or
+mpos5ite
+m1pos
+m4po2us
+mpou2
+mpov5
+mp4tr
+m2p1t
+m2py
+4m3r
+4m1s2
+m4s2h
+m5si
+4mt
+1mu
+mul2a5r4
+mu1la
+5mu4lt
+mul1ti3
+3mum
+mun2
+4mup
+mu4u
+4mw
+1na
+2n1a2b
+n4abu
+4nac.
+na4ca
+n5a2c1t
+nag5er.
+nak4
+na4l1i
+na5l2i1a
+4na4lt
+na5mit
+n2a2n
+nan1ci4
+nan4it
+na4nk4
+nar3c
+n2a2r
+4nare
+nar3i
+nar4l
+n5ar1m
+n4as
+nas4c
+nas5t2i
+n2at
+na3ta2l
+na2ta
+nat5o5m2iz
+na2tom
+na1to
+n2au
+nau3se
+na2us
+3naut
+nav4e
+4n1b4
+nc2a2r5
+n1ca
+n4ces.
+n3cha
+n2ch
+n5cheo
+nche2
+n5ch4il2
+n3chis
+n2c1in
+n1ci
+n2c4it
+ncou1r5a
+n1co
+ncou2
+n1cr
+n1cu
+n4dai2
+n1d2a
+n5da2n
+n1de
+nd5e2st.
+ndes2
+ndi4b
+n5d2if
+n1dit
+n3diz
+n5du2c
+n1du
+ndu4r
+nd2we
+nd1w
+2ne.
+n3e2a2r
+n2e2b
+neb3u
+ne2c
+5neck1
+2ned
+ne4gat
+ne1ga
+ne4g5a1t2iv
+5nege
+ne4la
+nel5iz
+nel2i
+ne5mi
+ne4mo
+1nen
+4nene
+3neo
+ne4po
+ne2q
+n1er
+ne2ra5b
+ner1a
+n4er3a2r
+n2ere
+n4er5i
+ner4r4
+1nes
+2nes.
+4ne1sp
+2nest
+4nes4w2
+3net1ic
+ne4v
+n5eve
+ne4w
+n3f
+n4gab
+n1ga
+n3gel
+nge4n4e
+n1gen
+n5gere
+n3ger1i
+ng5ha
+n3gib
+ng1in
+n5git
+n4gla4
+ngl2
+ngov4
+n1go
+ng5s2h
+ngs2
+n1gu
+n4gum
+n2gy
+4n1h4
+nha4
+nhab3
+nhe4
+3n4i1a
+ni3a2n
+ni4ap
+ni3ba
+ni4b2l2
+n2i4d
+ni5di
+ni4er
+n2ie4
+ni2fi
+ni5ficat
+nifi1ca
+n5i1gr
+n2ig
+n4ik4
+n1im
+ni3m2iz
+nim1i
+n1in
+5ni2ne.
+n2ine
+nin4g
+n2i4o
+5n2is.
+nis4ta
+n2it
+n4ith
+3n2i1t2io
+ni1ti
+n3itor
+ni1to
+ni3tr
+n1j
+4nk2
+n5k2ero
+nk1er
+n3ket
+nk3in
+nk1i
+n1k1l
+4n1l
+n5m
+nme4
+nmet4
+4n1n2
+nne4
+nni3al
+n3n4i1a
+nn2i4v
+nob4l2
+no3ble
+n5o1c4l4
+4n3o2d
+3noe
+4nog
+no1ge4
+nois5i
+noi2
+no5l4i
+5nol1o1gis
+3nomic
+n5o5m2iz
+no4mo
+no3my
+no4n
+non4ag
+no1n1a
+non5i
+n5oniz
+4nop
+5nop5o5l2i
+no2r5ab
+no1ra
+no4rary
+nor2a2r
+4nos2c
+nos4e
+nos5t
+no5ta
+1nou2
+3noun
+nov3el3
+nowl3
+n1p4
+npi4
+npre4c
+npr2
+n1q
+n1r
+nru4
+2n1s2
+n2s5ab
+nsa2
+nsati4
+ns4c
+n2se
+n4s3e4s
+ns2id1
+ns2ig4
+n2s1l2
+n2s3m
+n4soc
+n1so
+ns4pe
+n5spi
+nsta5b2l2
+ns1ta
+ns2tab
+n1t
+n2ta4b
+n1ta
+nte4r3s2
+nt2i
+n5ti2b
+nti4er
+nt2ie4
+nti2f2
+n3t2ine
+n2t1in
+n4t3ing
+nt2i4p
+ntrol5l2i
+ntrol1l
+n4t4s2
+ntu3me
+n1tu
+n3tum
+nu1a
+nu4d
+nu5en
+nuf4fe
+nu4f1f
+n3ui4n
+n2ui2
+3nu3it
+n4um
+nu1me
+n5u1mi
+3nu4n
+n3uo
+nu3tr
+n1v2
+n1w4
+nym4
+nyp4
+4nz
+n3za1
+4oa
+oa2d3
+o5a5les2
+o2ale
+oard3
+o2a2r
+oas4e
+oast5e
+oat5i
+ob3a3b
+o5b2a2r
+o1be4l
+o1bi
+o2bin
+ob5ing
+o3br
+ob3ul
+o1ce
+o2ch4
+o3che4t
+oche2
+ocif3
+o1ci
+o4cil
+o4clam
+o1c4l4
+o4cod
+o1co
+oc3rac
+oc5ra1tiz
+ocre3
+5ocrit
+ocri2
+octo2r5a
+o2c1t
+oc1to
+oc3u1la
+o5cure
+od5d1ed
+od1d4
+od3ic
+o1d2i3o
+o2do4
+od4or3
+o4d5uct.
+o1du
+odu2c
+odu2c1t
+o4d5uc4t1s2
+o4el
+o5eng
+o3er
+oe4ta
+o3ev
+o2fi
+of5ite
+of4i4t4t2
+o2g5a5r
+o1ga
+o4g5a1t2iv
+o4ga1to
+o1ge
+o5gene
+o1gen
+o5geo
+o4ger
+o3g2ie4
+1o1gis
+og3it
+o4gl2
+o5g2ly
+3ogniz
+og1ni
+o4g4ro
+o1gr
+og2u5i2
+1o1gy
+2o2g5y3n
+o1h2
+ohab5
+oi2
+oic3es
+oi3der
+o2id
+oi4f1f4
+o2ig4
+oi5let
+o3ing
+oint5er
+oin1t
+o5i2s1m
+oi5so2n
+oi2so
+oist5en
+ois1te
+oi3ter
+o2ite
+o5j
+2ok
+o3ken
+ok5ie4
+ok1i
+o1la
+o4la2n
+ola2ss4
+o2l2d
+ol2d1e
+ol3er
+o3les2c
+oles2
+o3let
+ol4fi
+o2lf
+ol2i
+o3l2i1a
+o3lice
+ol5id.
+ol2id
+o3li4f
+o5l4i4l
+ol3ing
+o5l2io
+o5l2is.
+ol3is2h
+o5l2ite
+ol1it
+o5l2i1t2io
+oli1ti
+o5l2iv
+oll2i4e4
+ol1l
+oll2i
+ol5o3giz
+olo4r
+ol5p2l2
+o2lp
+o4l2t
+ol3ub
+ol3ume
+ol3un
+o5l2us
+ol2v
+o2ly
+o2m5ah
+o1ma
+oma5l
+om5a1tiz
+om2be
+o4m1b
+om4b2l2
+o2me
+om3e1n4a
+o1men
+om5er2se
+ome4r1s2
+o4met
+om5e3try
+om4etr
+o3m2i3a
+om3ic.
+om3i1ca
+o5m2id
+om1in
+o5m2ini
+5ommend
+om1m
+om1men
+omo4ge
+o1mo
+o4mo2n
+om3pi
+o4m1p
+ompro5
+ompr2
+o2n
+o1n1a
+on4ac
+o3n2a2n
+on1c
+3oncil
+on1ci
+2ond
+on5do
+o3nen
+o2n5est
+o1nes
+on4gu
+on1ic
+o3n2i4o
+on1is
+o5ni1u
+on3key
+o4nk2
+on4odi
+o4n3o2d
+on3o3my
+o2n3s2
+on5spi4
+onspi1r5a
+onsp4ir
+on1su4
+onten4
+on1t
+on3t4i
+onti2f5
+on5um
+on1va5
+on1v2
+oo2
+ood5e
+ood5i
+o2o4k
+oop3i
+o3ord
+oost5
+o2pa
+o2p2e5d
+op1er
+3oper1a
+4op4erag
+2oph
+o5pha2n
+o5ph4er
+op3ing
+opi2n
+o3pit
+o5po2n
+o4posi
+o1pos
+o1pr2
+op1u
+opy5
+o1q
+o1ra
+o5ra.
+o4r3ag
+or5al1iz
+oral1i
+or5an4ge
+ora2n
+or2ang
+ore5a
+o5re1a4l
+or3ei2
+or4e5s2h
+or5e2st.
+ores2t
+orew4
+or4gu
+org2
+4o5r2i3a
+or3i1ca
+o5ril
+or1in
+o1r2i1o
+or3i1ty
+o3ri1u
+or2mi
+or1m
+orn2e
+o5rof
+or3oug
+orou2
+or5pe
+or1p
+3orrh4
+or1r4
+or4se
+o4rs2
+ors5en
+orst4
+or3thi
+or3thy
+or4ty
+o5rum
+o1ry
+os3al
+osa2
+os2c
+os4ce
+o3scop
+os1co
+4oscopi
+o5scr
+os4i4e4
+os5i1t2iv
+osi1ti
+os3i1to
+os3i1ty
+o5si4u
+os4l2
+o2so
+o2s4pa
+os4po
+os2ta
+o5stati
+os5til
+ost2i
+os5tit
+o4ta2n
+o1ta
+otele4g
+ot3er.
+ot5e4r1s2
+o4tes
+4oth
+oth5e1si
+oth2e
+oth1es
+oth3i4
+ot3ic.
+ot5i1ca
+o3tice
+o3tif2
+o3tis
+oto5s2
+o1to
+ou2
+ou3b2l2
+ouch5i
+ou2ch
+ou5et
+ou4l
+ounc5er
+oun2d
+ou5v2
+ov4en
+over4ne
+ove4r3s2
+ov4ert
+o3vis
+o4vi1ti4
+o5v4ol
+ow3der
+ow3el
+ow5est3
+ow1i2
+own5i
+o4wo2
+oy1a
+1pa
+pa4ca
+pa4ce
+pa2c4t
+p4a2d
+5paga4n
+pa1ga
+p3agat
+p4ai2
+pa4i4n4
+p4al
+pa1n4a
+pa2n
+pan3el
+pan4ty
+pan1t
+pa3ny
+pa1p
+pa4pu
+para5b2l2
+p2a2r
+pa2rab
+par5age
+par5d2i
+3pare
+par5el
+p4a4r1i
+par4is
+pa2te
+pa5ter
+5pathic
+p4ath
+pa5thy
+pa4tric
+pa1tr
+pav4
+3pay
+4p1b
+pd4
+4pe.
+3pe4a
+pear4l
+pe2a2r
+pe2c
+2p2ed
+3pede
+3p4edi
+pe3d4i3a4
+ped4ic
+p4ee
+pee4d
+pek4
+pe4la
+pel2i4e4
+pel2i
+pe4n2a2n
+pe1na
+p4enc
+pen4th
+pen1t
+pe5o2n
+p4era.
+per1a
+pera5b2l2
+pe2ra4b
+p4erag
+p4er1i
+peri5st
+per2is
+per4mal
+per3m4
+per1ma
+per2me5
+p4ern
+p2er3o
+per3ti
+p4e5ru
+per1v
+pe2t
+pe5ten
+pe5tiz
+4pf
+4pg
+4ph.
+phar5i
+ph2a2r
+ph4e3no
+phe2n
+ph4er
+ph4es.
+ph1es
+ph1ic
+5ph2ie4
+ph5ing
+5phis1t2i
+3phiz
+p4h2l4
+3phob
+3phone
+pho2n
+5phoni
+pho4r
+4p4h1s2
+ph3t
+5phu
+1phy
+p2i3a
+pi2a2n4
+pi4c2ie4
+p2i1ci
+pi4cy
+p4id
+p5i1d2a
+pi3de
+5pi2di
+3piec
+p2ie4
+pi3en
+pi4grap
+p2ig
+pi1gr
+pi3lo
+pi2n
+p4in.
+p4ind4
+p4i1no
+3p2i1o
+pio2n4
+p3ith
+pi5tha
+pi2tu
+2p3k2
+1p2l2
+3pla2n
+plas5t
+pl2i3a
+pli5er
+pl2ie4
+4pl2ig
+pli4n
+ploi4
+plu4m
+plu4m4b
+4p1m
+2p3n
+po4c
+5pod.
+po5em
+po3et5
+5po4g
+poin2
+poi2
+5poin1t
+poly5t
+po2ly
+po4ni
+po2n
+po4p
+1p4or
+po4ry
+1pos
+po2s1s
+p4ot
+po4ta
+5poun
+pou2
+4p1p
+ppa5ra
+p1pa
+pp2a2r
+p2pe
+p4p2ed
+p5pel
+p3pen
+p3per
+p3pe2t
+ppo5s2ite
+p1pos
+pr2
+pray4e4
+5pre1c2i
+pre5co
+pre3e2m
+pre4f5ac
+pre1f
+pre1fa
+pre4la
+pr1e3r4
+p3re1s2e
+3pr2e2ss
+pre5ten
+pre3v2
+5pr2i4e4
+prin4t3
+pr2i4s
+pri2s3o
+p3ro1ca
+pr2oc
+prof5it
+pro2fi
+pro3l
+pros3e
+pro1t
+2p1s2
+p2se
+ps4h
+p4si1b
+2p1t
+p2t5a4b
+p1ta
+p2te
+p2th
+p1ti3m
+ptu4r
+p1tu
+p4tw4
+pub3
+pue4
+puf4
+pu4l3c2
+pu4m
+pu2n
+pur4r4
+5p2us
+pu2t
+5pute
+put3er
+pu3tr
+put4t1ed
+pu4t3t2
+put4t1in
+p3w
+qu2
+qua5v4
+2que.
+3quer
+3quet
+2rab
+ra3bi
+rach4e2
+ra2ch
+r5a1c4l4
+raf5fi
+ra2f
+ra4f1f4
+ra2f4t
+r2ai2
+ra4lo
+ram3et
+r2ami
+ra3ne5o
+ra2n
+ran4ge
+r2ang
+r4ani
+ra5no4
+rap3er
+3ra1phy
+rar5c
+r2a2r
+rare4
+rar5e1f
+4raril
+rar1i
+r2as
+ratio2n4
+ra1t2io
+rau4t
+ra5vai2
+ra2va
+rav3el
+ra5z2ie4
+ra2z1i
+r1b
+r4bab
+r4bag
+rbi2
+r2b3i4f
+r2bin
+r5b2ine
+rb5ing.
+rb4o
+r1c
+r2ce
+r1cen4
+r3cha
+r2ch
+rch4er
+rche2
+r4ci4b
+r1ci
+r2c4it
+rcum3
+r4dal
+r1d2a
+rd2i
+r1d4i4a
+rdi4er
+rd2ie4
+rd1in4
+rd3ing
+2re.
+re1a4l
+re3a2n
+re5ar1r4
+re2a2r
+5rea2v
+re4aw
+r5ebrat
+r2e1b
+re3br
+rec5ol1l
+re2col
+re1co
+re4c5ompe
+reco4m1p
+re4cre
+re1cr
+2r2ed
+re1de
+re3dis1
+r4edi
+red5it
+re4fac
+re1f
+re1fa
+re2fe
+re5fer.
+refer1
+re3fi
+re4fy
+reg3is
+re5it
+rei2
+re1l2i
+re5lu
+r4en4ta
+ren1t
+ren4te
+re1o
+re5pi2n
+re4posi
+re1po
+re1pos
+re1pu
+r1er4
+r4er1i
+r2ero4
+r4e5ru
+r4es.
+re4spi
+re1sp
+res4s5i4b
+r2e2ss
+res1si
+res2t
+re5s2ta2l
+res1ta
+r2e3st4r
+re4ter
+re4ti4z
+re3tri
+r4eu2
+re5u1t2i
+rev2
+re4val
+re1va
+rev3el
+r5ev5er.
+rev1er
+re5ve4r1s2
+re5vert
+re5vi4l
+re1vi
+rev5olu
+re4wh
+r1f
+r3fu4
+r4fy
+rg2
+rg3er
+r3get
+r3g1ic
+rgi4n
+rg3ing
+r5gis
+r5git
+r1gl2
+rgo4n2
+r1go
+r3gu
+rh4
+4rh.
+4rhal
+r2i3a
+ria4b
+ri4ag
+r4ib
+rib3a
+ric5as5
+ri1ca
+r4ice
+4r2i1ci
+5ri5c2id
+ri4c2ie4
+r4i1co
+rid5er
+r2id
+ri3enc
+r2ie4
+ri3en1t
+ri1er
+ri5et
+rig5a2n
+r2ig
+ri1ga
+5r4igi
+ril3iz
+ril1i
+5rima2n
+ri1ma
+rim5i
+3ri1mo
+rim4pe
+ri4m1p
+r2i1na
+5rina.
+r4in4d
+r2in4e
+rin4g
+r2i1o
+5riph
+r2ip
+riph5e
+ri2p2l2
+rip5lic
+r4iq
+r2is
+r4is.
+r2is4c
+r3is2h
+ris4p
+ri3ta3b
+ri1ta
+r5ited.
+r2ite
+ri2t1ed
+rit5er.
+rit5e4r1s2
+r4i2t3ic
+ri1ti
+ri2tu
+rit5ur
+riv5el
+r2iv
+riv3et
+riv3i
+r3j
+r3ket
+rk4le
+rk1l
+rk4lin
+r1l
+rle4
+r2led
+r4l2ig
+r4lis
+rl5is2h
+r3lo4
+r1m
+rma5c
+r1ma
+r2me
+r3men
+rm5e4r1s2
+rm3ing
+r4ming.
+r4m2io
+r3mit
+r4my
+r4n2a2r
+r1na
+r3nel
+r4n1er
+r5net
+r3ney
+r5nic
+r1nis4
+r3n2it
+r3n2iv
+rno4
+r4nou2
+r3nu
+rob3l2
+r2oc
+ro3cr
+ro4e
+ro1fe
+ro5fil
+ro2fi
+r2ok2
+ro5k1er
+5role.
+rom5e1te
+ro2me
+ro4met
+rom4i
+ro4m4p
+ron4al
+ro2n
+ro1n1a
+ron4e
+ro5n4is
+ron4ta
+ron1t
+1room
+roo2
+5root
+ro3pel
+rop3ic
+ror3i
+ro5ro
+ro2s5per
+ro2s4s
+ro4th2e
+r4oth
+ro4ty
+ro4va
+rov5el
+rox5
+r1p
+r4pe4a
+r5pen1t
+rp5er.
+r3pe2t
+rp4h4
+rp3ing
+rpi2n
+r3po
+r1r4
+rre4c
+rre4f
+r4re1o
+rre4s2t
+rr2i4o
+rr2i4v
+rro2n4
+rros4
+rrys4
+4rs2
+r1sa2
+rsa5ti
+rs4c
+r2se
+r3sec
+rse4cr
+r4s5er.
+rs3e4s
+r5se5v2
+r1s2h
+r5sha
+r1si
+r4si4b
+rso2n3
+r1so
+r1sp
+r5sw2
+rta2ch4
+r1ta
+r4tag
+r3t2e1b
+r3ten4d
+r1te5o
+r1ti
+r2t5i2b
+rt2i4d
+r4tier
+rt2ie4
+r3t2ig
+rtil3i
+rtil4l
+r4ti1ly
+r4tist
+r4t2iv
+r3tri
+rtr2oph4
+rt4s2h4
+r4t1s2
+ru3a
+ru3e4l
+ru3en
+ru4gl2
+ru3i4n
+r2ui2
+rum3p2l2
+ru4m2p
+ru2n
+ru4nk5
+run4ty
+run1t
+r5usc2
+r2us
+ru2t1i5n
+r4u1t2i
+rv4e
+rvel4i
+r3ven
+rv5er.
+r5vest
+rv4e2s
+r3vey
+r3vic
+r3v2i4v
+r3vo
+r1w
+ry4c
+5rynge
+ryn5g
+ry3t
+sa2
+2s1ab
+5sack1
+sac3ri2
+s3a2c1t
+5sai2
+sa4l2a2r4
+s4a2l4m
+sa5lo
+sa4l4t
+3sanc
+sa2n
+san4de
+s4and
+s1ap
+sa5ta
+5sa3t2io
+sa2t3u
+sau4
+sa5vor
+5saw
+4s5b
+scan4t5
+s1ca
+sca2n
+sca4p
+scav5
+s4ced
+4s3cei2
+s4ces
+s2ch2
+s4cho2
+3s4c2ie4
+s1ci
+5sc4in4d
+s2cin
+scle5
+s1c4l4
+s4cli
+scof4
+s1co
+4scopy5
+scou1r5a
+scou2
+s1cu
+4s5d
+4se.
+se4a
+seas4
+sea5w
+se2c3o
+3se2c1t
+4s4ed
+se4d4e
+s5edl
+se2g
+se1g3r
+5sei2
+se1le
+5se2l2f
+5selv
+4se1me
+se4mol
+se1mo
+sen5at
+se1na
+4senc
+sen4d
+s5e2ned
+sen5g
+s5en1in
+4sen4t1d
+sen1t
+4sen2tl
+se2p3a3
+4s1er.
+s4er1l
+s2er4o
+4ser3vo
+s1e4s
+s4e5s2h
+ses5t
+5se5um
+s4eu
+5sev
+sev3en
+sew4i2
+5sex
+4s3f
+2s3g
+s2h
+2sh.
+sh1er
+5shev
+sh1in
+sh3io
+3sh2i4p
+sh2i2v5
+sho4
+sh5o2l2d
+sho2n3
+shor4
+short5
+4sh1w
+si1b
+s5ic3c
+3si2de.
+s2id
+5side4s2
+5si2di
+si5diz
+4sig1n4a
+s2ig
+sil4e
+4si1ly
+2s1in
+s2i1na
+5si2ne.
+s2ine
+s3ing
+1s2io
+5sio2n
+sio1n5a
+s4i2r
+si1r5a
+1sis
+3s2i1t2io
+si1ti
+5si1u
+1s2iv
+5siz
+sk2
+4ske
+s3ket
+sk5ine
+sk1i
+sk5in4g
+s1l2
+s3lat
+s2le
+sl2ith5
+sl1it
+2s1m
+s3ma
+smal1l3
+sma2n3
+smel4
+s5men
+5s4m2ith
+smo2l5d4
+s1mo
+s1n4
+1so
+so4ce
+so2ft3
+so4lab
+so1la
+so2l3d2
+so3lic
+sol2i
+5sol2v
+3som
+3s4on.
+so2n
+so1n1a4
+son4g
+s4op
+5soph1ic
+s2oph
+s5o3phiz
+s5o1phy
+sor5c
+sor5d
+4sov
+so5vi
+2s1pa
+5sp4ai2
+spa4n
+spen4d
+2s5peo
+2sper
+s2phe
+3sph4er
+spho5
+spil4
+sp5ing
+spi2n
+4s3p2i1o
+s4p1ly
+s1p2l2
+s4po2n
+s1p4or4
+4sp4ot
+squal4l
+squ2
+s1r
+2ss
+s1sa2
+ssas3
+s2s5c
+s3sel
+s5sen5g
+s4ses.
+ss1e4s
+s5set
+s1si
+s4s2ie4
+ssi4er
+s4s5i1ly
+s4s1l2
+ss4li
+s4s1n4
+sspen4d4
+ss2t
+ssu1r5a
+s1su
+ssu2r
+ss5w2
+2st.
+s2tag
+s1ta
+s2ta2l
+stam4i
+5st4and
+sta2n
+s4ta4p
+5stat.
+s4t1ed
+stern5i
+s5t2ero
+ste2w
+ste1w5a
+s3th2e
+st2i
+s4ti.
+s5t2i1a
+s1tic
+5s4tick1
+s4t2ie4
+s3tif2
+st3ing
+s2t1in
+5st4ir
+s1tle
+s2tl
+5stock1
+s1to
+sto2m3a
+5stone
+sto2n
+s4top
+3store
+st4r
+s4tra2d
+s1tra
+5stra2tu
+s4tray
+s4tr2id
+4stry
+4st3w4
+s2ty
+1su
+su1al
+su4b3
+su2g3
+su5is
+s2ui2
+suit3
+s4ul
+su2m
+su1m3i
+su2n
+su2r
+4sv
+sw2
+4s1wo2
+s4y
+4sy1c
+3syl
+syn5o
+sy5rin
+1ta
+3ta.
+2tab
+ta5bles2
+tab2l2
+5tab5o5l1iz
+tabol2i
+4t4a2ci
+ta5do
+ta2d
+4ta2f4
+tai5lo
+tai2
+ta2l
+ta5la
+tal5en
+t2ale
+tal3i
+4talk
+tal4lis
+tal1l
+tall2i
+ta5log
+ta5mo
+tan4de
+ta2n
+t4and
+1tan1ta3
+tan1t
+ta5per
+ta5p2l2
+tar4a
+t2a2r
+4tar1c
+4tare
+ta3r2iz
+tar1i
+tas4e
+ta5s4y
+4tat1ic
+ta4tur
+ta2tu
+taun4
+tav4
+2taw
+tax4is
+tax3i
+2t1b
+4tc
+t4ch
+tch5e4t
+tche2
+4t1d
+4te.
+te2ad4i
+tea2d1
+4tea2t
+te1ce4
+5te2c1t
+2t1ed
+t4e5di
+1tee
+teg4
+te5ger4
+te5gi
+3tel.
+tel2i4
+5te2l1s2
+te2ma2
+tem3at
+3ten2a2n
+te1na
+3tenc
+3tend
+4te1nes
+1ten1t
+ten4tag
+ten1ta
+1teo
+te4p
+te5pe
+ter3c
+5ter3d
+1ter1i
+ter5ies
+ter2ie4
+ter3is
+teri5za1
+5t4er3n2it
+ter5v
+4tes.
+4t2e2ss
+t3ess.
+teth5e
+3t4eu
+3tex
+4tey
+2t1f
+4t1g
+2th.
+tha2n4
+th2e
+4thea
+th3eas
+the5a2t
+the3is
+thei2
+3the4t
+th5ic.
+th5i1ca
+4th4il2
+5th4i4nk2
+4t4h1l4
+th5ode
+5thod3ic
+4thoo2
+thor5it
+tho5riz
+2t4h1s2
+1t2i1a
+ti4ab
+ti4a1to
+2ti2b
+4tick1
+t4i1co
+t4ic1u
+5ti2di
+t2id
+3tien
+t2ie4
+tif2
+ti5fy
+2t2ig
+5tigu
+til2l5in4
+til1l
+till2i
+1tim
+4ti4m1p
+tim5ul
+ti2mu
+2t1in
+t2i1na
+3ti2ne.
+t2ine
+3t2ini
+1t2io
+ti5oc
+tion5ee
+tio2n
+5tiq
+ti3sa2
+3t4ise
+ti2s4m
+ti5so
+tis4p
+5tisti1ca
+tis1t2i
+tis1tic
+ti3tl
+ti4u
+1t2iv
+ti1v4a
+1tiz
+ti3za1
+ti3ze4n
+ti2ze
+2tl
+t5la
+tla2n4
+3tle.
+3tled
+3tles.
+tles2
+t5let.
+t5lo
+4t1m
+tme4
+2t1n2
+1to
+to3b
+to5crat
+4to2do4
+2tof
+to2gr
+to5ic
+toi2
+to2ma
+to4m4b
+to3my
+ton4a4l1i
+to2n
+to1n1a
+to3n2at
+4tono
+4tony
+to2ra
+to3r2ie4
+tor5iz
+tos2
+5tour
+tou2
+4tout
+to3w2a2r
+4t1p
+1tra
+t2ra3b
+tra5ch
+tr4a2ci4
+tra2c4it
+trac4te
+tra2c1t
+tr2as4
+tra5ven
+trav5e2s5
+tre5f
+tre4m
+trem5i
+5tr2i3a
+tri5ces
+tr4ice
+5tri3c2i1a
+t4r2i1ci
+4tri4c3s2
+2trim
+tr2i4v
+tro5m4i
+tron5i
+tro2n
+4trony
+tro5phe
+tr2oph
+tro3sp
+tro3v
+tr2u5i2
+tr2us4
+4t1s2
+t4sc
+ts2h4
+t4sw2
+4t3t2
+t4tes
+t5to
+t1tu4
+1tu
+tu1a
+tu3a2r
+tu4b4i
+tud2
+4tue
+4tuf4
+5t2u3i2
+3tum
+tu4nis
+tu1ni
+2t3up.
+3ture
+5turi
+tur3is
+tur5o
+tu5ry
+3t2us
+4tv
+tw4
+4t1wa
+twis4
+twi2
+4t1wo2
+1ty
+4tya
+2tyl
+type3
+ty5ph
+4tz
+t2z4e
+4uab
+uac4
+ua5na
+ua2n
+uan4i
+uar5an1t
+u2a2r
+uara2n
+uar2d
+uar3i
+uar3t
+u1at
+uav4
+ub4e
+u4bel
+u3ber
+u4b2ero
+u1b4i
+u4b5ing
+u3b4le.
+ub2l2
+u3ca
+uci4b
+u1ci
+u2c4it
+ucle3
+u1c4l4
+u3cr
+u3cu
+u4cy
+ud5d4
+ud3er
+ud5est
+udes2
+ude1v4
+u1dic
+ud3ied
+ud2ie4
+ud3ies
+ud5is1
+u5dit
+u4do2n
+u1do
+ud4si
+u2d1s2
+u4du
+u4ene
+ue2n1s4
+uen4te
+uen1t
+uer4il
+uer1i
+3u1fa
+u3f4l2
+ugh3e2n
+ug5in
+2ui2
+uil5iz
+uil1i
+ui4n
+u1ing
+uir4m
+u4ir
+ui1ta4
+u2iv3
+ui4v4er.
+u5j
+4uk
+u1la
+ula5b
+u5lati
+ul2ch4
+u4l1c2
+5ulche2
+ul3der
+u2ld
+ul2de
+ul4e
+u1len
+ul4gi
+u4l1g4
+ul2i
+u5l2i1a
+ul3ing
+ul5is2h
+ul4l2a2r
+ul1l
+ul4li4b
+ull2i
+ul4lis
+4u2l3m
+u1l4o
+4u2l1s2
+uls5e4s
+ul2se
+ul1ti
+u4lt
+ul1tra3
+ul1tr
+4ul1tu2
+u3lu
+ul5ul
+ul5v
+u2m5ab
+u1ma
+um4bi
+u4m1b
+um4b1ly
+umb2l2
+u1mi
+u4m3ing
+umor5o
+u1mo
+umo2r
+u4m2p
+un2at4
+u1na
+u2ne
+un4er
+u1ni
+un4im
+u2n1in
+un5is2h
+un2i3v
+u2n3s4
+un4sw2
+un2t3a4b
+un1t
+un1ta
+un4ter.
+un4tes
+unu4
+un5y
+u4n5z
+u4o4rs2
+u5os
+u1ou2
+u1pe
+upe4r5s2
+u5p2i3a
+up3ing
+upi2n
+u3p2l2
+u4p3p
+upport5
+up1p4or
+up2t5i2b
+u2p1t
+up1tu4
+u1ra
+4ura.
+u4rag
+u4r2as
+ur4be
+ur1b
+ur1c4
+ur1d
+ure5a2t
+ur4fer1
+ur1f
+ur4fr
+u3rif
+uri4fic
+uri1fi
+ur1in
+u3r2i1o
+u1rit
+ur3iz
+ur2l
+url5ing.
+ur4no4
+uros4
+ur4pe
+ur1p
+ur4pi
+urs5er
+u4rs2
+ur2se
+ur5tes
+ur3th2e
+ur1ti4
+ur4t2ie4
+u3ru
+2us
+u5sa2d
+usa2
+u5sa2n
+us4ap
+usc2
+us3ci
+use5a
+u5s2i1a
+u3sic
+us4lin
+us1l2
+us1p
+us5s1l2
+u2ss
+us5tere
+us1t4r
+u2su
+usu2r4
+u2ta4b
+u1ta
+u3tat
+4u4te.
+4utel
+4uten
+uten4i
+4u1t2i
+uti5l2iz
+util1i
+u3t2ine
+u2t1in
+ut3ing
+utio1n5a
+u1t2io
+utio2n
+u4tis
+5u5tiz
+u4t1l
+u2t5of
+u1to
+uto5g
+uto5mat1ic
+uto2ma
+u5to2n
+u4tou2
+u4t1s4
+u3u
+uu4m
+u1v2
+ux1u3
+u2z4e
+1va
+5va.
+2v1a4b
+vac5il
+v4a2ci
+vac3u
+vag4
+va4ge
+va5l2i4e4
+val1i
+val5o
+val1u
+va5mo
+va5niz
+va2n
+va5pi
+var5ied
+v2a2r
+var1i
+var2ie4
+3vat
+4ve.
+4ved
+veg3
+v3el.
+vel3l2i
+vel1l
+ve4lo
+v4e1ly
+ven3om
+v4eno
+v5enue
+v4erd
+5v2e2re.
+v4erel
+v3eren
+ver5enc
+v4eres
+ver3ie4
+ver1i
+vermi4n
+ver3m4
+3ver2se
+ve4r1s2
+ver3th
+v4e2s
+4ves.
+ves4te
+ve4te
+vet3er
+ve4ty
+vi5al1i
+v2i1a
+vi2al
+5vi2a2n
+5vi2de.
+v2id
+5vi2d1ed
+4v3i1den
+5vide4s2
+5vi2di
+v3if
+vi5gn
+v2ig
+v4ik4
+2vil
+5v2il1it
+vil1i
+v3i3l2iz
+v1in
+4vi4na
+v2inc
+v4in5d
+4ving
+vi1o3l
+v2io
+v3io4r
+vi1ou2
+v2i4p
+vi5ro
+v4ir
+vis3it
+vi3so
+vi3su
+4vi1ti
+vit3r
+4vi1ty
+3v2iv
+5vo.
+voi4
+3v2ok
+vo4la
+v5ole
+5vo4l2t
+3vol2v
+vom5i
+vo2r5ab
+vo1ra
+vori4
+vo4ry
+vo4ta
+4vo1tee
+4vv4
+v4y
+w5ab2l2
+2wac
+wa5ger
+wa2g5o
+wait5
+wai2
+w5al.
+wam4
+war4t
+w2a2r
+was4t
+wa1te
+wa5ver
+w1b
+wea5r2ie4
+we2a2r
+wear1i
+we4ath3
+wea2t
+we4d4n4
+weet3
+wee5v
+wel4l
+w1er
+west3
+w3ev
+whi4
+wi2
+wil2
+wil2l5in4
+wil1l
+will2i
+win4de
+w4ind
+win4g
+w4ir4
+3w4ise
+w2ith3
+wiz5
+w4k
+wl4es2
+wl3in
+w4no
+1wo2
+wom1
+wo5v4en
+w5p
+wra4
+wri4
+wri1ta4
+w3s2h
+ws4l2
+ws4pe
+w5s4t
+4wt
+wy4
+x1a
+xac5e
+x4a2go
+xam3
+x4ap
+xas5
+x3c2
+x1e
+xe4cu1to
+xe1cu
+xe3c4ut
+x2ed
+xer4i
+x2e5ro
+x1h
+xhi2
+xh4il5
+xhu4
+x3i
+x2i5a
+xi5c
+xi5di
+x2id
+x4ime
+xi5m2iz
+xim1i
+x3o
+x4ob
+x3p
+xp4an4d
+x1pa
+xpa2n
+xpec1to5
+xpe2c
+xpe2c1t
+x2p2e3d
+x1t2
+x3ti
+x1u
+xu3a
+xx4
+y5ac
+3y2a2r4
+y5at
+y1b
+y1c
+y2ce
+yc5er
+y3ch
+ych4e2
+ycom4
+y1co
+ycot4
+y1d
+y5ee
+y1er
+y4er1f
+yes4
+ye4t
+y5gi
+4y3h
+y1i
+y3la
+ylla5b2l2
+yl1l
+y3lo
+y5lu
+ymbol5
+y4m1b
+yme4
+ym1pa3
+y4m1p
+yn3c4hr4
+yn2ch
+yn5d
+yn5g
+yn5ic
+5ynx
+y1o4
+yo5d
+y4o5g
+yom4
+yo5net
+yo2n
+y4o2n3s2
+y4os
+y4p2ed
+yper5
+yp3i
+y3po
+y4po4c
+yp2ta
+y2p1t
+y5pu
+yra5m
+yr5i3a
+y3ro
+yr4r4
+ys4c
+y3s2e
+ys3i1ca
+y1s3io
+3y1sis
+y4so
+y2ss4
+ys1t
+ys3ta
+ysu2r4
+y1su
+y3thin
+yt3ic
+y1w
+za1
+z5a2b
+z2a2r2
+4zb
+2ze
+ze4n
+ze4p
+z1er
+z2e3ro
+zet4
+2z1i
+z4il
+z4is
+5zl
+4zm
+1zo
+zo4m
+zo5ol
+zoo2
+zte4
+4z1z2
+z4zy
+.as9s8o9c8i8a8te.
+.as1so
+.asso1ci
+.asso3c2i1a
+.as9s8o9c8i8a8t8es.
+.de8c9l8i9n8a9t8i8on.
+.de1c4l4
+.decl4i1na
+.declin2at
+.declina1t2io
+.declinatio2n
+.ob8l8i8g9a9t8o8ry.
+.ob2l2
+.obl2ig
+.obli1ga
+.obliga1to
+.obligato1ry
+.ph8i8l9a8n9t8h8r8o8p8ic.
+.ph4il2
+.phi1la
+.phila2n
+.philan1t
+.philant4hr4
+.philanthrop3ic
+.pr8e8s8e8nt.
+.p3re1s2e
+.presen1t
+.pr8e8s8e8n8ts.
+.presen4t4s2
+.pr8o8j8e8ct.
+.pro5j
+.pro1je
+.proje2c1t
+.pr8o8j8e8c8ts.
+.projec4t1s2
+.re8c9i9p8r8o8c9i9t8y.
+.re1c2i
+.rec2ip
+.recipr2
+.recipr2oc
+.re1cipro1ci
+.recipro2c1it
+.reciproci1ty
+.re9c8o8g9n8i9z8a8n8ce.
+.re1co
+.re2cog
+.rec3ogniz
+.recog1ni
+.recogniza1
+.recogniza2n
+.re8f9o8r9m8a9t8i8on.
+.re1f
+.re1fo
+.refo2r
+.refor1m
+.refor1ma
+.reforma1t2io
+.reformatio2n
+.re8t9r8i9b8u9t8i8on.
+.re3tri
+.retr4ib
+.retri3bu1t2io
+.retrib4u1t2i
+.retributio2n
+.ta9b8le.
+.2tab
+.tab2l2
+.ac8a8d9e9m8y.
+.a1ca
+.aca2d
+.acad4em
+.acade3my
+.ac8a8d9e9m8i8e8s.
+.academ2i4e4
+.ac9c8u9s8a9t8i8v8e.
+.ac3c
+.ac1c2us
+.accusa2
+.accusa1t2iv
+.ac8r8o9n8y8m.
+.acro2n
+.acronym4
+.ac8r8y8l9a8m8i8d8e.
+.acry3la
+.acrylam2id
+.ac8r8y8l9a8m8i8d8e8s.
+.acrylamide4s2
+.ac8r8y8l9a8l8d8e9h8y8d8e.
+.acryla2ld
+.acrylal2de
+.acrylalde1h4
+.acrylaldehy1d
+.ad8d9a9b8l8e.
+.ad1d2a
+.ad2d3a4b
+.addab2l2
+.ad8d9i9b8l8e.
+.addi1b2l2
+.ad8r8e8n9a9l8i8n8e.
+.a1dr
+.adre4
+.a5dren
+.adre1na
+.adrena4l1i
+.adrena1l4ine
+.ae8r8o9s8p8a8c8e.
+.ae4r
+.a2ero
+.aero2s4pa
+.aerospa4ce
+.af9t8e8r9t8h8o8u8g8h8t.
+.afterthou2
+.af9t8e8r9t8h8o8u8g8h8t8s.
+.afterthough4t1s2
+.ag8r8o8n9o9m8i8s8t.
+.a1gr
+.ag4ro
+.agro2n
+.agronom2is
+.ag8r8o8n9o9m8i8s8t8s.
+.agronomis4t1s2
+.al9g8e9b8r8a9i9c8a8l9l8y.
+.a4l1g4
+.alg2e1b
+.alge3br
+.algebr2ai2
+.algebrai1ca
+.algebraical1l
+.algebraical1ly
+.am9p8h8e8t9a9m8i8n8e.
+.a4m1p
+.amphe4t
+.amphe1ta
+.amphetam1in
+.amphetam2ine
+.am9p8h8e8t9a9m8i8n8e8s.
+.amphetami1nes
+.an9a9l8y8s8e.
+.3ana1ly
+.a1na
+.an4a2lys4
+.anal5y3s2e
+.an9a9l8y8s8e8d.
+.analy4s4ed
+.an8a8l8y9s8e8s.
+.analys1e4s
+.an9i8s8o9t8r8o8p9i8c.
+.ani2so
+.anisotrop3ic
+.an9i8s8o9t8r8o8p9i9c8a8l9l8y.
+.anisotropi1ca
+.anisotropical1l
+.anisotropical1ly
+.an9i8s8o8t9r8o9p8i8s8m.
+.anisotropi2s1m
+.an9i8s8o8t9r8o8p8y.
+.anisotropy5
+.an8o8m9a8l8y.
+.ano4
+.anoma5l
+.ano1ma
+.anoma1ly
+.an8o8m9a8l8i8e8s.
+.anomal1i
+.anomal2i4e4
+.an8t8i9d8e8r8i8v9a9t8i8v8e.
+.ant2id
+.antider1i
+.antider2i4v
+.antide4ri1va
+.antideri3vat
+.antider2iva1t2iv
+.an8t8i9d8e8r8i8v9a9t8i8v8e8s.
+.antiderivativ4e2s
+.an8t8i9h8o8l8o9m8o8r9p8h8i8c.
+.anti3h
+.antiholo1mo
+.antiholomo2r
+.antiholomor1p
+.antiholomorp4h4
+.antiholomorph1ic
+.an9t8i8n9o9m8y.
+.an2t1in
+.ant2i1no
+.antino3my
+.an9t8i8n9o9m8i8e8s.
+.antinom2ie4
+.an9t8i9n8u9c8l8e8a8r.
+.antin1u
+.antinucle3
+.antinu1c4l4
+.antinucle2a
+.antinucle2a2r
+.an9t8i9n8u9c8l8e9o8n.
+.antinucleo2n
+.an9t8i9r8e8v9o9l8u9t8i8o8n9a8r8y.
+.ant4ir
+.antirev2
+.antirev5olu
+.antirevo1lut
+.antirevol4u1t2i
+.antirevolutio1n5a
+.antirevolu1t2io
+.antirevolutio2n
+.antirevolution2a2r
+.ap8o8t8h9e9o9s8e8s.
+.ap4ot
+.ap4oth
+.apoth2e
+.apotheos4
+.apotheos1e4s
+.ap8o8t8h9e9o9s8i8s.
+.apotheo1sis
+.ap9p8e8n9d8i8x.
+.a4p1p
+.ap2pe
+.ap3pen
+.ar9c8h8i9m8e9d8e8a8n.
+.ar1c
+.ar2ch
+.archi2med
+.archimedea2n
+.ar9c8h8i9p8e8l9a8g8o.
+.arch2i4p
+.archipe4
+.archipe4la
+.archipela2go
+.ar9c8h8i9p8e8l9a9g8o8s.
+.ar9c8h8i8v8e.
+.arch2i2v
+.ar9c8h8i8v8e8s.
+.archiv4e2s
+.ar9c8h8i8v9i8n8g.
+.archiv1in
+.archi4ving
+.ar9c8h8i8v9i8s8t.
+.ar9c8h8i8v9i8s8t8s.
+.archivis4t1s2
+.ar9c8h8e9t8y8p9a8l.
+.arche2
+.arche4t
+.arche1ty
+.archety1pa
+.archetyp4al
+.ar9c8h8e9t8y8p9i9c8a8l.
+.archetyp3i
+.archetypi1ca
+.ar8c9t8a8n9g8e8n8t.
+.ar2c1t
+.arct5ang
+.arc1ta
+.arcta2n
+.arctan1gen
+.arctangen1t
+.ar8c9t8a8n9g8e8n8t8s.
+.arctangen4t4s2
+.as9s8i8g8n9a9b8l8e.
+.as1si
+.as4sig1n4a
+.ass2ig
+.assig2n1a2b
+.assignab2l2
+.as9s8i8g8n9o8r.
+.assig1no
+.as9s8i8g8n9o8r8s.
+.assigno4rs2
+.as9s8i8s8t9a8n8t9s8h8i8p.
+.as1sis
+.assis1ta
+.assista2n
+.assistan1t
+.assistan4t4s2
+.assistants2h4
+.assistant3sh2i4p
+.as9s8i8s8t9a8n8t9s8h8i8p8s.
+.assistantshi2p1s2
+.as8y8m8p9t8o9m8a8t8i8c.
+.as4y
+.asy4m1p
+.asym2p1t
+.asymp1to
+.asympto2ma
+.asymptomat1ic
+.as9y8m8p9t8o8t9i8c.
+.as8y8n9c8h8r8o9n8o8u8s.
+.asyn3c4hr4
+.asyn2ch
+.asynchro2n
+.asynchro1nou2
+.asynchrono2us
+.at8h9e8r9o9s8c8l8e9r8o9s8i8s.
+.4ath
+.ath2e
+.ath2ero
+.atheros2c
+.atheroscle5
+.atheros1c4l4
+.ath2eroscl4ero
+.atherosclero1sis
+.at9m8o8s9p8h8e8r8e.
+.a4t1m
+.at1mo
+.atmos2
+.atmo3sp
+.atmos2phe
+.atmo3sph4er
+.at9m8o8s9p8h8e8r8e8s.
+.at9t8r8i8b9u8t8e8d.
+.a4t3t2
+.attr4ib
+.attribu2t1ed
+.at9t8r8i8b9u8t9a8b8l8e.
+.attri4bu1ta
+.attribu2ta4b
+.attributab2l2
+.au9t8o9m8a9t8i8o8n.
+.au1to
+.auto2ma
+.automa1t2io
+.automatio2n
+.au9t8o8m9a9t8o8n.
+.automa1to
+.automato2n
+.au9t8o8m9a9t8a.
+.automa2ta
+.au9t8o9n8u8m9b8e8r9i8n8g.
+.au5to2n
+.auton5um
+.autonu4m1b
+.autonumber1i
+.autonumberin4g
+.au9t8o8n9o9m8o8u8s.
+.au4tono
+.autono4mo
+.autono3mo2us
+.autonomou2
+.au8t8o9r8o8u8n8d9i8n8g.
+.autorou2
+.autoroun2d
+.autoround1in
+.av9o8i8r9d8u9p8o8i8s.
+.avoi4
+.avo4ir
+.avoir1du
+.avoir4dup
+.avoirdupoi2
+.ba8n8d9l8e8a8d8e8r.
+.b4and
+.ban1dl
+.bandle2a
+.bandlea2d1
+.ba8n8d9l8e8a8d8e8r8s.
+.bandleade4r5s2
+.ba8n8k9r8u8p8t.
+.ba4nk2
+.bankru2p1t
+.ba8n8k9r8u8p8t9c8y.
+.bankrup4tc
+.bankrupt1cy
+.ba8n8k9r8u8p8t9c8i8e8s.
+.bankrupt1ci
+.bankruptc2ie4
+.ba8r9o8n8i8e8s.
+.b2a2r
+.ba5roni
+.baro2n
+.baron2ie4
+.ba8s8e9l8i8n8e9s8k8i8p.
+.basel2i
+.base1l4ine
+.baseli1nes
+.baselinesk2
+.baselinesk1i
+.baselinesk2i4p
+.ba9t8h8y8m9e9t8r8y.
+.1bat
+.b4ath
+.bathyme4
+.bathym4etr
+.bathyme3try
+.ba8t8h8y9s8c8a8p8h8e.
+.bathy2s
+.bathys4c
+.bathysca4p
+.bathys1ca
+.be8a8n9i8e8s.
+.bea2n
+.bea3nies
+.bean2ie4
+.be9h8a8v9i8o8u8r.
+.be1h4
+.behav1i
+.behavi1ou2
+.behav2io
+.behavi4our
+.be9h8a8v9i8o8u8r8s.
+.behaviou4rs2
+.be8v8i8e8s.
+.be1vi
+.bev2ie4
+.bi8b9l8i9o8g9r8a9p8h8y9s8t8y8l8e.
+.bi2b
+.bi1b2l2
+.bib3li
+.bibli5og
+.bibl2io
+.biblio2gr
+.biblio4g3ra1phy
+.bibliography2s
+.bibliographys1t
+.bibliographys2ty
+.bibliographys2tyl
+.bi9d8i8f9f8e8r9e8n9t8i8a8l.
+.b2i4d
+.bi2di
+.bid1if
+.bidi4f1f
+.bidiffer1
+.bidiffer3en1t
+.bidifferent2i
+.bidifferen1t2i1a
+.bidifferenti2al
+.bi8g9g8e8s8t.
+.b2ig
+.bi4g1g2
+.big2ge
+.bi8l8l9a8b8l8e.
+.1bil
+.bill5ab
+.bil1l
+.billab2l2
+.bi8o9m8a8t8h9e9m8a8t9i8c8s.
+.b2io
+.bio4m
+.bio1ma
+.biom4ath3
+.biomath5em
+.biomath2e
+.biomathe1ma
+.biomathemat1ic
+.biomathemati4c3s2
+.bi8o9m8e8d9i9c8a8l.
+.bio2me
+.bio2med
+.biom4edi
+.biomed3i1ca
+.bi8o9m8e8d9i9c8i8n8e.
+.biomed2i1ci
+.biomedi2cin
+.biomedic2ine
+.bi8o9r8h8y8t8h8m8s.
+.biorh4
+.biorhyt4h1m
+.biorhyth4m1s2
+.bi8t9m8a8p.
+.bi2t
+.bi4t1m
+.bit1ma
+.bit4map
+.bi8t9m8a8p8s.
+.bitma2p1s2
+.bl8a8n8d9e8r.
+.b2l2
+.b3l4and
+.bla2n
+.blan1de
+.bl8a8n8d9e8s8t.
+.blande4s2
+.bl8i8n8d9e8r.
+.bl4ind
+.blin1de
+.bl8o8n8d8e8s.
+.b4lo
+.blo2n
+.bl2ond
+.blon1de
+.blondes2
+.bl8u8e9p8r8i8n8t.
+.bluepr2
+.blueprin4t3
+.bl8u8e9p8r8i8n8t8s.
+.blueprin4t4s2
+.bo9l8o8m9e9t8e8r.
+.bolo2me
+.bolo4met
+.bolome1te
+.bo8o8k9s8e8l8l9e8r.
+.3boo2
+.bo2o4k
+.boo4k1s2
+.booksel1l
+.booksel2le
+.bo8o8k9s8e8l8l9e8r8s.
+.bookselle4r1s2
+.bo8o8l9e8a8n.
+.boole2a
+.boolea2n
+.bo8o8l9e8a8n8s.
+.boolea2n1s2
+.bo8r9n8o9l8o8g9i9c8a8l.
+.borno4
+.borno3log1ic
+.bornologi1ca
+.bo8t9u9l8i8s8m.
+.bo1tu
+.botul2i
+.botuli2s1m
+.br8u8s8q8u8e8r.
+.br2us
+.brusqu2
+.brus3quer
+.bu8f9f8e8r.
+.buf4fer1
+.bu4f1f
+.bu8f9f8e8r8s.
+.buffe4r1s2
+.bu8s8i8e8r.
+.bus5ie4
+.b2us
+.bu8s8i8e8s8t.
+.busi1est
+.bu8s8s8i8n8g.
+.bu2ss
+.bus1si
+.bus2s1in
+.buss3ing
+.bu8t8t8e8d.
+.but2t1ed
+.bu8z8z9w8o8r8d.
+.bu4z1z2
+.buzz1wo2
+.bu8z8z9w8o8r8d8s.
+.buzzwor2d1s2
+.ca9c8o8p8h9o9n8y.
+.ca1co
+.cac2oph
+.cacopho5ny
+.cacopho2n
+.ca9c8o8p8h9o9n8i8e8s.
+.caco5phoni
+.cacophon2ie4
+.ca8l8l9e8r.
+.cal1l
+.cal2le
+.ca8l8l9e8r8s.
+.calle4r1s2
+.ca8m9e8r8a9m8e8n.
+.cam5er1a
+.camera1men
+.ca8r8t9w8h8e8e8l.
+.cartw4
+.ca8r8t9w8h8e8e8l8s.
+.cartwhee2l1s2
+.ca9t8a8r8r8h8s.
+.ca2ta
+.cat2a2r
+.catar1r4
+.catarrh4
+.catarr4h1s2
+.ca8t9a9s8t8r8o8p8h9i8c.
+.catas1t4r
+.catastr2oph
+.catastroph1ic
+.ca8t9a9s8t8r8o8p8h9i9c8a8l8l8y.
+.catastrophi1ca
+.catastrophical1l
+.catastrophical1ly
+.ca8t9e9n8o8i8d.
+.cat4eno
+.catenoi2
+.cateno2id
+.ca8t9e9n8o8i8d8s.
+.catenoi2d1s2
+.ca8u9l8i9f8l8o8w9e8r.
+.cau4l2
+.caul2i
+.cauli4f4l2
+.cauliflow1er
+.ch8a8p9a8r9r8a8l.
+.chap2a2r4
+.cha1pa
+.chapar1r4
+.ch8a8r9t8r8e8u8s8e.
+.ch2a2r
+.chartr4eu2
+.chartre2us4
+.ch8e8m8o9t8h8e8r9a8p8y.
+.che2
+.che1mo
+.chem4oth3
+.chemoth2e
+.chemoth4er1a
+.chemothera3p
+.ch8e8m8o9t8h8e8r9a9p8i8e8s.
+.chemotherap2ie4
+.ch8l8o8r8o9m8e8t8h9a8n8e.
+.c4h1l4
+.ch2lo
+.chloro2me
+.chloro4met
+.chlorometha2n4
+.ch8l8o8r8o9m8e8t8h9a8n8e8s.
+.chlorometha1nes
+.ch8o9l8e8s9t8e8r8i8c.
+.3cho2
+.c3hol4e
+.choles2
+.choles1ter1i
+.ci8g9a9r8e8t8t8e.
+.c2ig
+.ci1ga
+.cig2a2r
+.cigare4t3t2
+.ci8g9a9r8e8t8t8e8s.
+.cigaret4tes
+.ci8n8q8u8e9f8o8i8l.
+.2cin
+.cin1q
+.cinqu2
+.cinque1f
+.cinque1fo
+.cinquefoi2
+.co9a8s8s8o9c8i8a9t8i8v8e.
+.c4oa
+.coa2ss
+.coas1so
+.coasso1ci
+.coasso3c2i1a
+.coassoci4a1t2iv
+.co9g8n8a8c.
+.2cog
+.cog1n4a
+.co9g8n8a8c8s.
+.cogna4c3s2
+.co9k8e8r9n8e8l.
+.c2ok
+.cok1er
+.coker3nel
+.co9k8e8r9n8e8l8s.
+.cokerne2l1s2
+.co8l9l8i8n9e8a9t8i8o8n.
+.col1l
+.coll2i
+.col2lin4
+.col1l4ine
+.collin3ea
+.collinea2t
+.collinea1t2io
+.collineatio2n
+.co8l9u8m8n8s.
+.colu4m1n
+.colum2n1s2
+.co8m9p8a8r9a8n8d.
+.co4m1p
+.compara5
+.com1pa
+.comp2a2r
+.compara2n
+.compar4and
+.co8m9p8a8r9a8n8d8s.
+.comparan2d1s2
+.co8m9p8e8n9d8i8u8m.
+.compendi1u
+.co8m9p8o9n8e8n8t9w8i8s8e.
+.compo2n
+.compo3nen
+.componen1t
+.componentw4
+.componentwis4
+.componentwi2
+.component3w4ise
+.co8m8p9t8r8o8l9l8e8r.
+.comp4tr
+.com2p1t
+.comptrol1l
+.comptrol2le
+.co8m8p9t8r8o8l9l8e8r8s.
+.comptrolle4r1s2
+.co8n9f8o8r8m9a8b8l8e.
+.co2n
+.con3f
+.con1fo
+.confo2r
+.confor1m
+.confor1ma
+.confor2mab
+.conformab2l2
+.co8n9f8o8r8m9i8s8t.
+.confor2mi
+.conform2is
+.co8n9f8o8r8m9i8s8t8s.
+.conformis4t1s2
+.co8n9f8o8r8m9i8t8y.
+.confor3mit
+.conformi1ty
+.co8n9g8r8e8s8s.
+.con3g
+.con1gr
+.congr2e2ss
+.co8n9g8r8e8s8s8e8s.
+.congress1e4s
+.co8n9t8r8i8b9u8t8e.
+.con5t
+.contr4ib
+.co8n9t8r8i8b9u8t8e8s.
+.co8n9t8r8i8b9u8t8e8d.
+.contribu2t1ed
+.co9r8e9l8a9t8i8o8n.
+.core1la
+.corela1t2io
+.corelatio2n
+.co9r8e9l8a9t8i8o8n8s.
+.corelatio2n3s2
+.co9r8e9l8i9g8i8o8n9i8s8t.
+.core1l2i
+.corel2ig
+.corel4igi
+.coreli5g2io
+.coreligion3i
+.coreligio2n
+.coreligion1is
+.co9r8e9l8i9g8i8o8n9i8s8t8s.
+.coreligionis4t1s2
+.co9r8e9o8p9s8i8s.
+.core1o
+.coreo2p1s2
+.coreop1sis
+.co9r8e9s8p8o8n9d8e8n8t.
+.core1sp
+.cores4po2n
+.coresp2ond
+.corespon1de
+.corespon1den
+.coresponden1t
+.co9r8e9s8p8o8n9d8e8n8t8s.
+.coresponden4t4s2
+.co9s8e9c8a8n8t.
+.cos4e
+.cose1ca
+.coseca2n
+.cosecan1t
+.co9t8a8n9g8e8n8t.
+.co4ta2n
+.co1ta
+.cot2ang
+.cotan1gen
+.cotangen1t
+.co8u8r9s8e8s.
+.cou2
+.cou4rs2
+.cour2se
+.cours3e4s
+.co9w8o8r8k9e8r.
+.co4wo2
+.cowork1er
+.co9w8o8r8k9e8r8s.
+.coworke4r1s2
+.cr8a8n8k9c8a8s8e.
+.cra2n
+.cra4nk2
+.crank1ca
+.cr8a8n8k9s8h8a8f8t.
+.cran4k1s2
+.cranks2h
+.cranksha2f
+.cranksha2ft
+.cr8o8c9o9d8i8l8e.
+.cr2oc
+.cro4cod
+.cro1co
+.cr8o8c9o9d8i8l8e8s.
+.crocodiles2
+.cr8o8s8s9h8a8t8c8h.
+.cro2s4s
+.cross2h
+.crossha4tc
+.crosshat4ch
+.cr8o8s8s9h8a8t8c8h8e8d.
+.crosshatche2
+.crosshat4ch4ed
+.cr8o8s8s9o8v8e8r.
+.cros1so
+.cros4sov
+.cr8y8p9t8o9g8r8a8m.
+.cry2p1t
+.cryp1to
+.crypto2gr
+.cr8y8p9t8o9g8r8a8m8s.
+.cryptogra4m1s2
+.cu8f8f9l8i8n8k.
+.c4uf
+.cu4f1f
+.cuff4l2
+.cufflin4
+.cuffl4i4nk2
+.cu8f8f9l8i8n8k8s.
+.cufflin4k1s2
+.cu9n8e8i9f8o8r8m.
+.3cun
+.cu2ne
+.cunei2
+.cunei1fo
+.cuneifo2r
+.cuneifor1m
+.cu8s9t8o8m9i8z9a9b8l8e.
+.1c2us
+.cus1to
+.custom2iz
+.customiza1
+.customiz5a2b
+.customizab2l2
+.cu8s9t8o8m9i8z8e.
+.customi2ze
+.cu8s9t8o8m9i8z8e8s.
+.cu8s9t8o8m9i8z8e8d.
+.da8c8h8s9h8u8n8d.
+.1d2a
+.da2ch4
+.dac4h1s2
+.dach4s2h
+.da8m9s8e8l9f8l8y.
+.da2m2
+.da4m1s2
+.dam5se2l2f
+.damself4l2
+.damself2ly5
+.da8m9s8e8l9f8l8i8e8s.
+.damselfl2ie4
+.da8c8t8y8l9o9g8r8a8m.
+.da2c1t
+.dac1ty
+.dac2tyl
+.dacty3lo
+.dactylo1gr
+.da8c8t8y8l9o9g8r8a8p8h.
+.da8t8a9b8a8s8e.
+.3dat
+.da2ta
+.da2tab
+.da8t8a9b8a8s8e8s.
+.databas1e4s
+.da8t8a9p8a8t8h.
+.dat5ap
+.datap5at
+.data1pa
+.datap4ath
+.da8t8a9p8a8t8h8s.
+.datapa2t4h1s2
+.da8t8e9s8t8a8m8p.
+.dat3est
+.dates1ta
+.datesta4m1p
+.da8t8e9s8t8a8m8p8s.
+.datestam2p1s2
+.de9c8l8a8r9a8b8l8e.
+.de4cl2a2r
+.decla2rab
+.declarab2l2
+.de9f8i8n9i9t8i8v8e.
+.de1f
+.de1fi
+.de2fin
+.def2ini
+.defin2it
+.defini1ti
+.defini1t2iv
+.de9l8e8c9t8a9b8l8e.
+.d5elec
+.dele2c1t
+.delec2ta4b
+.delec1ta
+.delectab2l2
+.de8m8i9s8e8m8i9q8u8a9v8e8r.
+.de4m2is
+.dem4ise
+.demisemi3qua
+.demisemiqu2
+.demisemiqua5v4
+.de8m8i9s8e8m8i9q8u8a9v8e8r8s.
+.demisemiquave4r1s2
+.de9m8o8c9r8a9t8i8s8m.
+.de4mocr
+.democrati2s4m
+.de8m8o8s.
+.demos2
+.de9r8i8v9a9t8i8v8e.
+.der2i4v
+.de4ri1va
+.deri3vat
+.der2iva1t2iv
+.de9r8i8v9a9t8i8v8e8s.
+.derivativ4e2s
+.di8a9l8e8c9t8i8c.
+.1d4i3a
+.di2al
+.di2ale
+.diale2c1t
+.di8a9l8e8c9t8i8c8s.
+.dialecti4c3s2
+.di8a9l8e8c9t8i9c8i8a8n.
+.dialect2i1ci
+.d2i1alecti3c2i1a
+.dialectici2a2n
+.di8a9l8e8c9t8i9c8i8a8n8s.
+.dialecticia2n1s2
+.di9c8h8l8o8r8o9m8e8t8h9a8n8e.
+.d4i2ch
+.dic4h1l4
+.dich2lo
+.dichloro2me
+.dichloro4met
+.dichlorometha2n4
+.di8f9f8r8a8c8t.
+.d1if
+.dif4fr
+.di4f1f
+.diffra2c1t
+.di8f9f8r8a8c8t8s.
+.diffrac4t1s2
+.di8f9f8r8a8c9t8i8o8n.
+.diffrac1t2io
+.diffractio2n
+.di8f9f8r8a8c9t8i8o8n8s.
+.diffractio2n3s2
+.di8r8e8r.
+.d4ir2
+.di1re
+.dir1er4
+.di8r8e9n8e8s8s.
+.dire1nes
+.diren2e2ss
+.di8s9p8a8r9a8n8d.
+.dis1
+.dis1p
+.di2s1pa
+.disp2a2r
+.dispara2n
+.dispar4and
+.di8s9p8a8r9a8n8d8s.
+.disparan2d1s2
+.di8s9t8r8a8u8g8h8t9l8y.
+.d4is3t
+.dist4r
+.dis1tra
+.distraugh3
+.distraugh2tl
+.distraught1ly
+.di8s9t8r8i8b9u8t8e.
+.distr4ib
+.di8s9t8r8i8b9u8t8e8s.
+.di8s9t8r8i8b9u8t8e8d.
+.distribu2t1ed
+.do8u9b8l8e9s8p8a8c8e.
+.dou2
+.dou3b2l2
+.dou5ble1sp
+.doubles2
+.double2s1pa
+.doublespa4ce
+.do8u9b8l8e9s8p8a8c9i8n8g.
+.doublesp4a2ci
+.doublespa2c1in
+.doublespac1ing
+.do8l8l9i8s8h.
+.dol1l
+.doll2i
+.dollis2h
+.dr8i8f8t9a8g8e.
+.1dr
+.dr4i2ft
+.drif1ta
+.dr8i8v9e8r8s.
+.dr2iv
+.drive4r1s2
+.dr8o8m9e9d8a8r8y.
+.dro2me
+.dro2med
+.drom2e2d2a
+.drome4dary
+.dromed2a2r
+.dr8o8m9e9d8a8r8i8e8s.
+.dromedar1i
+.dromedar2ie4
+.du9o8p9o9l8i8s8t.
+.duopol2i
+.du9o8p9o9l8i8s8t8s.
+.duopolis4t1s2
+.du9o8p9o8l8y.
+.duopo2ly
+.dy8s9l8e8x8i8a.
+.d2y
+.dys1l2
+.dys2le
+.dyslex3i
+.dyslex2i5a
+.dy8s9l8e8c9t8i8c.
+.dysle2c1t
+.ea8s8t9e8n8d9e8r8s.
+.east3
+.eas3ten
+.eas3tend
+.easten1de
+.eastende4r5s2
+.ec8o9n8o8m9i8c8s.
+.e1co
+.eco2n
+.eco3nomic
+.economi4c3s2
+.ec8o8n9o9m8i8s8t.
+.econom2is
+.ec8o8n9o9m8i8s8t8s.
+.economis4t1s2
+.ei9g8e8n9c8l8a8s8s.
+.ei2
+.e2ig2
+.ei1gen
+.eigen1c4l4
+.eigencla2ss
+.ei9g8e8n9c8l8a8s8s8e8s.
+.eigenclass1e4s
+.ei9g8e8n9v8a8l9u8e.
+.eigen1v2
+.eigen1va
+.eigenval1u
+.ei9g8e8n9v8a8l9u8e8s.
+.el8e8c8t8r8o9m8e8c8h8a8n9i9c8a8l.
+.5elec
+.ele2c1t
+.electro2me
+.electrome2ch
+.electrome5cha4n1ic
+.electromecha2n
+.electromechani1ca
+.el8e8c8t8r8o9m8e8c8h8a8n8o9a8c8o8u8s8t8i8c.
+.electromechano4
+.electromechan4oa
+.electromechanoa1co
+.electromechanoacou2
+.electromechanoaco2us
+.electromechanoacoust2i
+.electromechanoacous1tic
+.el8i8t9i8s8t.
+.el2i
+.el1it
+.eli1ti
+.el4itis
+.el8i8t9i8s8t8s.
+.elitis4t1s2
+.en9t8r8e9p8r8e9n8e8u8r.
+.en1t
+.entrepr2
+.entrepren4eu
+.en9t8r8e9p8r8e9n8e8u8r9i8a8l.
+.entrepreneur2i3a
+.entrepreneuri2al
+.ep9i9n8e8p8h9r8i8n8e.
+.epi2n
+.ep2ine
+.epinep4hr4
+.ep2inephr2in4e
+.eq8u8i9v8a8r8i9a8n8t.
+.equ2iv3
+.equi1va
+.equiv2a2r
+.equivar1i
+.equivar3i2a2n
+.equivar2i3a
+.equivar4ian4t
+.eq8u8i9v8a8r8i9a8n8c8e.
+.equivar4ianc
+.et8h9a8n8e.
+.etha2n4
+.et8h9y8l9e8n8e.
+.ev8e8r9s8i9b8l8e.
+.ev1er
+.eve4r1s2
+.ever1si
+.ever4si4b
+.eversi1b2l2
+.ev8e8r8t.
+.ev8e8r8t8s.
+.ever4t1s2
+.ev8e8r8t9e8d.
+.ever2t1ed
+.ev8e8r8t9i8n8g.
+.ever1ti
+.ever2t1in
+.ex9q8u8i8s9i8t8e.
+.exqu2
+.exq2ui2
+.exquis2ite
+.ex9t8r8a9o8r9d8i9n8a8r8y.
+.ex1t2
+.ex1tra
+.extr4ao
+.extraord2i
+.extraord1in4
+.extraor1di1na
+.extraordin2a2r
+.fa8l8l9i8n8g.
+.1fa
+.fal1l
+.fall2i
+.fal2lin4
+.fe8r8m8i9o8n8s.
+.fer1
+.fer3m4
+.fer4m2io
+.fermio2n
+.fermio2n3s2
+.fi9n8i8t8e9l8y.
+.1fi
+.2fin
+.f2ini
+.fin2it
+.fin2ite
+.finite1ly
+.fl8a9g8e8l9l8u8m.
+.f4l2
+.flag5el1l
+.fl8a9g8e8l9l8a.
+.flag4ella
+.fl8a8m9m8a9b8l8e8s.
+.flam1m
+.flam1ma
+.flam2mab
+.flammab2l2
+.flammables2
+.fl8e8d8g9l8i8n8g.
+.fledgl2
+.fl8o8w9c8h8a8r8t.
+.flow2ch
+.flowch2a2r
+.fl8o8w9c8h8a8r8t8s.
+.flowchar4t1s2
+.fl8u8o8r8o9c8a8r9b8o8n.
+.flu3o
+.fluo3r
+.fluor2oc
+.fluoro1ca
+.fluoroc2a2r
+.fluorocar1b
+.fluorocarb4o
+.fluorocarbo2n
+.fo8r9m8i9d8a9b8l8e.
+.for2mi
+.formi1d4a
+.form2id
+.formi2d3a4b
+.formidab2l2
+.fo8r9m8i9d8a9b8l8y.
+.formidab1ly
+.fo8r9s8y8t8h9i8a.
+.fo4rs2
+.fors4y
+.forsyth2i1a
+.fo8r8t8h9r8i8g8h8t.
+.fort4hr4
+.forthr2ig
+.fr8e8e9l8o8a8d8e8r.
+.freel4oa
+.freeloa2d3
+.fr8e8e9l8o8a8d8e8r8s.
+.freeloade4r5s2
+.fr8i8e8n8d9l8i8e8r.
+.fri2
+.fr2ie4
+.friendl2ie4
+.fr8i9v8o8l9i8t8y.
+.fr2iv
+.frivol2i
+.frivol1it
+.frivoli1ty
+.fr8i9v8o8l9i9t8i8e8s.
+.frivoli1ti
+.frivolit2ie4
+.fr8i8v9o9l8o8u8s.
+.frivolou2
+.frivolo2us
+.ga9l8a8c9t8i8c.
+.gala2c1t
+.ga8l9a8x8y.
+.ga8l9a8x9i8e8s.
+.galax3i
+.galax2ie4
+.ga8s9o8m9e9t8e8r.
+.ga1so
+.ga3som
+.gaso2me
+.gaso4met
+.gasome1te
+.ge9o9d8e8s9i8c.
+.geodes2
+.geode1si
+.geode2sic
+.ge9o9d8e8t9i8c.
+.geode1t
+.geodet1ic
+.ge8o9m8e8t9r8i8c.
+.ge3om
+.geo2me
+.geo4met
+.geom4etr
+.geo5met3ric
+.ge8o9m8e8t9r8i8c8s.
+.geome4tri4c3s2
+.ge9o9s8t8r8o8p8h8i8c.
+.geos4
+.geost4r
+.geostr2oph
+.geostroph1ic
+.ge8o9t8h8e8r9m8a8l.
+.ge4ot
+.ge4oth
+.geoth2e
+.geother3m4
+.geother1ma
+.ge9o8t9r8o9p8i8s8m.
+.geotropi2s1m
+.gn8o9m8o8n.
+.g1no
+.gno4mo
+.gno4mo2n
+.gn8o9m8o8n8s.
+.gnomo2n3s2
+.gr8a8n8d9u8n8c8l8e.
+.1gr
+.gra2n2
+.gr4and
+.gran1du
+.grandu4n
+.grandun1c4l4
+.gr8a8n8d9u8n8c8l8e8s.
+.granduncles2
+.gr8i8e8v9a8n8c8e.
+.gr2ie4
+.grie1va
+.grieva2n
+.gr8i8e8v9a8n8c8e8s.
+.gr8i8e8v9o8u8s.
+.grievou2
+.grievo2us
+.gr8i8e8v9o8u8s9l8y.
+.grievous1l2
+.grievous1ly
+.ha8i8r9s8t8y8l8e.
+.hai2
+.ha4ir
+.hai4rs2
+.hairs2ty
+.hairs2tyl
+.ha8i8r9s8t8y8l8e8s.
+.hairstyles2
+.ha8i8r9s8t8y8l9i8s8t.
+.ha8i8r9s8t8y8l9i8s8t8s.
+.hairstylis4t1s2
+.ha8l8f9s8p8a8c8e.
+.ha2lf
+.hal2f3s
+.half2s1pa
+.halfspa4ce
+.ha8l8f9s8p8a8c8e8s.
+.ha8l8f9w8a8y.
+.ha8r9b8i8n9g8e8r.
+.h2a2r
+.har1b
+.harbi2
+.har2bin
+.harb4inge
+.ha8r9b8i8n9g8e8r8s.
+.harbinge4r1s2
+.ha8r9l8e9q8u8i8n.
+.har4le4
+.har1l
+.harle1q
+.harlequ2
+.harleq2ui2
+.harlequi4n
+.ha8r9l8e9q8u8i8n8s.
+.harlequ2i2n1s2
+.ha8t8c8h9e8r8i8e8s.
+.ha4tc
+.hat4ch
+.hatche2
+.hatcher1i
+.hatcher2ie4
+.he8m8i9d8e8m8i9s8e8m8i9q8u8a9v8e8r.
+.hem2id
+.hemid4em
+.hemide4m2is
+.hemidem4ise
+.hemidemisemi3qua
+.hemidemisemiqu2
+.hemidemisemiqua5v4
+.he8m8i9d8e8m8i9s8e8m8i9q8u8a9v8e8r8s.
+.hemidemisemiquave4r1s2
+.he9m8o9g8l8o9b8i8n.
+.hemo4g
+.he1mo
+.hemo4gl2
+.hemo3glo
+.hemoglo1bi
+.hemoglo2bin
+.he9m8o9p8h8i8l9i8a.
+.hem2oph
+.hemoph4il2
+.hemophil1i
+.hemophil3i1a
+.he9m8o9p8h8i8l9i8a8c.
+.he9m8o9p8h8i8l9i8a8c8s.
+.hemophilia4c3s2
+.he8m8o9r8h8e9o8l9o8g8y.
+.hemo2r
+.hemorh4
+.hemorhe3ol
+.hemorheol1o1gy
+.he9p8a8t9i8c.
+.hep5
+.he2pa
+.hepat1ic
+.he8r9m8a8p8h9r8o9d8i8t8e.
+.her3m4
+.her1ma
+.her4map
+.hermap4hr4
+.hermaphrod2ite
+.he8r9m8a8p8h9r8o9d8i8t9i8c.
+.hermaphrod2i1ti
+.hermaphrod4i2tic
+.he9r8o8e8s.
+.hero4e
+.he8x8a9d8e8c9i9m8a8l.
+.hex1a
+.hexa2d
+.hexade1c2i
+.hexade2c3im
+.hexadeci1ma
+.ho9l8o9n8o9m8y.
+.holo2n
+.holon3o3my
+.ho9m8e8o9m8o8r9p8h8i8c.
+.ho2me3
+.homeo1mo
+.homeomo2r
+.homeomor1p
+.homeomorp4h4
+.homeomorph1ic
+.ho9m8e8o9m8o8r9p8h8i8s8m.
+.homeomorphi2s1m
+.ho9m8o9t8h8e8t8i8c.
+.ho1mo
+.hom4oth3
+.homoth2e
+.homo3the4t
+.homothet1ic
+.ho8r8s8e9r8a8d9i8s8h.
+.hor4se
+.ho4rs2
+.horser1a
+.horsera2d
+.horser2adi
+.horseradis1
+.horseradis2h
+.ho8t9b8e8d.
+.ho2t1b
+.hot4be2d
+.ho8t9b8e8d8s.
+.hotbe2d1s2
+.hy9d8r8o9t8h8e8r9m8a8l.
+.hy1d
+.hy1dr
+.hydro4th2e
+.hydr4oth
+.hydrother3m4
+.hydrother1ma
+.hy9p8o9t8h8a8l9a9m8u8s.
+.hy3po
+.hyp4ot
+.hyp4oth
+.hypotha3la
+.hypothala3m
+.hypothala1mu
+.hypothalam2us
+.id8e8a8l8s.
+.ide3a4l
+.idea2l1s2
+.id8e8o9g8r8a8p8h8s.
+.ideo2g
+.ideo1gr
+.ideogra4p4h1s2
+.id8i8o9s8y8n9c8r8a8s8y.
+.i2di
+.i1d3io
+.idi4os
+.idios4y
+.idiosyn1cr
+.idiosyncr2as
+.idiosyncras4y
+.id8i8o9s8y8n9c8r8a9s8i8e8s.
+.idiosyncras2ie4
+.id8i8o9s8y8n9c8r8a8t8i8c.
+.idiosyn5crat1ic
+.id8i8o9s8y8n9c8r8a8t9i9c8a8l9l8y.
+.idiosyncrati1ca
+.idiosyncratical1l
+.idiosyncratical1ly
+.ig9n8i8t9e8r.
+.2ig
+.ig1ni
+.ign2it
+.ign2ite
+.ig9n8i8t9e8r8s.
+.ignite4r1s2
+.ig9n8i9t8o8r.
+.ign3itor
+.igni1to
+.ig8n8o8r8e9s8p8a8c8e8s.
+.ig1no
+.ignore1sp
+.ignore2s1pa
+.ignorespa4ce
+.im9p8e8d9a8n8c8e.
+.im2p2ed
+.imp2e2d2a
+.impeda2n
+.im9p8e8d9a8n8c8e8s.
+.in9d8u9b8i9t8a9b8l8e.
+.4ind
+.in1du
+.indu1b4i
+.indubi2t
+.indubi1ta
+.indubi2tab
+.indubitab2l2
+.in9f8i8n9i8t8e9l8y.
+.in3f
+.in1fi
+.in2fin
+.inf2ini
+.infin2it
+.infin2ite
+.infinite1ly
+.in9f8i8n9i9t8e8s9i9m8a8l.
+.infinit4es
+.infinite1si
+.infinite2s5im
+.infinitesi1ma
+.in9f8r8a9s8t8r8u8c9t8u8r8e.
+.infr2as
+.infras1t4r
+.infrastru2c1t
+.infrastructu4r
+.infrastruc1tu
+.infrastruc3ture
+.in9f8r8a9s8t8r8u8c9t8u8r8e8s.
+.in9s8t8a8l8l9e8r.
+.ins2ta2l
+.ins1ta
+.instal1l
+.instal2le
+.in9s8t8a8l8l9e8r8s.
+.installe4r1s2
+.in9t8e8r9d8i8s9c8i9p8l8i9n8a8r8y.
+.in1t
+.in5ter3d
+.interd2i
+.interdis1
+.interd2is1c
+.interdis1ci
+.interdisc2ip
+.interdisci1p2l2
+.interdiscipli4n
+.interdiscipl4i1na
+.interdisciplin2a2r
+.in9t8e8r9g8a9l8a8c9t8i8c.
+.interg2
+.inter1ga
+.intergala2c1t
+.in9u8t8i8l8e.
+.in1u
+.in4u1t2i
+.in9u8t8i8l9i9t8y.
+.inutil1i
+.inut2il1it
+.inutili1ty
+.ir9r8e9d8u8c9i8b8l8e.
+.ir2r2ed
+.irre1du
+.irredu2c
+.irreduci4b
+.irredu1ci
+.irreduci1b2l2
+.ir9r8e9d8u8c9i8b8l8y.
+.irreducib1ly
+.ir9r8e8v9o9c8a9b8l8e.
+.irrev2
+.irre5voc
+.irrevo1ca
+.irrevoca1b2l2
+.is8o8t9r8o8p8y.
+.i2so
+.isotropy5
+.is8o9t8r8o8p9i8c.
+.isotrop3ic
+.it8i8n9e8r9a8r8y.
+.i1ti
+.i2t1in
+.it2ine
+.itin4er4a2r
+.itin1er
+.itiner1a
+.it8i8n9e8r9a8r9i8e8s.
+.itinerar1i
+.itinerar2ie4
+.je9r8e9m8i9a8d8s.
+.1je
+.jerem2i3a
+.jeremia2d
+.jeremia2d1s2
+.ke8y9n8o8t8e.
+.ke8y9n8o8t8e8s.
+.keyno4tes
+.ke8y9s8t8r8o8k8e.
+.keys4
+.keys1t
+.keyst4r
+.keystr2ok2
+.ke8y9s8t8r8o8k8e8s.
+.keystrokes4
+.ki8l8n9i8n8g.
+.k1i
+.k4i2l1n2
+.kiln1in
+.kilnin4g
+.la8c9i9e8s8t.
+.l4a2ci4
+.la3c2ie4
+.laci1est
+.la8m9e8n9t8a9b8l8e.
+.la1men
+.la3men1t
+.lamen2ta4b
+.lamen1ta
+.lamentab2l2
+.la8n8d9s8c8a8p9e8r.
+.3l4and
+.la2n
+.lan2d1s2
+.landsca4p
+.lands1ca
+.landsca5per
+.la8n8d9s8c8a8p9e8r8s.
+.landscape4r1s2
+.la8r9c8e9n8y.
+.l2a2r
+.lar1c
+.lar2ce
+.lar1cen4
+.la8r9c8e9n9i8s8t.
+.lar4ceni
+.le8a8f9h8o8p9p8e8r.
+.le2a
+.lea2f
+.lea4fh
+.leafho4p1p
+.leafhop2pe
+.leafhop3per
+.le8a8f9h8o8p9p8e8r8s.
+.leafhoppe4r1s2
+.le8t9t8e8r9s8p8a8c9i8n8g.
+.le4t3t2
+.lette4r1s2
+.letter1sp
+.letter2s1pa
+.lettersp4a2ci
+.letterspa2c1in
+.letterspac1ing
+.li8f8e9s8p8a8n.
+.life1sp
+.life2s1pa
+.lifespa4n
+.li8f8e9s8p8a8n8s.
+.lifespa2n1s2
+.li8f8e9s8t8y8l8e.
+.lifes2ty
+.lifes2tyl
+.li8f8e9s8t8y8l8e8s.
+.lifestyles2
+.li8g8h8t9w8e8i8g8h8t.
+.3ligh
+.lightw4
+.lightwei2
+.l2ightwe2ig2
+.li8m9o8u9s8i8n8e8s.
+.li4mo
+.li3mo2us
+.limou2
+.limou2s1in
+.limous2ine
+.limousi1nes
+.li8n8e9b8a8c8k8e8r.
+.1l4ine
+.lin2e2b
+.lineback1
+.lineback1er
+.li8n8e9s8p8a8c8i8n8g.
+.li1nes
+.li4ne1sp
+.line2s1pa
+.linesp4a2ci
+.linespa2c1in
+.linespac1ing
+.li9o8n9e8s8s.
+.lio2n
+.lio1nes
+.lion2e2ss
+.li8t8h9o9g8r8a8p8h8e8d.
+.l2ith
+.litho4g
+.litho1gr
+.lithograph4ed
+.li8t8h9o9g8r8a8p8h8s.
+.lithogra4p4h1s2
+.lo9b8o8t9o8m8y.
+.lobo4to
+.loboto3my
+.lo9b8o8t9o8m9i8z8e.
+.lobotom2iz
+.lobotomi2ze
+.lo8g8e8s.
+.lo1ge
+.lo8n8g9e8s8t.
+.5long
+.lo2n
+.lo9q8u8a8c9i8t8y.
+.lo1q
+.loqu2
+.loquac4
+.loqu4a2ci
+.loqua2c1it
+.loquaci1ty
+.lo8v8e9s8t8r8u8c8k.
+.4lov
+.lov4e2s
+.lov2est4r
+.lovestruc5
+.lovestruck1
+.ma8c8r8o9e8c8o9n8o8m8i8c8s.
+.macro4e
+.macroe1co
+.macroeco2n
+.macroeco3nomic
+.macroeconomi4c3s2
+.ma8l9a9p8r8o8p9i8s8m.
+.malapr2
+.malapropi2s1m
+.ma8l9a9p8r8o8p9i8s8m8s.
+.malaprop4is4m1s2
+.ma8n9s8l8a8u8g8h9t8e8r.
+.ma2n1s2
+.man2s1l2
+.manslaugh3
+.ma8n9u9s8c8r8i8p8t.
+.man2us
+.manusc2
+.manuscri2
+.manuscr2ip
+.manuscri2p1t
+.ma8r9g8i8n9a8l.
+.marg2
+.margi4n
+.margi1na
+.ma8t8h9e9m8a9t8i9c8i8a8n.
+.m4ath3
+.math5em
+.math2e
+.1mathe1ma
+.mathemat1ic
+.mathemat2i1ci
+.mathemati3c2i1a
+.mathematici2a2n
+.ma8t8h9e9m8a9t8i9c8i8a8n8s.
+.mathematicia2n1s2
+.ma8t8t8e8s.
+.mat5te
+.ma4t3t2
+.mat4tes
+.me8d9i8c9a8i8d.
+.2med
+.m4edi
+.med3i1ca
+.medicai2
+.medica2id
+.me8d8i9o8c8r8e.
+.me1d2io
+.mediocre3
+.me8d8i9o8c9r8i9t8i8e8s.
+.medi5ocrit
+.mediocri2
+.medio5cri1ti
+.mediocrit2ie4
+.me8g8a9l8i8t8h.
+.me2g
+.m4egal
+.me1ga
+.me3gal1i
+.megal1it
+.megal2ith
+.me8g8a9l8i8t8h8s.
+.megali2t4h1s2
+.me8t8a9b8o8l9i8c.
+.me4ta
+.me2ta4b
+.metabol3ic
+.metabol2i
+.me9t8a8b9o9l8i8s8m.
+.metaboli2s1m
+.me9t8a8b9o9l8i8s8m8s.
+.metabol4is4m1s2
+.me9t8a8b9o9l8i8t8e.
+.metabo5l2ite
+.metabol1it
+.me9t8a8b9o9l8i8t8e8s.
+.metabolit4es
+.me8t8a9l8a8n9g8u8a8g8e.
+.met3a2l
+.meta5la
+.metala2n
+.metal2ang
+.metalan1gu
+.metalangu4a
+.me8t8a9l8a8n9g8u8a8g8e8s.
+.me8t8a9p8h8o8r9i8c.
+.metapho4r
+.me8t8h9a8n8e.
+.metha2n4
+.me9t8r8o8p9o9l8i8s.
+.m4etr
+.metropol2i
+.me9t8r8o8p9o9l8i8s8e8s.
+.metropol4ise
+.metropolis1e4s
+.me8t9r8o9p8o8l9i9t8a8n.
+.metropol1it
+.metropoli3ta2n
+.metropoli1ta
+.me8t9r8o9p8o8l9i9t8a8n8s.
+.metropolita2n1s2
+.mi8c8r8o9e8c8o9n8o8m8i8c8s.
+.m4i1cr
+.micro4e
+.microe1co
+.microeco2n
+.microeco3nomic
+.microeconomi4c3s2
+.mi9c8r8o9f8i8c8h8e.
+.micro2fi
+.microf4i2ch
+.microfiche2
+.mi9c8r8o9f8i8c8h8e8s.
+.microfich1es
+.mi8c8r8o9o8r8g8a8n9i8s8m.
+.microo2
+.microorg2
+.microor1ga
+.microorgan5is
+.microorga2n
+.microorgani2s1m
+.mi8c8r8o9o8r8g8a8n9i8s8m8s.
+.microorgan4is4m1s2
+.mi8l8l9a8g8e.
+.m4il1l
+.mi8l9l8i9l8i8t8e8r.
+.mill2i
+.mil4l4i4l
+.millil1i
+.mill2il1it
+.millil2ite
+.mi8m8e8o9g8r8a8p8h8e8d.
+.mimeo2g
+.mimeo1gr
+.mimeograph4ed
+.mi8m8e8o9g8r8a8p8h8s.
+.mimeogra4p4h1s2
+.mi8m9i8c9r8i8e8s.
+.mim1i
+.mim4i1cr
+.mimicri2
+.mimicr2ie4
+.mi8n9i8s.
+.m2ini
+.min1is
+.mi8n8i9s8y8m9p8o9s8i8u8m.
+.minis4y
+.minisy4m1p
+.minisym1pos
+.minisympo5si4u
+.mi8n8i9s8y8m9p8o9s8i8a.
+.minisympos2i1a
+.mi9n8u8t9e8r.
+.m4in1u
+.mi9n8u8t9e8s8t.
+.mi8s9c8h8i8e9v8o8u8s9l8y.
+.m2is1c
+.mis3ch2
+.misch2ie4
+.mischievou2
+.mischievo2us
+.mischievous1l2
+.mischievous1ly
+.mi9s8e8r8s.
+.m4ise
+.mis3er
+.mise4r1s2
+.mi9s8o8g9a9m8y.
+.mi2so
+.miso1ga
+.miso2gam
+.mo8d9e8l9l8i8n8g.
+.mo2d1
+.model1l
+.modell2i
+.model2lin4
+.mo8l9e9c8u8l8e.
+.mole1cu
+.mole4cul
+.molecul4e
+.mo8l9e9c8u8l8e8s.
+.molecules2
+.mo8n9a8r8c8h8s.
+.mo1n1a
+.monar3c
+.mon2a2r
+.monar2ch
+.monarc4h1s2
+.mo8n8e8y9l8e8n9d8e8r.
+.moneylen1de
+.mo8n8e8y9l8e8n9d8e8r8s.
+.moneylende4r5s2
+.mo8n8o9c8h8r8o8m8e.
+.mono2ch4
+.monoc4hr4
+.monochro2me
+.mo8n8o9e8n9e8r9g8e8t8i8c.
+.mo3noe
+.monoen1er
+.monoenerg2
+.monoener3get
+.monoenerget1ic
+.mo8n9o8i8d.
+.monoi2
+.mono2id
+.mo8n8o9p8o8l8e.
+.mo4nop
+.mo8n8o9p8o8l8e8s.
+.monopoles2
+.mo9n8o8p9o8l8y.
+.monopo2ly
+.mo8n8o9s8p8l8i8n8e.
+.monos1p2l2
+.monospli4n
+.monosp1l4ine
+.mo8n8o9s8p8l8i8n8e8s.
+.monospli1nes
+.mo8n8o9s8t8r8o8f8i8c.
+.monos5t
+.monost4r
+.monostro2fi
+.mo9n8o8t9o9n8i8e8s.
+.mono1to
+.mo2noto2n
+.monoton2ie4
+.mo9n8o8t9o9n8o8u8s.
+.mono4tono
+.monoto1nou2
+.monotono2us
+.mo9r8o8n9i8s8m.
+.moro5n4is
+.moro2n
+.moroni2s1m
+.mo8s9q8u8i9t8o.
+.mos2
+.mosqu2
+.mosq2ui2
+.mosqui1to
+.mo8s9q8u8i9t8o8s.
+.mosquitos2
+.mo8s9q8u8i9t8o8e8s.
+.mu8d9r8o8o8m.
+.mu1dr
+.mud1room
+.mudroo2
+.mu8d9r8o8o8m8s.
+.mudroo4m1s2
+.mu8l9t8i9f8a8c9e8t8e8d.
+.5mu4lt
+.mul1ti3
+.multif2
+.multi1fa
+.multifa4ce
+.multifacet4
+.multiface2t1ed
+.mu8l9t8i9p8l8i8c9a8b8l8e.
+.mult2ip
+.multi1p2l2
+.multipli1ca
+.multiplica1b2l2
+.mu8l8t8i9u8s8e8r.
+.multi4u
+.multi2us
+.ne8o9f8i8e8l8d8s.
+.3neo
+.ne5of
+.neo2fi
+.neof2ie4
+.neofie2ld3
+.neofiel2d1s2
+.ne8o9n8a8z8i.
+.neo2n
+.neo1n1a
+.neona2z1i
+.ne8o9n8a8z8i8s.
+.neonaz4is
+.ne8p8h9e8w8s.
+.nephe4
+.ne8p8h9r8i8t8e.
+.nep4hr4
+.nephr2ite
+.ne8p8h9r8i8t8i8c.
+.nephr4i2t3ic
+.nephri1ti
+.ne8w9e8s8t.
+.ne4w
+.newest3
+.ne8w8s9l8e8t9t8e8r.
+.news4l2
+.news2le
+.newsle4t3t2
+.ne8w8s9l8e8t9t8e8r8s.
+.newslette4r1s2
+.ni8t8r8o9m8e8t8h9a8n8e.
+.n2it
+.ni3tr
+.nitro2me
+.nitro4met
+.nitrometha2n4
+.no9n8a8m8e.
+.no4n
+.no1n1a
+.no8n9a8r9i8t8h9m8e8t9i8c.
+.nonar3i
+.non2a2r
+.nonar2ith
+.nonarit4h1m
+.nonarithmet4
+.nonarithmet1ic
+.no8n9e8m8e8r9g8e8n8c8y.
+.none1me
+.nonemerg2
+.nonemer1gen
+.nonemergen1cy
+.no8n9e8q8u8i9v8a8r8i9a8n8c8e.
+.none2q
+.nonequ2
+.noneq2ui2
+.nonequ2iv3
+.nonequi1va
+.nonequiv2a2r
+.nonequivar1i
+.nonequivar3i2a2n
+.nonequivar2i3a
+.nonequivar4ianc
+.no8n8e9t8h8e9l8e8s8s.
+.noneth2e
+.nonethe1les2
+.nonethe3l2e2ss
+.no8n9e8u8c8l8i8d9e8a8n.
+.non4eu
+.noneu1c4l4
+.noneucl2id
+.noneuclidea2n
+.no8n9i8s8o9m8o8r9p8h8i8c.
+.non5i
+.non1is
+.noni2so
+.noni3som
+.noniso1mo
+.nonisomo2r
+.nonisomor1p
+.nonisomorp4h4
+.nonisomorph1ic
+.no8n9p8s8e8u8d8o9c8o8m9p8a8c8t.
+.non1p4
+.non2p1s2
+.nonp2se
+.nonps4eu
+.nonpseu1do
+.nonpseudo1co
+.nonpseudoco4m1p
+.nonpseudocom1pa
+.nonpseudocompa2c4t
+.no8n9s8m8o8o8t8h.
+.no2n3s2
+.non2s3m
+.nons1mo
+.nonsmoo2
+.nonsmo4oth
+.no8n9u8n8i9f8o8r8m.
+.no3nu4n
+.nonu1ni
+.nonuni1fo
+.nonunifo2r
+.nonunifor1m
+.no8n9u8n8i9f8o8r8m9l8y.
+.nonunifor4m1l
+.nonuniform1ly
+.no8r9e8p9i9n8e8p8h9r8i8n8e.
+.nore5pi2n
+.norep2ine
+.norepinep4hr4
+.norep2inephr2in4e
+.no8t9w8i8t8h9s8t8a8n8d9i8n8g.
+.notw4
+.notwi2
+.notw2ith3
+.notwi2t4h1s2
+.notwith5st4and
+.notwiths1ta
+.notwithsta2n
+.notwithstand1in
+.nu9c8l8e8o9t8i8d8e.
+.nucle3
+.nu1c4l4
+.nucle4ot
+.nucleot2id
+.nu9c8l8e8o9t8i8d8e8s.
+.nucleotide4s2
+.nu8t9c8r8a8c8k9e8r.
+.nu4tc
+.nutcrack1
+.nutcrack1er
+.nu8t9c8r8a8c8k9e8r8s.
+.nutcracke4r1s2
+.oe8r9s8t8e8d8s.
+.o3er
+.oe4r1s2
+.oers4t1ed
+.oerste2d1s2
+.of8f9l8i8n8e.
+.o4f1f
+.off4l2
+.offlin4
+.off1l4ine
+.of8f9l8o8a8d.
+.offl4oa
+.offloa2d3
+.of8f9l8o8a8d8s.
+.offloa2d1s2
+.of8f9l8o8a8d8e8d.
+.offloa2d1ed
+.ol8i9g8o8p9o9l8i8s8t.
+.ol2i
+.ol2ig
+.oli2go
+.ol2igopol2i
+.ol8i9g8o8p9o9l8i8s8t8s.
+.oligopolis4t1s2
+.ol8i9g8o8p9o8l8y.
+.oligopo2ly
+.ol8i9g8o8p9o8l9i8e8s.
+.oligopol2ie4
+.op9e8r9a8n8d.
+.op1er
+.3oper1a
+.op4er4and
+.opera2n
+.op9e8r9a8n8d8s.
+.operan2d1s2
+.or8a8n8g9u8t8a8n.
+.ora2n
+.or2ang
+.oran1gu
+.oran4gu4t
+.orangu1ta
+.ora2nguta2n
+.or8a8n8g9u8t8a8n8s.
+.oranguta2n1s2
+.or9t8h8o9d8o8n9t8i8s8t.
+.ortho2do4
+.orthodo2n
+.orthodon3t4i
+.orthodon1t
+.or9t8h8o9d8o8n9t8i8s8t8s.
+.orthodontis4t1s2
+.or9t8h8o9k8e8r9a9t8o8l9o8g8y.
+.orth2ok
+.orthok1er
+.orthoker1a
+.orthokera1to
+.orthokeratol1o1gy
+.or8t8h8o9n8i8t8r8o9t8o8l8u8e8n8e.
+.ortho2n
+.orthon2it
+.orthoni3tr
+.orthonitro1to
+.orthonitrotolu3en
+.orthonitrotolu4ene
+.ov8e8r9v8i8e8w.
+.overv2ie4
+.ov8e8r9v8i8e8w8s.
+.ox9i8d9i8c.
+.ox3i
+.oxi5di
+.ox2id
+.pa8d9d8i8n8g.
+.1pa
+.p4a2d
+.pad4d1in
+.pad1d4
+.pa8i8n9l8e8s8s9l8y.
+.p4ai2
+.pa4i4n4
+.pa4i4n1l
+.painles2
+.pain3l2e2ss
+.painles4s1l2
+.painless1ly
+.pa8l9e8t8t8e.
+.p4al
+.p2ale
+.pale4t3t2
+.pa8l9e8t8t8e8s.
+.palet4tes
+.pa8r9a9b8o8l8a.
+.p2a2r
+.pa2rab
+.parabo1la
+.pa8r9a9b8o8l9i8c.
+.parabol3ic
+.parabol2i
+.pa9r8a8b9o9l8o8i8d.
+.paraboloi2
+.parabolo2id
+.pa8r9a9d8i8g8m.
+.para2d
+.par2adi
+.parad2ig
+.paradig1m
+.pa8r9a9d8i8g8m8s.
+.paradig4m1s2
+.pa8r8a9c8h8u8t8e.
+.para2ch
+.parachu4t
+.pa8r8a9c8h8u8t8e8s.
+.pa8r8a9d8i9m8e8t8h8y8l9b8e8n8z8e8n8e.
+.parad4imet
+.paradimethy2l1b
+.paradimethylb4e4n3z
+.paradimethylben2ze
+.paradimethylbenze4n
+.pa8r8a9f8l8u8o8r8o9t8o8l8u8e8n8e.
+.para2f
+.paraf4l2
+.paraflu3o
+.parafluo3r
+.parafluoro1to
+.parafluorotolu3en
+.parafluorotolu4ene
+.pa8r8a9g8r8a8p8h9e8r.
+.para1gr
+.parag5ra3ph4er
+.pa8r8a9l8e9g8a8l.
+.par3al
+.par2ale
+.paral4egal
+.parale1ga
+.pa8r9a8l9l8e8l9i8s8m.
+.paral1l
+.paral2le
+.paral3lel
+.parallel2i
+.paralle2lis
+.paralleli2s1m
+.pa8r8a9m8a8g9n8e8t9i8s8m.
+.par4a1ma
+.param3ag
+.para5mag1n
+.paramagneti2s4m
+.pa8r8a9m8e8d8i8c.
+.para2med
+.param4edi
+.pa8r8a9m8e8t8h8y8l9a8n8i8s8o8l8e.
+.param3et
+.paramethy3la
+.paramethyla2n
+.paramethylani2so
+.pa9r8a8m9e9t8r8i8z8e.
+.param4etr
+.parametri2ze
+.pa8r8a9m8i8l9i9t8a8r8y.
+.par2ami
+.paramil1i
+.param2il1it
+.paramili1ta
+.paramilit2a2r
+.pa8r8a9m8o8u8n8t.
+.para2mo
+.paramou2
+.paramoun1t
+.pa8t8h9o9g8e8n9i8c.
+.p4ath
+.pat4ho
+.patho4g
+.patho1ge4
+.patho1gen
+.pe8e8v9i8s8h.
+.p4ee
+.pee1vi
+.peevis2h
+.pe8e8v9i8s8h9n8e8s8s.
+.peevis2h1n
+.peevish1nes
+.peevishn2e2ss
+.pe8n9t8a9g8o8n.
+.pen1t
+.pen1ta
+.penta2go
+.pentago2n2
+.pe8n9t8a9g8o8n8s.
+.pentago2n3s2
+.pe9t8r8o9l8e9u8m.
+.petrol4eu
+.ph8e9n8o8m9e9n8o8n.
+.ph4e3no
+.phe2n
+.pheno2me
+.pheno1men
+.phenom4eno
+.phenomeno4n
+.ph8e8n8y8l9a8l8a9n8i8n8e.
+.pheny3la
+.phenylala2n
+.phenylala5n2ine
+.phenylalan1in
+.ph8i9l8a8t9e9l8i8s8t.
+.phi4latel2i4
+.philate2lis
+.ph8i9l8a8t9e9l8i8s8t8s.
+.philatelis4t1s2
+.ph8o9n8e8m8e.
+.3phone
+.pho2n
+.phone1me
+.ph8o9n8e8m8e8s.
+.phone2mes
+.ph8o9n8e9m8i8c.
+.phone5mi
+.ph8o8s9p8h8o8r9i8c.
+.phos1p
+.phospho5
+.phospho4r
+.ph8o9t8o9g8r8a8p8h8s.
+.pho1to
+.photo2gr
+.photogra4p4h1s2
+.ph8o9t8o9o8f8f9s8e8t.
+.photoo2
+.photoo4f1f
+.photoof2f3s
+.pi8c9a9d8o8r.
+.pi1ca
+.pica2d
+.pica1do
+.picad4or
+.pi8c9a9d8o8r8s.
+.picado4rs2
+.pi8p8e9l8i8n8e.
+.p2ip
+.pipe4
+.pipel2i
+.pipe1l4ine
+.pi8p8e9l8i8n8e8s.
+.pipeli1nes
+.pi8p8e9l8i8n9i8n8g.
+.pipel2in3i
+.pipelin1in
+.pipelinin4g
+.pi9r8a9n8h8a8s.
+.p4ir
+.pi1ra
+.pira2n
+.pira4n1h4
+.piranha4
+.pl8a8c8a9b8l8e.
+.1p2l2
+.pla1ca
+.placa1b2l2
+.pl8a8n8t9h8o8p9p8e8r.
+.3pla2n
+.plan1t
+.plantho4p1p
+.planthop2pe
+.planthop3per
+.pl8a8n8t9h8o8p9p8e8r8s.
+.planthoppe4r1s2
+.pl8e8a8s9a8n8c8e.
+.ple2a
+.pleasa2
+.plea3sanc
+.pleasa2n
+.pl8u8g9i8n.
+.plug5in
+.pl8u8g9i8n8s.
+.plu5g4i2n1s2
+.po8l9t8e8r9g8e8i8s8t.
+.po4l2t
+.pol1te
+.polterg2
+.poltergei2
+.po8l8y9e8n8e.
+.po2ly
+.po8l8y9e8t8h9y8l9e8n8e.
+.polye4t
+.po9l8y8g9a9m8i8s8t.
+.poly1ga
+.poly2gam
+.polygam2is
+.po9l8y8g9a9m8i8s8t8s.
+.polygamis4t1s2
+.po8l8y8g9o8n9i9z8a9t8i8o8n.
+.poly1go
+.polygo2n2
+.polygo3ni
+.polygoniza1
+.polygoniza1t2io
+.polygonizatio2n
+.po9l8y8p8h9o9n8o8u8s.
+.polypho2n
+.polypho1nou2
+.polyphono2us
+.po8l8y9s8t8y8r8e8n8e.
+.po2lys4
+.polys1t
+.polys2ty
+.po8m8e9g8r8a8n9a8t8e.
+.po2me
+.pome2g
+.pome1gr
+.pomegra2n2
+.pomegra1na
+.pomegran2at
+.po8r8o9e8l8a8s9t8i8c.
+.1p4or
+.poro4e
+.poro4el
+.poroe1la
+.poroelast2i
+.poroelas1tic
+.po8r9o8u8s.
+.porou2
+.poro2us
+.po8r9t8a9b8l8e.
+.por1ta
+.por2tab
+.portab2l2
+.po8s8t9a8m9b8l8e.
+.1pos
+.pos2ta
+.posta4m1b
+.postamb2l2
+.po8s8t9a8m9b8l8e8s.
+.postambles2
+.po8s8t9h8u9m8o8u8s.
+.posthu1mo
+.posthu3mo2us
+.posthumou2
+.po8s8t9s8c8r8i8p8t.
+.pos4t1s2
+.post4sc
+.postscri2
+.postscr2ip
+.postscri2p1t
+.po8s8t9s8c8r8i8p8t8s.
+.postscrip4t1s2
+.po8s9t8u8r9a8l.
+.pos1tu
+.postu1ra
+.pr8e9a8m9b8l8e.
+.prea4m1b
+.preamb2l2
+.pr8e9a8m9b8l8e8s.
+.preambles2
+.pr8e9l8o8a8d8e8d.
+.prel4oa
+.preloa2d3
+.preloa2d1ed
+.pr8e9p8a8r9i8n8g.
+.pre2pa
+.prep4a4r1i
+.prep2a2r
+.preparin4g
+.pr8e9p8r8i8n8t.
+.pr2epr2
+.preprin4t3
+.pr8e9p8r8i8n8t8s.
+.preprin4t4s2
+.pr8e9p8r8o8c8e8s9s8o8r.
+.pre3pro
+.prepr2oc
+.prepro1ce
+.preproc2e2ss
+.preproces1so
+.pr8e9p8r8o8c8e8s9s8o8r8s.
+.preprocesso4rs2
+.pr8e9s8p8l8i8t9t8i8n8g.
+.pre1sp
+.pres1p2l2
+.prespl1it
+.prespl4i4t3t2
+.presplit2t1in
+.pr8e9w8r8a8p.
+.prewra4
+.pr8e9w8r8a8p8p8e8d.
+.prewra4p1p
+.prewrap2pe
+.prewrap4p2ed
+.pr8i8e8s8t9e8s8s8e8s.
+.5pr2i4e4
+.pri1est
+.pries4t2e2ss
+.priestess1e4s
+.pr8e8t9t8y9p8r8i8n9t8e8r.
+.pre4t3t2
+.pret1ty
+.pr2ettypr2
+.prettyprin4t3
+.pr8e8t9t8y9p8r8i8n9t8i8n8g.
+.prettyprint2i
+.prettyprin4t3ing
+.prettyprin2t1in
+.pr8o9c8e9d8u8r9a8l.
+.pr2oc
+.pro1ce
+.proce1du
+.procedu1ra
+.pr8o8c8e8s8s.
+.proc2e2ss
+.pr8o9c8u8r9a8n8c8e.
+.procu1ra
+.procura2n
+.pr8o8g9e9n8i8e8s.
+.pro1ge
+.pro1gen
+.proge5n2ie4
+.pr8o8g9e9n8y.
+.pro4geny
+.pr8o9g8r8a8m9m8a8b8l8e.
+.pro1gr
+.program1m
+.program1ma
+.program2mab
+.programmab2l2
+.pr8o8m9i9n8e8n8t.
+.prom4i
+.prom1in
+.prom2ine
+.promi1nen
+.prominen1t
+.pr8o9m8i8s9c8u9o8u8s.
+.prom2is
+.prom2is1c
+.promis1cu
+.promiscu1ou2
+.promiscuo2us
+.pr8o8m9i8s9s8o8r8y.
+.prom4i2s1s
+.promis1so
+.promisso1ry
+.pr8o8m9i8s8e.
+.prom4ise
+.pr8o8m9i8s8e8s.
+.promis1e4s
+.pr8o9p8e8l9l8e8r.
+.pro3pel
+.propel1l
+.propel2le
+.pr8o9p8e8l9l8e8r8s.
+.propelle4r1s2
+.pr8o9p8e8l9l8i8n8g.
+.propell2i
+.propel2lin4
+.pr8o9h8i8b9i9t8i8v8e.
+.pro1h2
+.prohibi2t
+.prohibi1ti
+.prohibi1t2iv
+.pr8o9h8i8b9i9t8i8v8e9l8y.
+.prohibitiv4e1ly
+.pr8o9s8c8i8u8t9t8o.
+.pros2c
+.pros1ci
+.prosci1u
+.prosciu4t3t2
+.prosciut5to
+.pr8o9t8e8s8t9e8r.
+.pro1t
+.pro4tes
+.pr8o9t8e8s8t9e8r8s.
+.proteste4r1s2
+.pr8o9t8e8s9t8o8r.
+.prot4es2to
+.pr8o9t8e8s9t8o8r8s.
+.protesto4rs2
+.pr8o9t8o9l8a8n9g8u8a8g8e.
+.pro1to
+.proto1la
+.proto4la2n
+.protol2ang
+.protolan1gu
+.protolangu4a
+.pr8o9t8o9t8y8p9a8l.
+.proto1ty
+.prototy1pa
+.prototyp4al
+.pr8o8v9i8n8c8e.
+.prov1in
+.prov2inc
+.pr8o8v9i8n8c8e8s.
+.pr8o9v8i8n9c8i8a8l.
+.provin1ci
+.provin3c2i1a
+.provinci2al
+.pr8o8w9e8s8s.
+.prow2e2ss
+.ps8e8u9d8o9d8i8f9f8e8r9e8n9t8i8a8l.
+.2p1s2
+.p2se
+.ps4eu
+.pseu1do
+.pseudod1if
+.pseudodi4f1f
+.pseudodiffer1
+.pseudodiffer3en1t
+.pseudodifferent2i
+.pseudodifferen1t2i1a
+.pseudodifferenti2al
+.ps8e8u9d8o9f8i9n8i8t8e.
+.pseu2d5of
+.pseudo2fi
+.pseudo2fin
+.pseudof2ini
+.pseudofin2it
+.pseudofin2ite
+.ps8e8u9d8o9f8i9n8i8t8e9l8y.
+.pseudofinite1ly
+.ps8e8u9d8o9f8o8r8c8e8s.
+.pseudo1fo
+.pseudofo2r
+.pseudofor1c
+.pseudofor2ce
+.ps8e8u9d8o8g9r8a9p8h8e8r.
+.pseud4og
+.pseudo1gr
+.pseudog5ra3ph4er
+.ps8e8u9d8o9g8r8o8u8p.
+.pseudo4g4ro
+.pseudogrou2
+.ps8e8u9d8o9g8r8o8u8p8s.
+.pseudogrou2p1s2
+.ps8e8u9d8o9n8y8m.
+.pseu4do2n
+.pseudonym4
+.ps8e8u9d8o9n8y8m8s.
+.pseudony4m1s2
+.ps8e8u9d8o9w8o8r8d.
+.pseudo4wo2
+.ps8e8u9d8o9w8o8r8d8s.
+.pseudowor2d1s2
+.ps8y9c8h8e9d8e8l9i8c.
+.ps4y
+.p4sy1c
+.psy3ch
+.psych4e2
+.psy4ch4ed
+.psychedel2i
+.ps8y8c8h8s.
+.psyc4h1s2
+.pu9b8e8s9c8e8n8c8e.
+.pub3
+.pub4e
+.pu4bes4
+.pubes2c
+.pubes1cen
+.pubes3cenc
+.qu8a8d9d8i8n8g.
+.qu2
+.qua2d
+.quad4d1in
+.quad1d4
+.qu8a9d8r8a8t9i8c.
+.qua1dr
+.quadrat1ic
+.qu8a9d8r8a8t9i8c8s.
+.quadrati4c3s2
+.qu8a8d9r8a9t8u8r8e.
+.quadra2tu
+.quadra3ture
+.qu8a8d9r8i9p8l8e8g9i8c.
+.quadri2p2l2
+.quadr2ip
+.quadripleg4ic
+.qu8a8i8n8t9e8r.
+.quai2
+.qua4i4n
+.quain1t
+.qu8a8i8n8t9e8s8t.
+.qu8a9s8i9e8q8u8i8v9a9l8e8n8c8e.
+.quas2ie4
+.quasie1q
+.qu2asiequ2
+.quasieq2ui2
+.quasiequ2iv3
+.quasiequi1va
+.quasiequiv2ale
+.quasiequiva3lenc
+.qu8a9s8i9e8q8u8i8v9a9l8e8n8c8e8s.
+.qu8a9s8i9e8q8u8i8v9a9l8e8n8t.
+.quasiequiva1len1t
+.qu8a9s8i9h8y9p8o9n8o8r9m8a8l.
+.quasi3h
+.quasihy3po
+.quasihypo2n
+.quasihyponor1m
+.quasihyponor1ma
+.qu8a9s8i9r8a8d9i9c8a8l.
+.quas4i2r
+.quasi1r5a
+.quasira2d
+.quasir2adi
+.quasirad3i1ca
+.qu8a9s8i9r8e8s8i8d9u8a8l.
+.quasi4res
+.quasire1si
+.quasire2s2id
+.quasiresi2du
+.quasiresid1u1a
+.qu8a9s8i9s8m8o8o8t8h.
+.qua1sis
+.quasi2s1m
+.quasis1mo
+.quasismoo2
+.quasismo4oth
+.qu8a9s8i9s8t8a9t8i8o8n9a8r8y.
+.quasis1ta
+.quasistation5a2r
+.quasista1t2io
+.quasistatio2n
+.quasistatio1n1a
+.qu8a9s8i9t8o8p8o8s.
+.qu5a5si4t
+.quasi1to
+.quasito1pos
+.qu8a9s8i9t8r8i9a8n9g8u9l8a8r.
+.quasi5tr2i3a
+.quasitri2a2n
+.quasitri2ang
+.quasitrian1gu
+.quasitriangu1la
+.quasitriangul2a2r
+.qu8a9s8i9t8r8i8v9i8a8l.
+.quasitr2i4v
+.quasitriv3i
+.quasitriv2i1a
+.quasitrivi2al
+.qu8i8n9t8e8s9s8e8n8c8e.
+.q2ui2
+.qui4n
+.quin1t
+.quin4t2e2ss
+.quintes4senc
+.qu8i8n9t8e8s9s8e8n8c8e8s.
+.qu8i8n9t8e8s9s8e8n9t8i8a8l.
+.quintessen1t
+.quintessent2i
+.quintessen1t2i1a
+.quintessenti2al
+.ra8b9b8i8t9r8y.
+.2rab
+.ra2b1b
+.rabbi2t
+.rabbi3tr
+.rabbit5ry
+.ra9d8i9o8g9r8a9p8h8y.
+.ra2d
+.r2adi
+.ra3d2io
+.radio5g
+.radio2gr
+.radio4g3ra1phy
+.ra8f8f9i8s8h.
+.raf5fi
+.ra2f
+.ra4f1f4
+.raf2f5is
+.raffis2h
+.ra8f8f9i8s8h9l8y.
+.raffis4h1l4
+.raffish1ly
+.ra8m9s8h8a8c8k8l8e.
+.ra4m1s2
+.ram4s2h
+.ramshack1
+.ramshack1l
+.ra8v9e8n9o8u8s.
+.rav4e4no
+.rave1nou2
+.raveno2us
+.re9a8r8r8a8n8g8e9m8e8n8t.
+.re5ar1r4
+.re2a2r
+.rearran4ge
+.rearra2n
+.rearr2ang
+.rearrange1me
+.rearrange1men
+.rearrange3men1t
+.re9a8r8r8a8n8g8e9m8e8n8t8s.
+.rearrangemen4t4s2
+.re8c9i9p8r8o8c9i9t8i8e8s.
+.reciproci1ti
+.reciprocit2ie4
+.re8c9t8a8n9g8l8e.
+.rec4ta2n
+.re2c1t
+.rect5ang
+.rec1ta
+.rectan1gl2
+.rectan1gle
+.re8c9t8a8n9g8l8e8s.
+.rectangles2
+.re8c9t8a8n9g8u9l8a8r.
+.rectan1gu
+.rectangu1la
+.rectangul2a2r
+.re9d8i9r8e8c8t.
+.2r2ed
+.r4edi
+.red4ir2
+.redi1re
+.redire2c1t
+.re9d8i9r8e8c8t9i8o8n.
+.redirec1t2io
+.redirectio2n
+.re9d8u8c9i8b8l8e.
+.re1du
+.redu2c
+.reduci4b
+.redu1ci
+.reduci1b2l2
+.re9e8c8h8o.
+.ree2c
+.ree2ch
+.ree3cho2
+.re9p8h8r8a8s8e.
+.rep4hr4
+.rephr2as
+.re9p8h8r8a8s8e8s.
+.rephras1e4s
+.re9p8h8r8a8s8e8d.
+.rephra4s4ed
+.re9p8o9s8i9t8i8o8n.
+.re4posi
+.re1po
+.re1pos
+.repo3s2i1t2io
+.reposi1ti
+.repositio2n
+.re9p8o9s8i9t8i8o8n8s.
+.repositio2n3s2
+.re9p8r8i8n8t.
+.repr2
+.reprin4t3
+.re9p8r8i8n8t8s.
+.reprin4t4s2
+.re9s8t8o8r9a8b8l8e.
+.r4es2to
+.resto2ra
+.resto2rab
+.restorab2l2
+.re8t8r8o9f8i8t.
+.retro2fi
+.re8t8r8o9f8i8t9t8e8d.
+.retrof4i4t4t2
+.retrofit2t1ed
+.re9u8s9a8b8l8e.
+.r4eu2
+.re2us4
+.reusa2
+.reu2s1ab
+.reusab2l2
+.re9u8s8e.
+.re9w8i8r8e.
+.rewi2
+.rew4ir4
+.re9w8r8a8p.
+.rewra4
+.re9w8r8a8p8p8e8d.
+.rewra4p1p
+.rewrap2pe
+.rewrap4p2ed
+.re9w8r8i8t8e.
+.rewri4
+.rewr2ite
+.rh8i9n8o8c9e8r9o8s.
+.rh4
+.rh2i1no
+.rhi4no4c
+.rhino1ce
+.rhinoc2ero
+.ri8g8h8t9e8o8u8s.
+.righ1teo
+.righteou2
+.righteo2us
+.ri8g8h8t9e8o8u8s9n8e8s8s.
+.righteous1n4
+.righteous1nes
+.righteousn2e2ss
+.ri8n8g9l8e8a8d8e8r.
+.rin4g
+.ringl2
+.rin1gle
+.ringle2a
+.ringlea2d1
+.ri8n8g9l8e8a8d8e8r8s.
+.ringleade4r5s2
+.ro9b8o8t.
+.ro9b8o8t8s.
+.robo4t1s2
+.ro9b8o8t8i8c.
+.ro9b8o8t9i8c8s.
+.roboti4c3s2
+.ro8u8n8d9t8a8b8l8e.
+.rou2
+.roun2d
+.round1ta
+.round2tab
+.roundtab2l2
+.ro8u8n8d9t8a8b8l8e8s.
+.roundta5bles2
+.sa8l8e8s9c8l8e8r8k.
+.sa2
+.s2ale
+.sales2
+.sales2c
+.salescle5
+.sales1c4l4
+.sa8l8e8s9c8l8e8r8k8s.
+.salescler4k1s2
+.sa8l8e8s9w8o8m8a8n.
+.sales4w2
+.sale4s1wo2
+.saleswom1
+.saleswo1ma
+.saleswoma2n
+.sa8l8e8s9w8o8m8e8n.
+.saleswo2me
+.saleswo1men
+.sa8l9m8o9n8e8l9l8a.
+.s4a2l4m
+.salmo2n4
+.sal1mo
+.salmon4ella
+.salmonel1l
+.sa8l9t8a9t8i8o8n.
+.sa4l4t
+.sal1ta
+.salta1t2io
+.saltatio2n
+.sa8r9s8a9p8a8r9i8l9l8a.
+.s2a2r
+.sa2r4sa2
+.sa4rs2
+.sars1ap
+.s2a2rsap2a2r4
+.sarsa1pa
+.sarsap4a4r1i
+.sarsaparil1l
+.sa8u8e8r9k8r8a8u8t.
+.sau4
+.sauerkrau4t
+.sc8a8t9o9l8o8g9i9c8a8l.
+.s1ca
+.sca1to
+.scato3log1ic
+.scatologi1ca
+.sc8h8e8d9u8l9i8n8g.
+.s2ch2
+.sche2
+.s4ch4ed
+.sche4dul
+.sche1du
+.schedul2i
+.schedul3ing
+.sc8h8i8z9o9p8h8r8e8n8i8c.
+.schi2z
+.schi1zo
+.schiz2oph
+.schizop4hr4
+.sc8h8n8a8u9z8e8r.
+.sc2h1n
+.sch1na
+.schn2au
+.schnau2z4e
+.schnauz1er
+.sc8h8o8o8l9c8h8i8l8d.
+.s4cho2
+.schoo2
+.schoo4l1c2
+.s2chool2ch
+.schoolch4il2
+.schoolchi2ld
+.sc8h8o8o8l9c8h8i8l8d9r8e8n.
+.schoolchil3dr
+.schoolchildre4
+.schoolchil5dren
+.sc8h8o8o8l9t8e8a8c8h8e8r.
+.schoo4l2t
+.school1te
+.s2chooltea2ch
+.schoolteache2
+.sc8h8o8o8l9t8e8a8c8h9e8r8s.
+.schoolteach3e4r1s2
+.sc8r8u9t8i9n8y.
+.scru2t1i5n
+.scr4u1t2i
+.scrut4iny
+.sc8y8t8h9i8n8g.
+.s1cy
+.scy3thin
+.se8l8l9e8r.
+.sel2le
+.se8l8l9e8r8s.
+.selle4r1s2
+.se8c9r8e9t8a8r9i8a8t.
+.se1cr
+.se4c3re1ta
+.secret2a2r
+.secretar1i
+.secretar2i3a
+.se8c9r8e9t8a8r9i8a8t8s.
+.secretaria4t1s2
+.se8m9a9p8h8o8r8e.
+.se1ma
+.se4map
+.semapho4r
+.se8m9a9p8h8o8r8e8s.
+.se9m8e8s9t8e8r.
+.4se1me
+.se2mes
+.se8m8i9d8e8f9i9n8i8t8e.
+.sem2id
+.semide1f
+.semidef5i5n2ite
+.semide1fi
+.semide2fin
+.semidef2ini
+.semidefin2it
+.se8m8i9d8i9r8e8c8t.
+.semi2di
+.semid4ir2
+.semidi1re
+.semidire2c1t
+.se8m8i9h8o9m8o9t8h8e8t9i8c.
+.semi3h
+.semiho1mo
+.semihom4oth3
+.semihomoth2e
+.semihomo3the4t
+.semihomothet1ic
+.se8m8i9r8i8n8g.
+.sem4ir
+.semir1i
+.semirin4g
+.se8m8i9r8i8n8g8s.
+.semirings2
+.se8m8i9s8i8m9p8l8e.
+.se4m2is
+.semisi4m1p
+.semisim1p2l2
+.se8m8i9s8k8i8l8l8e8d.
+.sem4is4k2
+.semisk1i
+.semisk4il1l
+.semiskil2le
+.se8r8o9e8p8i9d8e9m8i9o9l8o8g9i9c8a8l.
+.s2er4o
+.sero4e
+.seroep4id
+.seroepi3de
+.seroepid4em
+.seroepidem2io
+.seroepidemi1ol
+.seroepidemio3log1ic
+.seroepidemiologi1ca
+.se8r9v8o9m8e8c8h9a8n8i8s8m.
+.4ser3vo
+.servo2me
+.servome2ch
+.servomech5a5nis
+.servomecha2n
+.servomechani2s1m
+.se8r9v8o9m8e8c8h9a8n8i8s8m8s.
+.servomechan4is4m1s2
+.se8s9q8u8i9p8e9d8a9l8i8a8n.
+.s1e4s
+.sesqu2
+.sesq2ui2
+.sesqu2ip
+.sesquipe4
+.sesqui2p2ed
+.sesquip2e2d2a
+.sesquipedal1i
+.sesquipedal2i1a
+.sesquipedali2a2n
+.se8t9u8p.
+.se1tu
+.se8t9u8p8s.
+.setu2p1s2
+.se9v8e8r8e9l8y.
+.5sev
+.sev1er
+.sev4erel
+.severe1ly
+.sh8a8p8e9a8b8l8e.
+.sha3pe4a
+.shape1a4b
+.shapeab2l2
+.sh8o8e9s8t8r8i8n8g.
+.sho4
+.sho2est4r
+.shoestrin4g
+.sh8o8e9s8t8r8i8n8g8s.
+.shoestrings2
+.si8d8e9s8t8e8p.
+.5side4s2
+.s2id
+.sideste4p
+.si8d8e9s8t8e8p8s.
+.sideste2p1s2
+.si8d8e9s8w8i8p8e.
+.sides4w2
+.sideswi2
+.sidesw2ip
+.sideswipe4
+.sk8y9s8c8r8a8p8e8r.
+.sk2
+.skys4c
+.skyscrap3er
+.sk8y9s8c8r8a8p8e8r8s.
+.skyscrape4r1s2
+.sm8o8k8e9s8t8a8c8k.
+.2s1m
+.s1mo
+.s4m2ok
+.smokes4
+.smokes1ta
+.smokestack1
+.sm8o8k8e9s8t8a8c8k8s.
+.smokestac4k1s2
+.sn8o8r9k8e8l9i8n8g.
+.s1n4
+.snorke5l2i
+.snorke4l3ing
+.so9l8e9n8o8i8d.
+.1so
+.sol4eno
+.solenoi2
+.soleno2id
+.so9l8e9n8o8i8d8s.
+.solenoi2d1s2
+.so8l8u8t8e.
+.so1lut
+.so8l8u8t8e8s.
+.so8v9e8r9e8i8g8n.
+.4sov
+.soverei2
+.sovere2ig2
+.so8v9e8r9e8i8g8n8s.
+.sovereig2n1s2
+.sp8a9c8e8s.
+.2s1pa
+.spa4ce
+.sp8e9c8i8o8u8s.
+.spe2c
+.spe1c2i
+.spec2io
+.speciou2
+.specio2us
+.sp8e8l8l9e8r.
+.spel1l
+.spel2le
+.sp8e8l8l9e8r8s.
+.spelle4r1s2
+.sp8e8l8l9i8n8g.
+.spell2i
+.spel2lin4
+.sp8e9l8u8n8k9e8r.
+.spelu4nk2
+.spelunk1er
+.sp8e8n8d9t8h8r8i8f8t.
+.spen4d
+.spend2th
+.spendt4hr4
+.spendthr4i2ft
+.sp8h8e8r9o8i8d.
+.s2phe
+.3sph4er
+.sph2ero
+.spheroi2
+.sphero2id
+.sp8h8e8r9o8i8d9a8l.
+.spheroi1d2a
+.sp8h8i8n9g8e8s.
+.sph5ing
+.sph4inge
+.sp8i8c9i9l8y.
+.sp2i1ci
+.spici1ly
+.sp8i8n9o8r8s.
+.spi2n
+.sp4i1no
+.spino4rs2
+.sp8o8k8e8s9w8o8m8a8n.
+.sp2ok
+.spokes4
+.spokes4w2
+.spoke4s1wo2
+.spokeswom1
+.spokeswo1ma
+.spokeswoma2n
+.sp8o8k8e8s9w8o8m8e8n.
+.spokeswo2me
+.spokeswo1men
+.sp8o8r8t8s9c8a8s8t.
+.s1p4or4
+.spor4t1s2
+.sport4sc
+.sports1ca
+.sp8o8r8t8s9c8a8s8t9e8r.
+.sportscast5er
+.sp8o8r9t8i8v8e9l8y.
+.spor1ti
+.spor4t2iv
+.sportiv4e1ly
+.sp8o8r8t8s9w8e8a8r.
+.sport4sw2
+.sportswe2a2r
+.sp8o8r8t8s9w8r8i8t8e8r.
+.sportswri4
+.sportswr2ite
+.sp8o8r8t8s9w8r8i8t8e8r8s.
+.sportswrit5e4r1s2
+.sp8r8i8g8h8t9l8i8e8r.
+.spr2
+.spr2ig
+.sprigh2tl
+.sprightl2ie4
+.sq8u8e8a9m8i8s8h.
+.squ2
+.squeam2is
+.squeamis2h
+.st8a8n8d9a8l8o8n8e.
+.5st4and
+.sta2n
+.stan1d2a
+.standalo2n
+.st8a8r9t8l8i8n8g.
+.st2a2r
+.star2tl
+.st8a8r9t8l8i8n8g9l8y.
+.startlingl2
+.startling1ly
+.st8a9t8i8s9t8i8c8s.
+.statis1t2i
+.statis1tic
+.statisti4c3s2
+.st8e8a8l8t8h9i8l8y.
+.stea4l
+.stea4lt
+.stealth3i
+.steal4th4il2
+.stealthi1ly
+.st8e8e8p8l8e9c8h8a8s8e.
+.s1tee
+.stee4p1
+.stee1p2l2
+.steeple2ch
+.st8e8r8e8o9g8r8a8p8h9i8c.
+.stere1o
+.stereo2g
+.stereo1gr
+.stereo5graph1ic
+.stereogr4aphi
+.st8o9c8h8a8s9t8i8c.
+.s1to
+.sto2ch4
+.stochast2i
+.stochas1tic
+.st8r8a8n8g8e9n8e8s8s.
+.st4r
+.s1tra
+.stran4ge
+.stra2n
+.str2ang
+.strange4n4e
+.stran1gen
+.strange1nes
+.strangen2e2ss
+.st8r8a8p9h8a8n8g8e8r.
+.straph2an4g
+.straphang5er
+.strapha2n
+.st8r8a8t9a9g8e8m.
+.stra2ta
+.st8r8a8t9a9g8e8m8s.
+.stratage4m1s2
+.st8r8e8t8c8h9i9e8r.
+.stre4tc
+.stret4ch
+.stretch2ie4
+.st8r8i8p9t8e8a8s8e.
+.str2ip
+.stri2p1t
+.strip2te
+.st8r8o8n8g9h8o8l8d.
+.stro2n
+.strongho2l2d
+.st8r8o8n8g9e8s8t.
+.st8u9p8i8d9e8r.
+.s1tu
+.stup4id
+.stupi3de
+.st8u9p8i8d9e8s8t.
+.stupide4s2
+.su8b9d8i8f9f8e8r9e8n9t8i8a8l.
+.1su
+.su4b3
+.su4b1d
+.subd1if
+.subdi4f1f
+.subdiffer1
+.subdiffer3en1t
+.subdifferent2i
+.subdifferen1t2i1a
+.subdifferenti2al
+.su8b9e8x9p8r8e8s9s8i8o8n.
+.sub4e
+.sub1ex3p
+.subexpr2
+.subex3pr2e2ss
+.subexpres1si
+.subexpres1s2io
+.subexpres5sio2n
+.su8b9e8x9p8r8e8s9s8i8o8n8s.
+.subexpressio2n3s2
+.su8m9m8a9b8l8e.
+.su2m
+.sum1m
+.sum1ma
+.sum2mab
+.summab2l2
+.su8p8e8r9e8g8o.
+.su1pe
+.supere1go
+.su8p8e8r9e8g8o8s.
+.supere4gos
+.su9p8r8e8m9a9c8i8s8t.
+.supr2
+.supre4mac
+.supre1ma
+.suprem4a2ci
+.su9p8r8e8m9a9c8i8s8t8s.
+.supremacis4t1s2
+.su8r9v8e8i8l9l8a8n8c8e.
+.su2r
+.surv4e
+.survei2
+.surveil1l
+.surveilla2n
+.sw8i8m9m8i8n8g9l8y.
+.sw2
+.swi2
+.swim1m
+.swimm4ingl2
+.swimm5ing1ly
+.sy8m8p9t8o9m8a8t8i8c.
+.sy4m1p
+.sym2p1t
+.symp1to
+.sympto2ma
+.symptomat1ic
+.sy8n9c8h8r8o9m8e8s8h.
+.syn3c4hr4
+.syn2ch
+.synchro2me
+.synchro2mes
+.synchrom4es2h
+.sy8n9c8h8r8o9n8o8u8s.
+.synchro2n
+.synchro1nou2
+.synchrono2us
+.sy8n9c8h8r8o9t8r8o8n.
+.synchrotro2n
+.ta8f8f9r8a8i8l.
+.4ta2f4
+.ta4f1f4
+.taffr2ai2
+.ta8l8k9a9t8i8v8e.
+.ta2l
+.4talk
+.talka3
+.talka4t
+.talka1t2iv
+.ta9p8e8s9t8r8y.
+.tap2est4r
+.tape4stry
+.ta9p8e8s9t8r8i8e8s.
+.tapestr2ie4
+.ta8r9p8a8u9l8i8n.
+.t2a2r
+.tar2p
+.tar1pa
+.tarpau4l2
+.tarpaul2i
+.ta8r9p8a8u9l8i8n8s.
+.tarpaul2i2n1s2
+.te9l8e8g9r8a9p8h8e8r.
+.tele1gr
+.teleg5ra3ph4er
+.te9l8e8g9r8a9p8h8e8r8s.
+.telegraphe4r1s2
+.te8l8e9k8i9n8e8t9i8c.
+.teleki4n
+.telek1i
+.telek2ine
+.teleki3net1ic
+.te8l8e9k8i9n8e8t9i8c8s.
+.telekineti4c3s2
+.te8l8e9r8o9b8o8t9i8c8s.
+.te4l1er
+.tel4ero
+.teler5ob
+.teleroboti4c3s2
+.te8l8l9e8r.
+.tel1l
+.tel2le
+.te8l8l9e8r8s.
+.telle4r1s2
+.te8m9p8o9r8a8r9i8l8y.
+.te4m1p
+.tem1p4or
+.tempo1ra
+.tempo4raril
+.tempor2a2r
+.temporar1i
+.temporari1ly
+.te8n9u8r8e.
+.te8s8t9b8e8d.
+.tes2t1b
+.test4be2d
+.te8x8t9w8i8d8t8h.
+.3tex
+.tex1t2
+.textw4
+.textwi2
+.textw2id
+.textwid2th
+.th8a8l9a9m8u8s.
+.tha3la
+.thala3m
+.thala1mu
+.thalam2us
+.th8e8r9m8o9e8l8a8s9t8i8c.
+.th2e
+.ther3m4
+.ther1mo
+.thermo4el
+.thermoe1la
+.thermoelast2i
+.thermoelas1tic
+.ti8m8e9s8t8a8m8p.
+.ti2mes
+.times1ta
+.timesta4m1p
+.ti8m8e9s8t8a8m8p8s.
+.timestam2p1s2
+.to8o8l9k8i8t.
+.too2
+.toolk1i
+.to8o8l9k8i8t8s.
+.toolki4t1s2
+.to8p8o9g8r8a8p8h9i9c8a8l.
+.to5po4g
+.topo1gr
+.topo5graph1ic
+.topogr4aphi
+.topographi1ca
+.to8q8u8e8s.
+.to1q
+.toqu2
+.tr8a8i9t8o8r9o8u8s.
+.1tra
+.tr2ai2
+.trai1to
+.traitorou2
+.traitoro2us
+.tr8a8n8s9c8e8i8v8e8r.
+.tra2n
+.tra2n1s2
+.trans4c
+.tran4s3cei2
+.transce2iv
+.tr8a8n8s9c8e8i8v8e8r8s.
+.transceive4r1s2
+.tr8a8n8s9g8r8e8s8s.
+.tran2s3g
+.trans1gr
+.transgr2e2ss
+.tr8a8n8s9v8e8r9s8a8l.
+.tran4sv
+.transve4r1s2
+.transver1sa2
+.tr8a8n8s9v8e8r9s8a8l8s.
+.transversa2l1s2
+.tr8a8n8s9v8e8s9t8i8t8e.
+.transv4e2s
+.transvest2i
+.transvest2ite
+.tr8a8n8s9v8e8s9t8i8t8e8s.
+.transvestit4es
+.tr8a9v8e8r8s9a9b8l8e.
+.trave4r1s2
+.traver1sa2
+.traver2s1ab
+.traversab2l2
+.tr8a9v8e8r9s8a8l.
+.tr8a9v8e8r9s8a8l8s.
+.traversa2l1s2
+.tr8i9e8t8h8y8l9a8m8i8n8e.
+.tri5et
+.tr2ie4
+.triethy3la
+.triethylam1in
+.triethylam2ine
+.tr8e8a8c8h9e8r8i8e8s.
+.trea2ch
+.treache2
+.treacher1i
+.treacher2ie4
+.tr8o8u9b8a9d8o8u8r.
+.trou2
+.trouba2d
+.trouba1do
+.troubadou2
+.tu8r9k8e8y.
+.1tu
+.tu8r9k8e8y8s.
+.turkeys4
+.tu8r8n9a8r8o8u8n8d.
+.tur4n2a2r
+.tur1na
+.turnarou2
+.turnaroun2d
+.tu8r8n9a8r8o8u8n8d8s.
+.turnaroun2d1s2
+.ty8p9a8l.
+.1ty
+.ty1pa
+.typ4al
+.un9a8t9t8a8c8h8e8d.
+.un2at4
+.una4t3t2
+.unat1ta
+.unatta2ch
+.unattache2
+.unatta4ch4ed
+.un9e8r8r9i8n8g9l8y.
+.un4er
+.uner4r4
+.unerrin4g
+.unerringl2
+.unerring1ly
+.un9f8r8i8e8n8d9l8y.
+.un3f
+.unfri2
+.unfr2ie4
+.unfrien2d1ly
+.un9f8r8i8e8n8d9l8i9e8r.
+.unfriendl2ie4
+.va8g8u8e8r.
+.1va
+.vag4
+.va5guer
+.va2gue
+.va8u8d8e9v8i8l8l8e.
+.vaude1v4
+.vaude2v3i4l
+.vaude1vi
+.vaudevil1l
+.vaudevil2le
+.vi8c9a8r8s.
+.v4ic2a2r
+.vi1ca
+.vica4rs2
+.vi8l9l8a8i8n9e8s8s.
+.2vil
+.vil1l
+.villai2
+.villa4i4n
+.villa2ine
+.villai5n2e2ss
+.villai1nes
+.vi8s9u8a8l.
+.vi3su
+.visu1al
+.vi8s9u8a8l9l8y.
+.visual1l
+.visual1ly
+.vi9v8i8p9a9r8o8u8s.
+.3v2iv
+.viv2i4p
+.vivi1pa
+.vivip2a2r
+.viviparou2
+.viviparo2us
+.vo8i8c8e9p8r8i8n8t.
+.voi4
+.voi3cep
+.voicepr2
+.voiceprin4t3
+.vs8p8a8c8e.
+.v2s1pa
+.vspa4ce
+.wa8d9d8i8n8g.
+.wa2d
+.wad4d1in
+.wad1d4
+.wa8l8l9f8l8o8w8e8r.
+.wal1l
+.wal2lf
+.wallf4l2
+.wallflow1er
+.wa8l8l9f8l8o8w9e8r8s.
+.wallflowe4r1s2
+.wa8r8m9e8s8t.
+.w2a2r
+.war1m
+.war2me
+.war2mes
+.wa8s8t8e9w8a8t8e8r.
+.was4t
+.waste2w
+.waste1w5a
+.wastewa1te
+.wa8v8e9g8u8i8d8e.
+.waveg3
+.waveg2ui2
+.wavegu2id
+.wa8v8e9g8u8i8d8e8s.
+.waveguide4s2
+.wa8v8e9l8e8t.
+.wa8v8e9l8e8t8s.
+.wavele4t1s2
+.we8b9l8i8k8e.
+.w2e1b
+.web2l2
+.web3l4ik
+.we8e8k9n8i8g8h8t.
+.weekn2ig
+.we8e8k9n8i8g8h8t8s.
+.weeknigh4t1s2
+.wh8e8e8l9c8h8a8i8r.
+.whee4l1c2
+.wheel2ch
+.wheelchai2
+.wheelcha4ir
+.wh8e8e8l9c8h8a8i8r8s.
+.wheelchai4rs2
+.wh8i8c8h9e8v8e8r.
+.whi4
+.wh4i2ch
+.whiche2
+.whichev1er
+.wh8i8t8e9s8i8d8e8d.
+.wh2ite
+.whit4es
+.white1si
+.white2s2id
+.whitesi2d1ed
+.wh8i8t8e9s8p8a8c8e.
+.white1sp
+.white2s1pa
+.whitespa4ce
+.wh8i8t8e9s8p8a8c8e8s.
+.wi8d8e9s8p8r8e8a8d.
+.w2id
+.wide4s2
+.wide1sp
+.wides4pre
+.widespr2
+.widesprea2d1
+.wi8n8g9s8p8a8n.
+.win4g
+.wings2
+.wing2s1pa
+.wingspa4n
+.wi8n8g9s8p8a8n8s.
+.wingspa2n1s2
+.wi8n8g9s8p8r8e8a8d.
+.wingspr2
+.wingsprea2d1
+.wi8t8c8h9c8r8a8f8t.
+.wi4tc
+.wit4ch
+.witchcra2f4t
+.witchcra2f
+.wo8r8d9s8p8a8c9i8n8g.
+.1wo2
+.wor2d1s2
+.words4p
+.word2s1pa
+.wordsp4a2ci
+.wordspa2c1in
+.wordspac1ing
+.wo8r8k9a8r8o8u8n8d.
+.work2a2r
+.workarou2
+.workaroun2d
+.wo8r8k9a8r8o8u8n8d8s.
+.workaroun2d1s2
+.wo8r8k9h8o8r8s8e.
+.workh4
+.workhor4se
+.workho4rs2
+.wo8r8k9h8o8r8s8e8s.
+.workhors3e4s
+.wr8a8p9a8r8o8u8n8d.
+.wra4
+.wrap2a2r4
+.wra1pa
+.wraparou2
+.wraparoun2d
+.wr8e8t8c8h9e8d.
+.wre4tc
+.wret4ch
+.wretche2
+.wret4ch4ed
+.wr8e8t8c8h9e8d9l8y.
+.wretche2d1ly
+.ye8s9t8e8r9y8e8a8r.
+.yes4
+.yesterye2a2r
+.al9g8e9b8r8a8i9s8c8h8e.
+.algebra2is1c
+.algebrais3ch2
+.algebraische2
+.al9l8e9g8h8e9n8y.
+.al1l
+.al2le
+.al3leg
+.alleghe2n
+.ar9k8a8n9s8a8s.
+.arka2n
+.arkan2sa2
+.arka2n1s2
+.at8p9a8s8e.
+.a4t1p
+.at1pa
+.at8p9a8s8e8s.
+.atpas1e4s
+.au8s9t8r8a8l9a8s8i8a8n.
+.a2us
+.aus1t4r
+.aus1tra
+.australas2i1a
+.australasi2a2n
+.au8t8o9m8a8t8i9s8i8e8r9t8e8r.
+.automa3tis
+.automatis2ie4
+.automatisiert3er
+.be9d8i8e9n8u8n8g.
+.4be2d
+.b4e3di
+.be5di3en
+.bed2ie4
+.bedie3nu4n
+.be8m8b8o.
+.4be5m
+.be4m5b
+.bi8b9l8i9o9g8r8a9p8h8i9s8c8h8e.
+.bibliogr4aphi
+.bibliograph2is1c
+.bibliographis3ch2
+.bibliographische2
+.bo8s9t8o8n.
+.5bos4
+.bos1to
+.bosto2n
+.br8o8w8n9i8a8n.
+.brown5i
+.brow3n4i1a
+.browni3a2n
+.br8u8n8s9w8i8c8k.
+.bru2n
+.bru2n3s4
+.brun4sw2
+.brunswi2
+.brunswick1
+.bu9d8a9p8e8s8t.
+.bu1d2a
+.ca8r9i8b9b8e8a8n.
+.car1i
+.car4ib
+.cari2b1b
+.carib2be
+.caribbea2n
+.ch8a8r8l8e8s9t8o8n.
+.char4le4
+.char1l
+.charles2
+.charl4es2to
+.charle3sto2n
+.ch8a8r9l8o8t8t8e8s9v8i8l8l8e.
+.char3lo4
+.charlo4t3t2
+.charlot4tes
+.charlotte4sv
+.charlottes2vil
+.charlottesvil1l
+.charlottesvil2le
+.co9l8u8m9b8i8a.
+.colum4bi
+.colu4m1b
+.columb2i1a
+.cz8e8c8h8o9s8l8o9v8a9k8i8a.
+.c2ze4
+.cze2ch
+.cze3cho2
+.czechos4l2
+.czechos4lov
+.czechoslo1va
+.czechoslovak1i
+.czechoslovak2i1a
+.de8l9a9w8a8r8e.
+.de1la
+.de4law
+.delaw2a2r
+.di8j8k9s8t8r8a.
+.di3j
+.dij4k1s2
+.dijkst4r
+.dijks1tra
+.du8a8n8e.
+.d1u1a
+.dua2n
+.dy9n8a9m8i9s8c8h8e.
+.5dyn
+.dy1na
+.dynam2is
+.dynam2is1c
+.dynamis3ch2
+.dynamische2
+.en8g9l8i8s8h.
+.engl2
+.englis2h
+.eu8l8e8r9i8a8n.
+.eul4e
+.eu3l4er1i
+.eule1r2i3a4
+.euleri2a2n
+.ev8a8n9s8t8o8n.
+.e1va
+.eva2n
+.evan4st
+.eva2n1s2
+.evans1to
+.evansto2n
+.fe8b9r8u9a8r8y.
+.f2e4b
+.fe3br
+.febru3a
+.febru2a2r
+.fe8s8t9s8c8h8r8i8f8t.
+.fes4t1s2
+.fest4sc
+.fests2ch2
+.festsc4hr4
+.festschr4i2ft
+.fl8o8r9i9d8a.
+.flor2id
+.flori1d2a
+.fl8o8r9i9d9i8a8n.
+.flori2di
+.florid5i2a2n
+.flori1d4i3a
+.fo8r9s8c8h8u8n8g8s9i8n9s8t8i9t8u8t.
+.fors4c
+.fors2ch2
+.forschungs2
+.forschung2s1in
+.forschungs2i2n1s2
+.forschungsinst2i
+.forschungsinsti1tu
+.fr8e8e9b8s8d.
+.fre2e1b
+.free2b5s2
+.freeb4s5d
+.fu8n8k9t8s8i8o8n8a8l.
+.3fu
+.fu4nk2
+.funk5t
+.funk4t1s2
+.funkt1s2io
+.funkt5sio2n
+.funktsio1n5a
+.ga8u8s8s9i8a8n.
+.ga2us
+.gau2ss
+.gaus1si
+.gauss2i1a
+.gaussi2a2n
+.gh8o8s8t9s8c8r8i8p8t.
+.ghos4t1s2
+.ghost4sc
+.ghostscri2
+.ghostscr2ip
+.ghostscri2p1t
+.gh8o8s8t9v8i8e8w.
+.ghos4tv
+.ghostv2ie4
+.gr8a8s8s9m8a8n8n9i8a8n.
+.gr2as
+.gra2ss
+.gras2s1m
+.grass3ma
+.grassma2n3
+.grassma4n1n2
+.grassman3n4i1a
+.grassma2nni3a2n
+.gr8e8i8f8s9w8a8l8d.
+.grei2
+.grei2f3s
+.greifsw2
+.greifswa2ld
+.gr8o8t8h8e8n9d8i8e8c8k.
+.g4ro
+.gro4th2e
+.gr4oth
+.grothe2n
+.grothend2ie4
+.grothendieck1
+.gr8u8n8d9l8e8h9r8e8n.
+.gru2n
+.grundle1h4
+.grundle4hr4
+.ha9d8a9m8a8r8d.
+.ha2d
+.ha1d2a
+.hada2m2
+.had4a1ma
+.hadam2a2r
+.ha8i9f8a.
+.hai1fa
+.ha8m8i8l9t8o8n9i8a8n.
+.ha4m
+.hami4lt
+.hamil1to
+.hamilto2n
+.hamilto3n4i1a
+.hamiltoni3a2n
+.he8l9s8i8n8k8i.
+.he2l1s2
+.hel2s1in
+.hels4i4nk2
+.helsink1i
+.he8r9m8i8t9i8a8n.
+.her3mit
+.hermi1ti
+.herm4i1t2i1a
+.hermiti2a2n
+.hi8b8b8s.
+.hi2b1b
+.hib2b5s2
+.ho8k9k8a8i9d8o.
+.h2ok
+.hokk4
+.hokkai2
+.hokka2id
+.hokkai1do
+.ja8c9k8o8w9s8k8i.
+.5ja
+.jack1
+.jackowsk2
+.jackowsk1i
+.ja8n9u9a8r8y.
+.ja2n
+.jan3u1a
+.janu2a2r
+.ja9p8a9n8e8s8e.
+.ja4p
+.ja1pa
+.japa2n
+.japa1nes
+.japane1s2e
+.ka8d9o8m9t8s8e8v.
+.ka2d
+.ka1do
+.kado4mt
+.kadom4t1s2
+.kadomt5sev
+.ka8n9s8a8s.
+.ka2n
+.kan2sa2
+.ka2n1s2
+.ka8r8l8s9r8u8h8e.
+.k2a2r
+.kar1l
+.kar2l1s2
+.karls1r
+.ko8r9t8e9w8e8g.
+.ko5r
+.kr8i8s8h8n8a.
+.kr2is
+.kr3is2h
+.kris2h1n
+.krish1na
+.kr8i8s8h9n8a9i8s8m.
+.krishnai2
+.krishnai2s1m
+.kr8i8s8h9n8a8n.
+.krishn2a2n
+.la8n9c8a8s9t8e8r.
+.lan1ca
+.lancast5er
+.le9g8e8n8d8r8e.
+.le1gen
+.legen1dr
+.legendre4
+.le8i8c8e8s9t8e8r.
+.lei2
+.le5ic
+.leices5t
+.li8p9s8c8h8i8t8z.
+.l2ip
+.li2p1s2
+.lips2ch2
+.lips3chit
+.lipschi4tz
+.li8p9s8c8h8i8t8z9i8a8n.
+.lipschit2z1i
+.lipschitz2i1a
+.lipschitzi2a2n
+.lo8j9b8a8n.
+.lo5j
+.lojba2n
+.lo8u9i9s8i9a8n8a.
+.lou2
+.lo2ui2
+.louis2i1a
+.louisi2a2n
+.louisia1na
+.ma8c9o8s.
+.ma1co
+.ma8n9c8h8e8s9t8e8r.
+.man2ch
+.manche2
+.manch1es
+.ma8r9k8o8v9i8a8n.
+.marko5vi2a2n
+.markov2i1a
+.ma8r8k8t9o8b8e8r9d8o8r8f.
+.mark5t
+.mark1to
+.markto3b
+.marktober1do
+.marktoberd4or
+.marktoberdor1f
+.ma8s8s9a9c8h8u9s8e8t8t8s.
+.ma2ss
+.mas1sa2
+.massa2ch
+.massach2us
+.massachuse4t3t2
+.massachuset4t1s2
+.ma8x9w8e8l8l.
+.maxwel4l
+.mi9c8r8o9s8o8f8t.
+.micro2so
+.microso2ft3
+.mi8n9n8e9a8p9o9l8i8s.
+.m2i4n1n2
+.minne4
+.minneapol2i
+.mi8n9n8e9s8o8t8a.
+.min1nes
+.minne1so
+.minneso1ta
+.mo8s9c8o8w.
+.mos2c
+.mos1co
+.na8c8h9r8i8c8h8t8e8n.
+.1na
+.na2ch
+.nac4hr4
+.na2chr4i2ch
+.nachricht1en
+.na8s8h9v8i8l8l8e.
+.n4as
+.nas2h
+.nash2vil
+.nashvil1l
+.nashvil2le
+.ne8t9b8s8d.
+.ne2t1b
+.net2b5s2
+.netb4s5d
+.ne8t9s8c8a8p8e.
+.ne4t1s2
+.net4sc
+.netsca4p
+.nets1ca
+.ni8j9m8e9g8e8n.
+.ni3j
+.nijme2g
+.nijme1gen
+.no8e9t8h8e8r9i8a8n.
+.3noe
+.noeth2e
+.noether1i
+.noethe1r2i3a4
+.noetheri2a2n
+.no8o8r8d9w8i8j8k8e8r9h8o8u8t.
+.noo2
+.no3ord
+.noord1w
+.noordwi2
+.noordwi3j
+.noordwijk1er
+.noordwijker1h4
+.noordwijkerhou2
+.no9v8e8m9b8e8r.
+.nove4m5b
+.op8e8n9b8s8d.
+.ope4n1b4
+.open2b5s2
+.openb4s5d
+.op8e8n9o8f8f8i8c8e.
+.op4eno
+.openo4f1f
+.openof1fi
+.pa8l8a9t8i8n8o.
+.pala2t1in
+.palat2i1no
+.pa9l8e8r9m8o.
+.paler3m4
+.paler1mo
+.pe9t8r8o8v9s8k8i.
+.petro3v
+.petrovsk2
+.petrovsk1i
+.pf8a8f8f9i8a8n.
+.4pf
+.p1fa
+.pfa2f
+.pfa4f1f4
+.pfaf1fi
+.pfaff2i3a
+.pfaffi2a2n
+.ph8i8l9a9d8e8l9p8h8i8a.
+.phi4l4ade
+.phila2d
+.philade2lp
+.philadel5phi
+.philadelph2i1a
+.ph8i8l9o9s8o8p8h9i9s8c8h8e.
+.philo2so
+.philos4op
+.philos2oph
+.philosoph2is1c
+.philosophis3ch2
+.philosophische2
+.po8i8n9c8a8r8e.
+.poin2
+.poi2
+.poinc2a2r5
+.poin1ca
+.po9t8e8n9t8i8a8l9g8l8e8i9c8h8u8n8g.
+.p4ot
+.po1ten1t
+.potent2i
+.poten1t2i1a
+.potenti2al
+.potentia4l1g4
+.potentialgl2
+.potential1gle
+.potentialglei2
+.potentialgle5ic
+.potentialgle4i2ch
+.ra9d8h8a9k8r8i8s8h9n8a8n.
+.rad1h2
+.radhakr2is
+.radhakr3is2h
+.radhakris2h1n
+.radhakrish1na
+.radhakrishn2a2n
+.ra8t8h8s9k8e8l9l8e8r.
+.r4ath
+.ra2t4h1s2
+.rathsk2
+.rath4ske
+.rathskel1l
+.rathskel2le
+.ri8e9m8a8n8n9i8a8n.
+.r2ie4
+.rie5ma2n
+.rie1ma
+.riema4n1n2
+.rieman3n4i1a
+.riema2nni3a2n
+.ry8d9b8e8r8g.
+.ry1d
+.ryd1b
+.rydberg2
+.sc8h8o8t9t8i8s8c8h8e.
+.scho4t3t2
+.schott2is1c
+.s2ch2ottis3ch2
+.schottische2
+.sc8h8r8o9d8i8n8g9e8r.
+.sc4hr4
+.schrod1in
+.schrod4inge
+.sc8h8w8a9b8a9c8h8e8r.
+.sch1w
+.schwaba2ch
+.schwabache2
+.sc8h8w8a8r8z9s8c8h8i8l8d.
+.schw2a2r
+.s2chwarzs2ch2
+.schwarzsch4il2
+.schwarzschi2ld
+.se8p9t8e8m9b8e8r.
+.se2p1t
+.sep2te
+.septe4m5b
+.st8o8k8e8s9s8c8h8e.
+.st2ok
+.stokes4
+.stok2e2ss
+.stokes2s5c
+.stokess2ch2
+.stokessche2
+.st8u8t8t9g8a8r8t.
+.stu4t3t2
+.stut4t1g
+.stutt1ga
+.stuttg2a2r
+.su8s9q8u8e9h8a8n9n8a.
+.s2us
+.susqu2
+.susque1h4
+.susqueha2n
+.susqueha4n1n2
+.susquehan1na
+.ta8u9b8e8r9i8a8n.
+.tau4b
+.taub4e
+.tau3ber
+.tauber1i
+.taube1r2i3a4
+.tauberi2a2n
+.te8c8h9n8i9s8c8h8e.
+.te2ch
+.tec2h1n
+.techn2is1c
+.te2chnis3ch2
+.technische2
+.te8n9n8e8s9s8e8e.
+.t4e4n1n2
+.tenne4
+.ten1nes
+.tenn2e2ss
+.to9m8a9s8z8e8w9s8k8i.
+.to2ma
+.tomas2ze
+.tomaszewsk2
+.tomaszewsk1i
+.ty9p8o9g8r8a8p8h8i8q8u8e.
+.ty3po
+.ty5po4g
+.typo1gr
+.typogr4aphi
+.typographiqu2
+.uk8r8a8i8n9i8a8n.
+.4uk
+.ukr2ai2
+.ukra4i4n
+.ukra2ini
+.ukrai4n4i1a
+.ukraini3a2n
+.ve8r9a8l8l9g8e9m8e8i8n9e8r8t8e.
+.veral1l
+.veral4l1g4
+.verallge1me
+.verallgemei2
+.verallgeme2ine
+.verallgemein1er
+.ve8r9e8i8n9i9g8u8n8g.
+.vere3in
+.verei2
+.vere2ini
+.verein2ig
+.vereini3gun
+.ve8r9t8e8i9l8u8n9g8e8n.
+.vertei2
+.verteilun1gen
+.vi8i8i8t8h.
+.v4i5i4
+.vi4i5i4
+.vii2ith
+.vi8i8t8h.
+.vi2ith
+.wa8h8r9s8c8h8e8i8n9l8i8c8h9k8e8i8t8s9t8h8e8o9r8i8e.
+.wa4hr4
+.wah4rs2
+.wahrs4c
+.wahrs2ch2
+.wahrsche2
+.wahrschei2
+.wahrsche4i4n1l
+.wahrs2cheinl4i2ch
+.wahrscheinlic4hk
+.wahrscheinlichkei2
+.wahrscheinlichkei4t1s2
+.wahrscheinlichkeits3th2e
+.wahrscheinlichkeitsthe1o5r
+.wahrscheinlichkeitstheor2ie4
+.we8r9n8e8r.
+.w1er
+.wer4n1er
+.we8r9t8h8e8r9i8a8n.
+.werth2e
+.werther1i
+.werthe1r2i3a4
+.wertheri2a2n
+.wi8n9c8h8e8s9t8e8r.
+.win2ch
+.winche2
+.winch1es
+.wi8r8t9s8c8h8a8f8t.
+.w4ir4
+.wir4t1s2
+.wirt4sc
+.wirts2ch2
+.wirtscha2f
+.wirtscha2ft
+.wi8s9s8e8n9s8c8h8a8f8t9l8i8c8h.
+.w4i2s1s
+.wissen4
+.wisse2n1s2
+.wissens4c
+.wissens2ch2
+.wissenscha2f
+.wissenscha2ft
+.wissenschaf2tl
+.wissens2chaftl4i2ch
+.xv8i8i8i8t8h.
+.xv4i5i4
+.xvi4i5i4
+.xvii2ith
+.xv8i8i8t8h.
+.xvi2ith
+.xx8i8i8i8r8d.
+.xx4
+.xx3i
+.xx4i5i4
+.xxi4i5i4
+.xxii4ir
+.xx8i8i8n8d.
+.xxi4ind
+.yi8n8g9y8o8n8g.
+.y1i
+.yin2gy
+.yingy1o4
+.yingyo2n
+.sh8u9x8u8e.
+.shux1u3
+.ji9s8u8a8n.
+.ji2su
+.jisua2n
+.ze8a9l8a8n8d.
+.2ze
+.zea4l
+.zea3l4and
+.zeala2n
+.ze8i8t9s8c8h8r8i8f8t.
+.zei2
+.zei4t1s2
+.zeit4sc
+.zeits2ch2
+.zeitsc4hr4
+.zeitschr4i2ft
diff --git a/core/res/res/drawable-en-hdpi/sym_keyboard_feedback_delete.png b/core/res/res/drawable-en-hdpi/sym_keyboard_feedback_delete.png
deleted file mode 100755
index ca76375..0000000
--- a/core/res/res/drawable-en-hdpi/sym_keyboard_feedback_delete.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/cursor_controller.png b/core/res/res/drawable-hdpi/cursor_controller.png
new file mode 100644
index 0000000..720aded
--- /dev/null
+++ b/core/res/res/drawable-hdpi/cursor_controller.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/emo_im_embarrassed.png b/core/res/res/drawable-hdpi/emo_im_embarrassed.png
new file mode 100644
index 0000000..cf7daf7
--- /dev/null
+++ b/core/res/res/drawable-hdpi/emo_im_embarrassed.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_aggregated.png b/core/res/res/drawable-hdpi/ic_aggregated.png
deleted file mode 100644
index 7ca15b1..0000000
--- a/core/res/res/drawable-hdpi/ic_aggregated.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_btn_back.png b/core/res/res/drawable-hdpi/ic_btn_back.png
new file mode 100644
index 0000000..f8b3285
--- /dev/null
+++ b/core/res/res/drawable-hdpi/ic_btn_back.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_btn_next.png b/core/res/res/drawable-hdpi/ic_btn_next.png
new file mode 100644
index 0000000..b2c6e1b
--- /dev/null
+++ b/core/res/res/drawable-hdpi/ic_btn_next.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/selection_end_handle.png b/core/res/res/drawable-hdpi/selection_end_handle.png
new file mode 100644
index 0000000..624ab58
--- /dev/null
+++ b/core/res/res/drawable-hdpi/selection_end_handle.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/selection_start_handle.png b/core/res/res/drawable-hdpi/selection_start_handle.png
new file mode 100644
index 0000000..7d6f24c
--- /dev/null
+++ b/core/res/res/drawable-hdpi/selection_start_handle.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/status_bar_item_background_normal.9.png b/core/res/res/drawable-hdpi/status_bar_item_background_normal.9.png
index c01c018..810c950 100644
--- a/core/res/res/drawable-hdpi/status_bar_item_background_normal.9.png
+++ b/core/res/res/drawable-hdpi/status_bar_item_background_normal.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-land-hdpi/ic_jog_dial_sound_off.png b/core/res/res/drawable-land-hdpi/ic_jog_dial_sound_off.png
deleted file mode 100755
index d73db48..0000000
--- a/core/res/res/drawable-land-hdpi/ic_jog_dial_sound_off.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-land-hdpi/ic_jog_dial_sound_on.png b/core/res/res/drawable-land-hdpi/ic_jog_dial_sound_on.png
deleted file mode 100755
index 90da6e3..0000000
--- a/core/res/res/drawable-land-hdpi/ic_jog_dial_sound_on.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-land-hdpi/ic_jog_dial_unlock.png b/core/res/res/drawable-land-hdpi/ic_jog_dial_unlock.png
deleted file mode 100755
index a9af1af..0000000
--- a/core/res/res/drawable-land-hdpi/ic_jog_dial_unlock.png
+++ /dev/null
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/cursor_controller.png b/core/res/res/drawable-mdpi/cursor_controller.png
new file mode 100644
index 0000000..1a8a459
--- /dev/null
+++ b/core/res/res/drawable-mdpi/cursor_controller.png
Binary files differ
diff --git a/core/res/res/drawable/emo_im_embarrassed.png b/core/res/res/drawable-mdpi/emo_im_embarrassed.png
similarity index 100%
rename from core/res/res/drawable/emo_im_embarrassed.png
rename to core/res/res/drawable-mdpi/emo_im_embarrassed.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_aggregated.png b/core/res/res/drawable-mdpi/ic_aggregated.png
deleted file mode 100644
index 7c2e2b0..0000000
--- a/core/res/res/drawable-mdpi/ic_aggregated.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_btn_back.png b/core/res/res/drawable-mdpi/ic_btn_back.png
new file mode 100644
index 0000000..c9bff4c
--- /dev/null
+++ b/core/res/res/drawable-mdpi/ic_btn_back.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_btn_next.png b/core/res/res/drawable-mdpi/ic_btn_next.png
new file mode 100755
index 0000000..c6cf436
--- /dev/null
+++ b/core/res/res/drawable-mdpi/ic_btn_next.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-mdpi/selection_end_handle.png b/core/res/res/drawable-mdpi/selection_end_handle.png
new file mode 100644
index 0000000..7e075eb
--- /dev/null
+++ b/core/res/res/drawable-mdpi/selection_end_handle.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/selection_start_handle.png b/core/res/res/drawable-mdpi/selection_start_handle.png
new file mode 100644
index 0000000..d8022f7
--- /dev/null
+++ b/core/res/res/drawable-mdpi/selection_start_handle.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/status_bar_item_background_normal.9.png b/core/res/res/drawable-mdpi/status_bar_item_background_normal.9.png
index b8e399d..f0e4d06 100644
--- a/core/res/res/drawable-mdpi/status_bar_item_background_normal.9.png
+++ b/core/res/res/drawable-mdpi/status_bar_item_background_normal.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-xlarge/default_wallpaper.jpg b/core/res/res/drawable-xlarge/default_wallpaper.jpg
new file mode 100644
index 0000000..0302f00
--- /dev/null
+++ b/core/res/res/drawable-xlarge/default_wallpaper.jpg
Binary files differ
diff --git a/core/res/res/drawable/action_bar_background.xml b/core/res/res/drawable/action_bar_background.xml
new file mode 100644
index 0000000..3929d7f
--- /dev/null
+++ b/core/res/res/drawable/action_bar_background.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+    <gradient
+        android:startColor="#ffd1d2d4"
+        android:endColor="#ff85878a"
+        android:angle="270" />
+</shape>
diff --git a/core/res/res/drawable/action_bar_context_background.xml b/core/res/res/drawable/action_bar_context_background.xml
new file mode 100644
index 0000000..8789898
--- /dev/null
+++ b/core/res/res/drawable/action_bar_context_background.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+    <gradient
+        android:startColor="#ff000000"
+        android:centerColor="#ffd1d2d4"
+        android:endColor="#ff85878a"
+        android:angle="270" />
+</shape>
diff --git a/core/res/res/drawable/action_bar_divider.xml b/core/res/res/drawable/action_bar_divider.xml
new file mode 100644
index 0000000..414309f
--- /dev/null
+++ b/core/res/res/drawable/action_bar_divider.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+    <gradient
+        android:startColor="#ffe1e2e4"
+        android:endColor="#ff95979a"
+        android:angle="270" />
+</shape>
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-xlarge/screen_action_bar.xml b/core/res/res/layout-xlarge/screen_action_bar.xml
new file mode 100644
index 0000000..41ce8e4
--- /dev/null
+++ b/core/res/res/layout-xlarge/screen_action_bar.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<!--
+This is an optimized layout for a screen with the Action Bar enabled.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:fitsSystemWindows="true">
+    <ViewAnimator android:id="@+id/action_bar_animator"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:inAnimation="@anim/push_down_in"
+        android:outAnimation="@anim/push_down_out">
+        <com.android.internal.widget.ActionBarView
+            android:id="@+id/action_bar"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            style="?android:attr/windowActionBarStyle" />
+        <com.android.internal.widget.ActionBarContextView
+            android:id="@+id/action_context_bar"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content" />
+    </ViewAnimator>
+    <FrameLayout android:id="@android:id/content"
+        android:layout_width="match_parent"
+        android:layout_height="0dip"
+        android:layout_weight="1"
+        android:foregroundGravity="fill_horizontal|top"
+        android:foreground="?android:attr/windowContentOverlay" />
+</LinearLayout>
diff --git a/core/res/res/layout-xlarge/status_bar_latest_event_content.xml b/core/res/res/layout-xlarge/status_bar_latest_event_content.xml
new file mode 100644
index 0000000..c64b90e
--- /dev/null
+++ b/core/res/res/layout-xlarge/status_bar_latest_event_content.xml
@@ -0,0 +1,56 @@
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:orientation="vertical"
+        android:paddingTop="7dp"
+        android:paddingLeft="5dp"
+        >
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal"
+        android:paddingTop="3dp"
+        >
+        <!--com.android.server.status.AnimatedImageView android:id="@+id/icon" -->
+        <ImageView android:id="@+id/icon"
+            android:layout_width="25dp"
+            android:layout_height="25dp"
+            android:scaleType="fitCenter"
+            android:src="@drawable/arrow_down_float"/>
+        <TextView android:id="@+id/title"
+            android:textAppearance="@style/TextAppearance.StatusBar.EventContent.Title"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:singleLine="true"
+            android:ellipsize="marquee"
+            android:fadingEdge="horizontal"
+            android:paddingLeft="4dp"
+            />
+    </LinearLayout>
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal"
+        >
+        <TextView android:id="@+id/text"
+            android:textAppearance="@style/TextAppearance.StatusBar.EventContent"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:singleLine="true"
+            android:ellipsize="marquee"
+            android:fadingEdge="horizontal"
+            android:paddingLeft="4dp"
+            />
+        <android.widget.DateTimeView android:id="@+id/time"
+            android:textAppearance="@style/TextAppearance.StatusBar.EventContent"
+            android:layout_marginLeft="4dp"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:singleLine="true"
+            android:paddingRight="5dp"
+            />
+    </LinearLayout>
+</LinearLayout>
diff --git a/core/res/res/layout/action_bar_title_item.xml b/core/res/res/layout/action_bar_title_item.xml
new file mode 100644
index 0000000..3c046fe
--- /dev/null
+++ b/core/res/res/layout/action_bar_title_item.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:layout_width="wrap_content"
+              android:layout_height="wrap_content"
+              android:orientation="vertical" >
+    <TextView android:id="@+id/action_bar_title"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:singleLine="true"
+        android:ellipsize="end"
+        android:textAppearance="?android:attr/textAppearanceMediumInverse" />
+    <TextView android:id="@+id/action_bar_subtitle"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:singleLine="true"
+        android:ellipsize="end"
+        android:textAppearance="?android:attr/textAppearanceSmallInverse" />
+</LinearLayout>
diff --git a/core/res/res/layout/action_menu_item_layout.xml b/core/res/res/layout/action_menu_item_layout.xml
new file mode 100644
index 0000000..0d3cce5
--- /dev/null
+++ b/core/res/res/layout/action_menu_item_layout.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<com.android.internal.view.menu.ActionMenuItemView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content" >
+    <ImageButton android:id="@+id/imageButton"
+                 android:layout_width="wrap_content"
+                 android:layout_height="wrap_content"
+                 android:visibility="gone"
+                 style="?attr/actionButtonStyle" />
+    <Button android:id="@+id/textButton"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:visibility="gone" />
+</com.android.internal.view.menu.ActionMenuItemView>
diff --git a/core/res/res/layout/action_menu_layout.xml b/core/res/res/layout/action_menu_layout.xml
new file mode 100644
index 0000000..18d5531
--- /dev/null
+++ b/core/res/res/layout/action_menu_layout.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<com.android.internal.view.menu.ActionMenuView
+     xmlns:android="http://schemas.android.com/apk/res/android"
+     android:layout_width="wrap_content"
+     android:layout_height="wrap_content" />
diff --git a/core/res/res/layout/action_mode_bar.xml b/core/res/res/layout/action_mode_bar.xml
new file mode 100644
index 0000000..8e2e69d
--- /dev/null
+++ b/core/res/res/layout/action_mode_bar.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2010, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<com.android.internal.widget.ActionBarContextView
+     xmlns:android="http://schemas.android.com/apk/res/android"
+     android:layout_width="match_parent"
+     android:layout_height="wrap_content" />
diff --git a/core/res/res/layout/alert_dialog.xml b/core/res/res/layout/alert_dialog.xml
index 7ae68f9..b746d28 100644
--- a/core/res/res/layout/alert_dialog.xml
+++ b/core/res/res/layout/alert_dialog.xml
@@ -112,7 +112,7 @@
             android:paddingTop="4dip"
             android:paddingLeft="2dip"
             android:paddingRight="2dip"
-            android:useLargestChild="true">
+            android:measureWithLargestChild="true">
             <LinearLayout android:id="@+id/leftSpacer"
                 android:layout_weight="0.25"
                 android:layout_width="0dip"
diff --git a/core/res/res/layout/contact_header.xml b/core/res/res/layout/contact_header.xml
deleted file mode 100644
index bf467d3..0000000
--- a/core/res/res/layout/contact_header.xml
+++ /dev/null
@@ -1,116 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/banner"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:orientation="horizontal"
-    android:background="@drawable/title_bar_medium"
-    android:paddingRight="5dip">
-
-    <android.widget.QuickContactBadge android:id="@+id/photo"
-        android:layout_gravity="center_vertical"
-        android:layout_marginRight="8dip"
-        android:layout_marginLeft="-1dip"
-        style="@*android:style/Widget.QuickContactBadge.WindowSmall" />
-    />
-
-    <LinearLayout
-        android:layout_width="0dip"
-        android:layout_height="wrap_content"
-        android:layout_weight="1"
-        android:orientation="vertical"
-        android:layout_gravity="center_vertical" >
-
-        <LinearLayout
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:orientation="horizontal">
-
-            <ImageView
-                android:id="@+id/aggregate_badge"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:paddingRight="3dip"
-                android:paddingTop="3dip"
-                android:src="@drawable/ic_aggregated"
-                android:visibility="gone"
-            />
-
-            <TextView android:id="@+id/name"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:singleLine="true"
-                android:ellipsize="end"
-                android:textAppearance="?android:attr/textAppearanceMedium"
-                android:textStyle="bold"
-                android:shadowColor="#BB000000"
-                android:shadowRadius="2.75"
-                />
-        </LinearLayout>
-
-        <TextView android:id="@+id/phonetic_name"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:textAppearance="?android:attr/textAppearanceSmall"
-            android:singleLine="true"
-            android:ellipsize="end"
-            android:layout_marginTop="-2dip"
-            android:visibility="gone"
-        />
-
-        <TextView android:id="@+id/status"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:textAppearance="?android:attr/textAppearanceSmall"
-            android:singleLine="true"
-            android:ellipsize="end"
-            android:layout_marginTop="-2dip"
-            android:visibility="gone"
-        />
-
-        <TextView android:id="@+id/status_date"
-            android:layout_width="match_parent"
-            android:layout_height="0dip"
-            android:layout_weight="1"
-            android:textAppearance="?android:attr/textAppearanceSmall"
-            android:textSize="12sp"
-            android:layout_marginTop="-2dip"
-            android:visibility="gone"
-        />
-    </LinearLayout>
-
-    <ImageView
-        android:id="@+id/presence"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_gravity="center_vertical"
-        android:paddingLeft="3dip"
-        android:paddingRight="6dip"
-        android:visibility="gone"
-    />
-
-    <CheckBox
-        android:id="@+id/star"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_gravity="center_vertical"
-        android:visibility="gone"
-        android:contentDescription="@string/description_star"
-        style="?android:attr/starStyle" />
-
-</LinearLayout>
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/list_content.xml b/core/res/res/layout/list_content.xml
index 6f9f1e0..1414032 100644
--- a/core/res/res/layout/list_content.xml
+++ b/core/res/res/layout/list_content.xml
@@ -1,8 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-/* //device/apps/common/assets/res/layout/list_content.xml
-**
-** Copyright 2006, The Android Open Source Project
+/* Copyright 2010, The Android Open Source Project
 **
 ** Licensed under the Apache License, Version 2.0 (the "License"); 
 ** you may not use this file except in compliance with the License. 
@@ -17,8 +15,42 @@
 ** limitations under the License.
 */
 -->
-<ListView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@android:id/list"
-    android:layout_width="match_parent" 
-    android:layout_height="match_parent"
-    android:drawSelectorOnTop="false"
-    />
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+    
+    <LinearLayout android:id="@+id/progressContainer"
+            android:orientation="vertical"
+            android:layout_width="match_parent" 
+            android:layout_height="match_parent"
+            android:visibility="gone"
+            android:gravity="center">
+        
+        <ProgressBar style="?android:attr/progressBarStyleLarge"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content" />
+        <TextView android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:textAppearance="?android:attr/textAppearanceSmall"
+                android:text="@string/loading"
+                android:paddingTop="4dip"
+                android:singleLine="true" />
+            
+    </LinearLayout>
+        
+    <FrameLayout android:id="@+id/listContainer"
+            android:layout_width="match_parent" 
+            android:layout_height="match_parent">
+            
+        <ListView android:id="@android:id/list"
+                android:layout_width="match_parent" 
+                android:layout_height="match_parent"
+                android:drawSelectorOnTop="false" />
+        <TextView android:id="@+android:id/internalEmpty"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:gravity="center"
+                android:textAppearance="?android:attr/textAppearanceLarge" />
+    </FrameLayout>
+        
+</FrameLayout>
diff --git a/core/res/res/layout/list_content_simple.xml b/core/res/res/layout/list_content_simple.xml
new file mode 100644
index 0000000..6f9f1e0
--- /dev/null
+++ b/core/res/res/layout/list_content_simple.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/assets/res/layout/list_content.xml
+**
+** 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.
+*/
+-->
+<ListView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@android:id/list"
+    android:layout_width="match_parent" 
+    android:layout_height="match_parent"
+    android:drawSelectorOnTop="false"
+    />
diff --git a/core/res/res/layout/preference_list_content.xml b/core/res/res/layout/preference_list_content.xml
index 8f86981..d530e96 100644
--- a/core/res/res/layout/preference_list_content.xml
+++ b/core/res/res/layout/preference_list_content.xml
@@ -4,22 +4,70 @@
 **
 ** 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">
+
+    <LinearLayout
+        android:orientation="horizontal"
+        android:layout_width="match_parent"
+        android:layout_height="0px"
+        android:layout_weight="1">
+
+        <ListView android:id="@android:id/list"
+            android:layout_width="0px"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            android:drawSelectorOnTop="false"
+            android:scrollbarAlwaysDrawVerticalTrack="true" />
+
+        <FrameLayout android:id="@+id/prefs"
+                android:layout_width="0px"
+                android:layout_height="match_parent"
+                android:layout_weight="3"
+                android:visibility="gone" />
+    </LinearLayout>
+
+    <RelativeLayout android:id="@+id/button_bar"
+        android:layout_height="wrap_content"
+        android:layout_width="match_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/layout/preference_list_item.xml b/core/res/res/layout/preference_list_item.xml
new file mode 100644
index 0000000..3b888b4
--- /dev/null
+++ b/core/res/res/layout/preference_list_item.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<!-- Layout for a preference category item, containing an icon and label. -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+android:id/widget_frame"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:minHeight="?android:attr/listPreferredItemHeight"
+    android:gravity="center_vertical"
+    android:paddingRight="?android:attr/scrollbarSize">
+
+    <ImageView
+        android:id="@+id/icon"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginLeft="6dip"
+        android:layout_marginRight="6dip"
+        android:layout_gravity="center" />
+
+    <RelativeLayout
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginLeft="2dip"
+        android:layout_marginRight="6dip"
+        android:layout_marginTop="6dip"
+        android:layout_marginBottom="6dip"
+        android:layout_weight="1">
+
+        <TextView android:id="@+android:id/title"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:singleLine="true"
+            android:textAppearance="?android:attr/textAppearanceLarge"
+            android:ellipsize="marquee"
+            android:fadingEdge="horizontal" />
+
+        <TextView android:id="@+android:id/summary"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_below="@android:id/title"
+            android:layout_alignLeft="@android:id/title"
+            android:textAppearance="?android:attr/textAppearanceSmall"
+            android:maxLines="2" />
+
+    </RelativeLayout>
+
+</LinearLayout>
diff --git a/core/res/res/layout/screen.xml b/core/res/res/layout/screen.xml
index dfa9731..72f7392 100644
--- a/core/res/res/layout/screen.xml
+++ b/core/res/res/layout/screen.xml
@@ -25,6 +25,13 @@
     android:layout_width="match_parent"
     android:layout_height="match_parent"
 >
+    <!-- Popout bar for action modes -->
+    <ViewStub android:id="@+id/action_mode_bar_stub"
+              android:inflatedId="@+id/action_mode_bar"
+              android:layout="@layout/action_mode_bar"
+              android:layout_width="match_parent"
+              android:layout_height="wrap_content" />
+
     <!-- Title bar -->
     <RelativeLayout android:id="@android:id/title_container"
         style="?android:attr/windowTitleBackgroundStyle"
diff --git a/core/res/res/layout/screen_action_bar.xml b/core/res/res/layout/screen_action_bar.xml
new file mode 100644
index 0000000..f39852b
--- /dev/null
+++ b/core/res/res/layout/screen_action_bar.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<!--
+This is an optimized layout for a screen with the Action Bar enabled.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:fitsSystemWindows="true">
+    <ViewAnimator android:id="@+id/action_bar_animator"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:inAnimation="@anim/push_down_in"
+        android:outAnimation="@anim/push_down_out">
+        <com.android.internal.widget.ActionBarView
+            android:id="@+id/action_bar"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            style="?android:attr/windowActionBarStyle" />
+        <com.android.internal.widget.ActionBarContextView
+            android:id="@+id/action_context_bar"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content" />
+    </ViewAnimator>
+    <FrameLayout android:id="@android:id/content"
+        android:layout_width="match_parent" 
+        android:layout_height="0dip"
+        android:layout_weight="1"
+        android:foregroundGravity="fill_horizontal|top"
+        android:foreground="?android:attr/windowContentOverlay" />
+    <LinearLayout android:id="@+id/lower_action_context_bar"
+                  android:layout_width="match_parent"
+                  android:layout_height="wrap_content"
+                  style="?android:attr/windowActionBarStyle"
+                  android:visibility="gone" />
+</LinearLayout>
diff --git a/core/res/res/layout/screen_custom_title.xml b/core/res/res/layout/screen_custom_title.xml
index 34c9de0..c62a06a 100644
--- a/core/res/res/layout/screen_custom_title.xml
+++ b/core/res/res/layout/screen_custom_title.xml
@@ -21,6 +21,13 @@
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:orientation="vertical"
     android:fitsSystemWindows="true">
+    <!-- Popout bar for action modes -->
+    <ViewStub android:id="@+id/action_mode_bar_stub"
+              android:inflatedId="@+id/action_mode_bar"
+              android:layout="@layout/action_mode_bar"
+              android:layout_width="match_parent"
+              android:layout_height="wrap_content" />
+
     <FrameLayout android:id="@android:id/title_container" 
         android:layout_width="match_parent" 
         android:layout_height="?android:attr/windowTitleSize"
diff --git a/core/res/res/layout/screen_progress.xml b/core/res/res/layout/screen_progress.xml
index 6e694c1..c3f41fa 100644
--- a/core/res/res/layout/screen_progress.xml
+++ b/core/res/res/layout/screen_progress.xml
@@ -26,6 +26,13 @@
     android:layout_width="match_parent"
     android:layout_height="match_parent"
 >
+    <!-- Popout bar for action modes -->
+    <ViewStub android:id="@+id/action_mode_bar_stub"
+              android:inflatedId="@+id/action_mode_bar"
+              android:layout="@layout/action_mode_bar"
+              android:layout_width="match_parent"
+              android:layout_height="wrap_content" />
+
     <RelativeLayout android:id="@android:id/title_container" 
         style="?android:attr/windowTitleBackgroundStyle"
         android:layout_width="match_parent" 
diff --git a/core/res/res/layout/screen_simple.xml b/core/res/res/layout/screen_simple.xml
index df511c6..87c29f6 100644
--- a/core/res/res/layout/screen_simple.xml
+++ b/core/res/res/layout/screen_simple.xml
@@ -21,9 +21,19 @@
 enabled.
 -->
 
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@android:id/content"
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:fitsSystemWindows="true"
-    android:foregroundInsidePadding="false"
-    android:foregroundGravity="fill_horizontal|top"
-    android:foreground="?android:attr/windowContentOverlay" />
\ No newline at end of file
+    android:orientation="vertical">
+    <ViewStub android:id="@+id/action_mode_bar_stub"
+              android:inflatedId="@+id/action_mode_bar"
+              android:layout="@layout/action_mode_bar"
+              android:layout_width="match_parent"
+              android:layout_height="wrap_content" />
+    <FrameLayout
+         android:id="@android:id/content"
+         android:layout_width="match_parent"
+         android:layout_height="match_parent"
+         android:foregroundInsidePadding="false"
+         android:foregroundGravity="fill_horizontal|top"
+         android:foreground="?android:attr/windowContentOverlay" />
+</LinearLayout>
diff --git a/core/res/res/layout/screen_title.xml b/core/res/res/layout/screen_title.xml
index 39ea131..f5134f9 100644
--- a/core/res/res/layout/screen_title.xml
+++ b/core/res/res/layout/screen_title.xml
@@ -22,6 +22,12 @@
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:orientation="vertical"
     android:fitsSystemWindows="true">
+    <!-- Popout bar for action modes -->
+    <ViewStub android:id="@+id/action_mode_bar_stub"
+              android:inflatedId="@+id/action_mode_bar"
+              android:layout="@layout/action_mode_bar"
+              android:layout_width="match_parent"
+              android:layout_height="wrap_content" />
     <FrameLayout
         android:layout_width="match_parent" 
         android:layout_height="?android:attr/windowTitleSize"
diff --git a/core/res/res/layout/screen_title_icons.xml b/core/res/res/layout/screen_title_icons.xml
index 62a0228..51d6a15 100644
--- a/core/res/res/layout/screen_title_icons.xml
+++ b/core/res/res/layout/screen_title_icons.xml
@@ -23,6 +23,12 @@
     android:orientation="vertical"
     android:layout_width="match_parent"
     android:layout_height="match_parent">
+    <!-- Popout bar for action modes -->
+    <ViewStub android:id="@+id/action_mode_bar_stub"
+              android:inflatedId="@+id/action_mode_bar"
+              android:layout="@layout/action_mode_bar"
+              android:layout_width="match_parent"
+              android:layout_height="wrap_content" />
     <RelativeLayout android:id="@android:id/title_container"
         style="?android:attr/windowTitleBackgroundStyle"
         android:layout_width="match_parent"
diff --git a/core/res/res/layout/web_runtime.xml b/core/res/res/layout/web_runtime.xml
new file mode 100644
index 0000000..4ec0964
--- /dev/null
+++ b/core/res/res/layout/web_runtime.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    >
+    <WebView
+        android:id="@+id/webview"
+        android:layout_width="fill_parent"
+        android:layout_height="fill_parent"
+    />
+
+    <ImageView
+        android:id="@+id/splashscreen"
+        android:layout_width="fill_parent"
+        android:layout_height="fill_parent"
+        android:scaleType="fitStart"
+    />
+
+</FrameLayout>
+
diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml
index 8bf5c1b..5e7b693 100644
--- a/core/res/res/values-cs/strings.xml
+++ b/core/res/res/values-cs/strings.xml
@@ -34,7 +34,6 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2660020990097733077">"Hlasová schránka"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2850889754919584674">"MSISDN1"</string>
     <string name="mmiError" msgid="5154499457739052907">"Problém s připojením nebo neplatný kód MMI."</string>
-    <string name="mmiFdnError" msgid="5224398216385316471">"Operace je omezena pouze na povolená telefonní čísla."</string>
     <string name="serviceEnabled" msgid="8147278346414714315">"Služba byla zapnuta."</string>
     <string name="serviceEnabledFor" msgid="6856228140453471041">"Služba byla zapnuta pro:"</string>
     <string name="serviceDisabled" msgid="1937553226592516411">"Služba byla vypnuta."</string>
@@ -172,8 +171,6 @@
     <string name="permgroupdesc_storage" msgid="9203302214915355774">"Přístup ke kartě SD."</string>
     <string name="permlab_statusBar" msgid="7417192629601890791">"zakázání či změny stavového řádku"</string>
     <string name="permdesc_statusBar" msgid="1365473595331989732">"Umožňuje aplikaci zakázat stavový řádek nebo přidat či odebrat systémové ikony."</string>
-    <string name="permlab_statusBarService" msgid="7247281911387931485">"stavový řádek"</string>
-    <string name="permdesc_statusBarService" msgid="4097605867643520920">"Umožňuje aplikaci být stavovým řádkem."</string>
     <string name="permlab_expandStatusBar" msgid="1148198785937489264">"rozbalení a sbalení stavového řádku"</string>
     <string name="permdesc_expandStatusBar" msgid="7088604400110768665">"Umožňuje aplikaci rozbalit či sbalit stavový řádek."</string>
     <string name="permlab_processOutgoingCalls" msgid="1136262550878335980">"zachycení odchozích hovorů"</string>
@@ -314,8 +311,8 @@
     <string name="permdesc_modifyAudioSettings" msgid="5793461287365991922">"Umožňuje aplikaci změnit globální nastavení zvuku, například hlasitost či směrování."</string>
     <string name="permlab_recordAudio" msgid="3876049771427466323">"nahrání zvuku"</string>
     <string name="permdesc_recordAudio" msgid="6493228261176552356">"Umožňuje aplikaci získat přístup k nahrávání zvuku."</string>
-    <string name="permlab_camera" msgid="3616391919559751192">"pořizování fotografií a videí"</string>
-    <string name="permdesc_camera" msgid="6004878235852154239">"Umožňuje aplikaci pořizovat fotografie a videa pomocí fotoaparátu. Toto nastavení aplikaci umožní získávat snímky toho, na co je zrovna fotoaparát namířen."</string>
+    <string name="permlab_camera" msgid="8059288807274039014">"pořizování fotografií"</string>
+    <string name="permdesc_camera" msgid="9013476258810982546">"Umožňuje aplikaci pořizovat fotografie pomocí fotoaparátu. Toto nastavení aplikaci umožní shromažďovat fotografie toho, na co je zrovna fotoaparát namířen."</string>
     <string name="permlab_brick" msgid="8337817093326370537">"trvalé vypnutí telefonu"</string>
     <string name="permdesc_brick" msgid="5569526552607599221">"Umožňuje aplikaci trvale vypnout celý telefon. Toto je velmi nebezpečné nastavení."</string>
     <string name="permlab_reboot" msgid="2898560872462638242">"vynucení restartování telefonu"</string>
@@ -338,8 +335,6 @@
     <string name="permdesc_vibrate" msgid="2886677177257789187">"Umožňuje aplikaci ovládat vibrace."</string>
     <string name="permlab_flashlight" msgid="2155920810121984215">"ovládání kontrolky"</string>
     <string name="permdesc_flashlight" msgid="6433045942283802309">"Umožňuje aplikaci ovládat kontrolku."</string>
-    <string name="permlab_accessUsb" msgid="7362327818655760496">"přístup k zařízením USB"</string>
-    <string name="permdesc_accessUsb" msgid="2414271762914049292">"Umožní aplikaci přístup k zařízením USB."</string>
     <string name="permlab_hardware_test" msgid="4148290860400659146">"testování hardwaru"</string>
     <string name="permdesc_hardware_test" msgid="3668894686500081699">"Umožňuje aplikaci ovládat různé periferie pro účely testování hardwaru."</string>
     <string name="permlab_callPhone" msgid="3925836347681847954">"přímé volání na telefonní čísla"</string>
@@ -578,6 +573,16 @@
     <string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"Alt"</string>
     <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
     <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
+    <string name="status_bar_clear_all_button" msgid="7774721344716731603">"Vymazat"</string>
+    <string name="status_bar_no_notifications_title" msgid="4755261167193833213">"Žádná oznámení"</string>
+    <string name="status_bar_ongoing_events_title" msgid="1682504513316879202">"Probíhající"</string>
+    <string name="status_bar_latest_events_title" msgid="6594767438577593172">"Oznámení"</string>
+    <string name="battery_status_text_percent_format" msgid="7660311274698797147">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
+    <string name="battery_status_charging" msgid="756617993998772213">"Nabíjení..."</string>
+    <string name="battery_low_title" msgid="7923774589611311406">"Prosím připojte dobíjecí zařízení"</string>
+    <string name="battery_low_subtitle" msgid="7388781709819722764">"Baterie je vybitá:"</string>
+    <string name="battery_low_percent_format" msgid="696154104579022959">"Zbývá <xliff:g id="NUMBER">%d%%</xliff:g> nebo méně."</string>
+    <string name="battery_low_why" msgid="7279169609518386372">"Využití baterie"</string>
     <string name="factorytest_failed" msgid="5410270329114212041">"Test továrního nastavení se nezdařil"</string>
     <string name="factorytest_not_system" msgid="4435201656767276723">"Test FACTORY_TEST lze provést pouze u balíčků nainstalovaných ve složce /system/app."</string>
     <string name="factorytest_no_action" msgid="872991874799998561">"Nebyl nalezen žádný balíček umožňující test FACTORY_TEST."</string>
@@ -739,16 +744,6 @@
     <string name="force_close" msgid="3653416315450806396">"Ukončit aplikaci"</string>
     <string name="report" msgid="4060218260984795706">"Nahlásit"</string>
     <string name="wait" msgid="7147118217226317732">"Počkat"</string>
-    <string name="smv_application" msgid="295583804361236288">"Aplikace <xliff:g id="APPLICATION">%1$s</xliff:g> (proces<xliff:g id="PROCESS">%2$s</xliff:g>) porušila své samovynucované zásady StrictMode."</string>
-    <string name="smv_process" msgid="5120397012047462446">"Proces <xliff:g id="PROCESS">%1$s</xliff:g> porušil své samovynucované zásady StrictMode."</string>
-    <string name="heavy_weight_notification" msgid="9087063985776626166">"Běží aplikace <xliff:g id="APP">%1$s</xliff:g>"</string>
-    <string name="heavy_weight_notification_detail" msgid="2423977499339403402">"Tuto možnost vyberte, chcete-li přepnout na aplikaci."</string>
-    <string name="heavy_weight_switcher_title" msgid="1135403633766694316">"Přepnout mezi aplikacemi?"</string>
-    <string name="heavy_weight_switcher_text" msgid="4592075610079319667">"Než spustíte novou aplikaci, je třeba zastavit jinou spuštěnou aplikaci."</string>
-    <string name="old_app_action" msgid="493129172238566282">"Návrat do aplikace <xliff:g id="OLD_APP">%1$s</xliff:g>"</string>
-    <string name="old_app_description" msgid="942967900237208466">"Nespouštět novou aplikaci."</string>
-    <string name="new_app_action" msgid="5472756926945440706">"Spustit aplikaci <xliff:g id="OLD_APP">%1$s</xliff:g>"</string>
-    <string name="new_app_description" msgid="6830398339826789493">"Zastavit starou aplikaci bez uložení."</string>
     <string name="sendText" msgid="5132506121645618310">"Vyberte činnost s textem"</string>
     <string name="volume_ringtone" msgid="6885421406845734650">"Hlasitost vyzvánění"</string>
     <string name="volume_music" msgid="5421651157138628171">"Hlasitost médií"</string>
diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml
index 3ea8266..4b6668c 100644
--- a/core/res/res/values-da/strings.xml
+++ b/core/res/res/values-da/strings.xml
@@ -34,7 +34,6 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2660020990097733077">"Voicemail"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2850889754919584674">"MSISDN1"</string>
     <string name="mmiError" msgid="5154499457739052907">"Forbindelsesproblemer eller ugyldigt MMI-nummer."</string>
-    <string name="mmiFdnError" msgid="5224398216385316471">"Du kan kun foretage handlinger med faste opkaldsnumre."</string>
     <string name="serviceEnabled" msgid="8147278346414714315">"Tjenesten blev aktiveret."</string>
     <string name="serviceEnabledFor" msgid="6856228140453471041">"Tjenesten blev aktiveret for:"</string>
     <string name="serviceDisabled" msgid="1937553226592516411">"Tjenesten er deaktiveret."</string>
@@ -172,8 +171,6 @@
     <string name="permgroupdesc_storage" msgid="9203302214915355774">"Få adgang til SD-kortet."</string>
     <string name="permlab_statusBar" msgid="7417192629601890791">"deaktiver eller rediger statuslinje"</string>
     <string name="permdesc_statusBar" msgid="1365473595331989732">"Tillader, at et program deaktiverer statuslinjen eller tilføjer eller fjerner systemikoner."</string>
-    <string name="permlab_statusBarService" msgid="7247281911387931485">"statusbjælke"</string>
-    <string name="permdesc_statusBarService" msgid="4097605867643520920">"Tillader, at programmet er statusbjælken."</string>
     <string name="permlab_expandStatusBar" msgid="1148198785937489264">"udvid/skjul statuslinje"</string>
     <string name="permdesc_expandStatusBar" msgid="7088604400110768665">"Tillader, at et program udvider eller skjuler statuslinjen."</string>
     <string name="permlab_processOutgoingCalls" msgid="1136262550878335980">"opfang udgående opkald"</string>
@@ -314,8 +311,8 @@
     <string name="permdesc_modifyAudioSettings" msgid="5793461287365991922">"Tillader, at et program ændrer globale lydindstillinger som f.eks. lydstyrke og kanalisering."</string>
     <string name="permlab_recordAudio" msgid="3876049771427466323">"optag lyd"</string>
     <string name="permdesc_recordAudio" msgid="6493228261176552356">"Tillader, at et program får adgang til lydregistreringsstien."</string>
-    <string name="permlab_camera" msgid="3616391919559751192">"tag billeder og optag video"</string>
-    <string name="permdesc_camera" msgid="6004878235852154239">"Tillader, at programmet tager billeder og optager video med kameraet. Dette giver programmet mulighed for altid at indsamle de billeder, kameraet ser."</string>
+    <string name="permlab_camera" msgid="8059288807274039014">"tag billeder"</string>
+    <string name="permdesc_camera" msgid="9013476258810982546">"Tillader, at programmet tager billeder med kameraet. Dette giver programmet mulighed for altid at indsamle de billeder, kameraet ser."</string>
     <string name="permlab_brick" msgid="8337817093326370537">"deaktiver telefonen permanent"</string>
     <string name="permdesc_brick" msgid="5569526552607599221">"Tillader, at programmet deaktiverer hele telefonen permanent. Dette er meget farligt."</string>
     <string name="permlab_reboot" msgid="2898560872462638242">"tving telefon til at genstarte"</string>
@@ -338,8 +335,6 @@
     <string name="permdesc_vibrate" msgid="2886677177257789187">"Lader programmet kontrollere vibratoren."</string>
     <string name="permlab_flashlight" msgid="2155920810121984215">"kontroller lommelygte"</string>
     <string name="permdesc_flashlight" msgid="6433045942283802309">"Tillader, at programmet kontrollerer lommelygten."</string>
-    <string name="permlab_accessUsb" msgid="7362327818655760496">"adgang til USB-enheder."</string>
-    <string name="permdesc_accessUsb" msgid="2414271762914049292">"Tillader, at programmet har adgang til USB-enheder."</string>
     <string name="permlab_hardware_test" msgid="4148290860400659146">"test hardware"</string>
     <string name="permdesc_hardware_test" msgid="3668894686500081699">"Tillader, at et program kontrollerer forskellige perifere enheder for at teste hardwaren."</string>
     <string name="permlab_callPhone" msgid="3925836347681847954">"ring direkte op til telefonnumre"</string>
@@ -578,6 +573,16 @@
     <string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string>
     <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
     <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
+    <string name="status_bar_clear_all_button" msgid="7774721344716731603">"Ryd"</string>
+    <string name="status_bar_no_notifications_title" msgid="4755261167193833213">"Ingen meddelelser"</string>
+    <string name="status_bar_ongoing_events_title" msgid="1682504513316879202">"I gang"</string>
+    <string name="status_bar_latest_events_title" msgid="6594767438577593172">"Meddelelser"</string>
+    <string name="battery_status_text_percent_format" msgid="7660311274698797147">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
+    <string name="battery_status_charging" msgid="756617993998772213">"Oplader ..."</string>
+    <string name="battery_low_title" msgid="7923774589611311406">"Forbind oplader"</string>
+    <string name="battery_low_subtitle" msgid="7388781709819722764">"Batteriet er ved at være fladt:"</string>
+    <string name="battery_low_percent_format" msgid="696154104579022959">"<xliff:g id="NUMBER">%d%%</xliff:g> eller mindre tilbage."</string>
+    <string name="battery_low_why" msgid="7279169609518386372">"Batteriforbrug"</string>
     <string name="factorytest_failed" msgid="5410270329114212041">"Fabrikstest mislykkedes"</string>
     <string name="factorytest_not_system" msgid="4435201656767276723">"Handlingen FACTORY_TEST understøttes kun af pakker installeret i /system/app."</string>
     <string name="factorytest_no_action" msgid="872991874799998561">"Der blev ikke fundet nogen pakke, som leverer handlingen FACTORY_TEST."</string>
@@ -739,16 +744,6 @@
     <string name="force_close" msgid="3653416315450806396">"Tving til at lukke"</string>
     <string name="report" msgid="4060218260984795706">"Rapporter"</string>
     <string name="wait" msgid="7147118217226317732">"Vent"</string>
-    <string name="smv_application" msgid="295583804361236288">"Programmet <xliff:g id="APPLICATION">%1$s</xliff:g> (proces <xliff:g id="PROCESS">%2$s</xliff:g>) har overtrådt sin egen StrictMode-politik."</string>
-    <string name="smv_process" msgid="5120397012047462446">"Processen <xliff:g id="PROCESS">%1$s</xliff:g> har overtrådt sin egen StrictMode-politik."</string>
-    <string name="heavy_weight_notification" msgid="9087063985776626166">"<xliff:g id="APP">%1$s</xliff:g> er i gang"</string>
-    <string name="heavy_weight_notification_detail" msgid="2423977499339403402">"Vælg for at skifte program"</string>
-    <string name="heavy_weight_switcher_title" msgid="1135403633766694316">"Skift program?"</string>
-    <string name="heavy_weight_switcher_text" msgid="4592075610079319667">"Der kører et andet program, der skal stoppes, før du kan starte et nyt."</string>
-    <string name="old_app_action" msgid="493129172238566282">"Tilbage til <xliff:g id="OLD_APP">%1$s</xliff:g>"</string>
-    <string name="old_app_description" msgid="942967900237208466">"Start ikke det nye program."</string>
-    <string name="new_app_action" msgid="5472756926945440706">"Start <xliff:g id="OLD_APP">%1$s</xliff:g>"</string>
-    <string name="new_app_description" msgid="6830398339826789493">"Stop det gamle program uden at gemme."</string>
     <string name="sendText" msgid="5132506121645618310">"Vælg en handling for teksten"</string>
     <string name="volume_ringtone" msgid="6885421406845734650">"Lydstyrke for opkald"</string>
     <string name="volume_music" msgid="5421651157138628171">"Lydstyrke for medier"</string>
diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml
index c236c97..11095c0 100644
--- a/core/res/res/values-de/strings.xml
+++ b/core/res/res/values-de/strings.xml
@@ -34,7 +34,6 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2660020990097733077">"Mailbox"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2850889754919584674">"MSISDN1"</string>
     <string name="mmiError" msgid="5154499457739052907">"Verbindungsproblem oder ungültiger MMI-Code."</string>
-    <string name="mmiFdnError" msgid="5224398216385316471">"Der Vorgang ist nur für Ihre zugelassenen Rufnummern möglich."</string>
     <string name="serviceEnabled" msgid="8147278346414714315">"Dienst wurde aktiviert."</string>
     <string name="serviceEnabledFor" msgid="6856228140453471041">"Dienst wurde aktiviert für:"</string>
     <string name="serviceDisabled" msgid="1937553226592516411">"Dienst wurde deaktiviert."</string>
@@ -172,8 +171,6 @@
     <string name="permgroupdesc_storage" msgid="9203302214915355774">"Greift auf die SD-Karte zu."</string>
     <string name="permlab_statusBar" msgid="7417192629601890791">"Statusleiste deaktivieren oder ändern"</string>
     <string name="permdesc_statusBar" msgid="1365473595331989732">"Ermöglicht der Anwendung, die Statusanzeige zu deaktivieren oder Systemsymbole hinzuzufügen oder zu entfernen."</string>
-    <string name="permlab_statusBarService" msgid="7247281911387931485">"Statusleiste"</string>
-    <string name="permdesc_statusBarService" msgid="4097605867643520920">"Die Anwendung wird damit in der Statusleiste angezeigt."</string>
     <string name="permlab_expandStatusBar" msgid="1148198785937489264">"Statusleiste ein-/ausblenden"</string>
     <string name="permdesc_expandStatusBar" msgid="7088604400110768665">"Ermöglicht der Anwendung, die Statusleiste ein- oder auszublenden."</string>
     <string name="permlab_processOutgoingCalls" msgid="1136262550878335980">"Abgehende Anrufe abfangen"</string>
@@ -242,7 +239,7 @@
     <string name="permdesc_readInputState" msgid="5132879321450325445">"Ermöglicht Anwendungen, die von Ihnen gedrückten Tasten zu überwachen (etwa die Eingabe eines Passworts). Dies gilt auch für die Interaktion mit anderen Anwendungen. Sollte für normale Anwendungen nicht benötigt werden."</string>
     <string name="permlab_bindInputMethod" msgid="3360064620230515776">"An eine Eingabemethode binden"</string>
     <string name="permdesc_bindInputMethod" msgid="3734838321027317228">"Ermöglicht dem Halter, sich an die Oberfläche einer Eingabemethode auf oberster Ebene zu binden. Sollte nie für normale Anwendungen benötigt werden."</string>
-    <string name="permlab_bindWallpaper" msgid="8716400279937856462">"An einen Hintergrund binden"</string>
+    <string name="permlab_bindWallpaper" msgid="8716400279937856462">"An ein Hintergrundbild binden"</string>
     <string name="permdesc_bindWallpaper" msgid="5287754520361915347">"Ermöglicht dem Halter, sich an die Oberfläche einer Eingabemethode auf oberster Ebene zu binden. Sollte nie für normale Anwendungen benötigt werden."</string>
     <string name="permlab_bindDeviceAdmin" msgid="8704986163711455010">"Interaktion mit einem Geräteadministrator"</string>
     <string name="permdesc_bindDeviceAdmin" msgid="8714424333082216979">"Ermöglicht dem Halter, Intents an einen Geräteadministrator zu senden. Sollte nie für normale Anwendungen benötigt werden."</string>
@@ -314,8 +311,8 @@
     <string name="permdesc_modifyAudioSettings" msgid="5793461287365991922">"Ermöglicht der Anwendung, Änderungen an allgemeinen Audioeinstellungen wie Lautstärke und Weiterleitung vorzunehmen."</string>
     <string name="permlab_recordAudio" msgid="3876049771427466323">"Audio aufnehmen"</string>
     <string name="permdesc_recordAudio" msgid="6493228261176552356">"Ermöglicht der Anwendung, auf den Pfad für Audioaufzeichnungen zuzugreifen."</string>
-    <string name="permlab_camera" msgid="3616391919559751192">"Bilder und Videos aufnehmen"</string>
-    <string name="permdesc_camera" msgid="6004878235852154239">"Ermöglicht der Anwendung, Fotos und Videos mit der Kamera aufzunehmen. So kann die Anwendung jederzeit Bilder zusammentragen, die von der Kamera erfasst werden."</string>
+    <string name="permlab_camera" msgid="8059288807274039014">"Fotos aufnehmen"</string>
+    <string name="permdesc_camera" msgid="9013476258810982546">"Ermöglicht der Anwendung, Fotos mit der Kamera aufzunehmen. So kann die Anwendung jederzeit Bilder zusammentragen, die von der Kamera erfasst werden."</string>
     <string name="permlab_brick" msgid="8337817093326370537">"Telefon dauerhaft deaktivieren."</string>
     <string name="permdesc_brick" msgid="5569526552607599221">"Ermöglicht der Anwendung, das gesamte Telefon dauerhaft zu deaktivieren. Dies birgt hohe Risiken."</string>
     <string name="permlab_reboot" msgid="2898560872462638242">"Neustart des Telefons erzwingen"</string>
@@ -338,8 +335,6 @@
     <string name="permdesc_vibrate" msgid="2886677177257789187">"Ermöglicht der Anwendung, den Vibrationsalarm zu steuern."</string>
     <string name="permlab_flashlight" msgid="2155920810121984215">"Lichtanzeige steuern"</string>
     <string name="permdesc_flashlight" msgid="6433045942283802309">"Ermöglicht der Anwendung, die Lichtanzeige zu steuern."</string>
-    <string name="permlab_accessUsb" msgid="7362327818655760496">"auf USB-Geräte zugreifen"</string>
-    <string name="permdesc_accessUsb" msgid="2414271762914049292">"Die Anwendung erhält Zugriff auf USB-Geräte."</string>
     <string name="permlab_hardware_test" msgid="4148290860400659146">"Hardware testen"</string>
     <string name="permdesc_hardware_test" msgid="3668894686500081699">"Ermöglicht einer Anwendung, verschiedene Peripherie-Geräte zu Hardware-Testzwecken zu steuern."</string>
     <string name="permlab_callPhone" msgid="3925836347681847954">"Telefonnummern direkt anrufen"</string>
@@ -364,10 +359,10 @@
     <string name="permdesc_devicePower" msgid="4577331933252444818">"Ermöglicht der Anwendung, das Telefon ein- oder auszuschalten."</string>
     <string name="permlab_factoryTest" msgid="3715225492696416187">"In Werkstestmodus ausführen"</string>
     <string name="permdesc_factoryTest" msgid="8136644990319244802">"Führt einen systemnahen Herstellertest durch, in dessen Rahmen auf die gesamte Telefonhardware zugegriffen werden kann. Nur verfügbar, wenn ein Telefon im Werkstestmodus ausgeführt wird."</string>
-    <string name="permlab_setWallpaper" msgid="6627192333373465143">"Hintergrund festlegen"</string>
-    <string name="permdesc_setWallpaper" msgid="6417041752170585837">"Ermöglicht der Anwendung, den System-Hintergrund festzulegen."</string>
-    <string name="permlab_setWallpaperHints" msgid="3600721069353106851">"Größenhinweise für Hintergrund festlegen"</string>
-    <string name="permdesc_setWallpaperHints" msgid="6019479164008079626">"Ermöglicht der Anwendung, die Größenhinweise für den Hintergrund festzulegen."</string>
+    <string name="permlab_setWallpaper" msgid="6627192333373465143">"Hintergrundbild festlegen"</string>
+    <string name="permdesc_setWallpaper" msgid="6417041752170585837">"Ermöglicht der Anwendung, das System-Hintergrundbild festzulegen."</string>
+    <string name="permlab_setWallpaperHints" msgid="3600721069353106851">"Größenhinweise für Hintergrundbild festlegen"</string>
+    <string name="permdesc_setWallpaperHints" msgid="6019479164008079626">"Ermöglicht der Anwendung, die Größenhinweise für das Hintergrundbild festzulegen."</string>
     <string name="permlab_masterClear" msgid="2315750423139697397">"System auf Werkseinstellung zurücksetzen"</string>
     <string name="permdesc_masterClear" msgid="5033465107545174514">"Ermöglicht einer Anwendung, das System komplett auf Werkseinstellung zurückzusetzen. Hierbei werden alle Daten, Konfigurationen und installierten Anwendungen gelöscht."</string>
     <string name="permlab_setTime" msgid="2021614829591775646">"Zeit einstellen"</string>
@@ -578,6 +573,16 @@
     <string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string>
     <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
     <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
+    <string name="status_bar_clear_all_button" msgid="7774721344716731603">"Löschen"</string>
+    <string name="status_bar_no_notifications_title" msgid="4755261167193833213">"Keine Benachrichtigungen"</string>
+    <string name="status_bar_ongoing_events_title" msgid="1682504513316879202">"Aktuell"</string>
+    <string name="status_bar_latest_events_title" msgid="6594767438577593172">"Benachrichtigungen"</string>
+    <string name="battery_status_text_percent_format" msgid="7660311274698797147">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
+    <string name="battery_status_charging" msgid="756617993998772213">"Wird aufgeladen..."</string>
+    <string name="battery_low_title" msgid="7923774589611311406">"Ladegerät anschließen"</string>
+    <string name="battery_low_subtitle" msgid="7388781709819722764">"Akku ist fast leer."</string>
+    <string name="battery_low_percent_format" msgid="696154104579022959">"<xliff:g id="NUMBER">%d%%</xliff:g> oder weniger verbleiben."</string>
+    <string name="battery_low_why" msgid="7279169609518386372">"Akkuverbrauch"</string>
     <string name="factorytest_failed" msgid="5410270329114212041">"Werkstest fehlgeschlagen"</string>
     <string name="factorytest_not_system" msgid="4435201656767276723">"Die Aktion FACTORY_TEST wird nur für unter \"/system/app\" gespeicherte Pakete unterstützt."</string>
     <string name="factorytest_no_action" msgid="872991874799998561">"Es wurden kein Paket mit der Aktion FACTORY_TEST gefunden."</string>
@@ -739,16 +744,6 @@
     <string name="force_close" msgid="3653416315450806396">"Schließen erzwingen"</string>
     <string name="report" msgid="4060218260984795706">"Bericht"</string>
     <string name="wait" msgid="7147118217226317732">"Warten"</string>
-    <string name="smv_application" msgid="295583804361236288">"Die Anwendung <xliff:g id="APPLICATION">%1$s</xliff:g> (Prozess <xliff:g id="PROCESS">%2$s</xliff:g>) hat gegen ihre selbsterzwungene StrictMode-Richtlinie verstoßen."</string>
-    <string name="smv_process" msgid="5120397012047462446">"Der Prozess <xliff:g id="PROCESS">%1$s</xliff:g> hat gegen seine selbsterzwungene StrictMode-Richtlinie verstoßen."</string>
-    <string name="heavy_weight_notification" msgid="9087063985776626166">"<xliff:g id="APP">%1$s</xliff:g> läuft"</string>
-    <string name="heavy_weight_notification_detail" msgid="2423977499339403402">"Auswahl zum Wechseln in die Anwendung"</string>
-    <string name="heavy_weight_switcher_title" msgid="1135403633766694316">"Anwendung wechseln?"</string>
-    <string name="heavy_weight_switcher_text" msgid="4592075610079319667">"Eine andere Anwendung wird bereits ausgeführt und muss vor dem Start einer neuen Anwendung beendet werden."</string>
-    <string name="old_app_action" msgid="493129172238566282">"Zu <xliff:g id="OLD_APP">%1$s</xliff:g> zurückkehren"</string>
-    <string name="old_app_description" msgid="942967900237208466">"Starten Sie die neue Anwendung nicht."</string>
-    <string name="new_app_action" msgid="5472756926945440706">"<xliff:g id="OLD_APP">%1$s</xliff:g> starten"</string>
-    <string name="new_app_description" msgid="6830398339826789493">"Beenden Sie die Anwendung ohne zu speichern."</string>
     <string name="sendText" msgid="5132506121645618310">"Aktion für Text auswählen"</string>
     <string name="volume_ringtone" msgid="6885421406845734650">"Klingeltonlautstärke"</string>
     <string name="volume_music" msgid="5421651157138628171">"Medienlautstärke"</string>
@@ -849,7 +844,7 @@
     <string name="sync_binding_label" msgid="3687969138375092423">"Synchronisieren"</string>
     <string name="accessibility_binding_label" msgid="4148120742096474641">"Eingabehilfen"</string>
     <string name="wallpaper_binding_label" msgid="1240087844304687662">"Hintergrund"</string>
-    <string name="chooser_wallpaper" msgid="7873476199295190279">"Hintergrund ändern"</string>
+    <string name="chooser_wallpaper" msgid="7873476199295190279">"Hintergrundbild ändern"</string>
     <string name="pptp_vpn_description" msgid="2688045385181439401">"Point-to-Point-Tunneling-Protokoll"</string>
     <string name="l2tp_vpn_description" msgid="3750692169378923304">"Layer-2-Tunneling-Protokoll"</string>
     <string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"L2TP/IPSec-VPN mit vorinstalliertem Schlüssel"</string>
diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml
index 2c61e1f..d6b1e09 100644
--- a/core/res/res/values-el/strings.xml
+++ b/core/res/res/values-el/strings.xml
@@ -34,7 +34,6 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2660020990097733077">"Αυτόματος τηλεφωνητής"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2850889754919584674">"MSISDN1"</string>
     <string name="mmiError" msgid="5154499457739052907">"Πρόβλημα σύνδεσης ή μη έγκυρος κώδικας MMI."</string>
-    <string name="mmiFdnError" msgid="5224398216385316471">"Η λειτουργία περιορίζεται μόνο σε καθορισμένους αριθμούς κλήσης."</string>
     <string name="serviceEnabled" msgid="8147278346414714315">"Η υπηρεσία ενεργοποιήθηκε."</string>
     <string name="serviceEnabledFor" msgid="6856228140453471041">"Η υπηρεσία ενεργοποιήθηκε για:"</string>
     <string name="serviceDisabled" msgid="1937553226592516411">"Η υπηρεσία έχει απενεργοποιηθεί."</string>
@@ -172,8 +171,6 @@
     <string name="permgroupdesc_storage" msgid="9203302214915355774">"Πρόσβαση στην κάρτα SD."</string>
     <string name="permlab_statusBar" msgid="7417192629601890791">"απενεργοποίηση ή τροποποίηση γραμμής κατάστασης"</string>
     <string name="permdesc_statusBar" msgid="1365473595331989732">"Επιτρέπει στην εφαρμογή να απενεργοποιεί τη γραμμή κατάστασης ή να προσθέτει και να αφαιρεί εικονίδια συστήματος."</string>
-    <string name="permlab_statusBarService" msgid="7247281911387931485">"γραμμή κατάστασης"</string>
-    <string name="permdesc_statusBarService" msgid="4097605867643520920">"Επιτρέπει στην εφαρμογή να βρίσκεται στη γραμμή κατάστασης."</string>
     <string name="permlab_expandStatusBar" msgid="1148198785937489264">"ανάπτυξη/σύμπτυξη γραμμής κατάστασης"</string>
     <string name="permdesc_expandStatusBar" msgid="7088604400110768665">"Επιτρέπει σε μια εφαρμογή να αναπτύξει ή να συμπτύξει την γραμμή κατάστασης."</string>
     <string name="permlab_processOutgoingCalls" msgid="1136262550878335980">"φραγή εξερχόμενων κλήσεων"</string>
@@ -314,8 +311,8 @@
     <string name="permdesc_modifyAudioSettings" msgid="5793461287365991922">"Επιτρέπει σε μια εφαρμογή να τροποποιήσει καθολικές ρυθμίσεις ήχου όπως ένταση ήχου και δρομολόγηση ήχου."</string>
     <string name="permlab_recordAudio" msgid="3876049771427466323">"εγγραφή ήχου"</string>
     <string name="permdesc_recordAudio" msgid="6493228261176552356">"Επιτρέπει σε μια εφαρμογή την πρόσβαση στη διαδρομή εγγραφής ήχου."</string>
-    <string name="permlab_camera" msgid="3616391919559751192">"λήψη φωτογραφιών και βίντεο"</string>
-    <string name="permdesc_camera" msgid="6004878235852154239">"Επιτρέπει σε μια εφαρμογή τη λήψη φωτογραφιών και βίντεο με την κάμερα. Αυτό επιτρέπει στην εφαρμογή να συλλέξει εικόνες, στις οποίες εστιάζει η κάμερα οποιαδήποτε στιγμή."</string>
+    <string name="permlab_camera" msgid="8059288807274039014">"λήψη φωτογραφιών"</string>
+    <string name="permdesc_camera" msgid="9013476258810982546">"Επιτρέπει σε μια εφαρμογή τη λήψη φωτογραφιών με την κάμερα. Αυτό επιτρέπει στην εφαρμογή να συλλέξει εικόνες, στις οποίες εστιάζει η κάμερα οποιαδήποτε στιγμή."</string>
     <string name="permlab_brick" msgid="8337817093326370537">"μόνιμη απενεργοποίηση τηλεφώνου"</string>
     <string name="permdesc_brick" msgid="5569526552607599221">"Επιτρέπει στην εφαρμογή τη μόνιμη απενεργοποίηση όλων των λειτουργιών του τηλεφώνου, το οποίο είναι εξαιρετικά επικίνδυνο."</string>
     <string name="permlab_reboot" msgid="2898560872462638242">"αναγκαστική επανεκκίνηση τηλεφώνου"</string>
@@ -338,8 +335,6 @@
     <string name="permdesc_vibrate" msgid="2886677177257789187">"Επιτρέπει στην εφαρμογή τον έλεγχο του δονητή."</string>
     <string name="permlab_flashlight" msgid="2155920810121984215">"έλεγχος φακού"</string>
     <string name="permdesc_flashlight" msgid="6433045942283802309">"Επιτρέπει στην εφαρμογή τον έλεγχο του φακού."</string>
-    <string name="permlab_accessUsb" msgid="7362327818655760496">"πρόσβαση σε συσκευές USB"</string>
-    <string name="permdesc_accessUsb" msgid="2414271762914049292">"Επιτρέπει στην εφαρμογή την πρόσβαση σε συσκευές USB."</string>
     <string name="permlab_hardware_test" msgid="4148290860400659146">"δοκιμή υλικού"</string>
     <string name="permdesc_hardware_test" msgid="3668894686500081699">"Επιτρέπει σε μια εφαρμογή τον έλεγχο διαφόρων περιφερειακών για την εκτέλεση δοκιμών υλικού."</string>
     <string name="permlab_callPhone" msgid="3925836347681847954">"απευθείας κλήση τηλεφωνικών αριθμών"</string>
@@ -578,6 +573,16 @@
     <string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string>
     <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
     <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
+    <string name="status_bar_clear_all_button" msgid="7774721344716731603">"Εκκαθάριση"</string>
+    <string name="status_bar_no_notifications_title" msgid="4755261167193833213">"Δεν υπάρχουν ειδοποιήσεις"</string>
+    <string name="status_bar_ongoing_events_title" msgid="1682504513316879202">"Εν εξελίξει"</string>
+    <string name="status_bar_latest_events_title" msgid="6594767438577593172">"Ειδοποιήσεις"</string>
+    <string name="battery_status_text_percent_format" msgid="7660311274698797147">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
+    <string name="battery_status_charging" msgid="756617993998772213">"Φόρτιση..."</string>
+    <string name="battery_low_title" msgid="7923774589611311406">"Συνδέστε τον φορτιστή"</string>
+    <string name="battery_low_subtitle" msgid="7388781709819722764">"Η στάθμη της μπαταρίας είναι χαμηλή:"</string>
+    <string name="battery_low_percent_format" msgid="696154104579022959">"Απομένει <xliff:g id="NUMBER">%d%%</xliff:g> ή λιγότερο."</string>
+    <string name="battery_low_why" msgid="7279169609518386372">"Χρήση μπαταρίας"</string>
     <string name="factorytest_failed" msgid="5410270329114212041">"Η εργοστασιακή δοκιμή απέτυχε"</string>
     <string name="factorytest_not_system" msgid="4435201656767276723">"Η ενέργεια FACTORY_TEST υποστηρίζεται μόνο για πακέτα που είναι εγκατεστημένα στον κατάλογο /system/app."</string>
     <string name="factorytest_no_action" msgid="872991874799998561">"Δεν βρέθηκε πακέτο που να παρέχει την ενέργεια FACTORY_TEST."</string>
@@ -739,16 +744,6 @@
     <string name="force_close" msgid="3653416315450806396">"Αναγκαστικό κλείσιμο"</string>
     <string name="report" msgid="4060218260984795706">"Αναφορά"</string>
     <string name="wait" msgid="7147118217226317732">"Αναμονή"</string>
-    <string name="smv_application" msgid="295583804361236288">"Η εφαρμογή <xliff:g id="APPLICATION">%1$s</xliff:g> (διεργασία <xliff:g id="PROCESS">%2$s</xliff:g>) παραβίασε την αυτοεπιβαλόμενη πολιτική StrictMode."</string>
-    <string name="smv_process" msgid="5120397012047462446">"Η διεργασία <xliff:g id="PROCESS">%1$s</xliff:g> παραβίασε την αυτοεπιβαλόμενη πολιτική StrictMode."</string>
-    <string name="heavy_weight_notification" msgid="9087063985776626166">"Η εφαρμογή <xliff:g id="APP">%1$s</xliff:g> εκτελείται"</string>
-    <string name="heavy_weight_notification_detail" msgid="2423977499339403402">"Επιλέξτε για αλλαγή σε εφαρμογή"</string>
-    <string name="heavy_weight_switcher_title" msgid="1135403633766694316">"Αλλαγή εφαρμογών;"</string>
-    <string name="heavy_weight_switcher_text" msgid="4592075610079319667">"Εκτελείται ήδη άλλη εφαρμογή την οποία πρέπει να διακόψετε για να είναι δυνατή η εκτέλεση της νέας."</string>
-    <string name="old_app_action" msgid="493129172238566282">"Επιστροφή σε <xliff:g id="OLD_APP">%1$s</xliff:g>"</string>
-    <string name="old_app_description" msgid="942967900237208466">"Μην εκκινήσετε τη νέα εφαρμογή."</string>
-    <string name="new_app_action" msgid="5472756926945440706">"Εκκίνηση της εφαρμογής <xliff:g id="OLD_APP">%1$s</xliff:g>"</string>
-    <string name="new_app_description" msgid="6830398339826789493">"Διακόψτε την παλιά εφαρμογή χωρίς αποθήκευση."</string>
     <string name="sendText" msgid="5132506121645618310">"Επιλέξτε μια ενέργεια για το κείμενο"</string>
     <string name="volume_ringtone" msgid="6885421406845734650">"Ένταση ειδοποίησης ήχου"</string>
     <string name="volume_music" msgid="5421651157138628171">"Ένταση ήχου πολυμέσων"</string>
diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml
index afc2862..0953202 100644
--- a/core/res/res/values-es-rUS/strings.xml
+++ b/core/res/res/values-es-rUS/strings.xml
@@ -34,7 +34,6 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2660020990097733077">"Correo de voz"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2850889754919584674">"MSISDN1"</string>
     <string name="mmiError" msgid="5154499457739052907">"Problema de conexión o código incorrecto de MMI."</string>
-    <string name="mmiFdnError" msgid="5224398216385316471">"La operación está limitada a números de marcación fija."</string>
     <string name="serviceEnabled" msgid="8147278346414714315">"Se ha activado el servicio."</string>
     <string name="serviceEnabledFor" msgid="6856228140453471041">"Se activó el servicio para:"</string>
     <string name="serviceDisabled" msgid="1937553226592516411">"Se ha desactivado el servicio."</string>
@@ -172,8 +171,6 @@
     <string name="permgroupdesc_storage" msgid="9203302214915355774">"Acceder a la tarjeta SD."</string>
     <string name="permlab_statusBar" msgid="7417192629601890791">"desactivar o modificar la barra de estado"</string>
     <string name="permdesc_statusBar" msgid="1365473595331989732">"Admite que la aplicación desactive la barra de estado, o agregue y elimine íconos del sistema."</string>
-    <string name="permlab_statusBarService" msgid="7247281911387931485">"barra de estado"</string>
-    <string name="permdesc_statusBarService" msgid="4097605867643520920">"Permite que la aplicación sea la barra de estado."</string>
     <string name="permlab_expandStatusBar" msgid="1148198785937489264">"expandir o reducir la barra de estado"</string>
     <string name="permdesc_expandStatusBar" msgid="7088604400110768665">"Admite que la aplicación expanda o reduzca la barra de estado."</string>
     <string name="permlab_processOutgoingCalls" msgid="1136262550878335980">"interceptar llamadas salientes"</string>
@@ -314,8 +311,8 @@
     <string name="permdesc_modifyAudioSettings" msgid="5793461287365991922">"Admite que la aplicación modifique la configuración global de audio, como el volumen y el enrutamiento."</string>
     <string name="permlab_recordAudio" msgid="3876049771427466323">"grabar audio"</string>
     <string name="permdesc_recordAudio" msgid="6493228261176552356">"Admite que la aplicación acceda a la ruta de grabación de audio."</string>
-    <string name="permlab_camera" msgid="3616391919559751192">"tomar fotografías y grabar videos"</string>
-    <string name="permdesc_camera" msgid="6004878235852154239">"Admite una aplicación que toma fotografías y graba video con la cámara. Esto permite que la aplicación en cualquier momento recopile imágenes que esté viendo la cámara."</string>
+    <string name="permlab_camera" msgid="8059288807274039014">"tomar fotografías"</string>
+    <string name="permdesc_camera" msgid="9013476258810982546">"Admite una aplicación que toma fotografías con la cámara. Esto permite que la aplicación en cualquier momento recopile imágenes que esté viendo la cámara."</string>
     <string name="permlab_brick" msgid="8337817093326370537">"desactivar teléfono de manera permanente"</string>
     <string name="permdesc_brick" msgid="5569526552607599221">"Admite que la aplicación desactive todo el teléfono de manera permanente. Esto es muy peligroso."</string>
     <string name="permlab_reboot" msgid="2898560872462638242">"provocar el reinicio del teléfono"</string>
@@ -338,8 +335,6 @@
     <string name="permdesc_vibrate" msgid="2886677177257789187">"Admite que la aplicación controle el vibrador."</string>
     <string name="permlab_flashlight" msgid="2155920810121984215">"controlar linterna"</string>
     <string name="permdesc_flashlight" msgid="6433045942283802309">"Admite que la aplicación controle la linterna."</string>
-    <string name="permlab_accessUsb" msgid="7362327818655760496">"acceder a dispositivos USB"</string>
-    <string name="permdesc_accessUsb" msgid="2414271762914049292">"Permite que la aplicación acceda a dispositivos USB."</string>
     <string name="permlab_hardware_test" msgid="4148290860400659146">"probar el hardware"</string>
     <string name="permdesc_hardware_test" msgid="3668894686500081699">"Admite que la aplicación controle diversos periféricos con el fin de probar el hardware."</string>
     <string name="permlab_callPhone" msgid="3925836347681847954">"llamar directamente a números de teléfono"</string>
@@ -578,6 +573,16 @@
     <string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string>
     <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
     <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
+    <string name="status_bar_clear_all_button" msgid="7774721344716731603">"Borrar"</string>
+    <string name="status_bar_no_notifications_title" msgid="4755261167193833213">"No hay notificaciones"</string>
+    <string name="status_bar_ongoing_events_title" msgid="1682504513316879202">"Continuo"</string>
+    <string name="status_bar_latest_events_title" msgid="6594767438577593172">"Notificaciones"</string>
+    <string name="battery_status_text_percent_format" msgid="7660311274698797147">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
+    <string name="battery_status_charging" msgid="756617993998772213">"Cargando..."</string>
+    <string name="battery_low_title" msgid="7923774589611311406">"Conecta el cargador"</string>
+    <string name="battery_low_subtitle" msgid="7388781709819722764">"Hay poca batería:"</string>
+    <string name="battery_low_percent_format" msgid="696154104579022959">"Restan <xliff:g id="NUMBER">%d%%</xliff:g> o menos."</string>
+    <string name="battery_low_why" msgid="7279169609518386372">"Uso de la batería"</string>
     <string name="factorytest_failed" msgid="5410270329114212041">"Error en la prueba de fábrica"</string>
     <string name="factorytest_not_system" msgid="4435201656767276723">"La acción FACTORY_TEST se admite solamente en paquetes instalados en /system/app."</string>
     <string name="factorytest_no_action" msgid="872991874799998561">"No se ha encontrado ningún paquete que proporcione la acción FACTORY_TEST ."</string>
@@ -739,16 +744,6 @@
     <string name="force_close" msgid="3653416315450806396">"Provocar acercamiento"</string>
     <string name="report" msgid="4060218260984795706">"Notificar"</string>
     <string name="wait" msgid="7147118217226317732">"Espera"</string>
-    <string name="smv_application" msgid="295583804361236288">"La aplicación <xliff:g id="APPLICATION">%1$s</xliff:g> (proceso <xliff:g id="PROCESS">%2$s</xliff:g>) ha violado su política StrictMode autoimpuesta."</string>
-    <string name="smv_process" msgid="5120397012047462446">"El proceso <xliff:g id="PROCESS">%1$s</xliff:g> ha violado su política StrictMode autoimpuesta."</string>
-    <string name="heavy_weight_notification" msgid="9087063985776626166">"<xliff:g id="APP">%1$s</xliff:g> Correr"</string>
-    <string name="heavy_weight_notification_detail" msgid="2423977499339403402">"Selecciona cambiar la aplicación"</string>
-    <string name="heavy_weight_switcher_title" msgid="1135403633766694316">"¿Deseas cambiar aplicaciones?"</string>
-    <string name="heavy_weight_switcher_text" msgid="4592075610079319667">"Ya se está ejecutando una aplicación que debe detenerse antes de iniciar una nueva."</string>
-    <string name="old_app_action" msgid="493129172238566282">"Regresar a <xliff:g id="OLD_APP">%1$s</xliff:g>"</string>
-    <string name="old_app_description" msgid="942967900237208466">"No inicies la nueva aplicación."</string>
-    <string name="new_app_action" msgid="5472756926945440706">"Inicio <xliff:g id="OLD_APP">%1$s</xliff:g>"</string>
-    <string name="new_app_description" msgid="6830398339826789493">"Interrumpe la aplicación anterior sin guardar."</string>
     <string name="sendText" msgid="5132506121645618310">"Selecciona una acción para el texto"</string>
     <string name="volume_ringtone" msgid="6885421406845734650">"Volumen del timbre"</string>
     <string name="volume_music" msgid="5421651157138628171">"Volumen de los medios"</string>
diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml
index 045b6a1..3cb068e 100644
--- a/core/res/res/values-es/strings.xml
+++ b/core/res/res/values-es/strings.xml
@@ -34,7 +34,6 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2660020990097733077">"Buzón de voz"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2850889754919584674">"MSISDN1"</string>
     <string name="mmiError" msgid="5154499457739052907">"Se ha producido un problema de conexión o el código MMI no es válido."</string>
-    <string name="mmiFdnError" msgid="5224398216385316471">"La operación solo es válida para números de marcación fija."</string>
     <string name="serviceEnabled" msgid="8147278346414714315">"El servicio se ha habilitado."</string>
     <string name="serviceEnabledFor" msgid="6856228140453471041">"Se ha habilitado el servicio para:"</string>
     <string name="serviceDisabled" msgid="1937553226592516411">"El servicio se ha inhabilitado."</string>
@@ -172,8 +171,6 @@
     <string name="permgroupdesc_storage" msgid="9203302214915355774">"Acceder a la tarjeta SD"</string>
     <string name="permlab_statusBar" msgid="7417192629601890791">"inhabilitar o modificar la barra de estado"</string>
     <string name="permdesc_statusBar" msgid="1365473595331989732">"Permite que las aplicaciones inhabiliten la barra de estado, o añadan y eliminen iconos del sistema."</string>
-    <string name="permlab_statusBarService" msgid="7247281911387931485">"barra de estado"</string>
-    <string name="permdesc_statusBarService" msgid="4097605867643520920">"La aplicación puede aparecer en la barra de estado."</string>
     <string name="permlab_expandStatusBar" msgid="1148198785937489264">"expandir/contraer la barra de estado"</string>
     <string name="permdesc_expandStatusBar" msgid="7088604400110768665">"Permite que la aplicación expanda y contraiga la barra de estado."</string>
     <string name="permlab_processOutgoingCalls" msgid="1136262550878335980">"interceptar llamadas salientes"</string>
@@ -314,8 +311,8 @@
     <string name="permdesc_modifyAudioSettings" msgid="5793461287365991922">"Permite que la aplicación modifique la configuración de audio global como, por ejemplo, el volumen y la salida."</string>
     <string name="permlab_recordAudio" msgid="3876049771427466323">"grabar sonido"</string>
     <string name="permdesc_recordAudio" msgid="6493228261176552356">"Permite que la aplicación acceda a la ruta de grabación de audio."</string>
-    <string name="permlab_camera" msgid="3616391919559751192">"realizar fotografías y vídeos"</string>
-    <string name="permdesc_camera" msgid="6004878235852154239">"Permite que la aplicación realice fotografías y vídeos con la cámara. De este modo, puede recopilar en cualquier momento las imágenes que ve la cámara."</string>
+    <string name="permlab_camera" msgid="8059288807274039014">"realizar fotografías"</string>
+    <string name="permdesc_camera" msgid="9013476258810982546">"Permite que la aplicación realice fotografías con la cámara. De esta forma, la aplicación puede recopilar en cualquier momento las imágenes que ve la cámara."</string>
     <string name="permlab_brick" msgid="8337817093326370537">"inhabilitar el teléfono de forma permanente"</string>
     <string name="permdesc_brick" msgid="5569526552607599221">"Permite que la aplicación inhabilite todas las funciones del teléfono de forma permanente. Este permiso es muy peligroso."</string>
     <string name="permlab_reboot" msgid="2898560872462638242">"forzar reinicio del teléfono"</string>
@@ -338,8 +335,6 @@
     <string name="permdesc_vibrate" msgid="2886677177257789187">"Permite que la aplicación controle la función de vibración."</string>
     <string name="permlab_flashlight" msgid="2155920810121984215">"controlar linterna"</string>
     <string name="permdesc_flashlight" msgid="6433045942283802309">"Permite que la aplicación controle la función de linterna."</string>
-    <string name="permlab_accessUsb" msgid="7362327818655760496">"acceso a dispositivos USB"</string>
-    <string name="permdesc_accessUsb" msgid="2414271762914049292">"La aplicación puede acceder a dispositivos USB."</string>
     <string name="permlab_hardware_test" msgid="4148290860400659146">"probar hardware"</string>
     <string name="permdesc_hardware_test" msgid="3668894686500081699">"Permite que la aplicación controle distintos periféricos con fines de prueba del hardware."</string>
     <string name="permlab_callPhone" msgid="3925836347681847954">"llamar directamente a números de teléfono"</string>
@@ -578,6 +573,16 @@
     <string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string>
     <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g> <xliff:g id="AMPM">%P</xliff:g>"</string>
     <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g> <xliff:g id="AMPM">%p</xliff:g>"</string>
+    <string name="status_bar_clear_all_button" msgid="7774721344716731603">"Borrar"</string>
+    <string name="status_bar_no_notifications_title" msgid="4755261167193833213">"No tienes notificaciones"</string>
+    <string name="status_bar_ongoing_events_title" msgid="1682504513316879202">"Entrante"</string>
+    <string name="status_bar_latest_events_title" msgid="6594767438577593172">"Notificaciones"</string>
+    <string name="battery_status_text_percent_format" msgid="7660311274698797147">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
+    <string name="battery_status_charging" msgid="756617993998772213">"Cargando..."</string>
+    <string name="battery_low_title" msgid="7923774589611311406">"Conecta el cargador"</string>
+    <string name="battery_low_subtitle" msgid="7388781709819722764">"Se está agotando la batería:"</string>
+    <string name="battery_low_percent_format" msgid="696154104579022959">"<xliff:g id="NUMBER">%d%%</xliff:g> o menos disponible"</string>
+    <string name="battery_low_why" msgid="7279169609518386372">"Uso de la batería"</string>
     <string name="factorytest_failed" msgid="5410270329114212041">"Fallo en la prueba de fábrica"</string>
     <string name="factorytest_not_system" msgid="4435201656767276723">"La acción FACTORY_TEST solo es compatible con los paquetes instalados en /system/app."</string>
     <string name="factorytest_no_action" msgid="872991874799998561">"No se ha encontrado ningún paquete que proporcione la acción FACTORY_TEST."</string>
@@ -739,16 +744,6 @@
     <string name="force_close" msgid="3653416315450806396">"Forzar cierre"</string>
     <string name="report" msgid="4060218260984795706">"Informe"</string>
     <string name="wait" msgid="7147118217226317732">"Esperar"</string>
-    <string name="smv_application" msgid="295583804361236288">"La aplicación <xliff:g id="APPLICATION">%1$s</xliff:g> (proceso <xliff:g id="PROCESS">%2$s</xliff:g>) ha infringido su política StrictMode autoaplicable."</string>
-    <string name="smv_process" msgid="5120397012047462446">"El proceso <xliff:g id="PROCESS">%1$s</xliff:g> ha infringido su política StrictMode autoaplicable."</string>
-    <string name="heavy_weight_notification" msgid="9087063985776626166">"<xliff:g id="APP">%1$s</xliff:g> en ejecución"</string>
-    <string name="heavy_weight_notification_detail" msgid="2423977499339403402">"Seleccionar para cambiar a la aplicación"</string>
-    <string name="heavy_weight_switcher_title" msgid="1135403633766694316">"¿Cambiar de aplicación?"</string>
-    <string name="heavy_weight_switcher_text" msgid="4592075610079319667">"Se está ejecutando otra aplicación. Para iniciar una aplicación nueva debes detenerla."</string>
-    <string name="old_app_action" msgid="493129172238566282">"Volver a <xliff:g id="OLD_APP">%1$s</xliff:g>"</string>
-    <string name="old_app_description" msgid="942967900237208466">"No iniciar la aplicación nueva"</string>
-    <string name="new_app_action" msgid="5472756926945440706">"Iniciar <xliff:g id="OLD_APP">%1$s</xliff:g>"</string>
-    <string name="new_app_description" msgid="6830398339826789493">"Detener la aplicación anterior sin guardar"</string>
     <string name="sendText" msgid="5132506121645618310">"Seleccionar la opción para compartir"</string>
     <string name="volume_ringtone" msgid="6885421406845734650">"Volumen del timbre"</string>
     <string name="volume_music" msgid="5421651157138628171">"Volumen multimedia"</string>
diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml
index 7646b57..5efcf32 100644
--- a/core/res/res/values-fr/strings.xml
+++ b/core/res/res/values-fr/strings.xml
@@ -34,7 +34,6 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2660020990097733077">"Messagerie vocale"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2850889754919584674">"MSISDN1"</string>
     <string name="mmiError" msgid="5154499457739052907">"Problème de connexion ou code IHM non valide."</string>
-    <string name="mmiFdnError" msgid="5224398216385316471">"Opération réservée aux numéros autorisés"</string>
     <string name="serviceEnabled" msgid="8147278346414714315">"Le service a été activé."</string>
     <string name="serviceEnabledFor" msgid="6856228140453471041">"Ce service a été activé pour :"</string>
     <string name="serviceDisabled" msgid="1937553226592516411">"Ce service a été désactivé."</string>
@@ -172,8 +171,6 @@
     <string name="permgroupdesc_storage" msgid="9203302214915355774">"Accès à la carte SD"</string>
     <string name="permlab_statusBar" msgid="7417192629601890791">"Désactivation ou modification de la barre d\'état"</string>
     <string name="permdesc_statusBar" msgid="1365473595331989732">"Permet à une application de désactiver la barre d\'état ou d\'ajouter/supprimer des icônes système."</string>
-    <string name="permlab_statusBarService" msgid="7247281911387931485">"barre d\'état"</string>
-    <string name="permdesc_statusBarService" msgid="4097605867643520920">"Permet à l\'application de faire office de barre d\'état."</string>
     <string name="permlab_expandStatusBar" msgid="1148198785937489264">"Agrandir/réduire la barre d\'état"</string>
     <string name="permdesc_expandStatusBar" msgid="7088604400110768665">"Permet à l\'application de réduire ou d\'agrandir la barre d\'état."</string>
     <string name="permlab_processOutgoingCalls" msgid="1136262550878335980">"Interception d\'appels sortants"</string>
@@ -314,8 +311,8 @@
     <string name="permdesc_modifyAudioSettings" msgid="5793461287365991922">"Permet à l\'application de modifier les paramètres audio généraux (p. ex. le volume et le routage)."</string>
     <string name="permlab_recordAudio" msgid="3876049771427466323">"Enregistrement de fichier audio"</string>
     <string name="permdesc_recordAudio" msgid="6493228261176552356">"Permet à l\'application d\'accéder au chemin de l\'enregistrement audio."</string>
-    <string name="permlab_camera" msgid="3616391919559751192">"prendre des photos et enregistrer des vidéos"</string>
-    <string name="permdesc_camera" msgid="6004878235852154239">"Permet de prendre des photos et d\'enregistrer des vidéos avec l\'appareil photo. Cette fonctionnalité permet à l\'application de récupérer à tout moment les images perçues par l\'appareil."</string>
+    <string name="permlab_camera" msgid="8059288807274039014">"Prise de photos"</string>
+    <string name="permdesc_camera" msgid="9013476258810982546">"Permet à l\'application de prendre des clichés avec l\'appareil photo. Cette fonctionnalité permet à l\'application de récupérer à tout moment les images perçues par l\'appareil."</string>
     <string name="permlab_brick" msgid="8337817093326370537">"Désactivation définitive du téléphone"</string>
     <string name="permdesc_brick" msgid="5569526552607599221">"Permet à l\'application de désactiver définitivement le téléphone. Cette fonctionnalité est très dangereuse."</string>
     <string name="permlab_reboot" msgid="2898560872462638242">"Redémarrage forcé du téléphone"</string>
@@ -338,8 +335,6 @@
     <string name="permdesc_vibrate" msgid="2886677177257789187">"Permet à l\'application de contrôler le vibreur."</string>
     <string name="permlab_flashlight" msgid="2155920810121984215">"Contrôle de la lampe de poche"</string>
     <string name="permdesc_flashlight" msgid="6433045942283802309">"Permet à l\'application de contrôler la lampe de poche."</string>
-    <string name="permlab_accessUsb" msgid="7362327818655760496">"accéder aux périphériques USB"</string>
-    <string name="permdesc_accessUsb" msgid="2414271762914049292">"Autorise l\'application à accéder aux périphériques USB"</string>
     <string name="permlab_hardware_test" msgid="4148290860400659146">"Tests du matériel"</string>
     <string name="permdesc_hardware_test" msgid="3668894686500081699">"Permet à l\'application de contrôler différents périphériques à des fins de test matériel."</string>
     <string name="permlab_callPhone" msgid="3925836347681847954">"Appel direct des numéros de téléphone"</string>
@@ -578,6 +573,16 @@
     <string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string>
     <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
     <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
+    <string name="status_bar_clear_all_button" msgid="7774721344716731603">"Effacer"</string>
+    <string name="status_bar_no_notifications_title" msgid="4755261167193833213">"Aucune notification"</string>
+    <string name="status_bar_ongoing_events_title" msgid="1682504513316879202">"En cours"</string>
+    <string name="status_bar_latest_events_title" msgid="6594767438577593172">"Notifications"</string>
+    <string name="battery_status_text_percent_format" msgid="7660311274698797147">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
+    <string name="battery_status_charging" msgid="756617993998772213">"Chargement..."</string>
+    <string name="battery_low_title" msgid="7923774589611311406">"Branchez le chargeur"</string>
+    <string name="battery_low_subtitle" msgid="7388781709819722764">"Le niveau de la batterie est bas :"</string>
+    <string name="battery_low_percent_format" msgid="696154104579022959">"Maximum <xliff:g id="NUMBER">%d%%</xliff:g> restants."</string>
+    <string name="battery_low_why" msgid="7279169609518386372">"Utilisation de la batterie"</string>
     <string name="factorytest_failed" msgid="5410270329114212041">"Échec du test usine"</string>
     <string name="factorytest_not_system" msgid="4435201656767276723">"L\'action FACTORY_TEST est uniquement prise en charge pour les paquets de données installés dans in/system/app."</string>
     <string name="factorytest_no_action" msgid="872991874799998561">"Impossible de trouver un paquet proposant l\'action FACTORY_TEST."</string>
@@ -739,16 +744,6 @@
     <string name="force_close" msgid="3653416315450806396">"Forcer la fermeture"</string>
     <string name="report" msgid="4060218260984795706">"Rapport"</string>
     <string name="wait" msgid="7147118217226317732">"Attendre"</string>
-    <string name="smv_application" msgid="295583804361236288">"L\'application <xliff:g id="APPLICATION">%1$s</xliff:g> (processus <xliff:g id="PROCESS">%2$s</xliff:g>) a enfreint ses propres règles StrictMode."</string>
-    <string name="smv_process" msgid="5120397012047462446">"Le processus <xliff:g id="PROCESS">%1$s</xliff:g> a enfreint ses propres règles StrictMode."</string>
-    <string name="heavy_weight_notification" msgid="9087063985776626166">"<xliff:g id="APP">%1$s</xliff:g> en cours d\'exécution"</string>
-    <string name="heavy_weight_notification_detail" msgid="2423977499339403402">"Sélectionner pour changer d\'application"</string>
-    <string name="heavy_weight_switcher_title" msgid="1135403633766694316">"Passer d\'une application à l\'autre ?"</string>
-    <string name="heavy_weight_switcher_text" msgid="4592075610079319667">"Une autre application est déjà en cours d\'exécution. Veuillez l\'arrêter avant d\'en démarrer une nouvelle."</string>
-    <string name="old_app_action" msgid="493129172238566282">"Revenir à <xliff:g id="OLD_APP">%1$s</xliff:g>"</string>
-    <string name="old_app_description" msgid="942967900237208466">"Ne pas démarrer la nouvelle application"</string>
-    <string name="new_app_action" msgid="5472756926945440706">"Démarrer <xliff:g id="OLD_APP">%1$s</xliff:g>"</string>
-    <string name="new_app_description" msgid="6830398339826789493">"Arrêtez l\'ancienne application sans enregistrer."</string>
     <string name="sendText" msgid="5132506121645618310">"Sélectionner une action pour le texte"</string>
     <string name="volume_ringtone" msgid="6885421406845734650">"Volume de la sonnerie"</string>
     <string name="volume_music" msgid="5421651157138628171">"Volume"</string>
diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml
index 6e50d21..d0b3b8a 100644
--- a/core/res/res/values-it/strings.xml
+++ b/core/res/res/values-it/strings.xml
@@ -34,7 +34,6 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2660020990097733077">"Segreteria"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2850889754919584674">"MSISDN1"</string>
     <string name="mmiError" msgid="5154499457739052907">"Problema di connessione o codice MMI non valido."</string>
-    <string name="mmiFdnError" msgid="5224398216385316471">"Operazione limitata solo ai numeri di selezione fissa."</string>
     <string name="serviceEnabled" msgid="8147278346414714315">"Il servizio è stato attivato."</string>
     <string name="serviceEnabledFor" msgid="6856228140453471041">"Il servizio è stato attivato per:"</string>
     <string name="serviceDisabled" msgid="1937553226592516411">"Il servizio è stato disattivato."</string>
@@ -172,8 +171,6 @@
     <string name="permgroupdesc_storage" msgid="9203302214915355774">"Accesso alla scheda SD."</string>
     <string name="permlab_statusBar" msgid="7417192629601890791">"disattivare o modificare la barra di stato"</string>
     <string name="permdesc_statusBar" msgid="1365473595331989732">"Consente all\'applicazione di disattivare la barra di stato o di aggiungere e rimuovere icone di sistema."</string>
-    <string name="permlab_statusBarService" msgid="7247281911387931485">"barra di stato"</string>
-    <string name="permdesc_statusBarService" msgid="4097605867643520920">"Consente di visualizzare l\'applicazione nella barra di stato."</string>
     <string name="permlab_expandStatusBar" msgid="1148198785937489264">"espansione/compressione barra di stato"</string>
     <string name="permdesc_expandStatusBar" msgid="7088604400110768665">"Consente all\'applicazione di espandere o comprimere la barra di stato."</string>
     <string name="permlab_processOutgoingCalls" msgid="1136262550878335980">"intercettazione chiamate in uscita"</string>
@@ -314,8 +311,8 @@
     <string name="permdesc_modifyAudioSettings" msgid="5793461287365991922">"Consente all\'applicazione di modificare impostazioni audio globali come volume e routing."</string>
     <string name="permlab_recordAudio" msgid="3876049771427466323">"registrazione audio"</string>
     <string name="permdesc_recordAudio" msgid="6493228261176552356">"Consente l\'accesso dell\'applicazione al percorso di registrazione dell\'audio."</string>
-    <string name="permlab_camera" msgid="3616391919559751192">"acquisizione di foto e video"</string>
-    <string name="permdesc_camera" msgid="6004878235852154239">"Consente di scattare foto e riprendere video nell\'applicazione con la fotocamera. L\'applicazione può acquisire in qualsiasi momento le immagini rilevate dalla fotocamera."</string>
+    <string name="permlab_camera" msgid="8059288807274039014">"acquisizione foto"</string>
+    <string name="permdesc_camera" msgid="9013476258810982546">"Consente di scattare foto nell\'applicazione con la fotocamera. L\'applicazione può acquisire in qualsiasi momento le immagini rilevate dalla fotocamera."</string>
     <string name="permlab_brick" msgid="8337817093326370537">"disattivazione telefono"</string>
     <string name="permdesc_brick" msgid="5569526552607599221">"Consente all\'applicazione di disattivare l\'intero telefono in modo definitivo. Questa autorizzazione è molto pericolosa."</string>
     <string name="permlab_reboot" msgid="2898560872462638242">"riavvio forzato del telefono"</string>
@@ -338,8 +335,6 @@
     <string name="permdesc_vibrate" msgid="2886677177257789187">"Consente all\'applicazione di controllare la vibrazione."</string>
     <string name="permlab_flashlight" msgid="2155920810121984215">"controllo flash"</string>
     <string name="permdesc_flashlight" msgid="6433045942283802309">"Consente all\'applicazione di controllare il flash."</string>
-    <string name="permlab_accessUsb" msgid="7362327818655760496">"accesso a dispositivi USB"</string>
-    <string name="permdesc_accessUsb" msgid="2414271762914049292">"Consente all\'applicazione di accedere ai dispositivi USB"</string>
     <string name="permlab_hardware_test" msgid="4148290860400659146">"esecuzione test hardware"</string>
     <string name="permdesc_hardware_test" msgid="3668894686500081699">"Consente all\'applicazione di controllare varie periferiche per il test dell\'hardware."</string>
     <string name="permlab_callPhone" msgid="3925836347681847954">"chiamata diretta n. telefono"</string>
@@ -578,6 +573,16 @@
     <string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string>
     <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
     <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
+    <string name="status_bar_clear_all_button" msgid="7774721344716731603">"Cancella"</string>
+    <string name="status_bar_no_notifications_title" msgid="4755261167193833213">"Nessuna notifica"</string>
+    <string name="status_bar_ongoing_events_title" msgid="1682504513316879202">"In corso"</string>
+    <string name="status_bar_latest_events_title" msgid="6594767438577593172">"Notifiche"</string>
+    <string name="battery_status_text_percent_format" msgid="7660311274698797147">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
+    <string name="battery_status_charging" msgid="756617993998772213">"In carica..."</string>
+    <string name="battery_low_title" msgid="7923774589611311406">"Collegare il caricabatterie"</string>
+    <string name="battery_low_subtitle" msgid="7388781709819722764">"Batteria quasi scarica:"</string>
+    <string name="battery_low_percent_format" msgid="696154104579022959">"<xliff:g id="NUMBER">%d%%</xliff:g> rimanente o meno."</string>
+    <string name="battery_low_why" msgid="7279169609518386372">"Utilizzo batteria"</string>
     <string name="factorytest_failed" msgid="5410270329114212041">"Test di fabbrica non riuscito"</string>
     <string name="factorytest_not_system" msgid="4435201656767276723">"L\'azione FACTORY_TEST è supportata soltanto per i pacchetti installati in /system/app."</string>
     <string name="factorytest_no_action" msgid="872991874799998561">"Nessun pacchetto trovato che fornisca l\'azione FACTORY_TEST."</string>
@@ -739,16 +744,6 @@
     <string name="force_close" msgid="3653416315450806396">"Termina"</string>
     <string name="report" msgid="4060218260984795706">"Segnala"</string>
     <string name="wait" msgid="7147118217226317732">"Attendi"</string>
-    <string name="smv_application" msgid="295583804361236288">"L\'applicazione <xliff:g id="APPLICATION">%1$s</xliff:g> (processo <xliff:g id="PROCESS">%2$s</xliff:g>) ha violato la norma StrictMode autoimposta."</string>
-    <string name="smv_process" msgid="5120397012047462446">"Il processo <xliff:g id="PROCESS">%1$s</xliff:g> ha violato la norma StrictMode autoimposta."</string>
-    <string name="heavy_weight_notification" msgid="9087063985776626166">"<xliff:g id="APP">%1$s</xliff:g> in esecuzione"</string>
-    <string name="heavy_weight_notification_detail" msgid="2423977499339403402">"Seleziona per passare all\'applicazione"</string>
-    <string name="heavy_weight_switcher_title" msgid="1135403633766694316">"Scambiare le applicazioni?"</string>
-    <string name="heavy_weight_switcher_text" msgid="4592075610079319667">"Un\'altra applicazione già in esecuzione deve essere chiusa prima di poterne avviare un\'altra."</string>
-    <string name="old_app_action" msgid="493129172238566282">"Torna a <xliff:g id="OLD_APP">%1$s</xliff:g> "</string>
-    <string name="old_app_description" msgid="942967900237208466">"Non avviare la nuova applicazione."</string>
-    <string name="new_app_action" msgid="5472756926945440706">"Avvia <xliff:g id="OLD_APP">%1$s</xliff:g>"</string>
-    <string name="new_app_description" msgid="6830398339826789493">"Interrompi la vecchia applicazione senza salvare."</string>
     <string name="sendText" msgid="5132506121645618310">"Selezione un\'opzione di invio"</string>
     <string name="volume_ringtone" msgid="6885421406845734650">"Volume suoneria"</string>
     <string name="volume_music" msgid="5421651157138628171">"Volume app. multimediali"</string>
diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml
index f1e98da..5816ef7 100644
--- a/core/res/res/values-ja/strings.xml
+++ b/core/res/res/values-ja/strings.xml
@@ -34,7 +34,6 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2660020990097733077">"ボイスメール"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2850889754919584674">"MSISDN1"</string>
     <string name="mmiError" msgid="5154499457739052907">"接続に問題があるか、MMIコードが正しくありません。"</string>
-    <string name="mmiFdnError" msgid="5224398216385316471">"発信番号制限で指定された番号に対してのみ操作できます。"</string>
     <string name="serviceEnabled" msgid="8147278346414714315">"サービスが有効になりました。"</string>
     <string name="serviceEnabledFor" msgid="6856228140453471041">"次のサービスが有効になりました:"</string>
     <string name="serviceDisabled" msgid="1937553226592516411">"サービスが無効になりました。"</string>
@@ -172,8 +171,6 @@
     <string name="permgroupdesc_storage" msgid="9203302214915355774">"SDカードにアクセスします。"</string>
     <string name="permlab_statusBar" msgid="7417192629601890791">"ステータスバーの無効化や変更"</string>
     <string name="permdesc_statusBar" msgid="1365473595331989732">"ステータスバーの無効化やシステムアイコンの追加や削除をアプリケーションに許可します。"</string>
-    <string name="permlab_statusBarService" msgid="7247281911387931485">"ステータスバーへの表示"</string>
-    <string name="permdesc_statusBarService" msgid="4097605867643520920">"アプリケーションのステータスバーへの表示を許可します。"</string>
     <string name="permlab_expandStatusBar" msgid="1148198785937489264">"ステータスバーの拡大/縮小"</string>
     <string name="permdesc_expandStatusBar" msgid="7088604400110768665">"ステータスバーの拡大や縮小をアプリケーションに許可します。"</string>
     <string name="permlab_processOutgoingCalls" msgid="1136262550878335980">"発信の傍受"</string>
@@ -314,8 +311,8 @@
     <string name="permdesc_modifyAudioSettings" msgid="5793461287365991922">"音量や転送などの音声全般の設定の変更をアプリケーションに許可します。"</string>
     <string name="permlab_recordAudio" msgid="3876049771427466323">"録音"</string>
     <string name="permdesc_recordAudio" msgid="6493228261176552356">"オーディオ録音パスへのアクセスをアプリケーションに許可します。"</string>
-    <string name="permlab_camera" msgid="3616391919559751192">"写真と動画の撮影"</string>
-    <string name="permdesc_camera" msgid="6004878235852154239">"カメラでの写真と動画の撮影をアプリケーションに許可します。許可すると、カメラがとらえている画像をアプリケーションがいつでも取得できるようになります。"</string>
+    <string name="permlab_camera" msgid="8059288807274039014">"写真の撮影"</string>
+    <string name="permdesc_camera" msgid="9013476258810982546">"カメラでの写真撮影をアプリケーションに許可します。アプリケーションはカメラから画像をいつでも収集できます。"</string>
     <string name="permlab_brick" msgid="8337817093326370537">"端末を永続的に無効にする"</string>
     <string name="permdesc_brick" msgid="5569526552607599221">"携帯電話全体を永続的に無効にすることをアプリケーションに許可します。この許可は非常に危険です。"</string>
     <string name="permlab_reboot" msgid="2898560872462638242">"端末の再起動"</string>
@@ -338,8 +335,6 @@
     <string name="permdesc_vibrate" msgid="2886677177257789187">"バイブレーションの制御をアプリケーションに許可します。"</string>
     <string name="permlab_flashlight" msgid="2155920810121984215">"ライトのコントロール"</string>
     <string name="permdesc_flashlight" msgid="6433045942283802309">"ライトの制御をアプリケーションに許可します。"</string>
-    <string name="permlab_accessUsb" msgid="7362327818655760496">"USBデバイスへのアクセス"</string>
-    <string name="permdesc_accessUsb" msgid="2414271762914049292">"USBデバイスへのアクセスをアプリケーションに許可します。"</string>
     <string name="permlab_hardware_test" msgid="4148290860400659146">"ハードウェアのテスト"</string>
     <string name="permdesc_hardware_test" msgid="3668894686500081699">"ハードウェアのテストのためにさまざまな周辺機器を制御することをアプリケーションに許可します。"</string>
     <string name="permlab_callPhone" msgid="3925836347681847954">"電話番号発信"</string>
@@ -578,6 +573,16 @@
     <string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string>
     <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
     <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
+    <string name="status_bar_clear_all_button" msgid="7774721344716731603">"通知を消去"</string>
+    <string name="status_bar_no_notifications_title" msgid="4755261167193833213">"通知なし"</string>
+    <string name="status_bar_ongoing_events_title" msgid="1682504513316879202">"実行中"</string>
+    <string name="status_bar_latest_events_title" msgid="6594767438577593172">"通知"</string>
+    <string name="battery_status_text_percent_format" msgid="7660311274698797147">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
+    <string name="battery_status_charging" msgid="756617993998772213">"充電中..."</string>
+    <string name="battery_low_title" msgid="7923774589611311406">"充電してください"</string>
+    <string name="battery_low_subtitle" msgid="7388781709819722764">"電池が残り少なくなっています:"</string>
+    <string name="battery_low_percent_format" msgid="696154104579022959">"残り<xliff:g id="NUMBER">%d%%</xliff:g>未満です。"</string>
+    <string name="battery_low_why" msgid="7279169609518386372">"電池使用量"</string>
     <string name="factorytest_failed" msgid="5410270329114212041">"出荷時試験が失敗"</string>
     <string name="factorytest_not_system" msgid="4435201656767276723">"FACTORY_TEST操作は、/system/appにインストールされたパッケージのみが対象です。"</string>
     <string name="factorytest_no_action" msgid="872991874799998561">"FACTORY_TEST操作を行うパッケージは見つかりませんでした。"</string>
@@ -739,16 +744,6 @@
     <string name="force_close" msgid="3653416315450806396">"強制終了"</string>
     <string name="report" msgid="4060218260984795706">"レポート"</string>
     <string name="wait" msgid="7147118217226317732">"待機"</string>
-    <string name="smv_application" msgid="295583804361236288">"アプリケーション<xliff:g id="APPLICATION">%1$s</xliff:g>(プロセス<xliff:g id="PROCESS">%2$s</xliff:g>)でStrictModeポリシー違反がありました。"</string>
-    <string name="smv_process" msgid="5120397012047462446">"プロセス<xliff:g id="PROCESS">%1$s</xliff:g>でStrictModeポリシー違反がありました。"</string>
-    <string name="heavy_weight_notification" msgid="9087063985776626166">"<xliff:g id="APP">%1$s</xliff:g>を実行中"</string>
-    <string name="heavy_weight_notification_detail" msgid="2423977499339403402">"アプリケーションを切り替える"</string>
-    <string name="heavy_weight_switcher_title" msgid="1135403633766694316">"アプリケーションを切り替えますか?"</string>
-    <string name="heavy_weight_switcher_text" msgid="4592075610079319667">"別のアプリケーションが既に実行中です。新しいアプリケーションを起動する前に実行中のアプリケーションを停止してください。"</string>
-    <string name="old_app_action" msgid="493129172238566282">"<xliff:g id="OLD_APP">%1$s</xliff:g>に戻る"</string>
-    <string name="old_app_description" msgid="942967900237208466">"新しいアプリケーションを起動しません。"</string>
-    <string name="new_app_action" msgid="5472756926945440706">"<xliff:g id="OLD_APP">%1$s</xliff:g>を起動"</string>
-    <string name="new_app_description" msgid="6830398339826789493">"実行中のアプリケーションを保存せずに停止します。"</string>
     <string name="sendText" msgid="5132506121645618310">"アプリケーションを選択"</string>
     <string name="volume_ringtone" msgid="6885421406845734650">"着信音量"</string>
     <string name="volume_music" msgid="5421651157138628171">"メディアの音量"</string>
diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml
index 712100e..8aaf761 100644
--- a/core/res/res/values-ko/strings.xml
+++ b/core/res/res/values-ko/strings.xml
@@ -34,7 +34,6 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2660020990097733077">"음성메일"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2850889754919584674">"MSISDN1"</string>
     <string name="mmiError" msgid="5154499457739052907">"연결에 문제가 있거나 MMI 코드가 잘못되었습니다."</string>
-    <string name="mmiFdnError" msgid="5224398216385316471">"발신 허용 번호에서만 수행할 수 있는 작업입니다."</string>
     <string name="serviceEnabled" msgid="8147278346414714315">"서비스를 사용하도록 설정했습니다."</string>
     <string name="serviceEnabledFor" msgid="6856228140453471041">"사용 설정된 서비스 목록:"</string>
     <string name="serviceDisabled" msgid="1937553226592516411">"서비스가 사용 중지되었습니다."</string>
@@ -172,8 +171,6 @@
     <string name="permgroupdesc_storage" msgid="9203302214915355774">"SD 카드에 액세스합니다."</string>
     <string name="permlab_statusBar" msgid="7417192629601890791">"상태 표시줄 사용 중지 또는 수정"</string>
     <string name="permdesc_statusBar" msgid="1365473595331989732">"애플리케이션이 상태 표시줄을 사용 중지하거나 시스템 아이콘을 추가 및 제거할 수 있도록 합니다."</string>
-    <string name="permlab_statusBarService" msgid="7247281911387931485">"상태 표시줄"</string>
-    <string name="permdesc_statusBarService" msgid="4097605867643520920">"애플리케이션이 상태 표시줄이 되도록 허용합니다."</string>
     <string name="permlab_expandStatusBar" msgid="1148198785937489264">"상태 표시줄 확장/축소"</string>
     <string name="permdesc_expandStatusBar" msgid="7088604400110768665">"애플리케이션이 상태 표시줄을 확장하거나 축소할 수 있도록 합니다."</string>
     <string name="permlab_processOutgoingCalls" msgid="1136262550878335980">"발신전화 가로채기"</string>
@@ -198,8 +195,8 @@
     <string name="permdesc_setDebugApp" msgid="5584310661711990702">"애플리케이션이 다른 애플리케이션에 대해 디버깅을 사용할 수 있도록 합니다. 이 경우 악성 애플리케이션이 다른 애플리케이션을 중지시킬 수 있습니다."</string>
     <string name="permlab_changeConfiguration" msgid="8214475779521218295">"UI 설정 변경"</string>
     <string name="permdesc_changeConfiguration" msgid="3465121501528064399">"애플리케이션이 로케일 또는 전체 글꼴 크기와 같은 현재 구성을 변경할 수 있도록 합니다."</string>
-    <string name="permlab_enableCarMode" msgid="5684504058192921098">"운전모드 사용"</string>
-    <string name="permdesc_enableCarMode" msgid="5673461159384850628">"애플리케이션이 운전모드를 사용할 수 있도록 합니다."</string>
+    <string name="permlab_enableCarMode" msgid="5684504058192921098">"차량 모드 사용"</string>
+    <string name="permdesc_enableCarMode" msgid="5673461159384850628">"애플리케이션이 차량 모드를 사용할 수 있도록 합니다."</string>
     <string name="permlab_killBackgroundProcesses" msgid="8373714752793061963">"백그라운드 프로세스 종료"</string>
     <string name="permdesc_killBackgroundProcesses" msgid="2908829602869383753">"메모리가 부족하지 않은 경우에도 애플리케이션이 다른 애플리케이션의 백그라운드 프로세스를 중단할 수 있도록 합니다."</string>
     <string name="permlab_forceStopPackages" msgid="1447830113260156236">"다른 애플리케이션 강제 종료"</string>
@@ -314,8 +311,8 @@
     <string name="permdesc_modifyAudioSettings" msgid="5793461287365991922">"애플리케이션이 볼륨 및 경로 지정 같은 전체 오디오 설정을 수정할 수 있도록 합니다."</string>
     <string name="permlab_recordAudio" msgid="3876049771427466323">"오디오 녹음"</string>
     <string name="permdesc_recordAudio" msgid="6493228261176552356">"애플리케이션이 오디오 레코드 경로에 액세스할 수 있도록 합니다."</string>
-    <string name="permlab_camera" msgid="3616391919559751192">"사진과 동영상 찍기"</string>
-    <string name="permdesc_camera" msgid="6004878235852154239">"애플리케이션이 카메라로 사진과 동영상을 찍을 수 있도록 합니다. 이 경우 애플리케이션이 카메라에 보여지는 화면을 언제든지 수집할 수 있습니다."</string>
+    <string name="permlab_camera" msgid="8059288807274039014">"사진 촬영"</string>
+    <string name="permdesc_camera" msgid="9013476258810982546">"애플리케이션이 카메라로 사진을 찍을 수 있도록 합니다. 이 경우 애플리케이션이 카메라에 보여지는 화면을 언제든지 수집할 수 있습니다."</string>
     <string name="permlab_brick" msgid="8337817093326370537">"휴대전화를 영구적으로 사용 중지"</string>
     <string name="permdesc_brick" msgid="5569526552607599221">"애플리케이션이 휴대전화를 영구적으로 사용 중지할 수 있게 합니다. 이 기능은 매우 위험합니다."</string>
     <string name="permlab_reboot" msgid="2898560872462638242">"휴대전화 강제로 다시 부팅"</string>
@@ -338,8 +335,6 @@
     <string name="permdesc_vibrate" msgid="2886677177257789187">"애플리케이션이 진동을 제어할 수 있도록 합니다."</string>
     <string name="permlab_flashlight" msgid="2155920810121984215">"카메라 플래시 제어"</string>
     <string name="permdesc_flashlight" msgid="6433045942283802309">"애플리케이션이 카메라 플래시를 제어할 수 있도록 합니다."</string>
-    <string name="permlab_accessUsb" msgid="7362327818655760496">"USB 장치 액세스"</string>
-    <string name="permdesc_accessUsb" msgid="2414271762914049292">"애플리케이션이 USB 장치에 액세스하도록 허용합니다."</string>
     <string name="permlab_hardware_test" msgid="4148290860400659146">"하드웨어 테스트"</string>
     <string name="permdesc_hardware_test" msgid="3668894686500081699">"애플리케이션이 하드웨어를 테스트할 목적으로 다양한 주변장치를 제어할 수 있도록 합니다."</string>
     <string name="permlab_callPhone" msgid="3925836347681847954">"전화번호 자동 연결"</string>
@@ -578,6 +573,16 @@
     <string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string>
     <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="AMPM">%P</xliff:g> <xliff:g id="HOUR">%-l</xliff:g>:00"</string>
     <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="AMPM">%p</xliff:g> <xliff:g id="HOUR">%-l</xliff:g>:00"</string>
+    <string name="status_bar_clear_all_button" msgid="7774721344716731603">"지우기"</string>
+    <string name="status_bar_no_notifications_title" msgid="4755261167193833213">"알림 없음"</string>
+    <string name="status_bar_ongoing_events_title" msgid="1682504513316879202">"진행 중"</string>
+    <string name="status_bar_latest_events_title" msgid="6594767438577593172">"알림"</string>
+    <string name="battery_status_text_percent_format" msgid="7660311274698797147">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
+    <string name="battery_status_charging" msgid="756617993998772213">"충전 중..."</string>
+    <string name="battery_low_title" msgid="7923774589611311406">"충전기를 연결하세요."</string>
+    <string name="battery_low_subtitle" msgid="7388781709819722764">"배터리 전원이 부족합니다."</string>
+    <string name="battery_low_percent_format" msgid="696154104579022959">"잔여 배터리가 <xliff:g id="NUMBER">%d%%</xliff:g> 이하입니다."</string>
+    <string name="battery_low_why" msgid="7279169609518386372">"배터리 사용량"</string>
     <string name="factorytest_failed" msgid="5410270329114212041">"출고 테스트 불합격"</string>
     <string name="factorytest_not_system" msgid="4435201656767276723">"FACTORY_TEST 작업은 /system/app 디렉토리에 설치된 패키지에 대해서만 지원됩니다."</string>
     <string name="factorytest_no_action" msgid="872991874799998561">"FACTORY_TEST 작업을 제공하는 패키지가 없습니다."</string>
@@ -739,16 +744,6 @@
     <string name="force_close" msgid="3653416315450806396">"닫기"</string>
     <string name="report" msgid="4060218260984795706">"신고"</string>
     <string name="wait" msgid="7147118217226317732">"대기"</string>
-    <string name="smv_application" msgid="295583804361236288">"애플리케이션 <xliff:g id="APPLICATION">%1$s</xliff:g>(프로세스 <xliff:g id="PROCESS">%2$s</xliff:g>)이(가) 자체 시행 StrictMode 정책을 위반했습니다."</string>
-    <string name="smv_process" msgid="5120397012047462446">"프로세스(<xliff:g id="PROCESS">%1$s</xliff:g>)가 자체 시행 StrictMode 정책을 위반했습니다."</string>
-    <string name="heavy_weight_notification" msgid="9087063985776626166">"<xliff:g id="APP">%1$s</xliff:g> 실행 중"</string>
-    <string name="heavy_weight_notification_detail" msgid="2423977499339403402">"애플리케이션으로 전환하려면 선택"</string>
-    <string name="heavy_weight_switcher_title" msgid="1135403633766694316">"애플리케이션을 전환하시겠습니까?"</string>
-    <string name="heavy_weight_switcher_text" msgid="4592075610079319667">"다른 애플리케이션이 이미 실행 중입니다. 새 애플리케이션을 시작하려면 실행 중인 애플리케이션을 중단해야 합니다."</string>
-    <string name="old_app_action" msgid="493129172238566282">"<xliff:g id="OLD_APP">%1$s</xliff:g>(으)로 돌아가기"</string>
-    <string name="old_app_description" msgid="942967900237208466">"새 애플리케이션을 시작하지 마세요."</string>
-    <string name="new_app_action" msgid="5472756926945440706">"<xliff:g id="OLD_APP">%1$s</xliff:g> 시작"</string>
-    <string name="new_app_description" msgid="6830398339826789493">"저장하지 않고 이전 애플리케이션을 중단합니다."</string>
     <string name="sendText" msgid="5132506121645618310">"텍스트에 대한 작업 선택"</string>
     <string name="volume_ringtone" msgid="6885421406845734650">"벨소리 볼륨"</string>
     <string name="volume_music" msgid="5421651157138628171">"미디어 볼륨"</string>
@@ -858,8 +853,8 @@
     <string name="reset" msgid="2448168080964209908">"재설정"</string>
     <string name="submit" msgid="1602335572089911941">"제출"</string>
     <string name="description_star" msgid="2654319874908576133">"즐겨찾기"</string>
-    <string name="car_mode_disable_notification_title" msgid="3164768212003864316">"운전모드 사용"</string>
-    <string name="car_mode_disable_notification_message" msgid="668663626721675614">"운전모드를 종료하려면 선택하세요."</string>
+    <string name="car_mode_disable_notification_title" msgid="3164768212003864316">"차량 모드 사용"</string>
+    <string name="car_mode_disable_notification_message" msgid="668663626721675614">"차량 모드를 종료하려면 선택하세요."</string>
     <string name="tethered_notification_title" msgid="3146694234398202601">"테더링 또는 핫스팟 사용"</string>
     <string name="tethered_notification_message" msgid="3067108323903048927">"구성하려면 터치하세요."</string>
     <string name="throttle_warning_notification_title" msgid="4890894267454867276">"높은 모바일 데이터 사용량"</string>
diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml
index f09039d..6acf4a6 100644
--- a/core/res/res/values-nb/strings.xml
+++ b/core/res/res/values-nb/strings.xml
@@ -34,7 +34,6 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2660020990097733077">"Telefonsvarer"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2850889754919584674">"MSISDN1"</string>
     <string name="mmiError" msgid="5154499457739052907">"Tilkoblingsproblem eller ugyldig MMI-kode."</string>
-    <string name="mmiFdnError" msgid="5224398216385316471">"Handlingen kan kun utføres på numre med anropsbegrensning."</string>
     <string name="serviceEnabled" msgid="8147278346414714315">"Tjenesten ble aktivert."</string>
     <string name="serviceEnabledFor" msgid="6856228140453471041">"Tjenesten ble aktivert for:"</string>
     <string name="serviceDisabled" msgid="1937553226592516411">"Tjenesten ble deaktivert."</string>
@@ -172,8 +171,6 @@
     <string name="permgroupdesc_storage" msgid="9203302214915355774">"Tilgang til minnekortet."</string>
     <string name="permlab_statusBar" msgid="7417192629601890791">"deaktivere eller endre statusfeltet"</string>
     <string name="permdesc_statusBar" msgid="1365473595331989732">"Lar applikasjonen deaktivere statusfeltet, samt legge til og fjerne systemikoner."</string>
-    <string name="permlab_statusBarService" msgid="7247281911387931485">"statusrad"</string>
-    <string name="permdesc_statusBarService" msgid="4097605867643520920">"Tillater programmet å vises i statusfeltet."</string>
     <string name="permlab_expandStatusBar" msgid="1148198785937489264">"utvide/slå sammen statusfeltet"</string>
     <string name="permdesc_expandStatusBar" msgid="7088604400110768665">"Lar applikasjonen utvide eller slå sammen statusfeltet."</string>
     <string name="permlab_processOutgoingCalls" msgid="1136262550878335980">"avskjære utgående anrop"</string>
@@ -314,8 +311,8 @@
     <string name="permdesc_modifyAudioSettings" msgid="5793461287365991922">"Lar applikasjonen endre globale lydinnstillinger som volum og ruting."</string>
     <string name="permlab_recordAudio" msgid="3876049771427466323">"ta opp lyd"</string>
     <string name="permdesc_recordAudio" msgid="6493228261176552356">"Gir applikasjonen tilgang til opptaksstien for lyd."</string>
-    <string name="permlab_camera" msgid="3616391919559751192">"ta bilder og videoer"</string>
-    <string name="permdesc_camera" msgid="6004878235852154239">"Tillat programmet å ta bilder og videoer med kameraet. Programmet kan dermed til enhver tid samle inn bilder som kameraet fanger inn."</string>
+    <string name="permlab_camera" msgid="8059288807274039014">"ta bilder"</string>
+    <string name="permdesc_camera" msgid="9013476258810982546">"Lar applikasjonen ta bilder med kameraet. Dette gir applikasjonen til når som helst å se og lagre det kameraet ser."</string>
     <string name="permlab_brick" msgid="8337817093326370537">"deaktivere telefonen permanent"</string>
     <string name="permdesc_brick" msgid="5569526552607599221">"Lar applikasjonen deaktivere hele telefonen permanent. Dette er svært farlig."</string>
     <string name="permlab_reboot" msgid="2898560872462638242">"tvinge omstart av telefon"</string>
@@ -338,8 +335,6 @@
     <string name="permdesc_vibrate" msgid="2886677177257789187">"Lar applikasjonen kontrollere vibratoren."</string>
     <string name="permlab_flashlight" msgid="2155920810121984215">"kontrollere lommelykten"</string>
     <string name="permdesc_flashlight" msgid="6433045942283802309">"Lar applikasjonen kontrollere lommelykten."</string>
-    <string name="permlab_accessUsb" msgid="7362327818655760496">"tilgang til USB-enheter"</string>
-    <string name="permdesc_accessUsb" msgid="2414271762914049292">"Tillater programmet å få tilgang til USB-enheter."</string>
     <string name="permlab_hardware_test" msgid="4148290860400659146">"teste maskinvare"</string>
     <string name="permdesc_hardware_test" msgid="3668894686500081699">"Lar applikasjonen styre diverse enheter med det formål å teste maskinvaren."</string>
     <string name="permlab_callPhone" msgid="3925836347681847954">"ringe telefonnummer direkte"</string>
@@ -578,6 +573,16 @@
     <string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string>
     <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
     <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
+    <string name="status_bar_clear_all_button" msgid="7774721344716731603">"Fjern"</string>
+    <string name="status_bar_no_notifications_title" msgid="4755261167193833213">"Ingen varslinger"</string>
+    <string name="status_bar_ongoing_events_title" msgid="1682504513316879202">"Aktiviteter"</string>
+    <string name="status_bar_latest_events_title" msgid="6594767438577593172">"Varslinger"</string>
+    <string name="battery_status_text_percent_format" msgid="7660311274698797147">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
+    <string name="battery_status_charging" msgid="756617993998772213">"Lader…"</string>
+    <string name="battery_low_title" msgid="7923774589611311406">"Koble til en lader"</string>
+    <string name="battery_low_subtitle" msgid="7388781709819722764">"Batteriet er nesten tomt:"</string>
+    <string name="battery_low_percent_format" msgid="696154104579022959">"mindre enn <xliff:g id="NUMBER">%d%%</xliff:g> igjen."</string>
+    <string name="battery_low_why" msgid="7279169609518386372">"Batteribruk"</string>
     <string name="factorytest_failed" msgid="5410270329114212041">"Fabrikktesten feilet"</string>
     <string name="factorytest_not_system" msgid="4435201656767276723">"The FACTORY_TEST action is only supported for packages installed in /system/app."</string>
     <string name="factorytest_no_action" msgid="872991874799998561">"No package was found that provides the FACTORY_TEST action."</string>
@@ -739,16 +744,6 @@
     <string name="force_close" msgid="3653416315450806396">"Tving avslutning"</string>
     <string name="report" msgid="4060218260984795706">"Rapportér"</string>
     <string name="wait" msgid="7147118217226317732">"Vent"</string>
-    <string name="smv_application" msgid="295583804361236288">"Programmet <xliff:g id="APPLICATION">%1$s</xliff:g> (prosessen <xliff:g id="PROCESS">%2$s</xliff:g>) har brutt de selvpålagte StrictMode-retningslinjene."</string>
-    <string name="smv_process" msgid="5120397012047462446">"Prosessen<xliff:g id="PROCESS">%1$s</xliff:g> har brutt de selvpålagte StrictMode-retningslinjene."</string>
-    <string name="heavy_weight_notification" msgid="9087063985776626166">"<xliff:g id="APP">%1$s</xliff:g> kjører"</string>
-    <string name="heavy_weight_notification_detail" msgid="2423977499339403402">"Velg for å bytte til programmet"</string>
-    <string name="heavy_weight_switcher_title" msgid="1135403633766694316">"Bytt programmer?"</string>
-    <string name="heavy_weight_switcher_text" msgid="4592075610079319667">"Et annet program kjører og må stoppes før du kan starte et nytt program."</string>
-    <string name="old_app_action" msgid="493129172238566282">"Gå tilbake til <xliff:g id="OLD_APP">%1$s</xliff:g>"</string>
-    <string name="old_app_description" msgid="942967900237208466">"Ikke start det nye programmet."</string>
-    <string name="new_app_action" msgid="5472756926945440706">"Start <xliff:g id="OLD_APP">%1$s</xliff:g>"</string>
-    <string name="new_app_description" msgid="6830398339826789493">"Stopp det gamle programmet uten å lagre det."</string>
     <string name="sendText" msgid="5132506121645618310">"Velg mål for tekst"</string>
     <string name="volume_ringtone" msgid="6885421406845734650">"Ringetonevolum"</string>
     <string name="volume_music" msgid="5421651157138628171">"Medievolum"</string>
diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml
index 17a2fe4..a9586e1 100644
--- a/core/res/res/values-nl/strings.xml
+++ b/core/res/res/values-nl/strings.xml
@@ -34,7 +34,6 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2660020990097733077">"Voicemail"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2850889754919584674">"MSISDN1"</string>
     <string name="mmiError" msgid="5154499457739052907">"Verbindingsprobleem of ongeldige MMI-code."</string>
-    <string name="mmiFdnError" msgid="5224398216385316471">"Bewerking is beperkt tot vaste nummers."</string>
     <string name="serviceEnabled" msgid="8147278346414714315">"Service is ingeschakeld."</string>
     <string name="serviceEnabledFor" msgid="6856228140453471041">"Service is ingeschakeld voor:"</string>
     <string name="serviceDisabled" msgid="1937553226592516411">"Service is uitgeschakeld."</string>
@@ -172,8 +171,6 @@
     <string name="permgroupdesc_storage" msgid="9203302214915355774">"Toegang tot de SD-kaart."</string>
     <string name="permlab_statusBar" msgid="7417192629601890791">"statusbalk uitschakelen of wijzigen"</string>
     <string name="permdesc_statusBar" msgid="1365473595331989732">"Hiermee kan een toepassing de statusbalk uitschakelen of systeempictogrammen toevoegen en verwijderen."</string>
-    <string name="permlab_statusBarService" msgid="7247281911387931485">"statusbalk"</string>
-    <string name="permdesc_statusBarService" msgid="4097605867643520920">"Hiermee kan de toepassing de statusbalk zijn."</string>
     <string name="permlab_expandStatusBar" msgid="1148198785937489264">"statusbalk uitvouwen/samenvouwen"</string>
     <string name="permdesc_expandStatusBar" msgid="7088604400110768665">"Hiermee kan de toepassing de statusbalk uitvouwen of samenvouwen."</string>
     <string name="permlab_processOutgoingCalls" msgid="1136262550878335980">"uitgaande oproepen onderscheppen"</string>
@@ -314,8 +311,8 @@
     <string name="permdesc_modifyAudioSettings" msgid="5793461287365991922">"Hiermee kan een toepassing de algemene audio-instellingen, zoals volume en omleiding, wijzigen."</string>
     <string name="permlab_recordAudio" msgid="3876049771427466323">"audio opnemen"</string>
     <string name="permdesc_recordAudio" msgid="6493228261176552356">"Hiermee krijgt de toepassing toegang tot het audio-opnamepad."</string>
-    <string name="permlab_camera" msgid="3616391919559751192">"foto\'s en video\'s maken"</string>
-    <string name="permdesc_camera" msgid="6004878235852154239">"Hiermee kan een toepassing foto\'s en video\'s maken met de camera. De toepassing kan op deze manier op elk gewenste moment beelden verzamelen van wat de camera ziet."</string>
+    <string name="permlab_camera" msgid="8059288807274039014">"foto\'s maken"</string>
+    <string name="permdesc_camera" msgid="9013476258810982546">"Hiermee kan een toepassing foto\'s maken met de camera. De toepassing kan op deze manier op elk gewenste moment foto\'s verzamelen van wat de camera ziet."</string>
     <string name="permlab_brick" msgid="8337817093326370537">"telefoon permanent uitschakelen"</string>
     <string name="permdesc_brick" msgid="5569526552607599221">"Hiermee kan de toepassing de telefoon permanent uitschakelen. Dit is erg gevaarlijk."</string>
     <string name="permlab_reboot" msgid="2898560872462638242">"telefoon nu opnieuw opstarten"</string>
@@ -338,8 +335,6 @@
     <string name="permdesc_vibrate" msgid="2886677177257789187">"Hiermee kan de toepassing de trilstand beheren."</string>
     <string name="permlab_flashlight" msgid="2155920810121984215">"zaklamp bedienen"</string>
     <string name="permdesc_flashlight" msgid="6433045942283802309">"Hiermee kan de toepassing de zaklamp bedienen."</string>
-    <string name="permlab_accessUsb" msgid="7362327818655760496">"toegang krijgen tot USB-apparaten"</string>
-    <string name="permdesc_accessUsb" msgid="2414271762914049292">"Hiermee kan de toepassing toegang krijgen tot USB-apparaten."</string>
     <string name="permlab_hardware_test" msgid="4148290860400659146">"hardware testen"</string>
     <string name="permdesc_hardware_test" msgid="3668894686500081699">"Hiermee kan de toepassing verschillende randapparaten beheren om de hardware te testen."</string>
     <string name="permlab_callPhone" msgid="3925836347681847954">"telefoonnummers rechtstreeks bellen"</string>
@@ -578,6 +573,16 @@
     <string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"Alt"</string>
     <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
     <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
+    <string name="status_bar_clear_all_button" msgid="7774721344716731603">"Wissen"</string>
+    <string name="status_bar_no_notifications_title" msgid="4755261167193833213">"Geen meldingen"</string>
+    <string name="status_bar_ongoing_events_title" msgid="1682504513316879202">"Actief"</string>
+    <string name="status_bar_latest_events_title" msgid="6594767438577593172">"Meldingen"</string>
+    <string name="battery_status_text_percent_format" msgid="7660311274698797147">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
+    <string name="battery_status_charging" msgid="756617993998772213">"Opladen..."</string>
+    <string name="battery_low_title" msgid="7923774589611311406">"Sluit de oplader aan"</string>
+    <string name="battery_low_subtitle" msgid="7388781709819722764">"De accu raakt op:"</string>
+    <string name="battery_low_percent_format" msgid="696154104579022959">"<xliff:g id="NUMBER">%d%%</xliff:g> of minder resterend."</string>
+    <string name="battery_low_why" msgid="7279169609518386372">"Accugebruik"</string>
     <string name="factorytest_failed" msgid="5410270329114212041">"Fabriekstest mislukt"</string>
     <string name="factorytest_not_system" msgid="4435201656767276723">"De actie FACTORY_TEST wordt alleen ondersteund voor pakketten die zijn geïnstalleerd in /system/app."</string>
     <string name="factorytest_no_action" msgid="872991874799998561">"Er is geen pakket gevonden dat de actie FACTORY_TEST levert."</string>
@@ -739,16 +744,6 @@
     <string name="force_close" msgid="3653416315450806396">"Nu sluiten"</string>
     <string name="report" msgid="4060218260984795706">"Rapport"</string>
     <string name="wait" msgid="7147118217226317732">"Wachten"</string>
-    <string name="smv_application" msgid="295583804361236288">"De toepassing <xliff:g id="APPLICATION">%1$s</xliff:g> (proces <xliff:g id="PROCESS">%2$s</xliff:g>) heeft het zelf afgedwongen StrictMode-beleid geschonden."</string>
-    <string name="smv_process" msgid="5120397012047462446">"Het proces <xliff:g id="PROCESS">%1$s</xliff:g> heeft het zelf afgedwongen StrictMode-beleid geschonden."</string>
-    <string name="heavy_weight_notification" msgid="9087063985776626166">"<xliff:g id="APP">%1$s</xliff:g> wordt uitgevoerd"</string>
-    <string name="heavy_weight_notification_detail" msgid="2423977499339403402">"Selecteren om over te schakelen naar toepassing"</string>
-    <string name="heavy_weight_switcher_title" msgid="1135403633766694316">"Toepassingen wijzigen?"</string>
-    <string name="heavy_weight_switcher_text" msgid="4592075610079319667">"Er wordt al een andere toepassing uitgevoerd die moet worden gestopt voordat u een nieuwe toepassing kunt starten."</string>
-    <string name="old_app_action" msgid="493129172238566282">"Terug naar <xliff:g id="OLD_APP">%1$s</xliff:g>"</string>
-    <string name="old_app_description" msgid="942967900237208466">"De nieuwe toepassing niet starten."</string>
-    <string name="new_app_action" msgid="5472756926945440706">"<xliff:g id="OLD_APP">%1$s</xliff:g> starten"</string>
-    <string name="new_app_description" msgid="6830398339826789493">"De oude toepassing stoppen zonder opslaan."</string>
     <string name="sendText" msgid="5132506121645618310">"Selecteer een actie voor tekst"</string>
     <string name="volume_ringtone" msgid="6885421406845734650">"Belvolume"</string>
     <string name="volume_music" msgid="5421651157138628171">"Mediavolume"</string>
diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml
index 87e045df3..c1f4033 100644
--- a/core/res/res/values-pl/strings.xml
+++ b/core/res/res/values-pl/strings.xml
@@ -34,7 +34,6 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2660020990097733077">"Poczta głosowa"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2850889754919584674">"MSISDN1"</string>
     <string name="mmiError" msgid="5154499457739052907">"Problem z połączeniem lub błędny kod MMI."</string>
-    <string name="mmiFdnError" msgid="5224398216385316471">"Operacja jest ograniczona wyłącznie do numerów ustalonych."</string>
     <string name="serviceEnabled" msgid="8147278346414714315">"Usługa była włączona."</string>
     <string name="serviceEnabledFor" msgid="6856228140453471041">"Usługa została włączona dla:"</string>
     <string name="serviceDisabled" msgid="1937553226592516411">"Usługa została wyłączona."</string>
@@ -172,8 +171,6 @@
     <string name="permgroupdesc_storage" msgid="9203302214915355774">"Dostęp do karty SD."</string>
     <string name="permlab_statusBar" msgid="7417192629601890791">"wyłączanie lub zmienianie paska stanu"</string>
     <string name="permdesc_statusBar" msgid="1365473595331989732">"Pozwala aplikacjom na wyłączenie paska stanu lub dodawanie i usuwanie ikon systemowych."</string>
-    <string name="permlab_statusBarService" msgid="7247281911387931485">"pasek stanu"</string>
-    <string name="permdesc_statusBarService" msgid="4097605867643520920">"Zezwala aplikacji na występowanie na pasku stanu."</string>
     <string name="permlab_expandStatusBar" msgid="1148198785937489264">"rozwijanie/zwijanie paska stanu"</string>
     <string name="permdesc_expandStatusBar" msgid="7088604400110768665">"Pozwala aplikacji na rozwijanie lub zwijanie paska stanu."</string>
     <string name="permlab_processOutgoingCalls" msgid="1136262550878335980">"przechwytywanie połączeń wychodzących"</string>
@@ -314,8 +311,8 @@
     <string name="permdesc_modifyAudioSettings" msgid="5793461287365991922">"Pozwala aplikacjom na zmianę globalnych ustawień audio, takich jak głośność i routing."</string>
     <string name="permlab_recordAudio" msgid="3876049771427466323">"nagrywanie dźwięku"</string>
     <string name="permdesc_recordAudio" msgid="6493228261176552356">"Pozwala aplikacji na dostęp do ścieżki nagrywania dźwięku."</string>
-    <string name="permlab_camera" msgid="3616391919559751192">"wykonywanie zdjęć i filmów wideo"</string>
-    <string name="permdesc_camera" msgid="6004878235852154239">"Zezwala aplikacji na wykonywanie zdjęć i rejestrowanie filmów wideo przy użyciu aparatu fotograficznego. Umożliwia to aplikacji gromadzenie w dowolnym momencie zdjęć tego, na co skierowany jest obiektyw aparatu."</string>
+    <string name="permlab_camera" msgid="8059288807274039014">"robienie zdjęć"</string>
+    <string name="permdesc_camera" msgid="9013476258810982546">"Pozwala aplikacji na wykonywanie zdjęć za pomocą aparatu. Dzięki temu może ona pobierać zdjęcia z aparatu w dowolnym momencie."</string>
     <string name="permlab_brick" msgid="8337817093326370537">"wyłączenie telefonu na stałe"</string>
     <string name="permdesc_brick" msgid="5569526552607599221">"Pozwala aplikacji na wyłączenie całego telefonu na stałe. Jest to bardzo niebezpieczne."</string>
     <string name="permlab_reboot" msgid="2898560872462638242">"wymuszanie ponownego uruchomienia telefonu"</string>
@@ -338,8 +335,6 @@
     <string name="permdesc_vibrate" msgid="2886677177257789187">"Pozwala aplikacjom na kontrolowanie wibracji."</string>
     <string name="permlab_flashlight" msgid="2155920810121984215">"kontrolowanie latarki"</string>
     <string name="permdesc_flashlight" msgid="6433045942283802309">"Pozwala aplikacji kontrolować latarkę."</string>
-    <string name="permlab_accessUsb" msgid="7362327818655760496">"dostęp do urządzeń USB"</string>
-    <string name="permdesc_accessUsb" msgid="2414271762914049292">"Zezwala aplikacji na dostęp do urządzeń USB."</string>
     <string name="permlab_hardware_test" msgid="4148290860400659146">"testowanie sprzętu"</string>
     <string name="permdesc_hardware_test" msgid="3668894686500081699">"Pozwala aplikacji na kontrolowanie różnych urządzeń peryferyjnych w celu testowania sprzętu."</string>
     <string name="permlab_callPhone" msgid="3925836347681847954">"bezpośrednie wybieranie numerów telefonów"</string>
@@ -578,6 +573,16 @@
     <string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string>
     <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
     <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
+    <string name="status_bar_clear_all_button" msgid="7774721344716731603">"Wyczyść"</string>
+    <string name="status_bar_no_notifications_title" msgid="4755261167193833213">"Brak powiadomień"</string>
+    <string name="status_bar_ongoing_events_title" msgid="1682504513316879202">"Bieżące"</string>
+    <string name="status_bar_latest_events_title" msgid="6594767438577593172">"Powiadomienia"</string>
+    <string name="battery_status_text_percent_format" msgid="7660311274698797147">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
+    <string name="battery_status_charging" msgid="756617993998772213">"Ładowanie..."</string>
+    <string name="battery_low_title" msgid="7923774589611311406">"Podłącz ładowarkę"</string>
+    <string name="battery_low_subtitle" msgid="7388781709819722764">"Bateria się rozładowuje:"</string>
+    <string name="battery_low_percent_format" msgid="696154104579022959">"Pozostało: <xliff:g id="NUMBER">%d%%</xliff:g> lub mniej."</string>
+    <string name="battery_low_why" msgid="7279169609518386372">"Użycie baterii"</string>
     <string name="factorytest_failed" msgid="5410270329114212041">"Nieudany test fabryczny"</string>
     <string name="factorytest_not_system" msgid="4435201656767276723">"Czynność FACTORY_TEST jest obsługiwana tylko dla pakietów zainstalowanych w katalogu /system/app."</string>
     <string name="factorytest_no_action" msgid="872991874799998561">"Nie znaleziono żadnego pakietu, który zapewnia działanie FACTORY_TEST."</string>
@@ -739,16 +744,6 @@
     <string name="force_close" msgid="3653416315450806396">"Wymuś zamknięcie"</string>
     <string name="report" msgid="4060218260984795706">"Zgłoś"</string>
     <string name="wait" msgid="7147118217226317732">"Czekaj"</string>
-    <string name="smv_application" msgid="295583804361236288">"Aplikacja <xliff:g id="APPLICATION">%1$s</xliff:g> (proces <xliff:g id="PROCESS">%2$s</xliff:g>) naruszyła wymuszone przez siebie zasady StrictMode."</string>
-    <string name="smv_process" msgid="5120397012047462446">"Proces <xliff:g id="PROCESS">%1$s</xliff:g> naruszył wymuszone przez siebie zasady StrictMode."</string>
-    <string name="heavy_weight_notification" msgid="9087063985776626166">"Działa <xliff:g id="APP">%1$s</xliff:g>"</string>
-    <string name="heavy_weight_notification_detail" msgid="2423977499339403402">"Wybierz, aby przełączyć się na aplikację"</string>
-    <string name="heavy_weight_switcher_title" msgid="1135403633766694316">"Czy przełączyć aplikacje?"</string>
-    <string name="heavy_weight_switcher_text" msgid="4592075610079319667">"Działa już inna aplikacja, którą trzeba zatrzymać, aby możliwe było uruchomienie nowej."</string>
-    <string name="old_app_action" msgid="493129172238566282">"Powrót do aplikacji <xliff:g id="OLD_APP">%1$s</xliff:g>"</string>
-    <string name="old_app_description" msgid="942967900237208466">"Nie uruchamiaj nowej aplikacji."</string>
-    <string name="new_app_action" msgid="5472756926945440706">"Uruchom <xliff:g id="OLD_APP">%1$s</xliff:g>"</string>
-    <string name="new_app_description" msgid="6830398339826789493">"Zatrzymaj starą aplikację bez zapisywania."</string>
     <string name="sendText" msgid="5132506121645618310">"Jak wysłać tekst?"</string>
     <string name="volume_ringtone" msgid="6885421406845734650">"Głośność dzwonka"</string>
     <string name="volume_music" msgid="5421651157138628171">"Głośność multimediów"</string>
diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml
index 3fb8d55..750d257 100644
--- a/core/res/res/values-pt-rPT/strings.xml
+++ b/core/res/res/values-pt-rPT/strings.xml
@@ -34,7 +34,6 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2660020990097733077">"Correio de voz"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2850889754919584674">"MSISDN1"</string>
     <string name="mmiError" msgid="5154499457739052907">"Problema de ligação ou código MMI inválido."</string>
-    <string name="mmiFdnError" msgid="5224398216385316471">"A operação está restringida a números fixos autorizados."</string>
     <string name="serviceEnabled" msgid="8147278346414714315">"O serviço foi activado."</string>
     <string name="serviceEnabledFor" msgid="6856228140453471041">"O serviço foi activado para:"</string>
     <string name="serviceDisabled" msgid="1937553226592516411">"O serviço foi desactivado."</string>
@@ -172,8 +171,6 @@
     <string name="permgroupdesc_storage" msgid="9203302214915355774">"Aceder ao cartão SD."</string>
     <string name="permlab_statusBar" msgid="7417192629601890791">"desactivar ou modificar barra de estado"</string>
     <string name="permdesc_statusBar" msgid="1365473595331989732">"Permite à aplicação desactivar a barra de estado ou adicionar e remover ícones do sistema."</string>
-    <string name="permlab_statusBarService" msgid="7247281911387931485">"barra de estado"</string>
-    <string name="permdesc_statusBarService" msgid="4097605867643520920">"Permite que a aplicação seja apresentada na barra de estado."</string>
     <string name="permlab_expandStatusBar" msgid="1148198785937489264">"expandir/fechar barra de estado"</string>
     <string name="permdesc_expandStatusBar" msgid="7088604400110768665">"Permite à aplicação expandir ou fechar a barra de estado."</string>
     <string name="permlab_processOutgoingCalls" msgid="1136262550878335980">"interceptar chamadas efectuadas"</string>
@@ -314,8 +311,8 @@
     <string name="permdesc_modifyAudioSettings" msgid="5793461287365991922">"Permite à aplicação modificar as definições de áudio globais, tais como o volume e o encaminhamento."</string>
     <string name="permlab_recordAudio" msgid="3876049771427466323">"gravar áudio"</string>
     <string name="permdesc_recordAudio" msgid="6493228261176552356">"Permite à aplicação aceder ao caminho de gravação de áudio."</string>
-    <string name="permlab_camera" msgid="3616391919559751192">"tirar fotografias e vídeos"</string>
-    <string name="permdesc_camera" msgid="6004878235852154239">"Permite à aplicação tirar fotografias e vídeos com a câmara, permitindo que a aplicação recolha imagens captadas pela câmara em qualquer momento."</string>
+    <string name="permlab_camera" msgid="8059288807274039014">"tirar fotografias"</string>
+    <string name="permdesc_camera" msgid="9013476258810982546">"Permite à aplicação tirar fotografias com a câmara. Isto permite que a aplicação recolha imagens captadas pela câmara em qualquer momento."</string>
     <string name="permlab_brick" msgid="8337817093326370537">"desactivar telefone de forma permanente"</string>
     <string name="permdesc_brick" msgid="5569526552607599221">"Permite à aplicação desactivar permanentemente todo o telefone. Esta acção é muito perigosa."</string>
     <string name="permlab_reboot" msgid="2898560872462638242">"forçar reinício do telefone"</string>
@@ -338,8 +335,6 @@
     <string name="permdesc_vibrate" msgid="2886677177257789187">"Permite à aplicação controlar o vibrador."</string>
     <string name="permlab_flashlight" msgid="2155920810121984215">"controlar lanterna"</string>
     <string name="permdesc_flashlight" msgid="6433045942283802309">"Permite à aplicação controlar a lanterna."</string>
-    <string name="permlab_accessUsb" msgid="7362327818655760496">"aceder a dispositivos USB"</string>
-    <string name="permdesc_accessUsb" msgid="2414271762914049292">"Permite à aplicação aceder a dispositivos USB."</string>
     <string name="permlab_hardware_test" msgid="4148290860400659146">"testar hardware"</string>
     <string name="permdesc_hardware_test" msgid="3668894686500081699">"Permite à aplicação controlar vários periféricos para fins de teste de hardware."</string>
     <string name="permlab_callPhone" msgid="3925836347681847954">"marcar números de telefone directamente"</string>
@@ -578,6 +573,16 @@
     <string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string>
     <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g> <xliff:g id="AMPM">%P</xliff:g>"</string>
     <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g> <xliff:g id="AMPM">%p</xliff:g>"</string>
+    <string name="status_bar_clear_all_button" msgid="7774721344716731603">"Limpar"</string>
+    <string name="status_bar_no_notifications_title" msgid="4755261167193833213">"Sem notificações"</string>
+    <string name="status_bar_ongoing_events_title" msgid="1682504513316879202">"Em curso"</string>
+    <string name="status_bar_latest_events_title" msgid="6594767438577593172">"Notificações"</string>
+    <string name="battery_status_text_percent_format" msgid="7660311274698797147">"<xliff:g id="NUMBER">%d</xliff:g> <xliff:g id="PERCENT">%%</xliff:g>"</string>
+    <string name="battery_status_charging" msgid="756617993998772213">"A carregar..."</string>
+    <string name="battery_low_title" msgid="7923774589611311406">"Ligue o carregador"</string>
+    <string name="battery_low_subtitle" msgid="7388781709819722764">"A bateria está a ficar fraca:"</string>
+    <string name="battery_low_percent_format" msgid="696154104579022959">"Restam <xliff:g id="NUMBER">%d%%</xliff:g> ou menos."</string>
+    <string name="battery_low_why" msgid="7279169609518386372">"Utilização da bateria"</string>
     <string name="factorytest_failed" msgid="5410270329114212041">"O teste de fábrica falhou"</string>
     <string name="factorytest_not_system" msgid="4435201656767276723">"A acção FACTORY_TEST apenas é suportada para pacotes instalados em /system/app."</string>
     <string name="factorytest_no_action" msgid="872991874799998561">"Não foi localizado qualquer pacote que forneça a acção FACTORY_TEST."</string>
@@ -739,16 +744,6 @@
     <string name="force_close" msgid="3653416315450806396">"Forçar fecho"</string>
     <string name="report" msgid="4060218260984795706">"Relatório"</string>
     <string name="wait" msgid="7147118217226317732">"Esperar"</string>
-    <string name="smv_application" msgid="295583804361236288">"A aplicação <xliff:g id="APPLICATION">%1$s</xliff:g> (processo <xliff:g id="PROCESS">%2$s</xliff:g>) violou a política StrictMode auto-imposta."</string>
-    <string name="smv_process" msgid="5120397012047462446">"O processo <xliff:g id="PROCESS">%1$s</xliff:g> violou a política StrictMode auto-imposta."</string>
-    <string name="heavy_weight_notification" msgid="9087063985776626166">"<xliff:g id="APP">%1$s</xliff:g> em execução"</string>
-    <string name="heavy_weight_notification_detail" msgid="2423977499339403402">"Seleccione para mudar para a aplicação"</string>
-    <string name="heavy_weight_switcher_title" msgid="1135403633766694316">"Mudar aplicações?"</string>
-    <string name="heavy_weight_switcher_text" msgid="4592075610079319667">"Já está em execução uma outra aplicação que terá de ser parada para que possa iniciar uma nova."</string>
-    <string name="old_app_action" msgid="493129172238566282">"Regressar a <xliff:g id="OLD_APP">%1$s</xliff:g>"</string>
-    <string name="old_app_description" msgid="942967900237208466">"Não iniciar a nova aplicação."</string>
-    <string name="new_app_action" msgid="5472756926945440706">"Iniciar <xliff:g id="OLD_APP">%1$s</xliff:g>"</string>
-    <string name="new_app_description" msgid="6830398339826789493">"Parar a aplicação antiga sem guardar."</string>
     <string name="sendText" msgid="5132506121645618310">"Seleccionar uma acção para texto"</string>
     <string name="volume_ringtone" msgid="6885421406845734650">"Volume da campainha"</string>
     <string name="volume_music" msgid="5421651157138628171">"Volume de multimédia"</string>
diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml
index 81ceaf3..12f77bc 100644
--- a/core/res/res/values-pt/strings.xml
+++ b/core/res/res/values-pt/strings.xml
@@ -34,7 +34,6 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2660020990097733077">"Correio de voz"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2850889754919584674">"MSISDN1"</string>
     <string name="mmiError" msgid="5154499457739052907">"Problema de conexão ou código MMI inválido."</string>
-    <string name="mmiFdnError" msgid="5224398216385316471">"A operação é limitada somente a números de chamadas fixas."</string>
     <string name="serviceEnabled" msgid="8147278346414714315">"O serviço foi ativado."</string>
     <string name="serviceEnabledFor" msgid="6856228140453471041">"O serviço foi ativado para:"</string>
     <string name="serviceDisabled" msgid="1937553226592516411">"O serviço foi desativado."</string>
@@ -172,8 +171,6 @@
     <string name="permgroupdesc_storage" msgid="9203302214915355774">"Acessar o cartão SD."</string>
     <string name="permlab_statusBar" msgid="7417192629601890791">"desativar ou modificar a barra de status"</string>
     <string name="permdesc_statusBar" msgid="1365473595331989732">"Permite que o aplicativo desative a barra de status ou adicione e remova ícones do sistema."</string>
-    <string name="permlab_statusBarService" msgid="7247281911387931485">"barra de status"</string>
-    <string name="permdesc_statusBarService" msgid="4097605867643520920">"Permite que o aplicativo seja a barra de status."</string>
     <string name="permlab_expandStatusBar" msgid="1148198785937489264">"expandir/recolher barra de status"</string>
     <string name="permdesc_expandStatusBar" msgid="7088604400110768665">"Permite que o aplicativo expanda ou recolha a barra de status."</string>
     <string name="permlab_processOutgoingCalls" msgid="1136262550878335980">"interceptar chamadas enviadas"</string>
@@ -314,8 +311,8 @@
     <string name="permdesc_modifyAudioSettings" msgid="5793461287365991922">"Permite que o aplicativo modifique as configurações globais de áudio como volume e roteamento."</string>
     <string name="permlab_recordAudio" msgid="3876049771427466323">"gravar áudio"</string>
     <string name="permdesc_recordAudio" msgid="6493228261176552356">"Permite que um aplicativo acesse o caminho de gravação do áudio."</string>
-    <string name="permlab_camera" msgid="3616391919559751192">"tirar fotos e gravar vídeos"</string>
-    <string name="permdesc_camera" msgid="6004878235852154239">"Permite que o aplicativo tire fotos e grave vídeos com a câmera. Isso permite que o aplicativo colete imagens vistas pela câmera a qualquer momento."</string>
+    <string name="permlab_camera" msgid="8059288807274039014">"tirar fotos"</string>
+    <string name="permdesc_camera" msgid="9013476258810982546">"Permite que o aplicativo tire fotos com a câmera. Isso permite que o aplicativo colete imagens vistas pela câmera a qualquer momento."</string>
     <string name="permlab_brick" msgid="8337817093326370537">"desativar permanentemente o telefone"</string>
     <string name="permdesc_brick" msgid="5569526552607599221">"Permite que o aplicativo desative o telefone inteiro permanentemente. Isso é muito perigoso."</string>
     <string name="permlab_reboot" msgid="2898560872462638242">"forçar reinicialização do telefone"</string>
@@ -338,8 +335,6 @@
     <string name="permdesc_vibrate" msgid="2886677177257789187">"Permite que o aplicativo controle o vibrador."</string>
     <string name="permlab_flashlight" msgid="2155920810121984215">"controlar lanterna"</string>
     <string name="permdesc_flashlight" msgid="6433045942283802309">"Permite que o aplicativo controle a lanterna."</string>
-    <string name="permlab_accessUsb" msgid="7362327818655760496">"acessar dispositivos USB"</string>
-    <string name="permdesc_accessUsb" msgid="2414271762914049292">"Permitir que o aplicativo acesse dispositivos USB."</string>
     <string name="permlab_hardware_test" msgid="4148290860400659146">"testar hardware"</string>
     <string name="permdesc_hardware_test" msgid="3668894686500081699">"Permite que o aplicativo controle diversos periféricos para teste do hardware."</string>
     <string name="permlab_callPhone" msgid="3925836347681847954">"chamar diretamente os números de telefone"</string>
@@ -578,6 +573,16 @@
     <string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string>
     <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
     <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
+    <string name="status_bar_clear_all_button" msgid="7774721344716731603">"Limpar"</string>
+    <string name="status_bar_no_notifications_title" msgid="4755261167193833213">"Sem notificações"</string>
+    <string name="status_bar_ongoing_events_title" msgid="1682504513316879202">"Em andamento"</string>
+    <string name="status_bar_latest_events_title" msgid="6594767438577593172">"Notificações"</string>
+    <string name="battery_status_text_percent_format" msgid="7660311274698797147">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
+    <string name="battery_status_charging" msgid="756617993998772213">"Carregando..."</string>
+    <string name="battery_low_title" msgid="7923774589611311406">"Conecte o carregador"</string>
+    <string name="battery_low_subtitle" msgid="7388781709819722764">"A bateria está ficando baixa:"</string>
+    <string name="battery_low_percent_format" msgid="696154104579022959">"<xliff:g id="NUMBER">%d%%</xliff:g> ou menos restante(s)."</string>
+    <string name="battery_low_why" msgid="7279169609518386372">"Uso da bateria"</string>
     <string name="factorytest_failed" msgid="5410270329114212041">"Falha no teste de fábrica"</string>
     <string name="factorytest_not_system" msgid="4435201656767276723">"A ação FACTORY_TEST é suportada apenas para pacotes instalados em /system/app."</string>
     <string name="factorytest_no_action" msgid="872991874799998561">"Nenhum pacote que forneça a ação FACTORY_TEST foi encontrado."</string>
@@ -739,16 +744,6 @@
     <string name="force_close" msgid="3653416315450806396">"Forçar fechamento"</string>
     <string name="report" msgid="4060218260984795706">"Informar"</string>
     <string name="wait" msgid="7147118217226317732">"Aguardar"</string>
-    <string name="smv_application" msgid="295583804361236288">"O aplicativo <xliff:g id="APPLICATION">%1$s</xliff:g> (processo <xliff:g id="PROCESS">%2$s</xliff:g>) violou a política StrictMode imposta automaticamente."</string>
-    <string name="smv_process" msgid="5120397012047462446">"O processo <xliff:g id="PROCESS">%1$s</xliff:g> violou a política StrictMode imposta automaticamente."</string>
-    <string name="heavy_weight_notification" msgid="9087063985776626166">"<xliff:g id="APP">%1$s</xliff:g> em execução"</string>
-    <string name="heavy_weight_notification_detail" msgid="2423977499339403402">"Selecionar para alternar para o aplicativo"</string>
-    <string name="heavy_weight_switcher_title" msgid="1135403633766694316">"Alternar aplicativos?"</string>
-    <string name="heavy_weight_switcher_text" msgid="4592075610079319667">"Outro aplicativo já está em execução e deve ser interrompido antes que você inicie um novo aplicativo."</string>
-    <string name="old_app_action" msgid="493129172238566282">"Voltar para <xliff:g id="OLD_APP">%1$s</xliff:g>"</string>
-    <string name="old_app_description" msgid="942967900237208466">"Não inicie o novo aplicativo."</string>
-    <string name="new_app_action" msgid="5472756926945440706">"Iniciar <xliff:g id="OLD_APP">%1$s</xliff:g>"</string>
-    <string name="new_app_description" msgid="6830398339826789493">"Pare o aplicativo antigo sem salvar."</string>
     <string name="sendText" msgid="5132506121645618310">"Selecione uma ação para o texto"</string>
     <string name="volume_ringtone" msgid="6885421406845734650">"Volume da campainha"</string>
     <string name="volume_music" msgid="5421651157138628171">"Volume da mídia"</string>
diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml
index 974e848..bd41d60 100644
--- a/core/res/res/values-ru/strings.xml
+++ b/core/res/res/values-ru/strings.xml
@@ -34,7 +34,6 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2660020990097733077">"Голосовая почта"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2850889754919584674">"MSISDN1"</string>
     <string name="mmiError" msgid="5154499457739052907">"Неполадки подключения или неверный код MMI."</string>
-    <string name="mmiFdnError" msgid="5224398216385316471">"Операция возможна только для разрешенных номеров."</string>
     <string name="serviceEnabled" msgid="8147278346414714315">"Служба включена."</string>
     <string name="serviceEnabledFor" msgid="6856228140453471041">"Служба подключена для:"</string>
     <string name="serviceDisabled" msgid="1937553226592516411">"Служба отключена."</string>
@@ -172,8 +171,6 @@
     <string name="permgroupdesc_storage" msgid="9203302214915355774">"Доступ к SD-карте."</string>
     <string name="permlab_statusBar" msgid="7417192629601890791">"отключать или изменять строку состояния"</string>
     <string name="permdesc_statusBar" msgid="1365473595331989732">"Позволяет приложению отключать строку состояния или добавлять/удалять системные значки."</string>
-    <string name="permlab_statusBarService" msgid="7247281911387931485">"строка состояния"</string>
-    <string name="permdesc_statusBarService" msgid="4097605867643520920">"Позволяет приложению быть строкой состояния."</string>
     <string name="permlab_expandStatusBar" msgid="1148198785937489264">"разворачивать/сворачивать строку состояния"</string>
     <string name="permdesc_expandStatusBar" msgid="7088604400110768665">"Позволяет приложению разворачивать или сворачивать строку состояния."</string>
     <string name="permlab_processOutgoingCalls" msgid="1136262550878335980">"перехватывать исходящие вызовы"</string>
@@ -314,8 +311,8 @@
     <string name="permdesc_modifyAudioSettings" msgid="5793461287365991922">"Позволяет приложению изменять глобальные аудионастройки, такие как громкость и маршрутизацию."</string>
     <string name="permlab_recordAudio" msgid="3876049771427466323">"записывать аудио"</string>
     <string name="permdesc_recordAudio" msgid="6493228261176552356">"Позволяет приложению получать доступ к пути аудиозаписи."</string>
-    <string name="permlab_camera" msgid="3616391919559751192">"снимать фото и видео"</string>
-    <string name="permdesc_camera" msgid="6004878235852154239">"Позволяет приложению делать снимки и видео с помощью камеры. Это дает приложению возможность в любое время получать изображения с объектива камеры."</string>
+    <string name="permlab_camera" msgid="8059288807274039014">"снимать фотографии"</string>
+    <string name="permdesc_camera" msgid="9013476258810982546">"Позволяет приложению делать снимки с помощью камеры. Это разрешение позволяет приложению в любое время собирать изображения, видимые через объектив камеры."</string>
     <string name="permlab_brick" msgid="8337817093326370537">"отключать телефон"</string>
     <string name="permdesc_brick" msgid="5569526552607599221">"Позволяет данному приложению отключить телефон навсегда. Это очень опасно."</string>
     <string name="permlab_reboot" msgid="2898560872462638242">"принудительно перезагружать телефон"</string>
@@ -338,8 +335,6 @@
     <string name="permdesc_vibrate" msgid="2886677177257789187">"Позволяет приложению управлять виброзвонком."</string>
     <string name="permlab_flashlight" msgid="2155920810121984215">"управлять вспышкой"</string>
     <string name="permdesc_flashlight" msgid="6433045942283802309">"Позволяет приложению управлять вспышкой."</string>
-    <string name="permlab_accessUsb" msgid="7362327818655760496">"доступ к USB-устройствам"</string>
-    <string name="permdesc_accessUsb" msgid="2414271762914049292">"Позволяет приложению получать доступ к USB-устройствам."</string>
     <string name="permlab_hardware_test" msgid="4148290860400659146">"проверять аппаратное обеспечение"</string>
     <string name="permdesc_hardware_test" msgid="3668894686500081699">"Позволяет приложению управлять различными периферийными устройствами для проверки аппаратного обеспечения."</string>
     <string name="permlab_callPhone" msgid="3925836347681847954">"посылать прямые вызовы на номера телефонов"</string>
@@ -578,6 +573,16 @@
     <string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string>
     <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
     <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
+    <string name="status_bar_clear_all_button" msgid="7774721344716731603">"Очистить"</string>
+    <string name="status_bar_no_notifications_title" msgid="4755261167193833213">"Нет уведомлений"</string>
+    <string name="status_bar_ongoing_events_title" msgid="1682504513316879202">"Текущие"</string>
+    <string name="status_bar_latest_events_title" msgid="6594767438577593172">"Уведомления"</string>
+    <string name="battery_status_text_percent_format" msgid="7660311274698797147">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
+    <string name="battery_status_charging" msgid="756617993998772213">"Идет зарядка..."</string>
+    <string name="battery_low_title" msgid="7923774589611311406">"Подключите зарядное устройство"</string>
+    <string name="battery_low_subtitle" msgid="7388781709819722764">"Батарея разряжена:"</string>
+    <string name="battery_low_percent_format" msgid="696154104579022959">"Осталось <xliff:g id="NUMBER">%d%%</xliff:g> или меньше."</string>
+    <string name="battery_low_why" msgid="7279169609518386372">"Расход заряда батареи"</string>
     <string name="factorytest_failed" msgid="5410270329114212041">"Не удалось провести стандартный тест"</string>
     <string name="factorytest_not_system" msgid="4435201656767276723">"Действие FACTORY_TEST поддерживается только для пакетов, установленных в /system/app."</string>
     <string name="factorytest_no_action" msgid="872991874799998561">"Пакет, обеспечивающий действие FACTORY_TEST, не найден."</string>
@@ -723,8 +728,8 @@
     <string name="dialog_alert_title" msgid="2049658708609043103">"Внимание"</string>
     <string name="capital_on" msgid="1544682755514494298">"ВКЛ"</string>
     <string name="capital_off" msgid="6815870386972805832">"ВЫКЛ"</string>
-    <string name="whichApplication" msgid="4533185947064773386">"Что использовать?"</string>
-    <string name="alwaysUse" msgid="4583018368000610438">"По умолчанию для этого действия"</string>
+    <string name="whichApplication" msgid="4533185947064773386">"Завершить действие с помощью"</string>
+    <string name="alwaysUse" msgid="4583018368000610438">"Использовать по умолчанию для этого действия."</string>
     <string name="clearDefaultHintMsg" msgid="4815455344600932173">"Удалить настройки по умолчанию: главный экран &gt; \"Настройки\" &gt; \"Приложения\" &gt; \"Управление приложениями\"."</string>
     <string name="chooseActivity" msgid="1009246475582238425">"Выберите действие"</string>
     <string name="noApplications" msgid="1691104391758345586">"Это действие не может выполнять ни одно приложение."</string>
@@ -739,16 +744,6 @@
     <string name="force_close" msgid="3653416315450806396">"Закрыть"</string>
     <string name="report" msgid="4060218260984795706">"Отчет"</string>
     <string name="wait" msgid="7147118217226317732">"Подождать"</string>
-    <string name="smv_application" msgid="295583804361236288">"Приложение <xliff:g id="APPLICATION">%1$s</xliff:g> (процесс <xliff:g id="PROCESS">%2$s</xliff:g>) нарушило собственную политику StrictMode."</string>
-    <string name="smv_process" msgid="5120397012047462446">"Процесс <xliff:g id="PROCESS">%1$s</xliff:g> нарушил собственную политику StrictMode."</string>
-    <string name="heavy_weight_notification" msgid="9087063985776626166">"Приложение <xliff:g id="APP">%1$s</xliff:g> запущено"</string>
-    <string name="heavy_weight_notification_detail" msgid="2423977499339403402">"Нажмите, чтобы переключиться в приложение"</string>
-    <string name="heavy_weight_switcher_title" msgid="1135403633766694316">"Переключить приложения?"</string>
-    <string name="heavy_weight_switcher_text" msgid="4592075610079319667">"Выполняется другое приложение, которое должно быть остановлено прежде, чем запускать новое."</string>
-    <string name="old_app_action" msgid="493129172238566282">"Вернуться к приложению <xliff:g id="OLD_APP">%1$s</xliff:g>"</string>
-    <string name="old_app_description" msgid="942967900237208466">"Не запускать новое приложение."</string>
-    <string name="new_app_action" msgid="5472756926945440706">"Запустить приложение <xliff:g id="OLD_APP">%1$s</xliff:g>"</string>
-    <string name="new_app_description" msgid="6830398339826789493">"Остановить старое приложение без сохранения изменений."</string>
     <string name="sendText" msgid="5132506121645618310">"Выберите действие для текста"</string>
     <string name="volume_ringtone" msgid="6885421406845734650">"Громкость звонка"</string>
     <string name="volume_music" msgid="5421651157138628171">"Громкость мультимедиа"</string>
diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml
index 4c78f4f..b7cbc52 100644
--- a/core/res/res/values-sv/strings.xml
+++ b/core/res/res/values-sv/strings.xml
@@ -34,7 +34,6 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2660020990097733077">"Röstbrevlåda"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2850889754919584674">"MSISDN1"</string>
     <string name="mmiError" msgid="5154499457739052907">"Anslutningsproblem eller ogiltig MMI-kod."</string>
-    <string name="mmiFdnError" msgid="5224398216385316471">"Endast fasta nummer kan användas."</string>
     <string name="serviceEnabled" msgid="8147278346414714315">"Tjänsten har aktiverats."</string>
     <string name="serviceEnabledFor" msgid="6856228140453471041">"Tjänsten har aktiverats för:"</string>
     <string name="serviceDisabled" msgid="1937553226592516411">"Tjänsten har inaktiverats."</string>
@@ -172,8 +171,6 @@
     <string name="permgroupdesc_storage" msgid="9203302214915355774">"Få åtkomst till SD-kortet."</string>
     <string name="permlab_statusBar" msgid="7417192629601890791">"inaktivera eller ändra statusfält"</string>
     <string name="permdesc_statusBar" msgid="1365473595331989732">"Tillåter att programmet inaktiverar statusfältet eller lägger till och tar bort systemikoner."</string>
-    <string name="permlab_statusBarService" msgid="7247281911387931485">"statusfält"</string>
-    <string name="permdesc_statusBarService" msgid="4097605867643520920">"Tillåter att programmet visas i statusfältet."</string>
     <string name="permlab_expandStatusBar" msgid="1148198785937489264">"expandera/komprimera statusfält"</string>
     <string name="permdesc_expandStatusBar" msgid="7088604400110768665">"Tillåter att program expanderar eller komprimerar statusfältet."</string>
     <string name="permlab_processOutgoingCalls" msgid="1136262550878335980">"spärra utgående samtal"</string>
@@ -314,8 +311,8 @@
     <string name="permdesc_modifyAudioSettings" msgid="5793461287365991922">"Tillåter att ett program ändrar globala ljudinställningar, till exempel volym och routning."</string>
     <string name="permlab_recordAudio" msgid="3876049771427466323">"spela in ljud"</string>
     <string name="permdesc_recordAudio" msgid="6493228261176552356">"Tillåter att program får åtkomst till sökvägen för ljudinspelning."</string>
-    <string name="permlab_camera" msgid="3616391919559751192">"ta bilder och spela in videoklipp"</string>
-    <string name="permdesc_camera" msgid="6004878235852154239">"Tillåter att program tar kort och spelar in video med kameran. Då kan programmet när som helst fotografera eller spela in det som visas i kameran."</string>
+    <string name="permlab_camera" msgid="8059288807274039014">"ta bilder"</string>
+    <string name="permdesc_camera" msgid="9013476258810982546">"Tillåter att program tar kort med kameran. Då kan programmet när som helst samla bilderna som visas i kameran."</string>
     <string name="permlab_brick" msgid="8337817093326370537">"inaktivera telefonen permanent"</string>
     <string name="permdesc_brick" msgid="5569526552607599221">"Tillåter att programmet inaktiverar hela telefonen permanent. Detta är mycket farligt."</string>
     <string name="permlab_reboot" msgid="2898560872462638242">"tvinga omstart av telefon"</string>
@@ -338,8 +335,6 @@
     <string name="permdesc_vibrate" msgid="2886677177257789187">"Tillåter att programmet styr vibratorn."</string>
     <string name="permlab_flashlight" msgid="2155920810121984215">"styra lampa"</string>
     <string name="permdesc_flashlight" msgid="6433045942283802309">"Tillåter att programmet styr lampan."</string>
-    <string name="permlab_accessUsb" msgid="7362327818655760496">"åtkomst till USB-enheter"</string>
-    <string name="permdesc_accessUsb" msgid="2414271762914049292">"Tillåter att programmet använder USB-enheter."</string>
     <string name="permlab_hardware_test" msgid="4148290860400659146">"testa maskinvara"</string>
     <string name="permdesc_hardware_test" msgid="3668894686500081699">"Tillåter att ett program styr kringutrustning i syfte att testa maskinvara."</string>
     <string name="permlab_callPhone" msgid="3925836347681847954">"ringa telefonnummer direkt"</string>
@@ -578,6 +573,16 @@
     <string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string>
     <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
     <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
+    <string name="status_bar_clear_all_button" msgid="7774721344716731603">"Ta bort"</string>
+    <string name="status_bar_no_notifications_title" msgid="4755261167193833213">"Inga aviseringar"</string>
+    <string name="status_bar_ongoing_events_title" msgid="1682504513316879202">"Pågående"</string>
+    <string name="status_bar_latest_events_title" msgid="6594767438577593172">"Meddelanden"</string>
+    <string name="battery_status_text_percent_format" msgid="7660311274698797147">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
+    <string name="battery_status_charging" msgid="756617993998772213">"Laddar…"</string>
+    <string name="battery_low_title" msgid="7923774589611311406">"Anslut laddaren"</string>
+    <string name="battery_low_subtitle" msgid="7388781709819722764">"Batteriet håller på att ta slut:"</string>
+    <string name="battery_low_percent_format" msgid="696154104579022959">"<xliff:g id="NUMBER">%d%%</xliff:g> eller mindre kvar."</string>
+    <string name="battery_low_why" msgid="7279169609518386372">"Batteriförbrukning"</string>
     <string name="factorytest_failed" msgid="5410270329114212041">"Det gick fel vid fabrikstestet"</string>
     <string name="factorytest_not_system" msgid="4435201656767276723">"Åtgärden FACTORY_TEST stöds endast för paket som har installerats i /system/app."</string>
     <string name="factorytest_no_action" msgid="872991874799998561">"Vi hittade inget paket som erbjuder åtgärden FACTORY_TEST."</string>
@@ -739,16 +744,6 @@
     <string name="force_close" msgid="3653416315450806396">"Tvinga fram en stängning"</string>
     <string name="report" msgid="4060218260984795706">"Rapportera"</string>
     <string name="wait" msgid="7147118217226317732">"Vänta"</string>
-    <string name="smv_application" msgid="295583804361236288">"Programmet <xliff:g id="APPLICATION">%1$s</xliff:g> (processen <xliff:g id="PROCESS">%2$s</xliff:g>) har brutit mot sin egen StrictMode-policy."</string>
-    <string name="smv_process" msgid="5120397012047462446">"Processen <xliff:g id="PROCESS">%1$s</xliff:g> har brutit mot sin egen StrictMode-policy."</string>
-    <string name="heavy_weight_notification" msgid="9087063985776626166">"<xliff:g id="APP">%1$s</xliff:g> körs"</string>
-    <string name="heavy_weight_notification_detail" msgid="2423977499339403402">"Välj om du vill växla till programmet"</string>
-    <string name="heavy_weight_switcher_title" msgid="1135403633766694316">"Vill du byta program?"</string>
-    <string name="heavy_weight_switcher_text" msgid="4592075610079319667">"Ett annat program som körs måste avslutas innan du kan starta ett nytt."</string>
-    <string name="old_app_action" msgid="493129172238566282">"Gå tillbaka till <xliff:g id="OLD_APP">%1$s</xliff:g>"</string>
-    <string name="old_app_description" msgid="942967900237208466">"Starta inte det nya programmet."</string>
-    <string name="new_app_action" msgid="5472756926945440706">"Starta <xliff:g id="OLD_APP">%1$s</xliff:g>"</string>
-    <string name="new_app_description" msgid="6830398339826789493">"Avbryt det gamla programmet utan att spara."</string>
     <string name="sendText" msgid="5132506121645618310">"Välj en åtgärd för text"</string>
     <string name="volume_ringtone" msgid="6885421406845734650">"Ringvolym"</string>
     <string name="volume_music" msgid="5421651157138628171">"Mediavolym"</string>
diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml
index 6d56991..cb1e399 100644
--- a/core/res/res/values-tr/strings.xml
+++ b/core/res/res/values-tr/strings.xml
@@ -34,7 +34,6 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2660020990097733077">"Sesli Mesaj"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2850889754919584674">"MSISDN1"</string>
     <string name="mmiError" msgid="5154499457739052907">"Bağlantı sorunu veya geçersiz MMI kodu."</string>
-    <string name="mmiFdnError" msgid="5224398216385316471">"İşlem sadece sabit arama numaralarıyla sınırlandırılmıştır."</string>
     <string name="serviceEnabled" msgid="8147278346414714315">"Hizmet etkindi."</string>
     <string name="serviceEnabledFor" msgid="6856228140453471041">"Hizmet şunun için etkinleştirildi:"</string>
     <string name="serviceDisabled" msgid="1937553226592516411">"Hizmet devre dışı bırakıldı."</string>
@@ -172,8 +171,6 @@
     <string name="permgroupdesc_storage" msgid="9203302214915355774">"SD karta erişin."</string>
     <string name="permlab_statusBar" msgid="7417192629601890791">"durum çubuğunu devre dışı bırak veya değiştir"</string>
     <string name="permdesc_statusBar" msgid="1365473595331989732">"Uygulamanın durum çubuğunu devre dışı bırakmasına veya sistem simgeleri ekleyip kaldırmasına izin verir."</string>
-    <string name="permlab_statusBarService" msgid="7247281911387931485">"durum çubuğu"</string>
-    <string name="permdesc_statusBarService" msgid="4097605867643520920">"Uygulamanın durum çubuğu olmasına izin verir."</string>
     <string name="permlab_expandStatusBar" msgid="1148198785937489264">"durum çubuğunu genişlet/daralt"</string>
     <string name="permdesc_expandStatusBar" msgid="7088604400110768665">"Uygulamanın, durum çubuğunu genişletip daraltmasına izin verir."</string>
     <string name="permlab_processOutgoingCalls" msgid="1136262550878335980">"giden aramalarda araya gir"</string>
@@ -314,8 +311,8 @@
     <string name="permdesc_modifyAudioSettings" msgid="5793461287365991922">"Uygulamaların, ses düzeyi ve yönlendirme gibi genel ses ayarlarını değiştirmesine izin verir."</string>
     <string name="permlab_recordAudio" msgid="3876049771427466323">"ses kaydet"</string>
     <string name="permdesc_recordAudio" msgid="6493228261176552356">"Uygulamanın, ses kayıt yoluna erişmesine izin verir."</string>
-    <string name="permlab_camera" msgid="3616391919559751192">"resim çekme ve görüntü kaydetme"</string>
-    <string name="permdesc_camera" msgid="6004878235852154239">"Uygulamanın kamera ile resim çekmesine ve görüntü almasına izin verir. Bu işlev herhangi bir zamanda uygulamanın kamera görüntülerini yakalamasına olanak tanır."</string>
+    <string name="permlab_camera" msgid="8059288807274039014">"resim çek"</string>
+    <string name="permdesc_camera" msgid="9013476258810982546">"Uygulamaların kamera ile resim çekmesine izin verir. Bu işlev herhangi bir zamanda kameranın görmekte olduğu görüntüyü uygulamaların toplamasına izin verir."</string>
     <string name="permlab_brick" msgid="8337817093326370537">"telefonu tamamen devre dışı bırak"</string>
     <string name="permdesc_brick" msgid="5569526552607599221">"Uygulamaların tüm telefonu kalıcı olarak devre dışı bırakmasına izin verir. Bu çok tehlikelidir."</string>
     <string name="permlab_reboot" msgid="2898560872462638242">"telefonu yeniden başlamaya zorla"</string>
@@ -338,8 +335,6 @@
     <string name="permdesc_vibrate" msgid="2886677177257789187">"Uygulamanın titreşimi denetlemesine izin verir."</string>
     <string name="permlab_flashlight" msgid="2155920810121984215">"flaşı denetle"</string>
     <string name="permdesc_flashlight" msgid="6433045942283802309">"Uygulamaların flaş ışığını denetlemesine izin verir."</string>
-    <string name="permlab_accessUsb" msgid="7362327818655760496">"USB cihazlarına erişme"</string>
-    <string name="permdesc_accessUsb" msgid="2414271762914049292">"Uygulamaların USB cihazlarına erişimine izin verir"</string>
     <string name="permlab_hardware_test" msgid="4148290860400659146">"donanımı test et"</string>
     <string name="permdesc_hardware_test" msgid="3668894686500081699">"Uygulamanın donanım testi için çeşitli çevre birimlerini denetlemesine izin verir."</string>
     <string name="permlab_callPhone" msgid="3925836347681847954">"telefon numaralarına doğrudan çağrı yap"</string>
@@ -578,6 +573,16 @@
     <string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string>
     <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g> <xliff:g id="AMPM">%P</xliff:g>"</string>
     <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g> <xliff:g id="AMPM">%p</xliff:g>"</string>
+    <string name="status_bar_clear_all_button" msgid="7774721344716731603">"Temizle"</string>
+    <string name="status_bar_no_notifications_title" msgid="4755261167193833213">"Bildirim yok"</string>
+    <string name="status_bar_ongoing_events_title" msgid="1682504513316879202">"Sürüyor"</string>
+    <string name="status_bar_latest_events_title" msgid="6594767438577593172">"Bildirimler"</string>
+    <string name="battery_status_text_percent_format" msgid="7660311274698797147">"<xliff:g id="NUMBER">%d</xliff:g> <xliff:g id="PERCENT">%%</xliff:g>"</string>
+    <string name="battery_status_charging" msgid="756617993998772213">"Şarj oluyor…"</string>
+    <string name="battery_low_title" msgid="7923774589611311406">"Lütfen şarj cihazını takın"</string>
+    <string name="battery_low_subtitle" msgid="7388781709819722764">"Pil tükeniyor:"</string>
+    <string name="battery_low_percent_format" msgid="696154104579022959">"<xliff:g id="NUMBER">%d%%</xliff:g> veya daha az kaldı."</string>
+    <string name="battery_low_why" msgid="7279169609518386372">"Pil kullanımı"</string>
     <string name="factorytest_failed" msgid="5410270329114212041">"Fabrika testi yapılamadı"</string>
     <string name="factorytest_not_system" msgid="4435201656767276723">"FACTORY_TEST işlemi yalnızca /system/app dizinine yüklenmiş paketler için desteklenir."</string>
     <string name="factorytest_no_action" msgid="872991874799998561">"FACTORY_TEST işlemini sağlayan hiçbir paket bulunamadı."</string>
@@ -739,16 +744,6 @@
     <string name="force_close" msgid="3653416315450806396">"Kapanmaya zorla"</string>
     <string name="report" msgid="4060218260984795706">"Rapor"</string>
     <string name="wait" msgid="7147118217226317732">"Bekle"</string>
-    <string name="smv_application" msgid="295583804361236288">"<xliff:g id="APPLICATION">%1$s</xliff:g> uygulaması (<xliff:g id="PROCESS">%2$s</xliff:g> işlemi) kendiliğinden zorunlu StrictMode politikasını ihlal etti."</string>
-    <string name="smv_process" msgid="5120397012047462446">"<xliff:g id="PROCESS">%1$s</xliff:g> işlemi kendiliğinden zorunlu StrictMode politikasını ihlal etti."</string>
-    <string name="heavy_weight_notification" msgid="9087063985776626166">"<xliff:g id="APP">%1$s</xliff:g> çalışıyor"</string>
-    <string name="heavy_weight_notification_detail" msgid="2423977499339403402">"Uygulama değiştirmeyi seçin"</string>
-    <string name="heavy_weight_switcher_title" msgid="1135403633766694316">"Uygulamaların arasında geçiş yapılsın mı?"</string>
-    <string name="heavy_weight_switcher_text" msgid="4592075610079319667">"Başka bir uygulama zaten çalışıyor. Yeni bir uygulama başlatmadan bu uygulama durdurulmalıdır."</string>
-    <string name="old_app_action" msgid="493129172238566282">"<xliff:g id="OLD_APP">%1$s</xliff:g> öğesine geri dön"</string>
-    <string name="old_app_description" msgid="942967900237208466">"Yeni uygulamayı başlatmayın."</string>
-    <string name="new_app_action" msgid="5472756926945440706">"<xliff:g id="OLD_APP">%1$s</xliff:g> uygulamasını başlat"</string>
-    <string name="new_app_description" msgid="6830398339826789493">"Eski uygulamayı kaydetmeden durdurun."</string>
     <string name="sendText" msgid="5132506121645618310">"Metin için bir işlem seçin"</string>
     <string name="volume_ringtone" msgid="6885421406845734650">"Zil sesi düzeyi"</string>
     <string name="volume_music" msgid="5421651157138628171">"Medya ses düzeyi"</string>
diff --git a/core/res/res/values-xlarge/config.xml b/core/res/res/values-xlarge/config.xml
new file mode 100644
index 0000000..c5a53b2
--- /dev/null
+++ b/core/res/res/values-xlarge/config.xml
@@ -0,0 +1,28 @@
+<?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.
+*/
+-->
+
+<!-- These resources are around just to allow their values to be customized
+     for different hardware and product builds. -->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- Component to be used as the status bar service.  Must implement the IStatusBar
+         interface.  This name is in the ComponentName flattened format (package/class)  -->
+    <string name="config_statusBarComponent">com.android.systemui/com.android.systemui.statusbar.tablet.TabletStatusBarService</string>
+    <bool name="config_statusBarCanHide">false</bool>
+</resources>
+
diff --git a/core/res/res/values-xlarge/dimens.xml b/core/res/res/values-xlarge/dimens.xml
new file mode 100644
index 0000000..b3fdf46
--- /dev/null
+++ b/core/res/res/values-xlarge/dimens.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/assets/res/any/dimens.xml
+**
+** 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.
+*/
+-->
+<resources>
+    <dimen name="status_bar_height">50dip</dimen>
+    <!-- Height of the status bar -->
+    <dimen name="status_bar_icon_size">50dip</dimen>
+    <!-- Margin at the edge of the screen to ignore touch events for in the windowshade. -->
+</resources>
+
diff --git a/core/res/res/values-xlarge/strings.xml b/core/res/res/values-xlarge/strings.xml
new file mode 100644
index 0000000..fc20be6
--- /dev/null
+++ b/core/res/res/values-xlarge/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2010, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+
+    <!-- Do not translate.  WebView User Agent targeted content -->
+    <string name="web_user_agent_target_content" translatable="false"></string>
+
+</resources>
\ No newline at end of file
diff --git a/core/res/res/values-xlarge/styles.xml b/core/res/res/values-xlarge/styles.xml
new file mode 100644
index 0000000..40e423e
--- /dev/null
+++ b/core/res/res/values-xlarge/styles.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<resources>
+    <!-- Status Bar Styles -->
+
+    <style name="TextAppearance.StatusBar">
+        <item name="android:textAppearance">?android:attr/textAppearanceSmall</item>
+        <item name="android:textColor">?android:attr/textColorPrimaryInverse</item>
+    </style>
+    <style name="TextAppearance.StatusBar.Ticker">
+    </style>
+    <style name="TextAppearance.StatusBar.Title">
+        <item name="android:textStyle">bold</item>
+    </style>
+
+    <style name="TextAppearance.StatusBar.Icon">
+        <item name="android:textStyle">bold</item>
+    </style>
+    <style name="TextAppearance.StatusBar.EventContent">
+        <item name="android:textColor">?android:attr/textColorPrimary</item>
+    </style>
+    <style name="TextAppearance.StatusBar.EventContent.Title">
+        <item name="android:textSize">18sp</item>
+        <item name="android:textStyle">bold</item>
+    </style>
+</resources>
diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml
index 486e125..834a84c 100644
--- a/core/res/res/values-zh-rCN/strings.xml
+++ b/core/res/res/values-zh-rCN/strings.xml
@@ -34,7 +34,6 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2660020990097733077">"语音信箱"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2850889754919584674">"MSISDN1"</string>
     <string name="mmiError" msgid="5154499457739052907">"出现连接问题或 MMI 码无效。"</string>
-    <string name="mmiFdnError" msgid="5224398216385316471">"只能对固定拨号号码执行此类操作。"</string>
     <string name="serviceEnabled" msgid="8147278346414714315">"已启用服务。"</string>
     <string name="serviceEnabledFor" msgid="6856228140453471041">"已针对以下内容启用了服务:"</string>
     <string name="serviceDisabled" msgid="1937553226592516411">"已停用服务。"</string>
@@ -172,8 +171,6 @@
     <string name="permgroupdesc_storage" msgid="9203302214915355774">"访问 SD 卡。"</string>
     <string name="permlab_statusBar" msgid="7417192629601890791">"停用或修改状态栏"</string>
     <string name="permdesc_statusBar" msgid="1365473595331989732">"允许应用程序停用状态栏或者增删系统图标。"</string>
-    <string name="permlab_statusBarService" msgid="7247281911387931485">"状态栏"</string>
-    <string name="permdesc_statusBarService" msgid="4097605867643520920">"允许以状态栏形式显示应用程序。"</string>
     <string name="permlab_expandStatusBar" msgid="1148198785937489264">"展开/收拢状态栏"</string>
     <string name="permdesc_expandStatusBar" msgid="7088604400110768665">"允许应用程序展开或收拢状态栏。"</string>
     <string name="permlab_processOutgoingCalls" msgid="1136262550878335980">"拦截外拨电话"</string>
@@ -314,8 +311,8 @@
     <string name="permdesc_modifyAudioSettings" msgid="5793461287365991922">"允许应用程序修改整个系统的音频设置,如音量和路由。"</string>
     <string name="permlab_recordAudio" msgid="3876049771427466323">"录音"</string>
     <string name="permdesc_recordAudio" msgid="6493228261176552356">"允许应用程序访问录音路径。"</string>
-    <string name="permlab_camera" msgid="3616391919559751192">"拍摄照片和视频"</string>
-    <string name="permdesc_camera" msgid="6004878235852154239">"允许应用程序使用相机拍摄照片和视频,这样应用程序可随时收集进入相机镜头中看到的图片。"</string>
+    <string name="permlab_camera" msgid="8059288807274039014">"拍照"</string>
+    <string name="permdesc_camera" msgid="9013476258810982546">"允许应用程序使用相机拍照,这样应用程序可随时收集进入相机镜头的图像。"</string>
     <string name="permlab_brick" msgid="8337817093326370537">"永久停用手机"</string>
     <string name="permdesc_brick" msgid="5569526552607599221">"允许应用程序永久停用整个手机,这非常危险。"</string>
     <string name="permlab_reboot" msgid="2898560872462638242">"强行重新启动手机"</string>
@@ -338,8 +335,6 @@
     <string name="permdesc_vibrate" msgid="2886677177257789187">"允许应用程序控制振动器。"</string>
     <string name="permlab_flashlight" msgid="2155920810121984215">"控制闪光灯"</string>
     <string name="permdesc_flashlight" msgid="6433045942283802309">"允许应用程序控制闪光灯。"</string>
-    <string name="permlab_accessUsb" msgid="7362327818655760496">"访问 USB 设备"</string>
-    <string name="permdesc_accessUsb" msgid="2414271762914049292">"允许应用程序访问 USB 设备。"</string>
     <string name="permlab_hardware_test" msgid="4148290860400659146">"测试硬件"</string>
     <string name="permdesc_hardware_test" msgid="3668894686500081699">"允许应用程序控制各外围设备以进行硬件测试。"</string>
     <string name="permlab_callPhone" msgid="3925836347681847954">"直接拨打电话号码"</string>
@@ -578,6 +573,16 @@
     <string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string>
     <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="AMPM">%P</xliff:g><xliff:g id="HOUR">%-l</xliff:g>点"</string>
     <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="AMPM">%p</xliff:g><xliff:g id="HOUR">%-l</xliff:g>点"</string>
+    <string name="status_bar_clear_all_button" msgid="7774721344716731603">"清除"</string>
+    <string name="status_bar_no_notifications_title" msgid="4755261167193833213">"无通知"</string>
+    <string name="status_bar_ongoing_events_title" msgid="1682504513316879202">"正在进行的"</string>
+    <string name="status_bar_latest_events_title" msgid="6594767438577593172">"通知"</string>
+    <string name="battery_status_text_percent_format" msgid="7660311274698797147">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
+    <string name="battery_status_charging" msgid="756617993998772213">"正在充电..."</string>
+    <string name="battery_low_title" msgid="7923774589611311406">"请连接充电器"</string>
+    <string name="battery_low_subtitle" msgid="7388781709819722764">"电量所剩不多:"</string>
+    <string name="battery_low_percent_format" msgid="696154104579022959">"电量剩余 <xliff:g id="NUMBER">%d%%</xliff:g> 或更少。"</string>
+    <string name="battery_low_why" msgid="7279169609518386372">"电量使用情况"</string>
     <string name="factorytest_failed" msgid="5410270329114212041">"出厂测试失败"</string>
     <string name="factorytest_not_system" msgid="4435201656767276723">"只有在 /system/app 中安装的包支持 FACTORY_TEST 操作。"</string>
     <string name="factorytest_no_action" msgid="872991874799998561">"未发现支持 FACTORY_TEST 操作的包。"</string>
@@ -739,16 +744,6 @@
     <string name="force_close" msgid="3653416315450806396">"强行关闭"</string>
     <string name="report" msgid="4060218260984795706">"报告"</string>
     <string name="wait" msgid="7147118217226317732">"等待"</string>
-    <string name="smv_application" msgid="295583804361236288">"应用程序<xliff:g id="APPLICATION">%1$s</xliff:g>(<xliff:g id="PROCESS">%2$s</xliff:g> 进程)违反了自我强制执行的严格模式 (StrictMode) 政策。"</string>
-    <string name="smv_process" msgid="5120397012047462446">"进程 <xliff:g id="PROCESS">%1$s</xliff:g> 违反了自我强制执行的严格模式 (StrictMode) 政策"</string>
-    <string name="heavy_weight_notification" msgid="9087063985776626166">"<xliff:g id="APP">%1$s</xliff:g>正在运行"</string>
-    <string name="heavy_weight_notification_detail" msgid="2423977499339403402">"选择以切换到该应用程序"</string>
-    <string name="heavy_weight_switcher_title" msgid="1135403633766694316">"要切换应用程序吗?"</string>
-    <string name="heavy_weight_switcher_text" msgid="4592075610079319667">"已有一个应用程序正在运行,要启动新的应用程序,您必须先停止该应用程序。"</string>
-    <string name="old_app_action" msgid="493129172238566282">"返回至<xliff:g id="OLD_APP">%1$s</xliff:g>"</string>
-    <string name="old_app_description" msgid="942967900237208466">"不要启动新的应用程序。"</string>
-    <string name="new_app_action" msgid="5472756926945440706">"启动<xliff:g id="OLD_APP">%1$s</xliff:g>"</string>
-    <string name="new_app_description" msgid="6830398339826789493">"停止旧的应用程序,但不保存。"</string>
     <string name="sendText" msgid="5132506121645618310">"选择要对文字执行的操作"</string>
     <string name="volume_ringtone" msgid="6885421406845734650">"铃声音量"</string>
     <string name="volume_music" msgid="5421651157138628171">"媒体音量"</string>
diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml
index 560d407..549f571 100644
--- a/core/res/res/values-zh-rTW/strings.xml
+++ b/core/res/res/values-zh-rTW/strings.xml
@@ -34,7 +34,6 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2660020990097733077">"語音留言"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2850889754919584674">"MSISDN1"</string>
     <string name="mmiError" msgid="5154499457739052907">"連線發生問題或錯誤的 MMI 碼。"</string>
-    <string name="mmiFdnError" msgid="5224398216385316471">"僅允許在固定撥號時使用此操作。"</string>
     <string name="serviceEnabled" msgid="8147278346414714315">"服務已啟用。"</string>
     <string name="serviceEnabledFor" msgid="6856228140453471041">"已啟用服務:"</string>
     <string name="serviceDisabled" msgid="1937553226592516411">"服務已停用。"</string>
@@ -172,8 +171,6 @@
     <string name="permgroupdesc_storage" msgid="9203302214915355774">"存取 SD 卡。"</string>
     <string name="permlab_statusBar" msgid="7417192629601890791">"停用或變更狀態列"</string>
     <string name="permdesc_statusBar" msgid="1365473595331989732">"允許應用程式停用狀態列或新增、移除系統圖示。"</string>
-    <string name="permlab_statusBarService" msgid="7247281911387931485">"狀態列"</string>
-    <string name="permdesc_statusBarService" msgid="4097605867643520920">"允許應用程式成為狀態列。"</string>
     <string name="permlab_expandStatusBar" msgid="1148198785937489264">"展開/收攏狀態列"</string>
     <string name="permdesc_expandStatusBar" msgid="7088604400110768665">"允許應用程式展開或收攏狀態列。"</string>
     <string name="permlab_processOutgoingCalls" msgid="1136262550878335980">"攔截撥出電話"</string>
@@ -314,8 +311,8 @@
     <string name="permdesc_modifyAudioSettings" msgid="5793461287365991922">"允許應用程式編輯全域音訊設定,例如音量與路由。"</string>
     <string name="permlab_recordAudio" msgid="3876049771427466323">"錄製音訊"</string>
     <string name="permdesc_recordAudio" msgid="6493228261176552356">"允許應用程式存取音訊錄製路徑。"</string>
-    <string name="permlab_camera" msgid="3616391919559751192">"拍照和錄影"</string>
-    <string name="permdesc_camera" msgid="6004878235852154239">"允許應用程式使用相機拍照和錄影。此功能可讓應用程式隨時透過相機收集圖片。"</string>
+    <string name="permlab_camera" msgid="8059288807274039014">"照相"</string>
+    <string name="permdesc_camera" msgid="9013476258810982546">"允許應用程式使用相機拍照。此功能可讓應用程式隨時透過相機拍攝照片。"</string>
     <string name="permlab_brick" msgid="8337817093326370537">"永久停用電話"</string>
     <string name="permdesc_brick" msgid="5569526552607599221">"允許應用程式永久停用手機。此項操作非常危險。"</string>
     <string name="permlab_reboot" msgid="2898560872462638242">"強制重開機"</string>
@@ -338,8 +335,6 @@
     <string name="permdesc_vibrate" msgid="2886677177257789187">"允許應用程式控制震動。"</string>
     <string name="permlab_flashlight" msgid="2155920810121984215">"控制閃光燈"</string>
     <string name="permdesc_flashlight" msgid="6433045942283802309">"允許應用程式控制閃光燈。"</string>
-    <string name="permlab_accessUsb" msgid="7362327818655760496">"存取 USB 裝置"</string>
-    <string name="permdesc_accessUsb" msgid="2414271762914049292">"允許應用程式存取 USB 裝置。"</string>
     <string name="permlab_hardware_test" msgid="4148290860400659146">"測試硬體"</string>
     <string name="permdesc_hardware_test" msgid="3668894686500081699">"允許應用程式控制各種週邊設備,以供測試用。"</string>
     <string name="permlab_callPhone" msgid="3925836347681847954">"直接撥打電話號碼"</string>
@@ -578,6 +573,16 @@
     <string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string>
     <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
     <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
+    <string name="status_bar_clear_all_button" msgid="7774721344716731603">"清除"</string>
+    <string name="status_bar_no_notifications_title" msgid="4755261167193833213">"沒有通知"</string>
+    <string name="status_bar_ongoing_events_title" msgid="1682504513316879202">"進行中"</string>
+    <string name="status_bar_latest_events_title" msgid="6594767438577593172">"通知"</string>
+    <string name="battery_status_text_percent_format" msgid="7660311274698797147">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
+    <string name="battery_status_charging" msgid="756617993998772213">"充電中"</string>
+    <string name="battery_low_title" msgid="7923774589611311406">"請連接充電器"</string>
+    <string name="battery_low_subtitle" msgid="7388781709819722764">"電池電量即將不足:"</string>
+    <string name="battery_low_percent_format" msgid="696154104579022959">"還剩 <xliff:g id="NUMBER">%d%%</xliff:g> 以下。"</string>
+    <string name="battery_low_why" msgid="7279169609518386372">"電池使用狀況"</string>
     <string name="factorytest_failed" msgid="5410270329114212041">"出廠測試失敗"</string>
     <string name="factorytest_not_system" msgid="4435201656767276723">"只有安裝在 /system/app 裡的程式才能支援 FACTORY_TEST 操作。"</string>
     <string name="factorytest_no_action" msgid="872991874799998561">"找不到提供 FACTORY_TEST 的程式。"</string>
@@ -739,16 +744,6 @@
     <string name="force_close" msgid="3653416315450806396">"強制關閉"</string>
     <string name="report" msgid="4060218260984795706">"回報"</string>
     <string name="wait" msgid="7147118217226317732">"等待"</string>
-    <string name="smv_application" msgid="295583804361236288">"應用程式 <xliff:g id="APPLICATION">%1$s</xliff:g> (處理程序 <xliff:g id="PROCESS">%2$s</xliff:g>) 已違反其自行實施的 StrictMode 政策。"</string>
-    <string name="smv_process" msgid="5120397012047462446">"處理程序 <xliff:g id="PROCESS">%1$s</xliff:g> 已違反其自行實施的 StrictMode 政策。"</string>
-    <string name="heavy_weight_notification" msgid="9087063985776626166">"<xliff:g id="APP">%1$s</xliff:g> 執行中"</string>
-    <string name="heavy_weight_notification_detail" msgid="2423977499339403402">"選取切換應用程式"</string>
-    <string name="heavy_weight_switcher_title" msgid="1135403633766694316">"切換應用程式?"</string>
-    <string name="heavy_weight_switcher_text" msgid="4592075610079319667">"其他應用程式已在執行中,您必須停止執行,才能啟動新的應用程式。"</string>
-    <string name="old_app_action" msgid="493129172238566282">"返回 <xliff:g id="OLD_APP">%1$s</xliff:g>"</string>
-    <string name="old_app_description" msgid="942967900237208466">"請勿啟動新的應用程式。"</string>
-    <string name="new_app_action" msgid="5472756926945440706">"啟動 <xliff:g id="OLD_APP">%1$s</xliff:g>"</string>
-    <string name="new_app_description" msgid="6830398339826789493">"停止舊的應用程式,且不儲存。"</string>
     <string name="sendText" msgid="5132506121645618310">"訊息傳送方式"</string>
     <string name="volume_ringtone" msgid="6885421406845734650">"鈴聲音量"</string>
     <string name="volume_music" msgid="5421651157138628171">"媒體音量"</string>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index d16b91c..97c5822 100755
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -14,8 +14,8 @@
      limitations under the License.
 -->
 
-<!-- Formatting note: terminate all comments with a period, to avoid breaking 
-     the documentation output. To suppress comment lines from the documentation 
+<!-- Formatting note: terminate all comments with a period, to avoid breaking
+     the documentation output. To suppress comment lines from the documentation
      output, insert an eat-comment element after the comment lines.
 -->
 
@@ -244,6 +244,17 @@
              {@link android.R.styleable#WindowAnimation}. -->
         <attr name="windowAnimationStyle" format="reference" />
 
+        <!-- Flag indicating whether this window should have an Action Bar
+             in place of the usual title bar. -->
+        <attr name="windowActionBar" format="boolean" />
+
+        <!-- Reference to a style for the Action Bar -->
+        <attr name="windowActionBarStyle" format="reference" />
+
+        <!-- Flag indicating whether action modes should overlay window content
+             when there is not reserved space for their UI (such as an Action Bar). -->
+        <attr name="windowActionModeOverlay" format="boolean" />
+
         <!-- Defines the default soft input state that this window would
              like when it is displayed. -->
         <attr name="windowSoftInputMode">
@@ -390,6 +401,12 @@
         <attr name="horizontalScrollViewStyle" format="reference" />
         <!-- Default Spinner style. -->
         <attr name="spinnerStyle" format="reference" />
+        <!-- Default dropdown Spinner style. -->
+        <attr name="dropDownSpinnerStyle" format="reference" />
+        <!-- Default ActionBar dropdown style. -->
+        <attr name="actionDropDownStyle" format="reference" />
+        <!-- Default action button style. -->
+        <attr name="actionButtonStyle" format="reference" />
         <!-- Default Star style. -->
         <attr name="starStyle" format="reference" />
         <!-- Default TabWidget style. -->
@@ -426,6 +443,22 @@
         <attr name="quickContactBadgeStyleSmallWindowLarge" format="reference" />
 
         <!-- =================== -->
+        <!-- Action bar styles   -->
+        <!-- =================== -->
+        <eat-comment />
+        <!-- Default amount of padding to use between action buttons. -->
+        <attr name="actionButtonPadding" format="dimension" />
+
+        <!-- =================== -->
+        <!-- Action mode styles  -->
+        <!-- =================== -->
+        <eat-comment />
+        <!-- Background drawable to use for action mode UI -->
+        <attr name="actionModeBackground" format="reference" />
+        <!-- Drawable to use for the close action mode button -->
+        <attr name="actionModeCloseDrawable" format="reference" />
+
+        <!-- =================== -->
         <!-- Preference styles   -->
         <!-- =================== -->
         <eat-comment />
@@ -963,6 +996,9 @@
         <attr name="textColor" />
         <attr name="backgroundDimEnabled" />
         <attr name="backgroundDimAmount" />
+        <attr name="windowActionBar" />
+        <attr name="windowActionBarStyle" />
+        <attr name="windowActionModeOverlay" />
     </declare-styleable>
 
     <!-- The set of attributes that describe a AlertDialog's theme. -->
@@ -989,7 +1025,7 @@
         <attr name="windowShowAnimation" format="reference" />
         <!-- The animation used when a window is going from VISIBLE to INVISIBLE. -->
         <attr name="windowHideAnimation" format="reference" />
-        
+
         <!--  When opening a new activity, this is the animation that is
               run on the next activity (which is entering the screen). -->
         <attr name="activityOpenEnterAnimation" format="reference" />
@@ -1030,7 +1066,7 @@
               animation that is run on the top activity of the current task
               (which is exiting the screen). -->
         <attr name="taskToBackExitAnimation" format="reference" />
-        
+
         <!--  When opening a new activity that shows the wallpaper, while
               currently not showing the wallpaper, this is the animation that
               is run on the new wallpaper activity (which is entering the screen). -->
@@ -1047,7 +1083,7 @@
               currently showing the wallpaper, this is the animation that
               is run on the old wallpaper activity (which is exiting the screen). -->
         <attr name="wallpaperCloseExitAnimation" format="reference" />
-        
+
         <!--  When opening a new activity that is on top of the wallpaper
               when the current activity is also on top of the wallpaper,
               this is the animation that is run on the new activity
@@ -1535,6 +1571,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,
@@ -1730,7 +1768,7 @@
         <!-- When set to true, all children with a weight will be considered having
              the minimum size of the largest child. If false, all children are
              measured normally. -->
-        <attr name="useLargestChild" format="boolean" />
+        <attr name="measureWithLargestChild" format="boolean" />
     </declare-styleable>
     <declare-styleable name="ListView">
         <!-- Reference to an array resource that will populate the ListView.  For static content,
@@ -1752,6 +1790,8 @@
             <enum name="singleChoice" value="1" />
             <!-- The list allows multiple choices. -->
             <enum name="multipleChoice" value="2" />
+            <!-- The list allows multiple choices in a custom selection mode. -->
+            <enum name="multipleChoiceModal" value="3" />
         </attr>
         <!-- When set to false, the ListView will not draw the divider after each header view.
              The default value is true. -->
@@ -2154,7 +2194,7 @@
         <attr name="dropDownAnchor" format="reference" />
         <!-- Specifies the basic width of the dropdown. Its value may
              be a dimension (such as "12dip") for a constant width,
-             fill_parent or match_parent to match the width of the 
+             fill_parent or match_parent to match the width of the
              screen, or wrap_content to match the width of
              the anchored view. -->
         <attr name="dropDownWidth" format="dimension">
@@ -2190,14 +2230,33 @@
         <attr name="popupBackground" format="reference|color" />
     </declare-styleable>
     <declare-styleable name="ViewAnimator">
+        <!-- Identifier for the animation to use when a view is shown. -->
         <attr name="inAnimation" format="reference" />
+        <!-- Identifier for the animation to use when a view is hidden. -->
         <attr name="outAnimation" format="reference" />
+        <!-- Defines whether to animate the current View when the ViewAnimation
+             is first displayed. -->
+        <attr name="animateFirstView" format="boolean" />
     </declare-styleable>
     <declare-styleable name="ViewFlipper">
         <attr name="flipInterval" format="integer" min="0" />
         <!-- When true, automatically start animating -->
         <attr name="autoStart" format="boolean" />
     </declare-styleable>
+    <declare-styleable name="AdapterViewAnimator">
+        <!-- Identifier for the animation to use when a view is shown. -->
+        <attr name="inAnimation" />
+        <!-- Identifier for the animation to use when a view is hidden. -->
+        <attr name="outAnimation" />
+        <!-- Defines whether to animate the current View when the ViewAnimation
+             is first displayed. -->
+        <attr name="animateFirstView" />
+    </declare-styleable>
+    <declare-styleable name="AdapterViewFlipper">
+        <attr name="flipInterval" />
+        <!-- When true, automatically start animating -->
+        <attr name="autoStart" />
+    </declare-styleable>
     <declare-styleable name="ViewSwitcher">
     </declare-styleable>
     <declare-styleable name="ScrollView">
@@ -2211,6 +2270,30 @@
     <declare-styleable name="Spinner">
         <!-- The prompt to display when the spinner's dialog is shown. -->
         <attr name="prompt" format="reference" />
+        <!-- Display mode for spinner options. -->
+        <attr name="spinnerMode" format="enum">
+            <!-- Spinner options will be presented to the user as a dialog window. -->
+            <enum name="dialog" value="0" />
+            <!-- Spinner options will be presented to the user as an inline dropdown
+                 anchored to the spinner widget itself. -->
+            <enum name="dropdown" value="1" />
+        </attr>
+        <!-- List selector to use for spinnerMode="dropdown" display. -->
+        <attr name="dropDownSelector" />
+        <!-- Background drawable to use for the dropdown in spinnerMode="dropdown". -->
+        <attr name="popupBackground" />
+        <!-- Vertical offset from the spinner widget for positioning the dropdown in
+             spinnerMode="dropdown". -->
+        <attr name="dropDownVerticalOffset" />
+        <!-- Horizontal offset from the spinner widget for positioning the dropdown
+             in spinnerMode="dropdown". -->
+        <attr name="dropDownHorizontalOffset" />
+        <!-- Width of the dropdown in spinnerMode="dropdown". -->
+        <attr name="dropDownWidth" />
+        <!-- Reference to a layout to use for displaying a prompt in the dropdown for
+             spinnerMode="dropdown". This layout must contain a TextView with the id
+             @android:id/text1 to be populated with the prompt text. -->
+        <attr name="popupPromptView" format="reference" />
     </declare-styleable>
     <declare-styleable name="DatePicker">
         <!-- The first year (inclusive), for example "1940". -->
@@ -2521,6 +2604,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" />
@@ -2844,6 +2931,69 @@
     </declare-styleable>
 
     <!-- ========================== -->
+    <!-- Animator class attributes -->
+    <!-- ========================== -->
+    <eat-comment />
+
+    <declare-styleable name="Animator">
+        <!-- Defines the interpolator used to smooth the animation movement in time. -->
+        <attr name="interpolator" />
+        <!-- When set to true, fillAfter is taken into account. -->
+        <!-- Amount of time (in milliseconds) for the animation to run. -->
+        <attr name="duration" />
+        <!-- Delay in milliseconds before the animation runs, once start time is reached. -->
+        <attr name="startOffset"/>
+        <!-- Defines how many times the animation should repeat. The default value is 0. -->
+        <attr name="repeatCount"/>
+        <!-- Defines the animation behavior when it reaches the end and the repeat count is
+             greater than 0 or infinite. The default value is restart. -->
+        <attr name="repeatMode"/>
+        <!-- Value the animation starts from. -->
+        <attr name="valueFrom" format="float|integer"/>
+        <!-- Value the animation animates to. -->
+        <attr name="valueTo" format="float|integer"/>
+        <!-- The type of valueFrom and valueTo. -->
+        <attr name="valueType">
+            <!-- valueFrom and valueTo are floats. -->
+            <enum name="floatType" value="0" />
+            <!-- valueFrom and valueTo are integers. -->
+            <enum name="intType"   value="1" />
+            <!-- valueFrom and valueTo are doubles. -->
+            <enum name="doubleType" value="2" />
+            <!-- valueFrom and valueTo are colors. -->
+            <enum name="colorType" value="3" />
+            <!-- valueFrom and valueTo are a custom type. -->
+            <enum name="customType" value="4" />
+        </attr>
+    </declare-styleable>
+
+    <!-- ========================== -->
+    <!-- PropertyAnimator class attributes -->
+    <!-- ========================== -->
+    <eat-comment />
+
+    <declare-styleable name="PropertyAnimator">
+        <!-- Name of the property being animated. -->
+        <attr name="propertyName" format="string"/>
+    </declare-styleable>
+
+
+    <!-- ========================== -->
+    <!-- Sequencer class attributes -->
+    <!-- ========================== -->
+    <eat-comment />
+
+    <declare-styleable name="Sequencer">
+        <!-- Name of the property being animated. -->
+        <attr name="ordering">
+            <!-- child animations should be played together. -->
+            <enum name="together" value="0" />
+            <!-- child animations should be played sequentially, in the same order as the xml. -->
+            <enum name="sequentially" value="1" />
+        </attr>
+    </declare-styleable>
+
+    <!-- ========================== -->
     <!-- State attributes           -->
     <!-- ========================== -->
     <eat-comment />
@@ -3252,6 +3402,17 @@
         <!-- Whether the item is enabled. -->
         <attr name="enabled" />
 
+        <!-- Name of a method on the Context used to inflate the menu that will be
+             called when the item is clicked. -->
+        <attr name="onClick" />
+
+        <!-- How this item should display in the Action Bar, if present. -->
+        <attr name="showAsAction" format="enum">
+            <enum name="never" value="0" />
+            <enum name="ifRoom" value="1" />
+            <enum name="always" value="2" />
+        </attr>
+
     </declare-styleable>
 
     <!-- **************************************************************** -->
@@ -3267,6 +3428,19 @@
         <attr name="orderingFromXml" format="boolean" />
     </declare-styleable>
 
+    <!-- Attribute for a header describing the item shown in the top-level list
+         from which the selects the set of preference to dig in to. -->
+    <declare-styleable name="PreferenceHeader">
+        <!-- The title of the item that is shown to the user. -->
+        <attr name="title" />
+        <!-- The summary for the item. -->
+        <attr name="summary" format="string" />
+        <!-- An icon for the item. -->
+        <attr name="icon" />
+        <!-- The fragment that is displayed when the user selects this item. -->
+        <attr name="fragment" format="string" />
+    </declare-styleable>
+
     <!-- WARNING:  If adding attributes to Preference, make sure it does not conflict
                    with a View's attributes.  Some subclasses (e.g., EditTextPreference)
                    proxy all attributes to its EditText widget. -->
@@ -3279,10 +3453,13 @@
         <!-- The title for the Preference in a PreferenceActivity screen. -->
         <attr name="title" />
         <!-- The summary for the Preference in a PreferenceActivity screen. -->
-        <attr name="summary" format="string" />
+        <attr name="summary" />
         <!-- The order for the Preference (lower values are to be ordered first). If this is not
              specified, the default orderin will be alphabetic. -->
         <attr name="order" format="integer" />
+        <!-- When used inside of a modern PreferenceActivity, this declares
+             a new PreferenceFragment to be shown when the user selects this item. -->
+        <attr name="fragment" />
         <!-- The layout for the Preference in a PreferenceActivity screen. This should
              rarely need to be changed, look at widgetLayout instead. -->
         <attr name="layout" />
@@ -3354,6 +3531,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. -->
@@ -3505,7 +3692,7 @@
     <!-- AppWidget package class attributes -->
     <!-- =============================== -->
     <eat-comment />
-    
+
     <!-- Use <code>appwidget-provider</code> as the root tag of the XML resource that
          describes an AppWidget provider.  See {@link android.appwidget android.appwidget}
          package for more info.
@@ -3522,13 +3709,49 @@
         <!-- A class name in the AppWidget's package to be launched to configure.
              If not supplied, then no activity will be launched. -->
         <attr name="configure" format="string" />
+        <!-- A preview of what the AppWidget will look like after it's configured.
+       	     If not supplied, the AppWidget's icon will be used. -->
+        <attr name="previewImage" format="reference" />
     </declare-styleable>
 
     <!-- =============================== -->
     <!-- App package class attributes -->
     <!-- =============================== -->
     <eat-comment />
-    
+
+    <!-- ============================= -->
+    <!-- View package class attributes -->
+    <!-- ============================= -->
+    <eat-comment />
+
+    <!-- Attributes that can be used with <code>&lt;fragment&gt;</code>
+         tags inside of the layout of an Activity.  This instantiates
+         the given {@link android.app.Fragment} and inserts its content
+         view into the current location in the layout. -->
+    <declare-styleable name="Fragment">
+        <!-- Supply the name of the fragment class to instantiate. -->
+        <attr name="name" />
+
+        <!-- Supply an identifier name for the top-level view, to later retrieve it
+             with {@link android.view.View#findViewById View.findViewById()} or
+             {@link android.app.Activity#findViewById Activity.findViewById()}.
+             This must be a
+             resource reference; typically you set this using the
+             <code>@+</code> syntax to create a new ID resources.
+             For example: <code>android:id="@+id/my_id"</code> which
+             allows you to later retrieve the view
+             with <code>findViewById(R.id.my_id)</code>. -->
+        <attr name="id" />
+
+        <!-- Supply a tag for the top-level view containing a String, to be retrieved
+             later with {@link android.view.View#getTag View.getTag()} or
+             searched for with {@link android.view.View#findViewWithTag
+             View.findViewWithTag()}.  It is generally preferable to use
+             IDs (through the android:id attribute) instead of tags because
+             they are faster and allow for compile-time type checking. -->
+        <attr name="tag" />
+    </declare-styleable>
+
     <!-- Use <code>device-admin</code> as the root tag of the XML resource that
          describes a
          {@link android.app.admin.DeviceAdminReceiver}, which is
@@ -3568,7 +3791,7 @@
     <!-- Accounts package class attributes -->
     <!-- =============================== -->
     <eat-comment />
-    
+
     <!-- Use <code>account-authenticator</code> as the root tag of the XML resource that
          describes an account authenticator.
      -->
@@ -3589,7 +3812,7 @@
     <!-- Accounts package class attributes -->
     <!-- =============================== -->
     <eat-comment />
-    
+
     <!-- Use <code>account-authenticator</code> as the root tag of the XML resource that
          describes an account authenticator.
      -->
@@ -3605,7 +3828,7 @@
     <!-- Contacts meta-data attributes -->
     <!-- =============================== -->
     <eat-comment />
-    
+
     <!-- TODO: remove this deprecated styleable. -->
     <eat-comment />
     <declare-styleable name="Icon">
@@ -3631,6 +3854,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>
 
     <!-- =============================== -->
@@ -3662,4 +3888,105 @@
         <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>
+
+    <!-- Attributes used to style the Action Bar. -->
+    <declare-styleable name="ActionBar">
+        <!-- The type of navigation to use. -->
+        <attr name="navigationMode">
+            <!-- Normal static title text -->
+            <enum name="normal" value="0" />
+            <!-- The action bar will use a drop-down selection in place of title text. -->
+            <enum name="dropdownList" value="1" />
+            <!-- The action bar will use a series of horizontal tabs in place of title text. -->
+            <enum name="tabBar" value="2" />
+        </attr>
+        <!-- Options affecting how the action bar is displayed. -->
+        <attr name="displayOptions">
+            <flag name="useLogo" value="1" />
+            <flag name="hideHome" value="2" />
+        </attr>
+        <!-- Specifies the color used to style the action bar. -->
+        <attr name="colorFilter" format="color" />
+        <!-- Specifies title text used for navigationMode="normal" -->
+        <attr name="title" />
+        <!-- Specifies subtitle text used for navigationMode="normal" -->
+        <attr name="subtitle" format="string" />
+        <!-- Specifies a style to use for title text. -->
+        <attr name="titleTextStyle" format="reference" />
+        <!-- Specifies a style to use for subtitle text. -->
+        <attr name="subtitleTextStyle" format="reference" />
+        <!-- Specifies the drawable used for the application icon. -->
+        <attr name="icon" />
+        <!-- Specifies the drawable used for the application logo. -->
+        <attr name="logo" />
+        <!-- Specifies the drawable used for item dividers. -->
+        <attr name="divider" />
+        <!-- Specifies a background drawable for the action bar. -->
+        <attr name="background" />
+        <!-- Specifies a layout for custom navigation. Overrides navigationMode. -->
+        <attr name="customNavigationLayout" format="reference" />
+    </declare-styleable>
+
 </resources>
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index f18d14e..bc130f2 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -235,6 +235,10 @@
          the safe mode.  -->
     <attr name="vmSafeMode" format="boolean" />
 
+    <!-- Flag indicating whether the application's rendering should be hardware
+         accelerated if possible.  -->
+    <attr name="hardwareAccelerated" format="boolean" />
+
     <!-- Flag indicating whether the given application component is available
          to other applications.  If false, it can only be accessed by
          applications with its same user id (which usually means only by
@@ -734,6 +738,7 @@
         <attr name="enabled" />
         <attr name="debuggable" />
         <attr name="vmSafeMode" />
+        <attr name="hardwareAccelerated" />
         <!-- Name of activity to be launched for managing the application's space on the device. -->
         <attr name="manageSpaceActivity" />
         <attr name="allowClearUserData" />
@@ -1205,6 +1210,7 @@
              any value in the theme. -->
         <attr name="windowSoftInputMode" />
         <attr name="immersive" />
+        <attr name="hardwareAccelerated" />
     </declare-styleable>
     
     <!-- The <code>activity-alias</code> tag declares a new
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index d565c68..cf2b423 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -22,7 +22,8 @@
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <!-- Component to be used as the status bar service.  Must implement the IStatusBar
          interface.  This name is in the ComponentName flattened format (package/class)  -->
-    <string name="config_statusBarComponent">com.android.systemui/com.android.systemui.statusbar.StatusBarService</string>
+    <string name="config_statusBarComponent">com.android.systemui/com.android.systemui.statusbar.PhoneStatusBarService</string>
+    <bool name="config_statusBarCanHide">true</bool>
 
     <!-- Do not translate. Defines the slots for the right-hand side icons.  That is to say, the
          icons in the status bar that are not notifications. -->
@@ -72,6 +73,13 @@
          when there's no network connection. If the scan doesn't timeout, use zero -->
     <integer name="config_radioScanningTimeout">0</integer>
 
+    <!-- Set to true if the location returned Environment.getExternalStorageDirectory()
+         is actually a subdirectory of the internal storage.
+         If this is set then Environment.getExternalStorageState() will always return
+         MEDIA_MOUNTED and Intent.ACTION_MEDIA_MOUNTED will be broadcast at boot time
+         for backward compatibility with apps that require external storage. -->
+    <bool name="config_emulateExternalStorage">false</bool>
+
     <!-- XXXXX NOTE THE FOLLOWING RESOURCES USE THE WRONG NAMING CONVENTION.
          Please don't copy them, copy anything else. -->
 
@@ -98,6 +106,9 @@
         <item>"0,1"</item>
     </string-array>
 
+    <!-- The maximum duration (in milliseconds) we expect a network transition to take -->
+    <integer name="config_networkTransitionTimeout">60000</integer>
+
     <!-- List of regexpressions describing the interface (if any) that represent tetherable
          USB interfaces.  If the device doesn't want to support tething over USB this should
          be empty.  An example would be "usb.*" -->
@@ -191,6 +202,9 @@
     <!-- Indicate whether the SD card is accessible without removing the battery. -->
     <bool name="config_batterySdCardAccessibility">false</bool>
 
+    <!-- Indicate whether the device has USB host support. -->
+    <bool name="config_hasUsbHostSupport">false</bool>
+
     <!-- Vibrator pattern for feedback about a long screen/key press -->
     <integer-array name="config_longPressVibePattern">
         <item>0</item>
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 11f3e50..679e642 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -25,6 +25,9 @@
     <!-- The standard size (both width and height) of an application icon that
          will be displayed in the app launcher and elsewhere. -->
     <dimen name="app_icon_size">48dip</dimen>
+    <!-- The standard size (both width and height) of an action icon that will
+         be displayed in application action bars. -->
+    <dimen name="action_icon_size">48dip</dimen>
     <dimen name="toast_y_offset">64dip</dimen>
     <!-- Height of the status bar -->
     <dimen name="status_bar_height">25dip</dimen>
@@ -42,4 +45,6 @@
     <dimen name="password_keyboard_key_height">56dip</dimen>
     <!-- Default correction for the space key in the password keyboard -->
     <dimen name="password_keyboard_spacebar_vertical_correction">4dip</dimen>
+    <!-- Distance between the text base line and virtual finger position used to position cursor -->
+    <dimen name="cursor_controller_vertical_offset">12dp</dimen>
 </resources>
diff --git a/core/res/res/values/ids.xml b/core/res/res/values/ids.xml
index 8b6af71..33cd100 100644
--- a/core/res/res/values/ids.xml
+++ b/core/res/res/values/ids.xml
@@ -59,6 +59,7 @@
   <item type="id" name="copy" />
   <item type="id" name="paste" />
   <item type="id" name="copyUrl" />
+  <item type="id" name="selectTextMode" />
   <item type="id" name="switchInputMethod" />
   <item type="id" name="keyboardView" />
   <item type="id" name="closeButton" />
@@ -68,4 +69,5 @@
   <item type="id" name="accountPreferences" />
   <item type="id" name="smallIcon" />
   <item type="id" name="custom" />
+  <item type="id" name="home" />
 </resources>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index bd65fee..99263d7 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -1183,7 +1183,7 @@
   <public type="attr" name="colorBackgroundCacheHint" id="0x010102ab" />
   <public type="attr" name="dropDownHorizontalOffset" id="0x010102ac" />
   <public type="attr" name="dropDownVerticalOffset" id="0x010102ad" />
-  
+
   <public type="style" name="Theme.Wallpaper" id="0x0103005e" />
   <public type="style" name="Theme.Wallpaper.NoTitleBar" id="0x0103005f" />
   <public type="style" name="Theme.Wallpaper.NoTitleBar.Fullscreen" id="0x01030060" />
@@ -1191,7 +1191,7 @@
   <public type="style" name="Theme.Light.WallpaperSettings" id="0x01030062" />
   <public type="style" name="TextAppearance.SearchResult.Title" id="0x01030063" />
   <public type="style" name="TextAppearance.SearchResult.Subtitle" id="0x01030064" />
-  
+
   <!-- Semi-transparent background that can be used when placing a dark
        themed UI on top of some arbitrary background (such as the
        wallpaper).  This darkens the background sufficiently that the UI
@@ -1199,7 +1199,7 @@
   <public type="drawable" name="screen_background_dark_transparent" id="0x010800a9" />
   <public type="drawable" name="screen_background_light_transparent" id="0x010800aa" />
   <public type="drawable" name="stat_notify_sdcard_prepare" id="0x010800ab" />
-  
+
 <!-- ===============================================================
      Resources added in version 6 of the platform (Eclair 2.0.1).
      =============================================================== -->
@@ -1211,7 +1211,7 @@
   <public type="attr" name="quickContactBadgeStyleSmallWindowSmall" id="0x010102b1" />
   <public type="attr" name="quickContactBadgeStyleSmallWindowMedium" id="0x010102b2" />
   <public type="attr" name="quickContactBadgeStyleSmallWindowLarge" id="0x010102b3" />
-  
+
 <!-- ===============================================================
      Resources added in version 7 of the platform (Eclair MR1).
      =============================================================== -->
@@ -1220,7 +1220,7 @@
   <public type="attr" name="author" id="0x010102b4" />
   <public type="attr" name="autoStart" id="0x010102b5" />
 
-    
+
 <!-- ===============================================================
      Resources added in version 8 of the platform (Eclair MR2).
      =============================================================== -->
@@ -1241,19 +1241,19 @@
   <public type="attr" name="tabStripEnabled" id="0x010102bd" />
 
   <public type="id" name="custom" id="0x0102002b" />
-    
+
   <public type="anim" name="cycle_interpolator" id="0x010a000c" />
 
 <!-- ===============================================================
-     Resources introduced in kraken.
+     Resources introduced in Gingerbread.
      =============================================================== -->
-     
+  <eat-comment />
   <public type="attr" name="logo" id="0x010102be" />
   <public type="attr" name="xlargeScreens" id="0x010102bf" />
   <public type="attr" name="heavyWeight" id="0x010102c0" />
   <public type="attr" name="immersive" id="0x010102c1" />
   <public-padding type="attr" name="kraken_resource_pad" end="0x01010300" />
-  
+
   <public-padding type="id" name="kraken_resource_pad" end="0x01020040" />
   <public-padding type="anim" name="kraken_resource_pad" end="0x010a0020" />
 
@@ -1263,9 +1263,9 @@
   <public type="drawable" name="presence_video_online" id="0x010800ae" />
   <public type="drawable" name="presence_audio_away" id="0x010800af" />
   <public type="drawable" name="presence_audio_busy" id="0x010800b0" />
-  <public type="drawable" name="presence_audio_online" id="0x010800b1" />  
+  <public type="drawable" name="presence_audio_online" id="0x010800b1" />
   <public-padding type="drawable" name="kraken_resource_pad" end="0x01080100" />
-  
+
   <public-padding type="style" name="kraken_resource_pad" end="0x01030090" />
   <public-padding type="string" name="kraken_resource_pad" end="0x01040020" />
   <public-padding type="integer" name="kraken_resource_pad" end="0x010e0010" />
@@ -1274,4 +1274,65 @@
   <public-padding type="color" name="kraken_resource_pad" end="0x01060020" />
   <public-padding type="array" name="kraken_resource_pad" end="0x01070010" />
 
+<!-- ===============================================================
+     Resources proposed for Honeycomb.
+     =============================================================== -->
+  <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" />
+  <public type="attr" name="windowActionBar" />
+  <public type="attr" name="windowActionBarStyle" />
+  <public type="attr" name="navigationMode" />
+  <public type="attr" name="displayOptions" />
+  <public type="attr" name="subtitle" />
+  <public type="attr" name="customNavigationLayout" />
+  <public type="attr" name="hardwareAccelerated" />
+  <public type="attr" name="measureWithLargestChild" />
+  <public type="attr" name="animateFirstView" />
+  <public type="attr" name="dropDownSpinnerStyle" />
+  <public type="attr" name="actionDropDownStyle" />
+  <public type="attr" name="actionButtonStyle" />
+  <public type="attr" name="showAsAction" />
+  <public type="attr" name="actionButtonPadding" />
+  <public type="attr" name="previewImage" />
+  <public type="attr" name="actionModeBackground" />
+  <public type="attr" name="actionModeCloseDrawable" />
+  <public type="attr" name="windowActionModeOverlay" />
+  <public type="attr" name="valueFrom" />
+  <public type="attr" name="valueTo" />
+  <public type="attr" name="valueType" />
+  <public type="attr" name="propertyName" />
+  <public type="attr" name="ordering" />
+  <public type="attr" name="fragment" />
+
+  <public type="id" name="home" />
+  <!-- Context menu ID for the "Select text..." menu item to switch to text
+       selection context mode in text views. -->
+  <public type="id" name="selectTextMode" />
+
+  <public type="style" name="Theme.WithActionBar" />
+  <public type="style" name="Widget.Spinner.DropDown" />
+  <public type="style" name="Widget.ActionButton" />
+  <public type="style" name="Theme.Dialog.NoFrame" />
+  <public type="style" name="Theme.NoTitleBar.OverlayActionModes" />
+
+  <!-- Standard content view for a {@link android.app.ListFragment}.
+       If you are implementing a subclass of ListFragment with your
+       own customized content, you can include this layout in that
+       content to still retain all of the standard functionality of
+       the base class. -->
+  <public type="layout" name="list_content" />
+
+  <public type="string" name="selectTextMode" id="0x01040030" />
 </resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 62fd169..32a1f0d 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1252,6 +1252,11 @@
     <!-- Description of policy access to wipe the user's data -->
     <string name="policydesc_wipeData">Perform a factory reset, deleting
         all of your data without any confirmation.</string>
+    <string name="policylab_setGlobalProxy">Set the device global proxy</string>
+    <!-- Description of policy access to wipe the user's data -->
+    <string name="policydesc_setGlobalProxy">Set the device global proxy
+        to be used while policy is enabled. Only the first device admin
+        sets the effective global proxy.</string>
 
     <!-- The order of these is important, don't reorder without changing Contacts.java --> <skip />
     <!-- Phone number types from android.provider.Contacts. This could be used when adding a new phone number for a contact, for example. -->
@@ -1421,20 +1426,18 @@
     <!-- Custom organization type -->
     <string name="orgTypeCustom">Custom</string>
 
-    <!-- Attbution of a contact status update, when the time of update is unknown -->
-    <string name="contact_status_update_attribution">via <xliff:g id="source" example="Google Talk">%1$s</xliff:g></string>
-
-    <!-- Attbution of a contact status update, when the time of update is known -->
-    <string name="contact_status_update_attribution_with_date"><xliff:g id="date" example="3 hours ago">%1$s</xliff:g> via <xliff:g id="source" example="Google Talk">%2$s</xliff:g></string>
-
     <!-- Instructions telling the user to enter their SIM PIN to unlock the keyguard.
          Displayed in one line in a large font.  -->
     <string name="keyguard_password_enter_pin_code">Enter PIN code</string>
 
-    <!-- Instructions telling the user to enter their PIN password to unlock the keyguard.
+    <!-- Instructions telling the user to enter their text password to unlock the keyguard.
          Displayed in one line in a large font.  -->
     <string name="keyguard_password_enter_password_code">Enter password to unlock</string>
 
+    <!-- Instructions telling the user to enter their PIN password to unlock the keyguard.
+         Displayed in one line in a large font.  -->
+    <string name="keyguard_password_enter_pin_password_code">Enter PIN to unlock</string>
+
     <!-- Instructions telling the user that they entered the wrong pin while trying
          to unlock the keyguard.  Displayed in one line in a large font.  -->
     <string name="keyguard_password_wrong_pin_code">Incorrect PIN code!</string>
@@ -1471,6 +1474,8 @@
     <string name="lockscreen_pattern_correct">Correct!</string>
     <!-- On the unlock pattern screen, shown when the user enters the wrong lock pattern and must try again. -->
     <string name="lockscreen_pattern_wrong">Sorry, try again</string>
+    <!-- On the unlock password screen, shown when the user enters the wrong lock password and must try again. -->
+    <string name="lockscreen_password_wrong">Sorry, try again</string>
 
     <!-- When the lock screen is showing and the phone plugged in, and the battery
          is not fully charged, show the current charge %.  -->
@@ -1514,12 +1519,27 @@
          progress dialog in the meantime.  this is the emssage. -->
     <string name="lockscreen_sim_unlock_progress_dialog_message">Unlocking SIM card\u2026</string>
 
-    <!-- For the unlock screen, Information message shown in dialog when user has too many failed attempts -->
+    <!-- For the unlock screen, Information message shown in dialog when user has too many failed attempts at
+         drawing the unlock pattern -->
     <string name="lockscreen_too_many_failed_attempts_dialog_message">
         You have incorrectly drawn your unlock pattern <xliff:g id="number">%d</xliff:g> times.
         \n\nPlease try again in <xliff:g id="number">%d</xliff:g> seconds.
     </string>
 
+    <!-- For the unlock screen, Information message shown in dialog when user has too many failed attempts at
+         entering the password -->
+    <string name="lockscreen_too_many_failed_password_attempts_dialog_message">
+        You have incorrectly entered your password <xliff:g id="number">%d</xliff:g> times.
+        \n\nPlease try again in <xliff:g id="number">%d</xliff:g> seconds.
+    </string>
+
+    <!-- For the unlock screen, Information message shown in dialog when user has too many failed attempts at
+         entering the PIN -->
+    <string name="lockscreen_too_many_failed_pin_attempts_dialog_message">
+        You have incorrectly entered your PIN <xliff:g id="number">%d</xliff:g> times.
+        \n\nPlease try again in <xliff:g id="number">%d</xliff:g> seconds.
+    </string>
+
     <!-- For the unlock screen, Information message shown in dialog when user is almost at the limit
          where they will be locked out and may have to enter an alternate username/password to unlock the phone -->
     <string name="lockscreen_failed_attempts_almost_glogin">
@@ -1589,8 +1609,10 @@
     <string name="factorytest_reboot">Reboot</string>
 
     <!-- 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>
+    <string name="web_user_agent" translatable="false">Mozilla/5.0 (Linux; U; <xliff:g id="x">Android %s</xliff:g>)
+        AppleWebKit/534.4 (KHTML, like Gecko) Version/4.0 <xliff:g id="mobile">%s</xliff:g>Safari/534.4</string>
+    <!-- Do not translate.  WebView User Agent targeted content -->
+    <string name="web_user_agent_target_content" translatable="false">"Mobile "</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>
@@ -1853,39 +1875,29 @@
     <!-- Item on EditText context menu. This action is used to select all text in the edit field. -->
     <string name="selectAll">Select all</string>
 
-    <!-- Item on EditText context menu. This action is used to start selecting text in the edit field. -->
-    <string name="selectText">Select text</string>
-
-    <!-- Item on EditText context menu. This action is used to start selecting text in the edit field. -->
-    <string name="stopSelectingText">Stop selecting text</string>
-
     <!-- Item on EditText context menu.  This action is used to cut selected the text into the clipboard.  -->
     <string name="cut">Cut</string>
 
-    <!-- Item on EditText context menu. This action is used to cut all the text into the clipboard. -->
-    <string name="cutAll">Cut all</string>
-
     <!-- Item on EditText context menu. This action is used to cut selected the text into the clipboard. -->
     <string name="copy">Copy</string>
 
-    <!-- Item on EditText context menu. This action is used to copy all the text into the clipboard. -->
-    <string name="copyAll">Copy all</string>
-
     <!-- Item on EditText context menu. This action is used t o paste from the clipboard into the eidt field -->
     <string name="paste">Paste</string>
 
     <!-- Item on EditText context menu. This action is used to copy a URL from the edit field into the clipboard. -->
     <string name="copyUrl">Copy URL</string>
 
+    <!-- Item on EditText context menu. Added only when the context menu is not empty, it enable selection context mode. -->
+    <string name="selectTextMode">Select text...</string>
+
+    <!-- Text selection contextual mode title, displayed in the CAB.. -->
+    <string name="textSelectionCABTitle">Text selection</string>
+
     <!-- EditText context menu -->
     <string name="inputMethod">Input method</string>
 
-    <!-- Item on EditText context menu, used to add a word to the
-         input method dictionary. -->
-    <string name="addToDictionary">"Add \"<xliff:g id="word" example="rickroll">%s</xliff:g>\" to dictionary</string>
-
     <!-- Title for EditText context menu -->
-    <string name="editTextMenuTitle">Edit text</string>
+    <string name="editTextMenuTitle">Text actions</string>
 
     <!-- If the device is getting low on internal storage, a notification is shown to the user.  This is the title of that notification. -->
     <string name="low_internal_storage_view_title">Low on space</string>
@@ -1904,6 +1916,9 @@
          combined with setIcon(android.R.drawable.ic_dialog_alert) -->
     <string name="dialog_alert_title">Attention</string>
 
+    <!-- Text shown by list fragment when waiting for data to display. -->
+    <string name="loading">Loading...</string>
+    
     <!-- Default text for a button that can be toggled on and off. -->
     <string name="capital_on">ON</string>
     <!-- Default text for a button that can be toggled on and off. -->
@@ -2242,17 +2257,13 @@
     <!-- Localized strings for WebView -->
     <!-- Label for button in a WebView that will open a chooser to choose a file to upload -->
     <string name="upload_file">Choose file</string>
+    <!-- Label for the file upload control when no file has been chosen yet -->
+    <string name="no_file_chosen">No file chosen</string>
     <!-- Label for <input type="reset"> button in html -->
     <string name="reset">Reset</string>
     <!-- Label for <input type="submit"> button in html -->
     <string name="submit">Submit</string>
 
-    <!-- String describing the Star/Favorite checkbox
-
-         Used by AccessibilityService to announce the purpose of the view.
-    -->
-    <string name="description_star">favorite</string>
-
     <!-- Strings for car mode notification -->
     <!-- Shown when car mode is enabled -->
     <string name="car_mode_disable_notification_title">Car mode enabled</string>
@@ -2263,6 +2274,10 @@
     <string name="tethered_notification_title">Tethering or hotspot 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 02a601a..993048d 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -458,6 +458,18 @@
     <style name="Widget.Spinner">
         <item name="android:background">@android:drawable/btn_dropdown</item>
         <item name="android:clickable">true</item>
+        <item name="android:spinnerMode">dialog</item>
+
+        <item name="android:dropDownSelector">@android:drawable/list_selector_background</item>
+        <item name="android:popupBackground">@android:drawable/spinner_dropdown_background</item>
+        <item name="android:dropDownVerticalOffset">-10dip</item>
+        <item name="android:dropDownHorizontalOffset">0dip</item>
+        <item name="android:dropDownWidth">wrap_content</item>
+        <item name="android:popupPromptView">@android:layout/simple_dropdown_hint</item>
+    </style>
+
+    <style name="Widget.Spinner.DropDown">
+        <item name="android:spinnerMode">dropdown</item>
     </style>
 
     <style name="Widget.TextView.PopupMenu">
@@ -574,6 +586,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">
@@ -613,7 +626,7 @@
 
     <style name="TextAppearance">
         <item name="android:textColor">?textColorPrimary</item>
-        <item name="android:textColorHighlight">#FFFF9200</item>
+        <item name="android:textColorHighlight">#D077A14B</item>
         <item name="android:textColorHint">?textColorHint</item>
         <item name="android:textColorLink">#5C5CFF</item>
         <item name="android:textSize">16sp</item>
@@ -861,4 +874,14 @@
         <item name="android:paddingBottom">1dip</item>
         <item name="android:background">@android:drawable/bottom_bar</item>
     </style>
+
+    <style name="ActionBar">
+        <item name="android:background">@android:drawable/action_bar_background</item>
+        <item name="android:displayOptions">useLogo</item>
+        <item name="android:divider">@android:drawable/action_bar_divider</item>
+    </style>
+
+    <style name="Widget.ActionButton">
+        <item name="android:background">@null</item>
+    </style>
 </resources>
diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml
index d585d9e..739912a1a 100644
--- a/core/res/res/values/themes.xml
+++ b/core/res/res/values/themes.xml
@@ -115,6 +115,9 @@
         <item name="windowTitleBackgroundStyle">@android:style/WindowTitleBackground</item>
         <item name="android:windowAnimationStyle">@android:style/Animation.Activity</item>
         <item name="android:windowSoftInputMode">stateUnspecified|adjustUnspecified</item>
+        <item name="windowActionBar">false</item>
+        <item name="windowActionModeOverlay">false</item>
+        <item name="windowActionBarStyle">@android:style/ActionBar</item>
 
         <!-- Dialog attributes -->
         <item name="alertDialogStyle">@android:style/AlertDialog</item>
@@ -167,6 +170,9 @@
         <item name="scrollViewStyle">@android:style/Widget.ScrollView</item>
         <item name="horizontalScrollViewStyle">@android:style/Widget.HorizontalScrollView</item>
         <item name="spinnerStyle">@android:style/Widget.Spinner</item>
+        <item name="dropDownSpinnerStyle">@android:style/Widget.Spinner.DropDown</item>
+        <item name="actionDropDownStyle">@android:style/Widget.Spinner.DropDown</item>
+        <item name="actionButtonStyle">@android:style/Widget.ActionButton</item>
         <item name="starStyle">@android:style/Widget.CompoundButton.Star</item>
         <item name="tabWidgetStyle">@android:style/Widget.TabWidget</item>
         <item name="textViewStyle">@android:style/Widget.TextView</item>
@@ -198,6 +204,11 @@
 
         <!-- Search widget styles -->
         <item name="searchWidgetCorpusItemBackground">@android:color/search_widget_corpus_item_background</item>
+
+        <!-- Action bar styles -->
+        <item name="actionButtonPadding">12dip</item>
+        <item name="actionModeBackground">@android:drawable/action_bar_context_background</item>
+        <item name="actionModeCloseDrawable">@android:drawable/ic_menu_close_clear_cancel</item>
     </style>
     
     <!-- Variant of the default (dark) theme with no title bar -->
@@ -416,13 +427,25 @@
         <item name="textAppearanceSmallInverse">@android:style/TextAppearance.Small.Inverse</item>
     </style>
 
+    <!-- Variation of Theme.Dialog that does not include a frame (or background).
+         The view hierarchy of the dialog is responsible for drawing all of
+         its pixels. -->
+    <style name="Theme.Dialog.NoFrame">
+        <item name="windowBackground">@android:color/transparent</item>
+        <item name="android:windowFrame">@null</item>
+        <item name="windowContentOverlay">@null</item>
+        <item name="android:windowAnimationStyle">@null</item>
+        <item name="android:backgroundDimEnabled">false</item>
+        <item name="android:windowIsTranslucent">true</item>
+        <item name="android:windowNoTitle">true</item>
+    </style>
+
     <!-- Default theme for alert dialog windows, which is used by the
          {@link android.app.AlertDialog} class.  This is basically a dialog
          but sets the background to empty so it can do two-tone backgrounds. -->
     <style name="Theme.Dialog.Alert">
         <item name="windowBackground">@android:color/transparent</item>
         <item name="windowTitleStyle">@android:style/DialogWindowTitle</item>
-        <item name="windowIsFloating">true</item>
         <item name="windowContentOverlay">@null</item>
     </style>
     
@@ -521,5 +544,15 @@
         <item name="android:windowAnimationStyle">@android:style/Animation.RecentApplications</item>
         <item name="android:textColor">@android:color/secondary_text_nofocus</item>
     </style>
-    
+
+    <!-- Default theme with an Action Bar. -->
+    <style name="Theme.WithActionBar">
+        <item name="android:windowActionBar">true</item>
+    </style>
+
+    <!-- No title bar, but Action Mode bars will overlay application content
+         instead of pushing it down to make room. -->
+    <style name="Theme.NoTitleBar.OverlayActionModes">
+        <item name="android:windowActionModeOverlay">true</item>
+    </style>
 </resources>
diff --git a/core/res/res/xml/password_kbd_numeric.xml b/core/res/res/xml/password_kbd_numeric.xml
index e3f1612..bdd8afb 100755
--- a/core/res/res/xml/password_kbd_numeric.xml
+++ b/core/res/res/xml/password_kbd_numeric.xml
@@ -24,30 +24,37 @@
     android:keyHeight="@dimen/password_keyboard_key_height"
     >
 
-    <Row>
-        <Key android:codes="49" android:keyIcon="@drawable/sym_keyboard_num1" android:keyEdgeFlags="left"/>
+    <Row android:rowEdgeFlags="top">
+        <Key android:codes="49" android:keyIcon="@drawable/sym_keyboard_num1"
+             android:keyEdgeFlags="left"/>
         <Key android:codes="50" android:keyIcon="@drawable/sym_keyboard_num2"/>
-        <Key android:codes="51" android:keyIcon="@drawable/sym_keyboard_num3"/>
+        <Key android:codes="51" android:keyIcon="@drawable/sym_keyboard_num3"
+             android:keyEdgeFlags="right"/>
     </Row>
 
     <Row>
-        <Key android:codes="52" android:keyIcon="@drawable/sym_keyboard_num4" android:keyEdgeFlags="left"/>
+        <Key android:codes="52" android:keyIcon="@drawable/sym_keyboard_num4"
+             android:keyEdgeFlags="left"/>
         <Key android:codes="53" android:keyIcon="@drawable/sym_keyboard_num5"/>
-        <Key android:codes="54" android:keyIcon="@drawable/sym_keyboard_num6"/>
+        <Key android:codes="54" android:keyIcon="@drawable/sym_keyboard_num6"
+             android:keyEdgeFlags="right"/>
     </Row>
 
     <Row>
-        <Key android:codes="55" android:keyIcon="@drawable/sym_keyboard_num7" android:keyEdgeFlags="left"/>
+        <Key android:codes="55" android:keyIcon="@drawable/sym_keyboard_num7"
+             android:keyEdgeFlags="left"/>
         <Key android:codes="56" android:keyIcon="@drawable/sym_keyboard_num8"/>
-        <Key android:codes="57" android:keyIcon="@drawable/sym_keyboard_num9"/>
+        <Key android:codes="57" android:keyIcon="@drawable/sym_keyboard_num9"
+             android:keyEdgeFlags="right"/>
     </Row>
 
     <Row android:rowEdgeFlags="bottom">
-        <Key android:codes="10" android:keyIcon="@drawable/sym_keyboard_ok"/>
+        <Key android:codes="10" android:keyIcon="@drawable/sym_keyboard_ok"
+             android:keyEdgeFlags="left"/>
         <Key android:codes="48" android:keyIcon="@drawable/sym_keyboard_num0_no_plus"/>
         <Key android:codes="-5" android:keyIcon="@drawable/sym_keyboard_delete"
-            android:iconPreview="@drawable/sym_keyboard_feedback_delete"
-            android:isRepeatable="true" android:keyEdgeFlags="right"/>
+             android:iconPreview="@drawable/sym_keyboard_feedback_delete"
+             android:isRepeatable="true" android:keyEdgeFlags="right"/>
     </Row>
 
 </Keyboard>
diff --git a/core/res/res/xml/password_kbd_symbols.xml b/core/res/res/xml/password_kbd_symbols.xml
index 14a7ec8..9901526 100755
--- a/core/res/res/xml/password_kbd_symbols.xml
+++ b/core/res/res/xml/password_kbd_symbols.xml
@@ -25,7 +25,7 @@
     android:keyHeight="@dimen/password_keyboard_key_height"
     >
 
-    <Row>
+    <Row android:rowEdgeFlags="top">
         <Key android:keyLabel="1" android:keyEdgeFlags="left"/>
         <Key android:keyLabel="2"/>
         <Key android:keyLabel="3"/>
diff --git a/core/res/res/xml/password_kbd_symbols_shift.xml b/core/res/res/xml/password_kbd_symbols_shift.xml
index 4b84f4b..5b73914 100755
--- a/core/res/res/xml/password_kbd_symbols_shift.xml
+++ b/core/res/res/xml/password_kbd_symbols_shift.xml
@@ -25,7 +25,7 @@
     android:keyHeight="@dimen/password_keyboard_key_height"
     >
 
-    <Row>
+    <Row android:rowEdgeFlags="top">
         <Key android:keyLabel="~" android:keyEdgeFlags="left"/>
         <Key android:keyLabel="`"/>
         <Key android:keyLabel="|"/>
diff --git a/core/tests/coretests/Android.mk b/core/tests/coretests/Android.mk
index 01faaad..693ef18 100644
--- a/core/tests/coretests/Android.mk
+++ b/core/tests/coretests/Android.mk
@@ -11,11 +11,9 @@
 	$(call all-java-files-under, DisabledTestApp/src) \
 	$(call all-java-files-under, EnabledTestApp/src)
 
-LOCAL_STATIC_JAVA_LIBRARIES += android-common
-
 LOCAL_DX_FLAGS := --core-library
-LOCAL_STATIC_JAVA_LIBRARIES := core-tests-supportlib
-LOCAL_JAVA_LIBRARIES := android.test.runner android-common
+LOCAL_STATIC_JAVA_LIBRARIES := core-tests-supportlib android-common
+LOCAL_JAVA_LIBRARIES := android.test.runner
 LOCAL_PACKAGE_NAME := FrameworksCoreTests
 
 LOCAL_CERTIFICATE := platform
diff --git a/core/tests/coretests/res/raw/v21_backslash.vcf b/core/tests/coretests/res/raw/v21_backslash.vcf
deleted file mode 100644
index bd3002b..0000000
--- a/core/tests/coretests/res/raw/v21_backslash.vcf
+++ /dev/null
@@ -1,5 +0,0 @@
-BEGIN:VCARD

-VERSION:2.1

-N:;A\;B\\;C\\\;;D;\:E;\\\\;

-FN:A;B\C\;D:E\\

-END:VCARD

diff --git a/core/tests/coretests/res/raw/v21_complicated.vcf b/core/tests/coretests/res/raw/v21_complicated.vcf
deleted file mode 100644
index de34e16..0000000
--- a/core/tests/coretests/res/raw/v21_complicated.vcf
+++ /dev/null
@@ -1,106 +0,0 @@
-BEGIN:VCARD

-VERSION:2.1

-N:Gump;Forrest;Hoge;Pos;Tao

-FN:Joe Due

-ORG:Gump Shrimp Co.;Sales Dept.\;Manager;Fish keeper

-ROLE:Fish Cake Keeper!

-X-CLASS:PUBLIC

-TITLE:Shrimp Man

-TEL;WORK;VOICE:(111) 555-1212

-TEL;HOME;VOICE:(404) 555-1212

-TEL;CELL:0311111111

-TEL;VIDEO:0322222222

-TEL;VOICE:0333333333

-ADR;WORK:;;100 Waters Edge;Baytown;LA;30314;United States of America

-LABEL;WORK;ENCODING=QUOTED-PRINTABLE:100 Waters Edge=0D=0ABaytown, LA 30314=0D=0AUnited  States of America

-ADR;HOME:;;42 Plantation St.;Baytown;LA;30314;United States of America

-LABEL;HOME;ENCODING=QUOTED-PRINTABLE:42 Plantation St.=0D=0A=

-Baytown, LA 30314=0D=0A=

-United  States of America

-EMAIL;PREF;INTERNET:forrestgump@walladalla.com

-EMAIL;CELL:cell@example.com

-NOTE:The following note is the example from RFC 2045.

-NOTE;ENCODING=QUOTED-PRINTABLE:Now's the time =

-for all folk to come=

- to the aid of their country.

-

-PHOTO;ENCODING=BASE64;TYPE=JPEG:

- /9j/4QoPRXhpZgAATU0AKgAAAAgADQEOAAIAAAAPAAAAqgEPAAIAAAAHAAAAugEQAAIAAAAG

- AAAAwgESAAMAAAABAAEAAAEaAAUAAAABAAAAyAEbAAUAAAABAAAA0AEoAAMAAAABAAIAAAEx

- AAIAAAAOAAAA2AEyAAIAAAAUAAAA5gITAAMAAAABAAEAAIKYAAIAAAAOAAAA+odpAAQAAAAB

- AAABhMSlAAcAAAB8AAABCAAABB4yMDA4MTAyOTEzNTUzMQAARG9Db01vAABEOTA1aQAAAABI

- AAAAAQAAAEgAAAABRDkwNWkgVmVyMS4wMAAyMDA4OjEwOjI5IDEzOjU1OjQ3ACAgICAgICAg

- ICAgICAAUHJpbnRJTQAwMzAwAAAABgABABQAFAACAQAAAAADAAAANAEABQAAAAEBAQAAAAEQ

- gAAAAAAAEQkAACcQAAAPCwAAJxAAAAWXAAAnEAAACLAAACcQAAAcAQAAJxAAAAJeAAAnEAAA

- AIsAACcQAAADywAAJxAAABvlAAAnEAAogpoABQAAAAEAAANqgp0ABQAAAAEAAANyiCIAAwAA

- AAEAAgAAkAAABwAAAAQwMjIwkAMAAgAAABQAAAN6kAQAAgAAABQAAAOOkQEABwAAAAQBAgMA

- kQIABQAAAAEAAAOikgEACgAAAAEAAAOqkgIABQAAAAEAAAOykgQACgAAAAEAAAO6kgUABQAA

- AAEAAAPCkgcAAwAAAAEAAgAAkggAAwAAAAEAAAAAkgkAAwAAAAEAAAAAkgoABQAAAAEAAAPK

- knwABwAAAAEAAAAAkoYABwAAABYAAAPSoAAABwAAAAQwMTAwoAEAAwAAAAEAAQAAoAIAAwAA

- AAEAYAAAoAMAAwAAAAEASAAAoAUABAAAAAEAAAQAog4ABQAAAAEAAAPoog8ABQAAAAEAAAPw

- ohAAAwAAAAEAAgAAohcAAwAAAAEAAgAAowAABwAAAAEDAAAAowEABwAAAAEBAAAApAEAAwAA

- AAEAAAAApAIAAwAAAAEAAAAApAMAAwAAAAEAAAAApAQABQAAAAEAAAP4pAUAAwAAAAEAHQAA

- pAYAAwAAAAEAAAAApAcAAwAAAAEAAAAApAgAAwAAAAEAAAAApAkAAwAAAAEAAAAApAoAAwAA

- AAEAAAAApAwAAwAAAAEAAgAAAAAAAAAAAFMAACcQAAABXgAAAGQyMDA4OjEwOjI5IDEzOjU1

- OjMxADIwMDg6MTA6MjkgMTM6NTU6NDcAAAApiAAAGwAAAAKyAAAAZAAAAV4AAABkAAAAAAAA

- AGQAAAAlAAAACgAADpIAAAPoAAAAAAAAAAAyMDA4MTAyOTEzNTUzMQAAICoAAAAKAAAq4gAA

- AAoAAAAAAAAAAQACAAEAAgAAAARSOTgAAAIABwAAAAQwMTAwAAAAAAAGAQMAAwAAAAEABgAA

- ARoABQAAAAEAAARsARsABQAAAAEAAAR0ASgAAwAAAAEAAgAAAgEABAAAAAEAAAR8AgIABAAA

- AAEAAAWLAAAAAAAAAEgAAAABAAAASAAAAAH/2P/bAIQAIBYYHBgUIBwaHCQiICYwUDQwLCww

- YkZKOlB0Znp4cmZwboCQuJyAiK6KbnCg2qKuvsTO0M58muLy4MjwuMrOxgEiJCQwKjBeNDRe

- xoRwhMbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbG

- /8AAEQgAeACgAwEhAAIRAQMRAf/EAaIAAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKCxAA

- AgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkK

- FhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWG

- h4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl

- 5ufo6erx8vP09fb3+Pn6AQADAQEBAQEBAQEBAAAAAAAAAQIDBAUGBwgJCgsRAAIBAgQEAwQH

- BQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBka

- JicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKT

- lJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz

- 9PX29/j5+v/aAAwDAQACEQMRAD8AFFSqKkZIoqRVpgSKKeBTEOApwFADsUYpgIRSEUANIppF

- ICNhUTCgCMio2FICJhULCgC0oqVaAJFFSqKBkgFOApiHCnCgB2KMUCENJQA0imEUDGMKiYUA

- RtUbUgIWqJhQBZSpFoAlWpVoGPFPFMQ7tSK2ODQA4yKO9HmKe9FxAzDHFIOlAAaYaAGNUTUD

- ImqNqQETVE1AE6VKKAJFNSqaAHg08GmANIFFQM5Y5qJMBuT60ZNQIcrkVYSQMKuLGKaaasQx

- qiagZE1RtSAjaomoAkQ1KpoAlU1IpoAkU07OBTArO+5qkV12Y71lfUBmaKkCRSuznrTFba2a

- oCwGyM0E1qIjY1GxoGRNUZNICNqiagByGplNAEimpFNMB4YDvSucpxSYEIU04KazsAu1qArU

- WELtPpTSposBNETt5pxNaoCNjUbGgCNjUZoGRtUTUgFU1KpoAkBqQHigCFnO7rUqOdlZp6gA

- c+tODn1pXAXzD60eYfWncQvmNSGQ07gOMhCVEJGz1ptgS5yKYxqwGE1GxoAiamGkMapqVTQB

- Kpp+eKAICfmqWM/Kaz6gANOBqQFzRmmAuaTNACsfkqMHmm9wJs8U0mtRDGNRsaAI2phpDI1N

- SqaAJFNSA8UCISfmqSM/Kaz6jAHmnA1ICg0uaAFzSZpgKx+SmDrTe4E2eKaTWoiMmmMaAIzT

- DSGRKakU0ASKaeDTERseakjPyms+oxAacDUgOBpc0gFzSZpgOY/KKYv3qrqIlpprQBjGoyaA

- GGmmkMgU1IppgPBqQGgQu0Gn4wvFKwEQpwNZDHZpc0ALmigRKBleaQKBWtgA001QDGqM0gGm

- mGkMrqakBoAepp4NMRIDTwaAE2A008GokgHxjd1pzKFpW0uAg5NSBBTirgOpDWgDTTTQAw0w

- 0gGGmmgZWBp4pASKaeDTEOBp4NADwajbrUyBEkXWnSUdAGr1qeiAMSkNWAhphoAaaYaQDDTT

- SGVRTwaYDxTwaBDwaeDQA4GlK5oauIeo20pGaLaAKqgU6hKwBSGmAhphoAaaYaQxhpppDKgN

- PFMB4p4oEPFOBpgPBp4NAhwpwoAWloAKSgBDTTQMYaYaQDTTTSGA/9n/2wCEAAoHBwgHBgoI

- CAgLCgoLDhgQDg0NDh0VFhEYIx8lJCIfIiEmKzcvJik0KSEiMEExNDk7Pj4+JS5ESUM8SDc9

- PjsBCgsLDg0OHBAQHDsoIig7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7

- Ozs7Ozs7Ozs7Ozs7O//AABEIAEgAYAMBIQACEQEDEQH/xAGiAAABBQEBAQEBAQAAAAAAAAAA

- AQIDBAUGBwgJCgsQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNC

- scEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hp

- anN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS

- 09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+gEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcI

- CQoLEQACAQIEBAMEBwUEBAABAncAAQIDEQQFITEGEkFRB2FxEyIygQgUQpGhscEJIzNS8BVi

- ctEKFiQ04SXxFxgZGiYnKCkqNTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4

- eXqCg4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY

- 2dri4+Tl5ufo6ery8/T19vf4+fr/2gAMAwEAAhEDEQA/AJ7SLgcVr20I4rNFGvbQAAHFaEUX

- SrQi5HCMdKk8oY6VSJYx4hjpVaWMelAFC4iGDxWPdR8mkxmRdxjBrEvI+tZjN20xtHNbNqAc

- UIDXg6Cr0WKtCY8XKQOyzOB3FKNWsyceZ+lS6sY6NkjvPSdwImBHUmmy4q076oCjOODWPdgc

- 0MpGPdAYNYl4o5rNjNKzkyorZtXxihAa1vIDip7m9Frb7/4jwKcnyxbEzN3ieJppZsyZ4U1H

- urzZau4mWVlNrGk0UuWPVa1YroXEIkHfrXZh5W90RWncAHmsi6bJNdQ0ZNw3BrGuiMGs2Mks

- puBzWzbzdOaEBeOpR2oUtkk9hTru7iuo4m8wgemKyqTi04sBsfkEf68j8KlUQZz9o/SuZRj3

- JYriAji4/Sp7W6htbV2aXcu70ramoxle4gN7HcIXjbis+4k5NdaaauhmVcv1rHuW61DGiG1m

- 6c1s20/TmgAv5vmj57VKk3+ixnPc1xVV70h9CVJuOtSrL71hFgxzScUkkn+iY/2q1i9xDrGT

- 9y31pJ5Otd1L+GhMy7mTrWXO2SapjRn28vTmta3nxjmgGOvJd2w1Kkv+ipz/ABGuOoveYdCe

- ObjrU6y5rlsA8ycUksn+ij/eNaw6iJLNsW59zTJn6816FP4EJmbO+Saz5m602UjIgk4HNadv

- LwKaBl+MpIMOMipp490SCJeF7CoqQvF2JuRqWQ4YEGrSiQJuKnHrXByMpki73GFBNXIoh9n2

- SrnnOK6MPTbd3sSwIVF2qMCqkzHmuy1lYRnTHrVGWpZaMKB+BWlbycYoQM0IZDxzV+GU8c1a

- IYy5Y+dnHatAsfsAHfArmS1mPoh1gT8x9qtk1rQX7tCe5DIapzGtGBQm71SlqGWjnIH6Vowt

- zmhAy/E3vV6F6tEMuxlWIyAfrVxCCAO1VZEEyYA4AApxNGwyJ+lVJRUsaKMw61SlFQzRAP/Z

-

-X-ATTRIBUTE:Some String

-BDAY:19800101

-GEO:35.6563854,139.6994233

-URL:http://www.example.com/

-REV:20080424T195243Z

-END:VCARD
\ No newline at end of file
diff --git a/core/tests/coretests/res/raw/v21_invalid_comment_line.vcf b/core/tests/coretests/res/raw/v21_invalid_comment_line.vcf
deleted file mode 100644
index f910710..0000000
--- a/core/tests/coretests/res/raw/v21_invalid_comment_line.vcf
+++ /dev/null
@@ -1,10 +0,0 @@
-BEGIN:vCard

-VERSION:2.1

-UID:357

-N:;Conference Call

-FN:Conference Call

-# This line must be ignored.

-NOTE;ENCODING=QUOTED-PRINTABLE:This is an (sharp ->=

-#<- sharp) example. This message must NOT be ignored.

-# This line must be ignored too.

-END:vCard

diff --git a/core/tests/coretests/res/raw/v21_japanese_1.vcf b/core/tests/coretests/res/raw/v21_japanese_1.vcf
deleted file mode 100644
index d05e2ff..0000000
--- a/core/tests/coretests/res/raw/v21_japanese_1.vcf
+++ /dev/null
@@ -1,6 +0,0 @@
-BEGIN:VCARD

-VERSION:2.1

-N;CHARSET=SHIFT_JIS:ˆÀ“¡ƒƒCƒh;;;;

-SOUND;X-IRMC-N;CHARSET=SHIFT_JIS:±ÝÄÞ³Û²ÄÞ;;;;

-TEL;PREF;VOICE:0300000000

-END:VCARD

diff --git a/core/tests/coretests/res/raw/v21_japanese_2.vcf b/core/tests/coretests/res/raw/v21_japanese_2.vcf
deleted file mode 100644
index fa54acb..0000000
--- a/core/tests/coretests/res/raw/v21_japanese_2.vcf
+++ /dev/null
@@ -1,10 +0,0 @@
-BEGIN:VCARD

-VERSION:2.1

-FN;CHARSET=SHIFT_JIS:ˆÀ“¡ ƒƒCƒh 1

-N;CHARSET=SHIFT_JIS:ˆÀ“¡;ƒƒCƒh1;;;

-SOUND;X-IRMC-N;CHARSET=SHIFT_JIS:±ÝÄÞ³;Û²ÄÞ1;;;

-ADR;HOME;CHARSET=SHIFT_JIS;ENCODING=QUOTED-PRINTABLE:;=93=8C=8B=9E=93=73=

-=8F=61=92=4A=8B=E6=8D=F7=8B=75=92=AC26-1=83=5A=83=8B=83=8A=83=41=83=93=

-=83=5E=83=8F=81=5B6=8A=4B;;;;150-8512;

-NOTE;CHARSET=SHIFT_JIS;ENCODING=QUOTED-PRINTABLE:=83=81=83=82

-END:VCARD

diff --git a/core/tests/coretests/res/raw/v21_multiple_entry.vcf b/core/tests/coretests/res/raw/v21_multiple_entry.vcf
deleted file mode 100644
index ebbb19a..0000000
--- a/core/tests/coretests/res/raw/v21_multiple_entry.vcf
+++ /dev/null
@@ -1,33 +0,0 @@
-BEGIN:VCARD

-VERSION:2.1

-N;CHARSET=SHIFT_JIS:ˆÀ“¡ƒƒCƒh3;;;;

-SOUND;X-IRMC-N;CHARSET=SHIFT_JIS:±ÝÄÞ³Û²ÄÞ3;;;;

-TEL;X-NEC-SECRET:9

-TEL;X-NEC-HOTEL:10

-TEL;X-NEC-SCHOOL:11

-TEL;HOME;FAX:12

-END:VCARD

-

-

-BEGIN:VCARD

-VERSION:2.1

-N;CHARSET=SHIFT_JIS:ˆÀ“¡ƒƒCƒh4;;;;

-SOUND;X-IRMC-N;CHARSET=SHIFT_JIS:±ÝÄÞ³Û²ÄÞ4;;;;

-TEL;MODEM:13

-TEL;PAGER:14

-TEL;X-NEC-FAMILY:15

-TEL;X-NEC-GIRL:16

-END:VCARD

-

-

-BEGIN:VCARD

-VERSION:2.1

-N;CHARSET=SHIFT_JIS:ˆÀ“¡ƒƒCƒh5;;;;

-SOUND;X-IRMC-N;CHARSET=SHIFT_JIS:±ÝÄÞ³Û²ÄÞ5;;;;

-TEL;X-NEC-BOY:17

-TEL;X-NEC-FRIEND:18

-TEL;X-NEC-PHS:19

-TEL;X-NEC-RESTAURANT:20

-END:VCARD

-

-

diff --git a/core/tests/coretests/res/raw/v21_org_before_title.vcf b/core/tests/coretests/res/raw/v21_org_before_title.vcf
deleted file mode 100644
index 8ff1190..0000000
--- a/core/tests/coretests/res/raw/v21_org_before_title.vcf
+++ /dev/null
@@ -1,6 +0,0 @@
-BEGIN:VCARD

-VERSION:2.1

-FN:Normal Guy

-ORG:Company;Organization;Devision;Room;Sheet No.

-TITLE:Excellent Janitor

-END:VCARD

diff --git a/core/tests/coretests/res/raw/v21_pref_handling.vcf b/core/tests/coretests/res/raw/v21_pref_handling.vcf
deleted file mode 100644
index 5105310..0000000
--- a/core/tests/coretests/res/raw/v21_pref_handling.vcf
+++ /dev/null
@@ -1,15 +0,0 @@
-BEGIN:VCARD
-VERSION:2.1
-FN:Smith
-TEL;HOME:1
-TEL;WORK;PREF:2
-TEL;ISDN:3
-EMAIL;PREF;HOME:test@example.com
-EMAIL;CELL;PREF:test2@examination.com
-ORG:Company
-TITLE:Engineer
-ORG:Mystery
-TITLE:Blogger
-ORG:Poetry
-TITLE:Poet
-END:VCARD
diff --git a/core/tests/coretests/res/raw/v21_simple_1.vcf b/core/tests/coretests/res/raw/v21_simple_1.vcf
deleted file mode 100644
index 6aabb4c..0000000
--- a/core/tests/coretests/res/raw/v21_simple_1.vcf
+++ /dev/null
@@ -1,3 +0,0 @@
-BEGIN:VCARD

-N:Ando;Roid;

-END:VCARD

diff --git a/core/tests/coretests/res/raw/v21_simple_2.vcf b/core/tests/coretests/res/raw/v21_simple_2.vcf
deleted file mode 100644
index f0d5ab5..0000000
--- a/core/tests/coretests/res/raw/v21_simple_2.vcf
+++ /dev/null
@@ -1,3 +0,0 @@
-BEGIN:VCARD

-FN:Ando Roid

-END:VCARD

diff --git a/core/tests/coretests/res/raw/v21_simple_3.vcf b/core/tests/coretests/res/raw/v21_simple_3.vcf
deleted file mode 100644
index beddabb..0000000
--- a/core/tests/coretests/res/raw/v21_simple_3.vcf
+++ /dev/null
@@ -1,4 +0,0 @@
-BEGIN:VCARD

-N:Ando;Roid;

-FN:Ando Roid

-END:VCARD

diff --git a/core/tests/coretests/res/raw/v21_title_before_org.vcf b/core/tests/coretests/res/raw/v21_title_before_org.vcf
deleted file mode 100644
index 9fdc738..0000000
--- a/core/tests/coretests/res/raw/v21_title_before_org.vcf
+++ /dev/null
@@ -1,6 +0,0 @@
-BEGIN:VCARD

-VERSION:2.1

-FN:Nice Guy

-TITLE:Cool Title

-ORG:Marverous;Perfect;Great;Good;Bad;Poor

-END:VCARD

diff --git a/core/tests/coretests/res/raw/v21_winmo_65.vcf b/core/tests/coretests/res/raw/v21_winmo_65.vcf
deleted file mode 100644
index f380d0d..0000000
--- a/core/tests/coretests/res/raw/v21_winmo_65.vcf
+++ /dev/null
@@ -1,10 +0,0 @@
-BEGIN:VCARD

-VERSION:2.1

-N:Example;;;;

-FN:Example

-ANNIVERSARY;VALUE=DATE:20091010

-AGENT:Invalid line which must be handled correctly.

-X-CLASS:PUBLIC

-X-REDUCTION:

-X-NO:

-END:VCARD

diff --git a/core/tests/coretests/res/raw/v30_comma_separated.vcf b/core/tests/coretests/res/raw/v30_comma_separated.vcf
deleted file mode 100644
index 98a7f20..0000000
--- a/core/tests/coretests/res/raw/v30_comma_separated.vcf
+++ /dev/null
@@ -1,5 +0,0 @@
-BEGIN:VCARD

-VERSION:3.0

-N:F;G;M;;

-TEL;TYPE=PAGER,WORK,MSG:6101231234@pagersample.com

-END:VCARD

diff --git a/core/tests/coretests/res/raw/v30_simple.vcf b/core/tests/coretests/res/raw/v30_simple.vcf
deleted file mode 100644
index 418661f..0000000
--- a/core/tests/coretests/res/raw/v30_simple.vcf
+++ /dev/null
@@ -1,13 +0,0 @@
-BEGIN:VCARD

-VERSION:3.0

-FN:And Roid

-N:And;Roid;;;

-ORG:Open;Handset; Alliance

-SORT-STRING:android

-TEL;TYPE=PREF;TYPE=VOICE:0300000000

-CLASS:PUBLIC

-X-GNO:0

-X-GN:group0

-X-REDUCTION:0

-REV:20081031T065854Z

-END:VCARD

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/DatabaseCursorTest.java b/core/tests/coretests/src/android/database/DatabaseCursorTest.java
index fb5a36f..0733229 100644
--- a/core/tests/coretests/src/android/database/DatabaseCursorTest.java
+++ b/core/tests/coretests/src/android/database/DatabaseCursorTest.java
@@ -33,7 +33,6 @@
 import android.test.PerformanceTestCase;
 import android.test.suitebuilder.annotation.LargeTest;
 import android.test.suitebuilder.annotation.MediumTest;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.test.suitebuilder.annotation.Suppress;
 import android.util.Log;
 
@@ -41,8 +40,6 @@
 import java.util.Arrays;
 import java.util.Random;
 
-import junit.framework.TestCase;
-
 public class DatabaseCursorTest extends AndroidTestCase implements PerformanceTestCase {
 
     private static final String sString1 = "this is a test";
@@ -92,43 +89,6 @@
     }
 
     @MediumTest
-    public void testCursorUpdate() {
-        mDatabase.execSQL(
-            "CREATE TABLE test (_id INTEGER PRIMARY KEY, d INTEGER, s INTEGER);");
-        for(int i = 0; i < 20; i++) {
-            mDatabase.execSQL("INSERT INTO test (d, s) VALUES (" + i + 
-                "," + i%2 + ");");
-        }
-        
-        Cursor c = mDatabase.query("test", null, "s = 0", null, null, null, null);
-        int dCol = c.getColumnIndexOrThrow("d");
-        int sCol = c.getColumnIndexOrThrow("s");
-        
-        int count = 0;
-        while (c.moveToNext()) {
-            assertTrue(c.updateInt(dCol, 3));
-            count++;
-        }
-        assertEquals(10, count);
-        
-        assertTrue(c.commitUpdates());
-        
-        assertTrue(c.requery());
-        
-        count = 0;
-        while (c.moveToNext()) {
-            assertEquals(3, c.getInt(dCol));
-            count++;
-        }
-        
-        assertEquals(10, count);
-        assertTrue(c.moveToFirst());
-        assertTrue(c.deleteRow());
-        assertEquals(9, c.getCount());
-        c.close();
-    }
-    
-    @MediumTest
     public void testBlob() throws Exception {
         // create table
         mDatabase.execSQL(
@@ -164,24 +124,7 @@
         assertTrue(Arrays.equals(blob, cBlob));
         assertEquals(s, c.getString(sCol));
         assertEquals((double)d, c.getDouble(dCol));
-        assertEquals((long)l, c.getLong(lCol));
-        
-        // new byte[]
-        byte[] newblob = new byte[1000];
-        value = 98;
-        Arrays.fill(blob, value);        
-        
-        c.updateBlob(bCol, newblob);
-        cBlob =  c.getBlob(bCol);
-        assertTrue(Arrays.equals(newblob, cBlob));
-        
-        // commit
-        assertTrue(c.commitUpdates());
-        assertTrue(c.requery());
-        c.moveToNext();
-        cBlob =  c.getBlob(bCol);
-        assertTrue(Arrays.equals(newblob, cBlob));        
-        c.close();
+        assertEquals((long)l, c.getLong(lCol));        
     }
     
     @MediumTest
diff --git a/core/tests/coretests/src/android/database/DatabaseGeneralTest.java b/core/tests/coretests/src/android/database/DatabaseGeneralTest.java
index 656029d..cd38bf07 100644
--- a/core/tests/coretests/src/android/database/DatabaseGeneralTest.java
+++ b/core/tests/coretests/src/android/database/DatabaseGeneralTest.java
@@ -21,18 +21,21 @@
 import android.content.ContentValues;
 import android.content.Context;
 import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteStatement;
+import android.database.sqlite.SQLiteException;
 import android.os.Handler;
 import android.os.Parcel;
 import android.test.AndroidTestCase;
 import android.test.PerformanceTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
 import android.test.suitebuilder.annotation.MediumTest;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.util.Log;
+import android.util.Pair;
 
 import junit.framework.Assert;
 
 import java.io.File;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Locale;
 
@@ -382,54 +385,28 @@
 
     @MediumTest
     public void testSchemaChange2() throws Exception {
-        SQLiteDatabase db1 = mDatabase;
-        SQLiteDatabase db2 = SQLiteDatabase.openOrCreateDatabase(mDatabaseFile, null);
-        Cursor cursor;
-
-        db1.execSQL("CREATE TABLE db1 (_id INTEGER PRIMARY KEY, data TEXT);");
-
-        cursor = db1.query("db1", null, null, null, null, null, null);
-        assertNotNull("Cursor is null", cursor);
+        mDatabase.execSQL("CREATE TABLE db1 (_id INTEGER PRIMARY KEY, data TEXT);");
+        Cursor cursor = mDatabase.query("db1", null, null, null, null, null, null);
+        assertNotNull(cursor);
         assertEquals(0, cursor.getCount());
-        cursor.deactivate();
-        // this cause exception because we're still using sqlite_prepate16 and not
-        // sqlite_prepare16_v2. The v2 variant added the ability to check the
-        // schema version and handle the case when the schema has changed
-        // Marco Nelissen claim it was 2x slower to compile SQL statements so
-        // I reverted back to the v1 variant.
-        /* db2.execSQL("CREATE TABLE db2 (_id INTEGER PRIMARY KEY, data TEXT);");
-
-        cursor = db1.query("db1", null, null, null, null, null, null);
-        assertNotNull("Cursor is null", cursor);
-        assertEquals(0, cursor.count());
-        cursor.deactivate();
-        */
+        cursor.close();
     }
 
     @MediumTest
     public void testSchemaChange3() throws Exception {
-        SQLiteDatabase db1 = mDatabase;
-        SQLiteDatabase db2 = SQLiteDatabase.openOrCreateDatabase(mDatabaseFile, null);
-        Cursor cursor;
-
-
-        db1.execSQL("CREATE TABLE db1 (_id INTEGER PRIMARY KEY, data TEXT);");
-        db1.execSQL("INSERT INTO db1 (data) VALUES ('test');");
-
-        cursor = db1.query("db1", null, null, null, null, null, null);
-        // this cause exception because we're still using sqlite_prepate16 and not
-        // sqlite_prepare16_v2. The v2 variant added the ability to check the
-        // schema version and handle the case when the schema has changed
-        // Marco Nelissen claim it was 2x slower to compile SQL statements so
-        // I reverted back to the v1 variant.
-        /* db2.execSQL("CREATE TABLE db2 (_id INTEGER PRIMARY KEY, data TEXT);");
-
-        assertNotNull("Cursor is null", cursor);
-        assertEquals(1, cursor.count());
-        assertTrue(cursor.first());
-        assertEquals("test", cursor.getString(cursor.getColumnIndexOrThrow("data")));
-        cursor.deactivate();
-        */
+        mDatabase.execSQL("CREATE TABLE db1 (_id INTEGER PRIMARY KEY, data TEXT);");
+        mDatabase.execSQL("INSERT INTO db1 (data) VALUES ('test');");
+        mDatabase.execSQL("ALTER TABLE db1 ADD COLUMN blah int;");
+        Cursor c = null;
+        try {
+            c = mDatabase.rawQuery("select blah from db1", null);
+        } catch (SQLiteException e) {
+            fail("unexpected exception: " + e.getMessage());
+        } finally {
+            if (c != null) {
+                c.close();
+            }
+        }
     }
 
     private class ChangeObserver extends ContentObserver {
@@ -467,45 +444,6 @@
     }
 
     @MediumTest
-    public void testNotificationTest1() throws Exception {
-        /*
-        Cursor c = mContentResolver.query(Notes.CONTENT_URI,
-                new String[] {Notes._ID, Notes.NOTE},
-                null, null);
-        c.registerContentObserver(new MyContentObserver(true));
-        int count = c.count();
-
-        MyContentObserver observer = new MyContentObserver(false);
-        mContentResolver.registerContentObserver(Notes.CONTENT_URI, true, observer);
-
-        Uri uri;
-
-        HashMap<String, String> values = new HashMap<String, String>();
-        values.put(Notes.NOTE, "test note1");
-        uri = mContentResolver.insert(Notes.CONTENT_URI, values);
-        assertEquals(1, mCursorNotificationCount);
-        assertEquals(1, mNotificationCount);
-
-        c.requery();
-        assertEquals(count + 1, c.count());
-        c.first();
-        assertEquals("test note1", c.getString(c.getColumnIndex(Notes.NOTE)));
-        c.updateString(c.getColumnIndex(Notes.NOTE), "test note2");
-        c.commitUpdates();
-
-        assertEquals(2, mCursorNotificationCount);
-        assertEquals(2, mNotificationCount);
-
-        mContentResolver.delete(uri, null);
-
-        assertEquals(3, mCursorNotificationCount);
-        assertEquals(3, mNotificationCount);
-
-        mContentResolver.unregisterContentObserver(observer);
-        */
-    }
-
-    @MediumTest
     public void testSelectionArgs() throws Exception {
         mDatabase.execSQL("CREATE TABLE test (_id INTEGER PRIMARY KEY, data TEXT);");
         ContentValues values = new ContentValues(1);
@@ -989,21 +927,6 @@
     }
 
     @MediumTest
-    public void testDbCloseReleasingAllCachedSql() {
-        mDatabase.execSQL("CREATE TABLE test (_id INTEGER PRIMARY KEY, text1 TEXT, text2 TEXT, " +
-                "num1 INTEGER, num2 INTEGER, image BLOB);");
-        final String statement = "DELETE FROM test WHERE _id=?;";
-        SQLiteStatement statementDoNotClose = mDatabase.compileStatement(statement);
-        assertTrue(statementDoNotClose.getUniqueId() > 0);
-        int nStatement = statementDoNotClose.getUniqueId();
-        assertTrue(statementDoNotClose.getUniqueId() == nStatement);
-        /* do not close statementDoNotClose object. 
-         * That should leave it in SQLiteDatabase.mPrograms.
-         * mDatabase.close() in tearDown() should release it.
-         */
-    }
-
-    @MediumTest
     public void testSemicolonsInStatements() throws Exception {
         mDatabase.execSQL("CREATE TABLE pragma_test (" +
                 "i INTEGER DEFAULT 1234, " +
@@ -1023,6 +946,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 +1059,113 @@
                 }
             }
         }
-    }    
+    }
+
+    @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"));
+        }
+    }
+
+    @LargeTest
+    public void testDefaultDatabaseErrorHandler() {
+        DefaultDatabaseErrorHandler errorHandler = new DefaultDatabaseErrorHandler();
+
+        // close the database. and call corruption handler.
+        // it should delete the database file.
+        File dbfile = new File(mDatabase.getPath());
+        mDatabase.close();
+        assertFalse(mDatabase.isOpen());
+        assertTrue(dbfile.exists());
+        try {
+            errorHandler.onCorruption(mDatabase);
+            assertFalse(dbfile.exists());
+        } catch (Exception e) {
+            fail("unexpected");
+        }
+
+        // create an in-memory database. and corruption handler shouldn't try to delete it
+        SQLiteDatabase memoryDb = SQLiteDatabase.openOrCreateDatabase(":memory:", null);
+        assertNotNull(memoryDb);
+        memoryDb.close();
+        assertFalse(memoryDb.isOpen());
+        try {
+            errorHandler.onCorruption(memoryDb);
+        } catch (Exception e) {
+            fail("unexpected");
+        }
+
+        // create a database, keep it open, call corruption handler. database file should be deleted
+        SQLiteDatabase dbObj = SQLiteDatabase.openOrCreateDatabase(mDatabase.getPath(), null);
+        assertTrue(dbfile.exists());
+        assertNotNull(dbObj);
+        assertTrue(dbObj.isOpen());
+        try {
+            errorHandler.onCorruption(dbObj);
+            assertFalse(dbfile.exists());
+        } catch (Exception e) {
+            fail("unexpected");
+        }
+
+        // create a database, attach 2 more databases to it
+        //    attached database # 1: ":memory:"
+        //    attached database # 2: mDatabase.getPath() + "1";
+        // call corruption handler. database files including the one for attached database # 2
+        // should be deleted
+        String attachedDb1File = mDatabase.getPath() + "1";
+        dbObj = SQLiteDatabase.openOrCreateDatabase(mDatabase.getPath(), null);
+        dbObj.execSQL("ATTACH DATABASE ':memory:' as memoryDb");
+        dbObj.execSQL("ATTACH DATABASE '" +  attachedDb1File + "' as attachedDb1");
+        assertTrue(dbfile.exists());
+        assertTrue(new File(attachedDb1File).exists());
+        assertNotNull(dbObj);
+        assertTrue(dbObj.isOpen());
+        ArrayList<Pair<String, String>> attachedDbs = dbObj.getAttachedDbs();
+        try {
+            errorHandler.onCorruption(dbObj);
+            assertFalse(dbfile.exists());
+            assertFalse(new File(attachedDb1File).exists());
+        } catch (Exception e) {
+            fail("unexpected");
+        }
+
+        // same as above, except this is a bit of stress testing. attach 5 database files
+        // and make sure they are all removed.
+        int N = 5;
+        ArrayList<String> attachedDbFiles = new ArrayList<String>(N);
+        for (int i = 0; i < N; i++) {
+            attachedDbFiles.add(mDatabase.getPath() + i);
+        }
+        dbObj = SQLiteDatabase.openOrCreateDatabase(mDatabase.getPath(), null);
+        dbObj.execSQL("ATTACH DATABASE ':memory:' as memoryDb");
+        for (int i = 0; i < N; i++) {
+            dbObj.execSQL("ATTACH DATABASE '" +  attachedDbFiles.get(i) + "' as attachedDb" + i);
+        }
+        assertTrue(dbfile.exists());
+        for (int i = 0; i < N; i++) {
+            assertTrue(new File(attachedDbFiles.get(i)).exists());
+        }
+        assertNotNull(dbObj);
+        assertTrue(dbObj.isOpen());
+        attachedDbs = dbObj.getAttachedDbs();
+        try {
+            errorHandler.onCorruption(dbObj);
+            assertFalse(dbfile.exists());
+            for (int i = 0; i < N; i++) {
+                assertFalse(new File(attachedDbFiles.get(i)).exists());
+            }
+        } catch (Exception e) {
+            fail("unexpected");
+        }
+    }
 }
diff --git a/core/tests/coretests/src/android/database/sqlite/AbstractJDBCDriverTest.java b/core/tests/coretests/src/android/database/sqlite/AbstractJDBCDriverTest.java
deleted file mode 100644
index 19c7bcb..0000000
--- a/core/tests/coretests/src/android/database/sqlite/AbstractJDBCDriverTest.java
+++ /dev/null
@@ -1,211 +0,0 @@
-/*
- * 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.
- */
-
-package android.database.sqlite;
-
-import java.io.File;
-import java.sql.Connection;
-import java.sql.DriverManager;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.sql.Statement;
-
-import junit.framework.TestCase;
-import android.test.suitebuilder.annotation.MediumTest;
-
-/**
- * Tests for the most commonly used methods of sql like creating a connection,
- * inserting, selecting, updating.
- */
-public abstract class AbstractJDBCDriverTest extends TestCase {
-
-    @MediumTest
-    public void testJDBCDriver() throws Exception {
-        Connection firstConnection = null;
-        Connection secondConnection = null;
-        File dbFile = getDbFile();
-        String connectionURL = getConnectionURL();
-        Statement firstStmt = null;
-        Statement secondStmt = null;
-        try {
-            Class.forName(getJDBCDriverClassName());
-            firstConnection = DriverManager.getConnection(connectionURL);
-            secondConnection = DriverManager.getConnection(connectionURL);
-
-            String[] ones = {"hello!", "goodbye"};
-            short[] twos = {10, 20};
-            String[] onesUpdated = new String[ones.length];
-            for (int i = 0; i < ones.length; i++) {
-                onesUpdated[i] = ones[i] + twos[i];
-            }
-            firstStmt = firstConnection.createStatement();
-            firstStmt.execute("create table tbl1(one varchar(10), two smallint)");
-            secondStmt = secondConnection.createStatement();
-
-            autoCommitInsertSelectTest(firstStmt, ones, twos);
-            updateSelectCommitSelectTest(firstStmt, secondStmt, ones, onesUpdated, twos);
-            updateSelectRollbackSelectTest(firstStmt, secondStmt, onesUpdated, ones, twos);
-        } finally {
-            closeConnections(firstConnection, secondConnection, dbFile, firstStmt, secondStmt);
-        }
-    }
-
-    protected abstract String getJDBCDriverClassName();
-    protected abstract String getConnectionURL();
-    protected abstract File getDbFile();
-
-    private void closeConnections(Connection firstConnection, Connection secondConnection,
-            File dbFile, Statement firstStmt, Statement secondStmt) {
-        String failText = null;
-        try {
-            if (firstStmt != null) {
-                firstStmt.execute("drop table tbl1");
-            }
-        } catch (SQLException e) {
-            failText = e.getLocalizedMessage();
-        }
-        try {
-            if (firstStmt != null) {
-                firstStmt.close();
-            }
-        } catch (SQLException e) {
-            failText = e.getLocalizedMessage();
-        }
-        try {
-            if (firstConnection != null) {
-                firstConnection.close();
-            }
-        } catch (SQLException e) {
-            failText = e.getLocalizedMessage();
-        }
-        try {
-            if (secondStmt != null) {
-                secondStmt.close();
-            }
-        } catch (SQLException e) {
-            failText = e.getLocalizedMessage();
-        }
-        try {
-            if (secondConnection != null) {
-                secondConnection.close();
-            }
-        } catch (SQLException e) {
-            failText = e.getLocalizedMessage();
-        }
-        dbFile.delete();
-        assertNull(failText, failText);
-    }
-
-    /**
-     * Inserts the values from 'ones' with the values from 'twos' into 'tbl1'
-     * @param stmt the statement to use for the inserts.
-     * @param ones the string values to insert into tbl1.
-     * @param twos the corresponding numerical values to insert into tbl1.
-     * @throws SQLException in case of a problem during insert.
-     */
-    private void autoCommitInsertSelectTest(Statement stmt, String[] ones,
-            short[] twos) throws SQLException {
-        for (int i = 0; i < ones.length; i++) {
-            stmt.execute("insert into tbl1 values('" + ones[i] + "'," + twos[i]
-                    + ")");
-        }
-        assertAllFromTbl1(stmt, ones, twos);
-    }
-
-    /**
-     * Asserts that all values that where added to tbl1 are actually in tbl1.
-     * @param stmt the statement to use for the select.
-     * @param ones the string values that where added.
-     * @param twos the numerical values that where added.
-     * @throws SQLException in case of a problem during select.
-     */
-    private void assertAllFromTbl1(Statement stmt, String[] ones, short[] twos)
-            throws SQLException {
-        ResultSet rs = stmt.executeQuery("select * from tbl1");
-        int i = 0;
-        for (; rs.next(); i++) {
-            assertTrue(i < ones.length);
-            assertEquals(ones[i], rs.getString("one"));
-            assertEquals(twos[i], rs.getShort("two"));
-        }
-        assertEquals(i, ones.length);
-    }
-
-    /**
-     * Tests the results of an update followed bz a select on a diffrent statement.
-     * After that the first statement commits its update. and now the second 
-     * statement should also be able to see the changed values in a select.
-     * @param firstStmt the statement to use for the update and commit.
-     * @param secondStmt the statement that should be used to check if the commit works
-     * @param ones the original string values.
-     * @param onesUpdated the updated string values.
-     * @param twos the numerical values.
-     * @throws SQLException in case of a problem during any of the executed commands.
-     */
-    private void updateSelectCommitSelectTest(Statement firstStmt,
-            Statement secondStmt, String[] ones, String[] onesUpdated,
-            short[] twos) throws SQLException {
-        firstStmt.getConnection().setAutoCommit(false);
-        try {
-            updateOnes(firstStmt, onesUpdated, twos);
-            assertAllFromTbl1(secondStmt, ones, twos);
-            firstStmt.getConnection().commit();
-            assertAllFromTbl1(secondStmt, onesUpdated, twos);
-        } finally {
-            firstStmt.getConnection().setAutoCommit(true);
-        }
-    }
-
-    /**
-     * Tests if an update followed by a select works. After that a rollback will 
-     * be made and again a select should show that the rollback worked. 
-     * @param firstStmt the statement to use for the update and the rollback
-     * @param secondStmt the statement to use for checking if the rollback worked as intended.
-     * @param ones the original string values.
-     * @param onesUpdated the updated string values.
-     * @param twos the nomerical values.
-     * @throws SQLException in case of a problem during any command.
-     */
-    private void updateSelectRollbackSelectTest(Statement firstStmt,
-            Statement secondStmt, String[] ones, String[] onesUpdated,
-            short[] twos) throws SQLException {
-        firstStmt.getConnection().setAutoCommit(false);
-        try {
-            updateOnes(firstStmt, onesUpdated, twos);
-            assertAllFromTbl1(secondStmt, ones, twos);
-            firstStmt.getConnection().rollback();
-            assertAllFromTbl1(secondStmt, ones, twos);
-        } finally {
-            firstStmt.getConnection().setAutoCommit(true);
-        }
-    }
-
-    /**
-     * updates the sring values. the original values are stored in 'ones'
-     * and the updated values in 'ones_updated'
-     * @param stmt the statement to use for the update.
-     * @param onesUpdated the new string values.
-     * @param twos the numerical values.
-     * @throws SQLException in case of a problem during update.
-     */
-    private void updateOnes(Statement stmt, String[] onesUpdated, short[] twos)
-            throws SQLException {
-        for (int i = 0; i < onesUpdated.length; i++) {
-            stmt.execute("UPDATE tbl1 SET one = '" + onesUpdated[i]
-                    + "' WHERE two = " + twos[i]);
-        }
-    }
-}
diff --git a/core/tests/coretests/src/android/database/sqlite/DatabaseConnectionPoolTest.java b/core/tests/coretests/src/android/database/sqlite/DatabaseConnectionPoolTest.java
new file mode 100644
index 0000000..525dd2d
--- /dev/null
+++ b/core/tests/coretests/src/android/database/sqlite/DatabaseConnectionPoolTest.java
@@ -0,0 +1,368 @@
+/*
+ * 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.sqlite;
+
+import android.content.Context;
+import android.database.sqlite.SQLiteDatabaseTest.ClassToTestSqlCompilationAndCaching;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Log;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+public class DatabaseConnectionPoolTest extends AndroidTestCase {
+    private static final String TAG = "DatabaseConnectionPoolTest";
+
+    private static final int MAX_CONN = 5;
+    private static final String TEST_SQL = "select * from test where i = ? AND j = 1";
+    private static final String[] TEST_SQLS = new String[] {
+        TEST_SQL, TEST_SQL + 1, TEST_SQL + 2, TEST_SQL + 3, TEST_SQL + 4
+    };
+
+    private SQLiteDatabase mDatabase;
+    private File mDatabaseFile;
+    private DatabaseConnectionPool mTestPool;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        File dbDir = getContext().getDir(this.getClass().getName(), Context.MODE_PRIVATE);
+        mDatabaseFile = new File(dbDir, "database_test.db");
+        if (mDatabaseFile.exists()) {
+            mDatabaseFile.delete();
+        }
+        mDatabase = SQLiteDatabase.openOrCreateDatabase(mDatabaseFile.getPath(), null);
+        assertNotNull(mDatabase);
+        mDatabase.execSQL("create table test (i int, j int);");
+        mTestPool = new DatabaseConnectionPool(mDatabase);
+        assertNotNull(mTestPool);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        mTestPool.close();
+        mDatabase.close();
+        mDatabaseFile.delete();
+        super.tearDown();
+    }
+
+    @SmallTest
+    public void testGetAndRelease() {
+        mTestPool.setMaxPoolSize(MAX_CONN);
+        // connections should be lazily created.
+        assertEquals(0, mTestPool.getSize());
+        // MAX pool size should be set to MAX_CONN
+        assertEquals(MAX_CONN, mTestPool.getMaxPoolSize());
+        // get a connection
+        SQLiteDatabase db = mTestPool.get(TEST_SQL);
+        // pool size should be one - since only one should be allocated for the above get()
+        assertEquals(1, mTestPool.getSize());
+        assertEquals(mDatabase, db.mParentConnObj);
+        // no free connections should be available
+        assertEquals(0, mTestPool.getFreePoolSize());
+        assertFalse(mTestPool.isDatabaseObjFree(db));
+        // release the connection
+        mTestPool.release(db);
+        assertEquals(1, mTestPool.getFreePoolSize());
+        assertEquals(1, mTestPool.getSize());
+        assertEquals(MAX_CONN, mTestPool.getMaxPoolSize());
+        assertTrue(mTestPool.isDatabaseObjFree(db));
+        // release the same object again and expect IllegalStateException
+        try {
+            mTestPool.release(db);
+            fail("illegalStateException expected");
+        } catch (IllegalStateException e ) {
+            // expected.
+        }
+    }
+
+    /**
+     * get all connections from the pool and ask for one more.
+     * should get one of the connections already got so far. 
+     */
+    @SmallTest
+    public void testGetAllConnAndOneMore() {
+        mTestPool.setMaxPoolSize(MAX_CONN);
+        assertEquals(MAX_CONN, mTestPool.getMaxPoolSize());
+        ArrayList<SQLiteDatabase> dbObjs = new ArrayList<SQLiteDatabase>();
+        for (int i = 0; i < MAX_CONN; i++) {
+            SQLiteDatabase db = mTestPool.get(TEST_SQL);
+            assertFalse(dbObjs.contains(db));
+            dbObjs.add(db);
+            assertEquals(mDatabase, db.mParentConnObj);
+        }
+        assertEquals(0, mTestPool.getFreePoolSize());
+        assertEquals(MAX_CONN, mTestPool.getSize());
+        assertEquals(MAX_CONN, mTestPool.getMaxPoolSize());
+        // pool is maxed out and no free connections. ask for one more connection
+        SQLiteDatabase db1 = mTestPool.get(TEST_SQL);
+        // make sure db1 is one of the existing ones
+        assertTrue(dbObjs.contains(db1));
+        // pool size should remain at MAX_CONN
+        assertEquals(0, mTestPool.getFreePoolSize());
+        assertEquals(MAX_CONN, mTestPool.getSize());
+        assertEquals(MAX_CONN, mTestPool.getMaxPoolSize());
+        // release db1 but since it is allocated 2 times, it should still remain 'busy'
+        mTestPool.release(db1);
+        assertFalse(mTestPool.isDatabaseObjFree(db1));
+        assertEquals(0, mTestPool.getFreePoolSize());
+        assertEquals(MAX_CONN, mTestPool.getSize());
+        assertEquals(MAX_CONN, mTestPool.getMaxPoolSize());
+        // release all connections
+        for (int i = 0; i < MAX_CONN; i++) {
+            mTestPool.release(dbObjs.get(i));
+        }
+        // all objects in the pool should be freed now
+        assertEquals(MAX_CONN, mTestPool.getFreePoolSize());
+        assertEquals(MAX_CONN, mTestPool.getSize());
+        assertEquals(MAX_CONN, mTestPool.getMaxPoolSize());
+    }
+    
+    /**
+     * same as above except that each connection has different SQL statement associated with it. 
+     */
+    @SmallTest
+    public void testConnRetrievalForPreviouslySeenSql() {
+        mTestPool.setMaxPoolSize(MAX_CONN);
+        assertEquals(MAX_CONN, mTestPool.getMaxPoolSize());
+
+        HashMap<String, SQLiteDatabase> dbObjs = new HashMap<String, SQLiteDatabase>();
+        for (int i = 0; i < MAX_CONN; i++) {
+            SQLiteDatabase db = mTestPool.get(TEST_SQLS[i]);
+            executeSqlOnDatabaseConn(db, TEST_SQLS[i]);
+            assertFalse(dbObjs.values().contains(db));
+            dbObjs.put(TEST_SQLS[i], db);
+        }
+        assertEquals(0, mTestPool.getFreePoolSize());
+        assertEquals(MAX_CONN, mTestPool.getSize());
+        assertEquals(MAX_CONN, mTestPool.getMaxPoolSize());
+        // pool is maxed out and no free connections. ask for one more connection
+        // use a previously seen SQL statement
+        String testSql = TEST_SQLS[MAX_CONN - 1];
+        SQLiteDatabase db1 = mTestPool.get(testSql);
+        assertEquals(0, mTestPool.getFreePoolSize());
+        assertEquals(MAX_CONN, mTestPool.getSize());
+        assertEquals(MAX_CONN, mTestPool.getMaxPoolSize());
+        // make sure db1 is one of the existing ones
+        assertTrue(dbObjs.values().contains(db1));
+        assertEquals(db1, dbObjs.get(testSql));
+        // do the same again
+        SQLiteDatabase db2 = mTestPool.get(testSql);
+        // make sure db1 is one of the existing ones
+        assertEquals(db2, dbObjs.get(testSql));
+
+        // pool size should remain at MAX_CONN
+        assertEquals(0, mTestPool.getFreePoolSize());
+        assertEquals(MAX_CONN, mTestPool.getSize());
+        assertEquals(MAX_CONN, mTestPool.getMaxPoolSize());
+
+        // release db1 but since the same connection is allocated 3 times,
+        // it should still remain 'busy'
+        mTestPool.release(db1);
+        assertFalse(mTestPool.isDatabaseObjFree(dbObjs.get(testSql)));
+        assertEquals(0, mTestPool.getFreePoolSize());
+        assertEquals(MAX_CONN, mTestPool.getSize());
+        assertEquals(MAX_CONN, mTestPool.getMaxPoolSize());
+
+        // release db2 but since the same connection is allocated 2 times,
+        // it should still remain 'busy'
+        mTestPool.release(db2);
+        assertFalse(mTestPool.isDatabaseObjFree(dbObjs.get(testSql)));
+        assertEquals(0, mTestPool.getFreePoolSize());
+        assertEquals(MAX_CONN, mTestPool.getSize());
+        assertEquals(MAX_CONN, mTestPool.getMaxPoolSize());
+
+        // release all connections
+        for (int i = 0; i < MAX_CONN; i++) {
+            mTestPool.release(dbObjs.get(TEST_SQLS[i]));
+        }
+        // all objects in the pool should be freed now
+        assertEquals(MAX_CONN, mTestPool.getFreePoolSize());
+        assertEquals(MAX_CONN, mTestPool.getSize());
+        assertEquals(MAX_CONN, mTestPool.getMaxPoolSize());
+    }
+
+    private void executeSqlOnDatabaseConn(SQLiteDatabase db, String sql) {
+        // get the given sql be compiled on the given database connection.
+        // this will help DatabaseConenctionPool figure out if a given SQL statement
+        // is already cached by a database connection.
+        ClassToTestSqlCompilationAndCaching c =
+                ClassToTestSqlCompilationAndCaching.create(db, sql);
+        c.close();
+    }
+
+    /**
+     * get a connection for a SQL statement 'blah'. (connection_s)
+     * make sure the pool has at least one free connection even after this get().
+     * and get a connection for the same SQL again.
+     *    this connection should be different from connection_s.
+     *    even though there is a connection with the given SQL pre-compiled, since is it not free
+     *    AND since the pool has free connections available, should get a new connection.
+     */
+    @SmallTest
+    public void testGetConnForTheSameSql() {
+        mTestPool.setMaxPoolSize(MAX_CONN);
+
+        SQLiteDatabase db = mTestPool.get(TEST_SQL);
+        executeSqlOnDatabaseConn(db, TEST_SQL);
+        assertEquals(0, mTestPool.getFreePoolSize());
+        assertEquals(1, mTestPool.getSize());
+        assertEquals(MAX_CONN, mTestPool.getMaxPoolSize());
+
+        assertFalse(mTestPool.isDatabaseObjFree(db));
+
+        SQLiteDatabase db1 = mTestPool.get(TEST_SQL);
+        assertEquals(0, mTestPool.getFreePoolSize());
+        assertEquals(2, mTestPool.getSize());
+        assertEquals(MAX_CONN, mTestPool.getMaxPoolSize());
+
+        assertFalse(mTestPool.isDatabaseObjFree(db1));
+        assertFalse(db1.equals(db));
+
+        mTestPool.release(db);
+        assertEquals(1, mTestPool.getFreePoolSize());
+        assertEquals(2, mTestPool.getSize());
+        assertEquals(MAX_CONN, mTestPool.getMaxPoolSize());
+
+        mTestPool.release(db1);
+        assertEquals(2, mTestPool.getFreePoolSize());
+        assertEquals(2, mTestPool.getSize());
+        assertEquals(MAX_CONN, mTestPool.getMaxPoolSize());
+    }
+
+    /**
+     * get the same connection N times and release it N times.
+     * this tests DatabaseConnectionPool.PoolObj.mNumHolders
+     */
+    @SmallTest
+    public void testGetSameConnNtimesAndReleaseItNtimes() {
+        mTestPool.setMaxPoolSize(MAX_CONN);
+        assertEquals(MAX_CONN, mTestPool.getMaxPoolSize());
+
+        HashMap<String, SQLiteDatabase> dbObjs = new HashMap<String, SQLiteDatabase>();
+        for (int i = 0; i < MAX_CONN; i++) {
+            SQLiteDatabase db = mTestPool.get(TEST_SQLS[i]);
+            executeSqlOnDatabaseConn(db, TEST_SQLS[i]);
+            assertFalse(dbObjs.values().contains(db));
+            dbObjs.put(TEST_SQLS[i], db);
+        }
+        assertEquals(0, mTestPool.getFreePoolSize());
+        assertEquals(MAX_CONN, mTestPool.getSize());
+        assertEquals(MAX_CONN, mTestPool.getMaxPoolSize());
+        // every connection in the pool should have numHolders = 1
+        for (int i = 0; i < MAX_CONN; i ++) {
+            assertEquals(1, mTestPool.getPool().get(i).getNumHolders());
+        }
+        // pool is maxed out and no free connections. ask for one more connection
+        // use a previously seen SQL statement
+        String testSql = TEST_SQLS[MAX_CONN - 1];
+        SQLiteDatabase db1 = mTestPool.get(testSql);
+        assertEquals(0, mTestPool.getFreePoolSize());
+        assertEquals(MAX_CONN, mTestPool.getSize());
+        assertEquals(MAX_CONN, mTestPool.getMaxPoolSize());
+        // make sure db1 is one of the existing ones
+        assertTrue(dbObjs.values().contains(db1));
+        assertEquals(db1, dbObjs.get(testSql));
+        assertFalse(mTestPool.isDatabaseObjFree(db1));
+        DatabaseConnectionPool.PoolObj poolObj = mTestPool.getPool().get(db1.mConnectionNum - 1);
+        int numHolders = poolObj.getNumHolders();
+        assertEquals(2, numHolders);
+        assertEquals(0, mTestPool.getFreePoolSize());
+        assertEquals(MAX_CONN, mTestPool.getSize());
+        assertEquals(MAX_CONN, mTestPool.getMaxPoolSize());
+        // get the same connection N times more
+        int N = 100;
+        for (int i = 0; i < N; i++) {
+            SQLiteDatabase db2 = mTestPool.get(testSql);
+            assertEquals(db1, db2);
+            assertFalse(mTestPool.isDatabaseObjFree(db2));
+            // numHolders for this object should be now up by 1
+            int prev = numHolders;
+            numHolders = poolObj.getNumHolders();
+            assertEquals(prev + 1, numHolders);
+        }
+        // release it N times
+        for (int i = 0; i < N; i++) {
+            mTestPool.release(db1);
+            int prev = numHolders;
+            numHolders = poolObj.getNumHolders();
+            assertEquals(prev - 1, numHolders);
+            assertFalse(mTestPool.isDatabaseObjFree(db1));
+        }
+        // the connection should still have 2 more holders
+        assertFalse(mTestPool.isDatabaseObjFree(db1));
+        assertEquals(2, poolObj.getNumHolders());
+        assertEquals(0, mTestPool.getFreePoolSize());
+        assertEquals(MAX_CONN, mTestPool.getSize());
+        assertEquals(MAX_CONN, mTestPool.getMaxPoolSize());
+        // release 2 more times
+        mTestPool.release(db1);
+        mTestPool.release(db1);
+        assertEquals(0, poolObj.getNumHolders());
+        assertEquals(1, mTestPool.getFreePoolSize());
+        assertEquals(MAX_CONN, mTestPool.getSize());
+        assertEquals(MAX_CONN, mTestPool.getMaxPoolSize());
+        assertTrue(mTestPool.isDatabaseObjFree(db1));
+    }
+
+    @SmallTest
+    public void testStressTest() {
+        mTestPool.setMaxPoolSize(MAX_CONN);
+        assertEquals(MAX_CONN, mTestPool.getMaxPoolSize());
+
+        HashMap<SQLiteDatabase, Integer> dbMap = new HashMap<SQLiteDatabase, Integer>();
+        for (int i = 0; i < MAX_CONN; i++) {
+            SQLiteDatabase db = mTestPool.get(TEST_SQLS[i]);
+            assertFalse(dbMap.containsKey(db));
+            dbMap.put(db, 1);
+            executeSqlOnDatabaseConn(db, TEST_SQLS[i]);
+        }
+        assertEquals(0, mTestPool.getFreePoolSize());
+        assertEquals(MAX_CONN, mTestPool.getSize());
+        assertEquals(MAX_CONN, mTestPool.getMaxPoolSize());
+        // ask for lot more connections but since the pool is maxed out, we should start receiving
+        // connections that we already got so far
+        for (int i = MAX_CONN; i < 1000; i++) {
+            SQLiteDatabase db = mTestPool.get(TEST_SQL + i);
+            assertTrue(dbMap.containsKey(db));
+            int k = dbMap.get(db);
+            dbMap.put(db, ++k);
+        }
+        assertEquals(0, mTestPool.getFreePoolSize());
+        assertEquals(MAX_CONN, mTestPool.getSize());
+        assertEquals(MAX_CONN, mTestPool.getMaxPoolSize());
+        // print the distribution of the database connection handles received, should be uniform.
+        for (SQLiteDatabase d : dbMap.keySet()) {
+            Log.i(TAG, "connection # " + d.mConnectionNum + ", numHolders: " + dbMap.get(d));
+        }
+        // print the pool info
+        Log.i(TAG, mTestPool.toString());
+        // release all
+        for (SQLiteDatabase d : dbMap.keySet()) {
+            int num = dbMap.get(d);
+            for (int i = 0; i < num; i++) {
+                mTestPool.release(d);
+            }
+        }
+        assertEquals(MAX_CONN, mTestPool.getFreePoolSize());
+        assertEquals(MAX_CONN, mTestPool.getSize());
+        assertEquals(MAX_CONN, mTestPool.getMaxPoolSize());
+    }
+}
diff --git a/core/tests/coretests/src/android/database/sqlite/SQLiteCursorTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteCursorTest.java
new file mode 100644
index 0000000..3c3ff3f
--- /dev/null
+++ b/core/tests/coretests/src/android/database/sqlite/SQLiteCursorTest.java
@@ -0,0 +1,94 @@
+/*
+ * 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.sqlite;
+
+import android.content.Context;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import java.io.File;
+
+public class SQLiteCursorTest extends AndroidTestCase {
+    private SQLiteDatabase mDatabase;
+    private File mDatabaseFile;
+    private static final String TABLE_NAME = "testCursor";
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        File dbDir = getContext().getDir(this.getClass().getName(), Context.MODE_PRIVATE);
+        mDatabaseFile = new File(dbDir, "sqlitecursor_test.db");
+        if (mDatabaseFile.exists()) {
+            mDatabaseFile.delete();
+        }
+        mDatabase = SQLiteDatabase.openOrCreateDatabase(mDatabaseFile.getPath(), null);
+        assertNotNull(mDatabase);
+        // create a test table
+        mDatabase.execSQL("CREATE TABLE " + TABLE_NAME + " (i int, j int);");
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        mDatabase.close();
+        mDatabaseFile.delete();
+        super.tearDown();
+    }
+
+    @SmallTest
+    public void testQueryObjReassignment() {
+        mDatabase.enableWriteAheadLogging();
+        // have a few connections in the database connection pool
+        DatabaseConnectionPool pool = mDatabase.mConnectionPool;
+        pool.setMaxPoolSize(5);
+        SQLiteCursor cursor =
+                (SQLiteCursor) mDatabase.rawQuery("select * from " + TABLE_NAME, null);
+        assertNotNull(cursor);
+        // it should use a pooled database connection
+        SQLiteDatabase db = cursor.getDatabase();
+        assertTrue(db.mConnectionNum > 0);
+        assertFalse(mDatabase.equals(db));
+        assertEquals(mDatabase, db.mParentConnObj);
+        assertTrue(pool.getConnectionList().contains(db));
+        assertTrue(db.isOpen());
+        // do a requery. cursor should continue to use the above pooled connection
+        cursor.requery();
+        SQLiteDatabase dbAgain = cursor.getDatabase();
+        assertEquals(db, dbAgain);
+        // disable WAL so that the pooled connection held by the above cursor is closed
+        mDatabase.disableWriteAheadLogging();
+        assertFalse(db.isOpen());
+        assertNull(mDatabase.mConnectionPool);
+        // requery - which should make the cursor use mDatabase connection since the pooled
+        // connection is no longer available
+        cursor.requery();
+        SQLiteDatabase db1 = cursor.getDatabase();
+        assertTrue(db1.mConnectionNum == 0);
+        assertEquals(mDatabase, db1);
+        assertNull(mDatabase.mConnectionPool);
+        assertTrue(db1.isOpen());
+        assertFalse(mDatabase.equals(db));
+        // enable WAL and requery - this time a pooled connection should be used
+        mDatabase.enableWriteAheadLogging();
+        cursor.requery();
+        db = cursor.getDatabase();
+        assertTrue(db.mConnectionNum > 0);
+        assertFalse(mDatabase.equals(db));
+        assertEquals(mDatabase, db.mParentConnObj);
+        assertTrue(mDatabase.mConnectionPool.getConnectionList().contains(db));
+        assertTrue(db.isOpen());
+    }
+}
diff --git a/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java
new file mode 100644
index 0000000..dc5613e
--- /dev/null
+++ b/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java
@@ -0,0 +1,912 @@
+/*
+ * 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.sqlite;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.DatabaseUtils;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteStatement;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.test.suitebuilder.annotation.Suppress;
+import android.util.Log;
+
+import java.io.File;
+import java.util.ArrayList;
+
+public class SQLiteDatabaseTest extends AndroidTestCase {
+    private static final String TAG = "DatabaseGeneralTest";
+    private static final String TEST_TABLE = "test";
+    private static final int CURRENT_DATABASE_VERSION = 42;
+    private SQLiteDatabase mDatabase;
+    private File mDatabaseFile;
+    private static final int INSERT = 1;
+    private static final int UPDATE = 2;
+    private static final int DELETE = 3;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        dbSetUp();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        dbTeardown();
+        super.tearDown();
+    }
+
+    private void dbTeardown() throws Exception {
+        mDatabase.close();
+        mDatabaseFile.delete();
+    }
+
+    private void dbSetUp() throws Exception {
+        File dbDir = getContext().getDir(this.getClass().getName(), Context.MODE_PRIVATE);
+        mDatabaseFile = new File(dbDir, "database_test.db");
+        if (mDatabaseFile.exists()) {
+            mDatabaseFile.delete();
+        }
+        mDatabase = SQLiteDatabase.openOrCreateDatabase(mDatabaseFile.getPath(), null, null);
+        assertNotNull(mDatabase);
+        mDatabase.setVersion(CURRENT_DATABASE_VERSION);
+    }
+
+    @SmallTest
+    public void testEnableWriteAheadLogging() {
+        mDatabase.disableWriteAheadLogging();
+        assertNull(mDatabase.mConnectionPool);
+        mDatabase.enableWriteAheadLogging();
+        DatabaseConnectionPool pool = mDatabase.mConnectionPool;
+        assertNotNull(pool);
+        // make the same call again and make sure the pool already setup is not re-created
+        mDatabase.enableWriteAheadLogging();
+        assertEquals(pool, mDatabase.mConnectionPool);
+    }
+
+    @SmallTest
+    public void testDisableWriteAheadLogging() {
+        mDatabase.execSQL("create table test (i int);");
+        mDatabase.enableWriteAheadLogging();
+        assertNotNull(mDatabase.mConnectionPool);
+        // get a pooled database connection
+        SQLiteDatabase db = mDatabase.getDbConnection("select * from test");
+        assertNotNull(db);
+        assertFalse(mDatabase.equals(db));
+        assertTrue(db.isOpen());
+        // disable WAL - which should close connection pool and all pooled connections
+        mDatabase.disableWriteAheadLogging();
+        assertNull(mDatabase.mConnectionPool);
+        assertFalse(db.isOpen());
+    }
+
+    @SmallTest
+    public void testCursorsWithClosedDbConnAfterDisableWriteAheadLogging() {
+        mDatabase.disableWriteAheadLogging();
+        mDatabase.beginTransactionNonExclusive();
+        mDatabase.execSQL("create table test (i int);");
+        mDatabase.execSQL("insert into test values(1);");
+        mDatabase.setTransactionSuccessful();
+        mDatabase.endTransaction();
+        mDatabase.enableWriteAheadLogging();
+        assertNotNull(mDatabase.mConnectionPool);
+        assertEquals(0, mDatabase.mConnectionPool.getSize());
+        assertEquals(0, mDatabase.mConnectionPool.getFreePoolSize());
+        // get a cursor which should use pooled database connection
+        Cursor c = mDatabase.rawQuery("select * from test", null);
+        assertEquals(1, c.getCount());
+        assertEquals(1, mDatabase.mConnectionPool.getSize());
+        assertEquals(1, mDatabase.mConnectionPool.getFreePoolSize());
+        SQLiteDatabase db = mDatabase.mConnectionPool.getConnectionList().get(0);
+        assertTrue(mDatabase.mConnectionPool.isDatabaseObjFree(db));
+        // disable WAL - which should close connection pool and all pooled connections
+        mDatabase.disableWriteAheadLogging();
+        assertNull(mDatabase.mConnectionPool);
+        assertFalse(db.isOpen());
+        // cursor data should still be accessible because it is fetching data from CursorWindow
+        c.moveToNext();
+        assertEquals(1, c.getInt(0));
+        c.requery();
+        assertEquals(1, c.getCount());
+        c.moveToNext();
+        assertEquals(1, c.getInt(0));
+        c.close();
+    }
+
+    @SmallTest
+    public void testSetConnectionPoolSize() {
+        mDatabase.enableWriteAheadLogging();
+        // can't set pool size to zero
+        try {
+            mDatabase.setConnectionPoolSize(0);
+            fail("IllegalStateException expected");
+        } catch (IllegalArgumentException e) {
+            assertTrue(e.getMessage().contains("less than the current max value"));
+        }
+        // set pool size to a valid value
+        mDatabase.setConnectionPoolSize(10);
+        assertEquals(10, mDatabase.mConnectionPool.getMaxPoolSize());
+        // can't set pool size to < the value above
+        try {
+            mDatabase.setConnectionPoolSize(1);
+            fail("IllegalStateException expected");
+        } catch (IllegalArgumentException e) {
+            assertTrue(e.getMessage().contains("less than the current max value"));
+        }
+    }
+
+    /**
+     * a transaction should be started before a standalone-update/insert/delete statement
+     */
+    @SmallTest
+    public void testStartXactBeforeUpdateSql() throws InterruptedException {
+        runTestForStartXactBeforeUpdateSql(INSERT);
+        runTestForStartXactBeforeUpdateSql(UPDATE);
+        runTestForStartXactBeforeUpdateSql(DELETE);
+    }
+    private void runTestForStartXactBeforeUpdateSql(int stmtType) throws InterruptedException {
+        createTableAndClearCache();
+
+        ContentValues values = new ContentValues();
+        // make some changes to data in TEST_TABLE
+        for (int i = 0; i < 5; i++) {
+            values.put("i", i);
+            values.put("j", "i" + System.currentTimeMillis());
+            mDatabase.insert(TEST_TABLE, null, values);
+            switch (stmtType) {
+                case UPDATE:
+                    values.put("j", "u" + System.currentTimeMillis());
+                    mDatabase.update(TEST_TABLE, values, "i = " + i, null);
+                    break;
+                case DELETE:
+                    mDatabase.delete(TEST_TABLE, "i = 1", null);
+                    break;
+            }
+        }
+        // do a query. even though query uses a different database connection,
+        // it should still see the above changes to data because the above standalone
+        // insert/update/deletes are done in transactions automatically.
+        String sql = "select count(*) from " + TEST_TABLE;
+        SQLiteStatement stmt = mDatabase.compileStatement(sql);
+        final int expectedValue = (stmtType == DELETE) ? 4 : 5;
+        assertEquals(expectedValue, stmt.simpleQueryForLong());
+        stmt.close();
+        Cursor c = mDatabase.rawQuery(sql, null);
+        assertEquals(1, c.getCount());
+        c.moveToFirst();
+        assertEquals(expectedValue, c.getLong(0));
+        c.close();
+
+        // do 5 more changes in a transaction but do a query before and after the commit
+        mDatabase.beginTransaction();
+        for (int i = 10; i < 15; i++) {
+            values.put("i", i);
+            values.put("j", "i" + System.currentTimeMillis());
+            mDatabase.insert(TEST_TABLE, null, values);
+            switch (stmtType) {
+                case UPDATE:
+                    values.put("j", "u" + System.currentTimeMillis());
+                    mDatabase.update(TEST_TABLE, values, "i = " + i, null);
+                    break;
+                case DELETE:
+                    mDatabase.delete(TEST_TABLE, "i = 1", null);
+                    break;
+            }
+        }
+        mDatabase.setTransactionSuccessful();
+        // do a query before commit - should still have 5 rows
+        // this query should run in a different thread to force it to use a different database
+        // connection
+        Thread t = new Thread() {
+            @Override public void run() {
+                String sql = "select count(*) from " + TEST_TABLE;
+                SQLiteStatement stmt = getDb().compileStatement(sql);
+                assertEquals(expectedValue, stmt.simpleQueryForLong());
+                stmt.close();
+                Cursor c = getDb().rawQuery(sql, null);
+                assertEquals(1, c.getCount());
+                c.moveToFirst();
+                assertEquals(expectedValue, c.getLong(0));
+                c.close();
+            }
+        };
+        t.start();
+        // wait until the above thread is done
+        t.join();
+        // commit and then query. should see changes from the transaction
+        mDatabase.endTransaction();
+        stmt = mDatabase.compileStatement(sql);
+        final int expectedValue2 = (stmtType == DELETE) ? 9 : 10;
+        assertEquals(expectedValue2, stmt.simpleQueryForLong());
+        stmt.close();
+        c = mDatabase.rawQuery(sql, null);
+        assertEquals(1, c.getCount());
+        c.moveToFirst();
+        assertEquals(expectedValue2, c.getLong(0));
+        c.close();
+    }
+    private synchronized SQLiteDatabase getDb() {
+        return mDatabase;
+    }
+
+    /**
+     * Test to ensure that readers are able to read the database data (old versions)
+     * EVEN WHEN the writer is in a transaction on the same database.
+     *<p>
+     * This test starts 1 Writer and 2 Readers and sets up connection pool for readers
+     * by calling the method {@link SQLiteDatabase#enableWriteAheadLogging()}.
+     * <p>
+     * Writer does the following in a tight loop
+     * <pre>
+     *     begin transaction
+     *     insert into table_1
+     *     insert into table_2
+     *     commit
+     * </pre>
+     * <p>
+     * As long a the writer is alive, Readers do the following in a tight loop at the same time
+     * <pre>
+     *     Reader_K does "select count(*) from table_K"  where K = 1 or 2
+     * </pre>
+     * <p>
+     * The test is run for TIME_TO_RUN_WAL_TEST_FOR sec.
+     * <p>
+     * The test is repeated for different connection-pool-sizes (1..3)
+     * <p>
+     * And at the end of of each test, the following statistics are printed
+     * <ul>
+     *    <li>connection-pool-size</li>
+     *    <li>number-of-transactions by writer</li>
+     *    <li>number of reads by reader_K while the writer is IN or NOT-IN xaction</li>
+     * </ul>
+     */
+    @LargeTest
+    @Suppress // run this test only if you need to collect the numbers from this test
+    public void testConcurrencyEffectsOfConnPool() throws Exception {
+        // run the test with sqlite WAL enable
+        runConnectionPoolTest(true);
+
+        // run the same test WITHOUT sqlite WAL enabled
+        runConnectionPoolTest(false);
+    }
+
+    private void runConnectionPoolTest(boolean useWal) throws Exception {
+        int M = 3;
+        StringBuilder[] buff = new StringBuilder[M];
+        for (int i = 0; i < M; i++) {
+            if (useWal) {
+                // set up connection pool
+                mDatabase.enableWriteAheadLogging();
+                mDatabase.setConnectionPoolSize(i + 1);
+            } else {
+                mDatabase.disableWriteAheadLogging();
+            }
+            mDatabase.execSQL("CREATE TABLE t1 (i int, j int);");
+            mDatabase.execSQL("CREATE TABLE t2 (i int, j int);");
+            mDatabase.beginTransaction();
+            for (int k = 0; k < 5; k++) {
+                mDatabase.execSQL("insert into t1 values(?,?);", new String[] {k+"", k+""});
+                mDatabase.execSQL("insert into t2 values(?,?);", new String[] {k+"", k+""});
+            }
+            mDatabase.setTransactionSuccessful();
+            mDatabase.endTransaction();
+
+            // start a writer
+            Writer w = new Writer(mDatabase);
+
+            // initialize an array of counters to be passed to the readers
+            Reader r1 = new Reader(mDatabase, "t1", w, 0);
+            Reader r2 = new Reader(mDatabase, "t2", w, 1);
+            w.start();
+            r1.start();
+            r2.start();
+
+            // wait for all threads to die
+            w.join();
+            r1.join();
+            r2.join();
+
+            // print the stats
+            int[][] counts = getCounts();
+            buff[i] = new StringBuilder();
+            buff[i].append("connpool-size = ");
+            buff[i].append(i + 1);
+            buff[i].append(", num xacts by writer = ");
+            buff[i].append(getNumXacts());
+            buff[i].append(", num-reads-in-xact/NOT-in-xact by reader1 = ");
+            buff[i].append(counts[0][1] + "/" + counts[0][0]);
+            buff[i].append(", by reader2 = ");
+            buff[i].append(counts[1][1] + "/" + counts[1][0]);
+
+            Log.i(TAG, "done testing for conn-pool-size of " + (i+1));
+
+            dbTeardown();
+            dbSetUp();
+        }
+        Log.i(TAG, "duration of test " + TIME_TO_RUN_WAL_TEST_FOR + " sec");
+        for (int i = 0; i < M; i++) {
+            Log.i(TAG, buff[i].toString());
+        }
+    }
+
+    private boolean inXact = false;
+    private int numXacts;
+    private static final int TIME_TO_RUN_WAL_TEST_FOR = 15; // num sec this test should run
+    private int[][] counts = new int[2][2];
+
+    private synchronized boolean inXact() {
+        return inXact;
+    }
+
+    private synchronized void setInXactFlag(boolean flag) {
+        inXact = flag;
+    }
+
+    private synchronized void setCounts(int readerNum, int[] numReads) {
+        counts[readerNum][0] = numReads[0];
+        counts[readerNum][1] = numReads[1];
+    }
+
+    private synchronized int[][] getCounts() {
+        return counts;
+    }
+
+    private synchronized void setNumXacts(int num) {
+        numXacts = num;
+    }
+
+    private synchronized int getNumXacts() {
+        return numXacts;
+    }
+
+    private class Writer extends Thread {
+        private SQLiteDatabase db = null;
+        public Writer(SQLiteDatabase db) {
+            this.db = db;
+        }
+        @Override public void run() {
+            // in a loop, for N sec, do the following
+            //    BEGIN transaction
+            //    insert into table t1, t2
+            //    Commit
+            long now = System.currentTimeMillis();
+            int k;
+            for (k = 0;(System.currentTimeMillis() - now) / 1000 < TIME_TO_RUN_WAL_TEST_FOR; k++) {
+                db.beginTransactionNonExclusive();
+                setInXactFlag(true);
+                for (int i = 0; i < 10; i++) {
+                    db.execSQL("insert into t1 values(?,?);", new String[] {i+"", i+""});
+                    db.execSQL("insert into t2 values(?,?);", new String[] {i+"", i+""});
+                }
+                db.setTransactionSuccessful();
+                setInXactFlag(false);
+                db.endTransaction();
+            }
+            setNumXacts(k);
+        }
+    }
+
+    private class Reader extends Thread {
+        private SQLiteDatabase db = null;
+        private String table = null;
+        private Writer w = null;
+        private int readerNum;
+        private int[] numReads = new int[2];
+        public Reader(SQLiteDatabase db, String table, Writer w, int readerNum) {
+            this.db = db;
+            this.table = table;
+            this.w = w;
+            this.readerNum = readerNum;
+        }
+        @Override public void run() {
+            // while the write is alive, in a loop do the query on a table
+            while (w.isAlive()) {
+                for (int i = 0; i < 10; i++) {
+                    DatabaseUtils.longForQuery(db, "select count(*) from " + this.table, null);
+                    // update count of reads
+                    numReads[inXact() ? 1 : 0] += 1;
+                }
+            }
+            setCounts(readerNum, numReads);
+        }
+    }
+
+    public static class ClassToTestSqlCompilationAndCaching extends SQLiteProgram {
+        private ClassToTestSqlCompilationAndCaching(SQLiteDatabase db, String sql) {
+            super(db, sql);
+        }
+        public static ClassToTestSqlCompilationAndCaching create(SQLiteDatabase db, String sql) {
+            db.lock();
+            try {
+                return new ClassToTestSqlCompilationAndCaching(db, sql);
+            } finally {
+                db.unlock();
+            }
+        }
+    }
+
+    @SmallTest
+    public void testLruCachingOfSqliteCompiledSqlObjs() {
+        createTableAndClearCache();
+        // 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<Integer> stmtObjs = new ArrayList<Integer>();
+        ArrayList<String> sqlStrings = new ArrayList<String>();
+        int stmt0 = 0;
+        for (int i = 0; i < N+1; i++) {
+            String s = "insert into test values(" + i + ",?);";
+            sqlStrings.add(s);
+            ClassToTestSqlCompilationAndCaching c =
+                    ClassToTestSqlCompilationAndCaching.create(mDatabase, s);
+            int n = c.getSqlStatementId();
+            stmtObjs.add(i, n);
+            if (i == 0) {
+                // save the statementId of this obj. we want to make sure it is thrown out of
+                // the cache at the end of this test.
+                stmt0 = n;
+            }
+            c.close();
+        }
+        // is 0'th entry out of the cache? it should be in the list of statementIds
+        // corresponding to the pre-compiled sql statements to be finalized.
+        assertTrue(mDatabase.getQueuedUpStmtList().contains(stmt0));
+        for (int i = 1; i < N+1; i++) {
+            SQLiteCompiledSql compSql = mDatabase.getCompiledStatementForSql(sqlStrings.get(i));
+            assertNotNull(compSql);
+            assertTrue(stmtObjs.contains(compSql.nStatement));
+        }
+    }
+
+    @MediumTest
+    public void testDbCloseReleasingAllCachedSql() {
+        mDatabase.execSQL("CREATE TABLE test (_id INTEGER PRIMARY KEY, text1 TEXT, text2 TEXT, " +
+                "num1 INTEGER, num2 INTEGER, image BLOB);");
+        final String statement = "DELETE FROM test WHERE _id=?;";
+        SQLiteStatement statementDoNotClose = mDatabase.compileStatement(statement);
+        statementDoNotClose.bindLong(1, 1);
+        /* do not close statementDoNotClose object.
+         * That should leave it in SQLiteDatabase.mPrograms.
+         * mDatabase.close() in tearDown() should release it.
+         */
+    }
+
+    private void createTableAndClearCache() {
+        mDatabase.disableWriteAheadLogging();
+        mDatabase.execSQL("DROP TABLE IF EXISTS " + TEST_TABLE);
+        mDatabase.execSQL("CREATE TABLE " + TEST_TABLE + " (i int, j int);");
+        mDatabase.enableWriteAheadLogging();
+        mDatabase.lock();
+        // flush the above statement from cache and close all the pending statements to be released
+        mDatabase.deallocCachedSqlStatements();
+        mDatabase.closePendingStatements();
+        mDatabase.unlock();
+        assertEquals(0, mDatabase.getQueuedUpStmtList().size());
+    }
+
+    /**
+     * test to make sure the statement finalizations are not done right away but
+     * piggy-backed onto the next sql statement execution on the same database.
+     */
+    @SmallTest
+    public void testStatementClose() {
+        createTableAndClearCache();
+        // fill up statement cache in mDatabase
+        int N = SQLiteDatabase.MAX_SQL_CACHE_SIZE;
+        mDatabase.setMaxSqlCacheSize(N);
+        SQLiteStatement stmt;
+        int stmt0Id = 0;
+        for (int i = 0; i < N; i ++) {
+            ClassToTestSqlCompilationAndCaching c =
+                    ClassToTestSqlCompilationAndCaching.create(mDatabase,
+                            "insert into test values(" + i + ", ?);");
+            // keep track of 0th entry
+            if (i == 0) {
+                stmt0Id = c.getSqlStatementId();
+            }
+            c.close();
+        }
+
+        // add one more to the cache - and the above 'stmt0Id' should fall out of cache
+        ClassToTestSqlCompilationAndCaching stmt1 =
+                ClassToTestSqlCompilationAndCaching.create(mDatabase,
+                        "insert into test values(100, ?);");
+        stmt1.close();
+
+        // the above close() should have queuedUp the statement for finalization
+        ArrayList<Integer> statementIds = mDatabase.getQueuedUpStmtList();
+        assertTrue(statementIds.contains(stmt0Id));
+
+        // execute something to see if this statement gets finalized
+        mDatabase.execSQL("delete from test where i = 10;");
+        statementIds = mDatabase.getQueuedUpStmtList();
+        assertFalse(statementIds.contains(stmt0Id));
+    }
+
+    /**
+     * same as above - except that the statement to be finalized is from Thread # 1.
+     * and it is eventually finalized in Thread # 2 when it executes a SQL statement.
+     * @throws InterruptedException
+     */
+    @LargeTest
+    public void testStatementCloseDiffThread() throws InterruptedException {
+        createTableAndClearCache();
+        final int N = SQLiteDatabase.MAX_SQL_CACHE_SIZE;
+        mDatabase.setMaxSqlCacheSize(N);
+        // fill up statement cache in mDatabase in a thread
+        Thread t1 = new Thread() {
+            @Override public void run() {
+                SQLiteStatement stmt;
+                for (int i = 0; i < N; i++) {
+                    ClassToTestSqlCompilationAndCaching c =
+                        ClassToTestSqlCompilationAndCaching.create(getDb(),
+                                "insert into test values(" + i + ", ?);");
+                    // keep track of 0th entry
+                    if (i == 0) {
+                        stmt0Id = c.getSqlStatementId();
+                    }
+                    c.close();
+                }
+            }
+        };
+        t1.start();
+        // wait for the thread to finish
+        t1.join();
+        // mDatabase shouldn't have any statements to be released
+        assertEquals(0, mDatabase.getQueuedUpStmtList().size());
+
+        // add one more to the cache - and the above 'stmt0Id' should fall out of cache
+        // just for the heck of it, do it in a separate thread
+        Thread t2 = new Thread() {
+            @Override public void run() {
+                ClassToTestSqlCompilationAndCaching stmt1 =
+                    ClassToTestSqlCompilationAndCaching.create(getDb(),
+                            "insert into test values(100, ?);");
+                stmt1.bindLong(1, 1);
+                stmt1.close();
+            }
+        };
+        t2.start();
+        t2.join();
+
+        // close() in the above thread should have queuedUp the stmt0Id for finalization
+        ArrayList<Integer> statementIds = getDb().getQueuedUpStmtList();
+        assertTrue(statementIds.contains(getStmt0Id()));
+        assertEquals(1, statementIds.size());
+
+        // execute something to see if this statement gets finalized
+        // again do it in a separate thread
+        Thread t3 = new Thread() {
+            @Override public void run() {
+                getDb().execSQL("delete from test where i = 10;");
+            }
+        };
+        t3.start();
+        t3.join();
+
+        // is the statement finalized?
+        statementIds = getDb().getQueuedUpStmtList();
+        assertFalse(statementIds.contains(getStmt0Id()));
+    }
+
+    private volatile int stmt0Id = 0;
+    private synchronized int getStmt0Id() {
+        return this.stmt0Id;
+    }
+
+    /**
+     * same as above - except that the queue of statements to be finalized are finalized
+     * by database close() operation.
+     */
+    @LargeTest
+    public void testStatementCloseByDbClose() throws InterruptedException {
+        createTableAndClearCache();
+        // fill up statement cache in mDatabase in a thread
+        Thread t1 = new Thread() {
+            @Override public void run() {
+                int N = SQLiteDatabase.MAX_SQL_CACHE_SIZE;
+                getDb().setMaxSqlCacheSize(N);
+                SQLiteStatement stmt;
+                for (int i = 0; i < N; i ++) {
+                    ClassToTestSqlCompilationAndCaching c =
+                            ClassToTestSqlCompilationAndCaching.create(getDb(),
+                                    "insert into test values(" + i + ", ?);");
+                    // keep track of 0th entry
+                    if (i == 0) {
+                        stmt0Id = c.getSqlStatementId();
+                    }
+                    c.close();
+                }
+            }
+        };
+        t1.start();
+        // wait for the thread to finish
+        t1.join();
+
+        // add one more to the cache - and the above 'stmt0Id' should fall out of cache
+        // just for the heck of it, do it in a separate thread
+        Thread t2 = new Thread() {
+            @Override public void run() {
+                ClassToTestSqlCompilationAndCaching stmt1 =
+                        ClassToTestSqlCompilationAndCaching.create(getDb(),
+                                "insert into test values(100, ?);");
+                stmt1.bindLong(1, 1);
+                stmt1.close();
+            }
+        };
+        t2.start();
+        t2.join();
+
+        // close() in the above thread should have queuedUp the statement for finalization
+        ArrayList<Integer> statementIds = getDb().getQueuedUpStmtList();
+        assertTrue(getStmt0Id() > 0);
+        assertTrue(statementIds.contains(stmt0Id));
+        assertEquals(1, statementIds.size());
+
+        // close the database. everything from mClosedStatementIds in mDatabase
+        // should be finalized and cleared from the list
+        // again do it in a separate thread
+        Thread t3 = new Thread() {
+            @Override public void run() {
+                getDb().close();
+            }
+        };
+        t3.start();
+        t3.join();
+
+        // check mClosedStatementIds in mDatabase. it should be empty
+        statementIds = getDb().getQueuedUpStmtList();
+        assertEquals(0, statementIds.size());
+    }
+
+    /**
+     * This test tests usage execSQL() to begin transaction works in the following way
+     *   Thread #1 does
+     *       execSQL("begin transaction");
+     *       insert()
+     *   Thread # 2
+     *       query()
+     *   Thread#1 ("end transaction")
+     * Thread # 2 query will execute - because java layer will not have locked the SQLiteDatabase
+     * object and sqlite will consider this query to be part of the transaction.
+     *
+     * but if thread # 1 uses beginTransaction() instead of execSQL() to start transaction,
+     * then Thread # 2's query will have been blocked by java layer
+     * until Thread#1 ends transaction.
+     *
+     * @throws InterruptedException
+     */
+    @SmallTest
+    public void testExecSqlToStartAndEndTransaction() throws InterruptedException {
+        runExecSqlToStartAndEndTransaction("END");
+        // same as above, instead now do "COMMIT" or "ROLLBACK" instead of "END" transaction
+        runExecSqlToStartAndEndTransaction("COMMIT");
+        runExecSqlToStartAndEndTransaction("ROLLBACK");
+    }
+    private void runExecSqlToStartAndEndTransaction(String str) throws InterruptedException {
+        createTableAndClearCache();
+        // disable WAL just so queries and updates use the same database connection
+        mDatabase.disableWriteAheadLogging();
+        mDatabase.execSQL("BEGIN transaction");
+        // even though mDatabase.beginTransaction() is not called to start transaction,
+        // mDatabase connection should now be in transaction as a result of
+        // mDatabase.execSQL("BEGIN transaction")
+        // but mDatabase.mLock should not be held by any thread
+        assertTrue(mDatabase.inTransaction());
+        assertFalse(mDatabase.isDbLockedByCurrentThread());
+        assertFalse(mDatabase.isDbLockedByOtherThreads());
+        assertTrue(mDatabase.amIInTransaction());
+        mDatabase.execSQL("INSERT into " + TEST_TABLE + " values(10, 999);");
+        assertTrue(mDatabase.inTransaction());
+        assertFalse(mDatabase.isDbLockedByCurrentThread());
+        assertFalse(mDatabase.isDbLockedByOtherThreads());
+        assertTrue(mDatabase.amIInTransaction());
+        Thread t = new Thread() {
+            @Override public void run() {
+                assertTrue(mDatabase.amIInTransaction());
+                assertEquals(999, DatabaseUtils.longForQuery(getDb(),
+                        "select j from " + TEST_TABLE + " WHERE i = 10", null));
+                assertTrue(getDb().inTransaction());
+                assertFalse(getDb().isDbLockedByCurrentThread());
+                assertFalse(getDb().isDbLockedByOtherThreads());
+                assertTrue(mDatabase.amIInTransaction());
+            }
+        };
+        t.start();
+        t.join();
+        assertTrue(mDatabase.amIInTransaction());
+        assertTrue(mDatabase.inTransaction());
+        assertFalse(mDatabase.isDbLockedByCurrentThread());
+        assertFalse(mDatabase.isDbLockedByOtherThreads());
+        mDatabase.execSQL(str);
+        assertFalse(mDatabase.amIInTransaction());
+        assertFalse(mDatabase.inTransaction());
+        assertFalse(mDatabase.isDbLockedByCurrentThread());
+        assertFalse(mDatabase.isDbLockedByOtherThreads());
+    }
+
+    /**
+     * test the following
+     * http://b/issue?id=2871037
+     *          Cursor cursor = db.query(...);
+     *          // with WAL enabled, the above uses a pooled database connection
+     *          db.beginTransaction()
+     *          try {
+     *            db.insert(......);
+     *            cursor.requery();
+     *            // since the cursor uses pooled database connection, the above requery
+     *            // will not return the results that were inserted above since the insert is
+     *            // done using main database connection AND the transaction is not committed yet.
+     *            // fix is to make the above cursor use the main database connection - and NOT
+     *            // the pooled database connection
+     *            db.setTransactionSuccessful()
+     *          } finally {
+     *            db.endTransaction()
+     *          }
+     *
+     * @throws InterruptedException
+     */
+    @SmallTest
+    public void testTransactionAndWalInterplay1() throws InterruptedException {
+        createTableAndClearCache();
+        mDatabase.execSQL("INSERT into " + TEST_TABLE + " values(10, 999);");
+        String sql = "select * from " + TEST_TABLE;
+        Cursor c = mDatabase.rawQuery(sql, null);
+        // should have 1 row in the table
+        assertEquals(1, c.getCount());
+        mDatabase.beginTransactionNonExclusive();
+        try {
+            mDatabase.execSQL("INSERT into " + TEST_TABLE + " values(100, 9909);");
+            assertEquals(2, DatabaseUtils.longForQuery(mDatabase,
+                    "select count(*) from " + TEST_TABLE, null));
+            // requery on the previously opened cursor
+            // cursor should now use the main database connection and see 2 rows
+            c.requery();
+            assertEquals(2, c.getCount());
+            mDatabase.setTransactionSuccessful();
+        } finally {
+            mDatabase.endTransaction();
+        }
+        c.close();
+
+        // do the same test but now do the requery in a separate thread.
+        createTableAndClearCache();
+        mDatabase.execSQL("INSERT into " + TEST_TABLE + " values(10, 999);");
+        final Cursor c1 = mDatabase.rawQuery("select count(*) from " + TEST_TABLE, null);
+        // should have 1 row in the table
+        assertEquals(1, c1.getCount());
+        mDatabase.beginTransactionNonExclusive();
+        try {
+            mDatabase.execSQL("INSERT into " + TEST_TABLE + " values(100, 9909);");
+            assertEquals(2, DatabaseUtils.longForQuery(mDatabase,
+                    "select count(*) from " + TEST_TABLE, null));
+            // query in a different thread. that causes the cursor to use a pooled connection
+            // and since this thread hasn't committed its changes, the cursor should still see only
+            // 1 row
+            Thread t = new Thread() {
+                @Override public void run() {
+                    c1.requery();
+                    assertEquals(1, c1.getCount());
+                }
+            };
+            t.start();
+            t.join();
+            // should be 2 rows now - including the the row inserted above
+            mDatabase.setTransactionSuccessful();
+        } finally {
+            mDatabase.endTransaction();
+        }
+        c1.close();
+    }
+
+    /**
+     * This test is same as {@link #testTransactionAndWalInterplay1()} except the following:
+     * instead of mDatabase.beginTransactionNonExclusive(), use execSQL("BEGIN transaction")
+     * and instead of mDatabase.endTransaction(), use execSQL("END");
+     */
+    @SmallTest
+    public void testTransactionAndWalInterplay2() throws InterruptedException {
+        createTableAndClearCache();
+        mDatabase.execSQL("INSERT into " + TEST_TABLE + " values(10, 999);");
+        String sql = "select * from " + TEST_TABLE;
+        Cursor c = mDatabase.rawQuery(sql, null);
+        // should have 1 row in the table
+        assertEquals(1, c.getCount());
+        mDatabase.execSQL("BEGIN transaction");
+        try {
+            mDatabase.execSQL("INSERT into " + TEST_TABLE + " values(100, 9909);");
+            assertEquals(2, DatabaseUtils.longForQuery(mDatabase,
+                    "select count(*) from " + TEST_TABLE, null));
+            // requery on the previously opened cursor
+            // cursor should now use the main database connection and see 2 rows
+            c.requery();
+            assertEquals(2, c.getCount());
+        } finally {
+            mDatabase.execSQL("commit;");
+        }
+        c.close();
+
+        // do the same test but now do the requery in a separate thread.
+        createTableAndClearCache();
+        mDatabase.execSQL("INSERT into " + TEST_TABLE + " values(10, 999);");
+        final Cursor c1 = mDatabase.rawQuery("select count(*) from " + TEST_TABLE, null);
+        // should have 1 row in the table
+        assertEquals(1, c1.getCount());
+        mDatabase.execSQL("BEGIN transaction");
+        try {
+            mDatabase.execSQL("INSERT into " + TEST_TABLE + " values(100, 9909);");
+            assertEquals(2, DatabaseUtils.longForQuery(mDatabase,
+                    "select count(*) from " + TEST_TABLE, null));
+            // query in a different thread. but since the transaction is started using
+            // execSQ() instead of beginTransaction(), cursor's query is considered part of
+            // the same ransaction - and hence it should see the above inserted row
+            Thread t = new Thread() {
+                @Override public void run() {
+                    c1.requery();
+                    assertEquals(1, c1.getCount());
+                }
+            };
+            t.start();
+            t.join();
+            // should be 2 rows now - including the the row inserted above
+        } finally {
+            mDatabase.execSQL("commit");
+        }
+        c1.close();
+    }
+
+    /**
+     * This test is same as {@link #testTransactionAndWalInterplay2()} except the following:
+     * instead of commiting the data, do rollback and make sure the data seen by the query
+     * within the transaction is now gone.
+     */
+    @SmallTest
+    public void testTransactionAndWalInterplay3() throws InterruptedException {
+        createTableAndClearCache();
+        mDatabase.execSQL("INSERT into " + TEST_TABLE + " values(10, 999);");
+        String sql = "select * from " + TEST_TABLE;
+        Cursor c = mDatabase.rawQuery(sql, null);
+        // should have 1 row in the table
+        assertEquals(1, c.getCount());
+        mDatabase.execSQL("BEGIN transaction");
+        try {
+            mDatabase.execSQL("INSERT into " + TEST_TABLE + " values(100, 9909);");
+            assertEquals(2, DatabaseUtils.longForQuery(mDatabase,
+                    "select count(*) from " + TEST_TABLE, null));
+            // requery on the previously opened cursor
+            // cursor should now use the main database connection and see 2 rows
+            c.requery();
+            assertEquals(2, c.getCount());
+        } finally {
+            // rollback the change
+            mDatabase.execSQL("rollback;");
+        }
+        // since the change is rolled back, do the same query again and should now find only 1 row
+        c.requery();
+        assertEquals(1, c.getCount());
+        assertEquals(1, DatabaseUtils.longForQuery(mDatabase,
+                "select count(*) from " + TEST_TABLE, null));
+        c.close();
+    }
+}
diff --git a/core/tests/coretests/src/android/database/sqlite/SQLiteGeneralTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteGeneralTest.java
deleted file mode 100644
index af7ccce..0000000
--- a/core/tests/coretests/src/android/database/sqlite/SQLiteGeneralTest.java
+++ /dev/null
@@ -1,136 +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.database.sqlite;
-
-import android.content.Context;
-import android.test.AndroidTestCase;
-import android.test.FlakyTest;
-import android.test.suitebuilder.annotation.LargeTest;
-
-import java.io.File;
-
-public class SQLiteGeneralTest extends AndroidTestCase {
-
-    private SQLiteDatabase mDatabase;
-    private File mDatabaseFile;
-    Boolean exceptionRecvd = false;
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        exceptionRecvd = false;
-        File dbDir = getContext().getDir(this.getClass().getName(), Context.MODE_PRIVATE);
-        mDatabaseFile = new File(dbDir, "database_test.db");
-        if (mDatabaseFile.exists()) {
-            mDatabaseFile.delete();
-        }
-        mDatabase = SQLiteDatabase.openOrCreateDatabase(mDatabaseFile.getPath(), null);
-        assertNotNull(mDatabase);
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        mDatabase.close();
-        mDatabaseFile.delete();
-        super.tearDown();
-    }
-
-    @LargeTest
-    public void testUseOfSameSqlStatementBy2Threads() throws Exception {
-        mDatabase.execSQL("CREATE TABLE test_pstmt (i INTEGER PRIMARY KEY, j text);");
-
-        // thread 1 creates a prepared statement
-        final String stmt = "SELECT * FROM test_pstmt WHERE i = ?";
-
-        // start 2 threads to do repeatedly execute "stmt"
-        // since these 2 threads are executing the same sql, they each should get
-        // their own copy and
-        // there SHOULD NOT be an error from sqlite: "prepared statement is busy"
-        class RunStmtThread extends Thread {
-            private static final int N = 1000;
-            @Override public void run() {
-                int i = 0;
-                try {
-                    // execute many times
-                    for (i = 0; i < N; i++) {
-                        SQLiteStatement s1 = mDatabase.compileStatement(stmt);
-                        s1.bindLong(1, i);
-                        s1.execute();
-                        s1.close();
-                    }
-                } catch (SQLiteException e) {
-                    fail("SQLiteException: " + e.getMessage());
-                    return;
-                } catch (Exception e) {
-                    e.printStackTrace();
-                    fail("random unexpected exception: " + e.getMessage());
-                    return;
-                }
-            }
-        }
-        RunStmtThread t1 = new RunStmtThread();
-        t1.start();
-        RunStmtThread t2 = new RunStmtThread();
-        t2.start();
-        while (t1.isAlive() || t2.isAlive()) {
-            Thread.sleep(1000);
-        }
-    }
-
-    @FlakyTest
-    public void testUseOfSamePreparedStatementBy2Threads() throws Exception {
-        mDatabase.execSQL("CREATE TABLE test_pstmt (i INTEGER PRIMARY KEY, j text);");
-
-        // thread 1 creates a prepared statement
-        final String stmt = "SELECT * FROM test_pstmt WHERE i = ?";
-        final SQLiteStatement s1 = mDatabase.compileStatement(stmt);
-
-        // start 2 threads to do repeatedly execute "stmt"
-        // since these 2 threads are executing the same prepared statement,
-        // should see an error from sqlite: "prepared statement is busy"
-        class RunStmtThread extends Thread {
-            private static final int N = 1000;
-            @Override public void run() {
-                int i = 0;
-                try {
-                    // execute many times
-                    for (i = 0; i < N; i++) {
-                        s1.bindLong(1, i);
-                        s1.execute();
-                    }
-                } catch (SQLiteException e) {
-                    // expect it
-                    assertTrue(e.getMessage().contains("library routine called out of sequence:"));
-                    exceptionRecvd = true;
-                    return;
-                } catch (Exception e) {
-                    e.printStackTrace();
-                    fail("random unexpected exception: " + e.getMessage());
-                    return;
-                }
-            }
-        }
-        RunStmtThread t1 = new RunStmtThread();
-        t1.start();
-        RunStmtThread t2 = new RunStmtThread();
-        t2.start();
-        while (t1.isAlive() || t2.isAlive()) {
-            Thread.sleep(1000);
-        }
-        assertTrue(exceptionRecvd);
-    }
-}
diff --git a/core/tests/coretests/src/android/database/sqlite/SQLiteJDBCDriverTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteJDBCDriverTest.java
deleted file mode 100644
index f74f3e6..0000000
--- a/core/tests/coretests/src/android/database/sqlite/SQLiteJDBCDriverTest.java
+++ /dev/null
@@ -1,138 +0,0 @@
-/*
- * 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.
- */
-
-package android.database.sqlite;
-
-import android.test.suitebuilder.annotation.LargeTest;
-
-import java.io.File;
-import java.sql.Connection;
-import java.sql.DriverManager;
-import java.sql.PreparedStatement;
-import java.sql.SQLException;
-import java.sql.Statement;
-
-/**
- * Minimal test for JDBC driver
- */
-public class SQLiteJDBCDriverTest extends AbstractJDBCDriverTest {
-
-    private File dbFile;
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        dbFile = File.createTempFile("sqliteTestDB", null);
-    }
-    
-    @Override
-    protected void tearDown() throws Exception {
-        if(dbFile != null) {
-            dbFile.delete();
-        }
-        super.tearDown();
-    }
-
-    @Override
-    protected String getConnectionURL() {
-        return "jdbc:sqlite:/" + dbFile;
-    }
-
-    @Override
-    protected File getDbFile() {
-        return dbFile;
-    }
-
-    @Override
-    protected String getJDBCDriverClassName() {
-        return "SQLite.JDBCDriver";
-    }
-    
-    // Regression test for (Noser) #255: PreparedStatement.executeUpdate results
-    // in VM crashing with SIGABRT.
-    @LargeTest
-    public void test_connection3() throws Exception {
-        PreparedStatement prst = null;
-        Statement st = null;
-        Connection conn = null;
-        try {
-            Class.forName("SQLite.JDBCDriver").newInstance();
-            if (dbFile.exists()) {
-                dbFile.delete();
-            }
-            conn = DriverManager.getConnection("jdbc:sqlite:/"
-                        + dbFile.getPath());
-            assertNotNull(conn);
-
-            // create table
-            st = conn.createStatement();
-            String sql = "CREATE TABLE zoo (ZID INTEGER NOT NULL, family VARCHAR (20) NOT NULL, name VARCHAR (20) NOT NULL, PRIMARY KEY(ZID) )";
-            st.executeUpdate(sql);
-            
-            String update = "update zoo set family = ? where name = ?;";
-            prst = conn.prepareStatement(update);
-            prst.setString(1, "cat");
-            prst.setString(2, "Yasha");
-            // st = conn.createStatement();
-            // st.execute("select * from zoo where family = 'cat'");
-            // ResultSet rs = st.getResultSet();
-            // assertEquals(0, getCount(rs));
-            prst.executeUpdate();
-            // st.execute("select * from zoo where family = 'cat'");
-            // ResultSet rs1 = st.getResultSet();
-            // assertEquals(1, getCount(rs1));
-            try {
-                prst = conn.prepareStatement("");
-                prst.execute();
-                fail("SQLException is not thrown");
-            } catch (SQLException e) {
-                // expected
-            }
-            
-            try {
-                conn.prepareStatement(null);
-                fail("NPE is not thrown");
-            } catch (Exception e) {
-                // expected
-            }
-            try {
-                st = conn.createStatement();
-                st.execute("drop table if exists zoo");
-                
-            } catch (SQLException e) {
-                fail("Couldn't drop table: " + e.getMessage());
-            } finally {
-                try {
-                    st.close();
-                    conn.close();
-                } catch (SQLException ee) {
-                }
-            }
-        } finally {
-            try {
-                if (prst != null) {
-                    prst.close();
-                }
-                if (st != null) {
-                    st.close();
-                }
-            } catch (SQLException ee) {
-            }
-        }
-
-    }
-    
-}
diff --git a/core/tests/coretests/src/android/database/sqlite/SQLiteStatementTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteStatementTest.java
new file mode 100644
index 0000000..217545f
--- /dev/null
+++ b/core/tests/coretests/src/android/database/sqlite/SQLiteStatementTest.java
@@ -0,0 +1,213 @@
+/*
+ * 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.sqlite;
+
+import android.content.Context;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import java.io.File;
+import java.util.Random;
+import java.util.concurrent.locks.ReentrantLock;
+
+public class SQLiteStatementTest extends AndroidTestCase {
+    private SQLiteDatabase mDatabase;
+    private File mDatabaseFile;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        File dbDir = getContext().getDir(this.getClass().getName(), Context.MODE_PRIVATE);
+        mDatabaseFile = new File(dbDir, "database_test.db");
+        if (mDatabaseFile.exists()) {
+            mDatabaseFile.delete();
+        }
+        mDatabase = SQLiteDatabase.openOrCreateDatabase(mDatabaseFile.getPath(), null);
+        assertNotNull(mDatabase);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        mDatabase.close();
+        mDatabaseFile.delete();
+        super.tearDown();
+    }
+
+    /**
+     * Start 2 threads to repeatedly execute the above SQL statement.
+     * Even though 2 threads are executing the same SQL, they each should get their own copy of
+     * prepared SQL statement id and there SHOULD NOT be an error from sqlite or android.
+     * @throws InterruptedException thrown if the test threads started by this test are interrupted
+     */
+    @LargeTest
+    public void testUseOfSameSqlStatementBy2Threads() throws InterruptedException {
+        mDatabase.execSQL("CREATE TABLE test_pstmt (i INTEGER PRIMARY KEY, j text);");
+        final String stmt = "SELECT * FROM test_pstmt WHERE i = ?";
+        class RunStmtThread extends Thread {
+            @Override public void run() {
+                // do it enough times to make sure there are no corner cases going untested
+                for (int i = 0; i < 1000; i++) {
+                    SQLiteStatement s1 = mDatabase.compileStatement(stmt);
+                    s1.bindLong(1, i);
+                    s1.execute();
+                    s1.close();
+                 }
+            }
+        }
+        RunStmtThread t1 = new RunStmtThread();
+        t1.start();
+        RunStmtThread t2 = new RunStmtThread();
+        t2.start();
+         while (t1.isAlive() || t2.isAlive()) {
+             Thread.sleep(10);
+         }
+     }
+
+    /**
+     * A simple test: start 2 threads to repeatedly execute the same {@link SQLiteStatement}.
+     * The 2 threads take turns to use the {@link SQLiteStatement}; i.e., it is NOT in use
+     * by both the threads at the same time.
+     *
+     * @throws InterruptedException thrown if the test threads started by this test are interrupted
+     */
+    @LargeTest
+    public void testUseOfSameSqliteStatementBy2Threads() throws InterruptedException {
+        mDatabase.execSQL("CREATE TABLE test_pstmt (i INTEGER PRIMARY KEY, j text);");
+        final String stmt = "SELECT * FROM test_pstmt WHERE i = ?";
+        final SQLiteStatement s1 = mDatabase.compileStatement(stmt);
+        class RunStmtThread extends Thread {
+            @Override public void run() {
+                // do it enough times to make sure there are no corner cases going untested
+                for (int i = 0; i < 1000; i++) {
+                    lock();
+                    try {
+                        s1.bindLong(1, i);
+                        s1.execute();
+                    } finally {
+                        unlock();
+                    }
+                    Thread.yield();
+                }
+            }
+        }
+        RunStmtThread t1 = new RunStmtThread();
+        t1.start();
+        RunStmtThread t2 = new RunStmtThread();
+        t2.start();
+        while (t1.isAlive() || t2.isAlive()) {
+            Thread.sleep(10);
+        }
+    }
+    /** Synchronize on this when accessing the SqliteStatemet in the above */
+    private final ReentrantLock mLock = new ReentrantLock(true);
+    private void lock() {
+        mLock.lock();
+    }
+    private void unlock() {
+        mLock.unlock();
+    }
+
+    /**
+     * Tests the following: a {@link SQLiteStatement} object should not refer to a
+     * pre-compiled SQL statement id except in during the period of binding the arguments
+     * and executing the SQL statement.
+     */
+    @SmallTest
+    public void testReferenceToPrecompiledStatementId() {
+        mDatabase.execSQL("create table t (i int, j text);");
+        verifyReferenceToPrecompiledStatementId(false);
+        verifyReferenceToPrecompiledStatementId(true);
+
+        // a small stress test to make sure there are no side effects of
+        // the acquire & release of pre-compiled statement id by SQLiteStatement object.
+        for (int i = 0; i < 100; i++) {
+            verifyReferenceToPrecompiledStatementId(false);
+            verifyReferenceToPrecompiledStatementId(true);
+        }
+    }
+
+    @SuppressWarnings("deprecation")
+    private void verifyReferenceToPrecompiledStatementId(boolean wal) {
+        if (wal) {
+            mDatabase.enableWriteAheadLogging();
+        } else {
+            mDatabase.disableWriteAheadLogging();
+        }
+        // test with INSERT statement - doesn't use connection pool, if WAL is set
+        SQLiteStatement stmt = mDatabase.compileStatement("insert into t values(?,?);");
+        assertEquals(mDatabase.mNativeHandle, stmt.nHandle);
+        assertEquals(mDatabase, stmt.mDatabase);
+        // sql statement should not be compiled yet
+        assertEquals(0, stmt.nStatement);
+        assertEquals(0, stmt.getSqlStatementId());
+        int colValue = new Random().nextInt();
+        stmt.bindLong(1, colValue);
+        // verify that the sql statement is still not compiled
+        assertEquals(0, stmt.getSqlStatementId());
+        // should still be using the mDatabase connection - verify
+        assertEquals(mDatabase.mNativeHandle, stmt.nHandle);
+        assertEquals(mDatabase, stmt.mDatabase);
+        stmt.bindString(2, "blah" + colValue);
+        // verify that the sql statement is still not compiled
+        assertEquals(0, stmt.getSqlStatementId());
+        assertEquals(mDatabase.mNativeHandle, stmt.nHandle);
+        assertEquals(mDatabase, stmt.mDatabase);
+        stmt.executeInsert();
+        // now that the statement is executed, pre-compiled statement should be released
+        assertEquals(0, stmt.nStatement);
+        assertEquals(0, stmt.getSqlStatementId());
+        assertEquals(mDatabase.mNativeHandle, stmt.nHandle);
+        assertEquals(mDatabase, stmt.mDatabase);
+        stmt.close();
+        // pre-compiled SQL statement should still remain released from this object
+        assertEquals(0, stmt.nStatement);
+        assertEquals(0, stmt.getSqlStatementId());
+        // but the database handle should still be the same
+        assertEquals(mDatabase, stmt.mDatabase);
+
+        // test with a SELECT statement - uses connection pool if WAL is set
+        stmt = mDatabase.compileStatement("select i from t where j=?;");
+        // sql statement should not be compiled yet
+        assertEquals(0, stmt.nStatement);
+        assertEquals(0, stmt.getSqlStatementId());
+        assertEquals(mDatabase.mNativeHandle, stmt.nHandle);
+        assertEquals(mDatabase, stmt.mDatabase);
+        stmt.bindString(1, "blah" + colValue);
+        // verify that the sql statement is still not compiled
+        assertEquals(0, stmt.nStatement);
+        assertEquals(0, stmt.getSqlStatementId());
+        assertEquals(mDatabase.mNativeHandle, stmt.nHandle);
+        assertEquals(mDatabase, stmt.mDatabase);
+        // execute the statement
+        Long l = stmt.simpleQueryForLong();
+        assertEquals(colValue, l.intValue());
+        // now that the statement is executed, pre-compiled statement should be released
+        assertEquals(0, stmt.nStatement);
+        assertEquals(0, stmt.getSqlStatementId());
+        assertEquals(mDatabase.mNativeHandle, stmt.nHandle);
+        assertEquals(mDatabase, stmt.mDatabase);
+        stmt.close();
+        // pre-compiled SQL statement should still remain released from this object
+        assertEquals(0, stmt.nStatement);
+        assertEquals(0, stmt.getSqlStatementId());
+        // but the database handle should still remain attached to the statement
+        assertEquals(mDatabase.mNativeHandle, stmt.nHandle);
+        assertEquals(mDatabase, stmt.mDatabase);
+    }
+}
diff --git a/core/tests/coretests/src/android/pim/vcard/ContentValuesBuilder.java b/core/tests/coretests/src/android/pim/vcard/ContentValuesBuilder.java
deleted file mode 100644
index b3c0773..0000000
--- a/core/tests/coretests/src/android/pim/vcard/ContentValuesBuilder.java
+++ /dev/null
@@ -1,81 +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.pim.vcard;
-
-import android.content.ContentValues;
-
-/**
- * ContentValues-like class which enables users to chain put() methods and restricts
- * the other methods.
- */
-/* package */ class ContentValuesBuilder {
-    private final ContentValues mContentValues;
-
-    public ContentValuesBuilder(final ContentValues contentValues) {
-        mContentValues = contentValues;
-    }
-
-    public ContentValuesBuilder put(String key, String value) {
-        mContentValues.put(key, value);
-        return this;
-    }
-
-    public ContentValuesBuilder put(String key, Byte value) {
-        mContentValues.put(key, value);
-        return this;
-    }
-
-    public ContentValuesBuilder put(String key, Short value) {
-        mContentValues.put(key, value);
-        return this;
-    }
-
-    public ContentValuesBuilder put(String key, Integer value) {
-        mContentValues.put(key, value);
-        return this;
-    }
-
-    public ContentValuesBuilder put(String key, Long value) {
-        mContentValues.put(key, value);
-        return this;
-    }
-
-    public ContentValuesBuilder put(String key, Float value) {
-        mContentValues.put(key, value);
-        return this;
-    }
-
-    public ContentValuesBuilder put(String key, Double value) {
-        mContentValues.put(key, value);
-        return this;
-    }
-
-    public ContentValuesBuilder put(String key, Boolean value) {
-        mContentValues.put(key, value);
-        return this;
-    }
-
-    public ContentValuesBuilder put(String key, byte[] value) {
-        mContentValues.put(key, value);
-        return this;
-    }
-
-    public ContentValuesBuilder putNull(String key) {
-        mContentValues.putNull(key);
-        return this;
-    }
-}
diff --git a/core/tests/coretests/src/android/pim/vcard/ContentValuesVerifier.java b/core/tests/coretests/src/android/pim/vcard/ContentValuesVerifier.java
deleted file mode 100644
index b9e9875..0000000
--- a/core/tests/coretests/src/android/pim/vcard/ContentValuesVerifier.java
+++ /dev/null
@@ -1,101 +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.pim.vcard;
-
-import android.pim.vcard.VCardConfig;
-import android.pim.vcard.VCardEntry;
-import android.pim.vcard.VCardEntryConstructor;
-import android.pim.vcard.VCardEntryHandler;
-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;
-
-/* package */ class ContentValuesVerifier implements VCardEntryHandler {
-    private AndroidTestCase mTestCase;
-    private List<ContentValuesVerifierElem> mContentValuesVerifierElemList =
-        new ArrayList<ContentValuesVerifierElem>();
-    private int mIndex;
-
-    public ContentValuesVerifierElem addElem(AndroidTestCase androidTestCase) {
-        mTestCase = androidTestCase;
-        ContentValuesVerifierElem importVerifier = new ContentValuesVerifierElem(androidTestCase);
-        mContentValuesVerifierElemList.add(importVerifier);
-        return importVerifier;
-    }
-
-    public void verify(int resId, int vCardType) throws IOException, VCardException {
-        verify(mTestCase.getContext().getResources().openRawResource(resId), vCardType);
-    }
-
-    public void verify(int resId, int vCardType, final VCardParser vCardParser)
-            throws IOException, VCardException {
-        verify(mTestCase.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 {
-        VCardEntryConstructor builder =
-            new VCardEntryConstructor(null, null, false, vCardType, null);
-        builder.addEntryHandler(this);
-        try {
-            vCardParser.parse(is, builder);
-        } finally {
-            if (is != null) {
-                try {
-                    is.close();
-                } catch (IOException e) {
-                }
-            }
-        }
-    }
-
-    public void onStart() {
-        for (ContentValuesVerifierElem elem : mContentValuesVerifierElemList) {
-            elem.onParsingStart();
-        }
-    }
-
-    public void onEntryCreated(VCardEntry entry) {
-        mTestCase.assertTrue(mIndex < mContentValuesVerifierElemList.size());
-        mContentValuesVerifierElemList.get(mIndex).onEntryCreated(entry);
-        mIndex++;
-    }
-
-    public void onEnd() {
-        for (ContentValuesVerifierElem elem : mContentValuesVerifierElemList) {
-            elem.onParsingEnd();
-            elem.verifyResolver();
-        }
-    }
-}
diff --git a/core/tests/coretests/src/android/pim/vcard/ContentValuesVerifierElem.java b/core/tests/coretests/src/android/pim/vcard/ContentValuesVerifierElem.java
deleted file mode 100644
index 2edbb36..0000000
--- a/core/tests/coretests/src/android/pim/vcard/ContentValuesVerifierElem.java
+++ /dev/null
@@ -1,95 +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.pim.vcard;
-
-import android.content.ContentValues;
-import android.pim.vcard.VCardConfig;
-import android.pim.vcard.VCardEntry;
-import android.pim.vcard.VCardEntryCommitter;
-import android.pim.vcard.VCardEntryConstructor;
-import android.pim.vcard.VCardEntryHandler;
-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.provider.ContactsContract.Data;
-import android.test.AndroidTestCase;
-
-import java.io.IOException;
-import java.io.InputStream;
-
-/* package */ class ContentValuesVerifierElem {
-    private final AndroidTestCase mTestCase;
-    private final ImportTestResolver mResolver;
-    private final VCardEntryHandler mHandler;
-
-    public ContentValuesVerifierElem(AndroidTestCase androidTestCase) {
-        mTestCase = androidTestCase;
-        mResolver = new ImportTestResolver(androidTestCase);
-        mHandler = new VCardEntryCommitter(mResolver);
-    }
-
-    public ContentValuesBuilder addExpected(String mimeType) {
-        ContentValues contentValues = new ContentValues();
-        contentValues.put(Data.MIMETYPE, mimeType);
-        mResolver.addExpectedContentValues(contentValues);
-        return new ContentValuesBuilder(contentValues);
-    }
-
-    public void verify(int resId, int vCardType)
-            throws IOException, VCardException {
-        verify(mTestCase.getContext().getResources().openRawResource(resId), vCardType);
-    }
-
-    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();
-        }
-        VCardEntryConstructor builder =
-                new VCardEntryConstructor(null, null, false, vCardType, null);
-        builder.addEntryHandler(mHandler);
-        try {
-            vCardParser.parse(is, builder);
-        } finally {
-            if (is != null) {
-                try {
-                    is.close();
-                } catch (IOException e) {
-                }
-            }
-        }
-        verifyResolver();
-    }
-
-    public void verifyResolver() {
-        mResolver.verify();
-    }
-
-    public void onParsingStart() {
-        mHandler.onStart();
-    }
-
-    public void onEntryCreated(VCardEntry entry) {
-        mHandler.onEntryCreated(entry);
-    }
-
-    public void onParsingEnd() {
-        mHandler.onEnd();
-    }
-}
diff --git a/core/tests/coretests/src/android/pim/vcard/ExportTestResolver.java b/core/tests/coretests/src/android/pim/vcard/ExportTestResolver.java
deleted file mode 100644
index 5968e83..0000000
--- a/core/tests/coretests/src/android/pim/vcard/ExportTestResolver.java
+++ /dev/null
@@ -1,216 +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.pim.vcard;
-
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.content.Entity;
-import android.content.EntityIterator;
-import android.database.Cursor;
-import android.net.Uri;
-import android.provider.ContactsContract.Contacts;
-import android.provider.ContactsContract.Data;
-import android.provider.ContactsContract.RawContacts;
-import android.test.mock.MockContentResolver;
-import android.test.mock.MockCursor;
-
-import junit.framework.TestCase;
-
-import java.util.ArrayList;
-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>();
-
-    public ExportTestProvider(TestCase testCase) {
-        mTestCase = testCase;
-    }
-
-    public ContactEntry buildInputEntry() {
-        ContactEntry contactEntry = new ContactEntry();
-        mContactEntryList.add(contactEntry);
-        return contactEntry;
-    }
-
-    /**
-     * <p>
-     * An old method which had existed but was removed from ContentResolver.
-     * </p>
-     * <p>
-     * 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) {
-        mTestCase.assertTrue(uri != null);
-        mTestCase.assertTrue(ContentResolver.SCHEME_CONTENT.equals(uri.getScheme()));
-        final String authority = uri.getAuthority();
-        mTestCase.assertTrue(RawContacts.CONTENT_URI.getAuthority().equals(authority));
-        mTestCase.assertTrue((Data.CONTACT_ID + "=?").equals(selection));
-        mTestCase.assertEquals(1, selectionArgs.length);
-        final int id = Integer.parseInt(selectionArgs[0]);
-        mTestCase.assertTrue(id >= 0 && id < mContactEntryList.size());
-
-        return new MockEntityIterator(mContactEntryList.get(id).getList());
-    }
-
-    @Override
-    public Cursor query(Uri uri,String[] projection,
-            String selection, String[] selectionArgs, String sortOrder) {
-        mTestCase.assertTrue(VCardComposer.CONTACTS_TEST_CONTENT_URI.equals(uri));
-        // In this test, following arguments are not supported.
-        mTestCase.assertNull(selection);
-        mTestCase.assertNull(selectionArgs);
-        mTestCase.assertNull(sortOrder);
-
-        return new MockCursor() {
-            int mCurrentPosition = -1;
-
-            @Override
-            public int getCount() {
-                return mContactEntryList.size();
-            }
-
-            @Override
-            public boolean moveToFirst() {
-                mCurrentPosition = 0;
-                return true;
-            }
-
-            @Override
-            public boolean moveToNext() {
-                if (mCurrentPosition < mContactEntryList.size()) {
-                    mCurrentPosition++;
-                    return true;
-                } else {
-                    return false;
-                }
-            }
-
-            @Override
-            public boolean isBeforeFirst() {
-                return mCurrentPosition < 0;
-            }
-
-            @Override
-            public boolean isAfterLast() {
-                return mCurrentPosition >= mContactEntryList.size();
-            }
-
-            @Override
-            public int getColumnIndex(String columnName) {
-                mTestCase.assertEquals(Contacts._ID, columnName);
-                return 0;
-            }
-
-            @Override
-            public int getInt(int columnIndex) {
-                mTestCase.assertEquals(0, columnIndex);
-                mTestCase.assertTrue(mCurrentPosition >= 0
-                        && mCurrentPosition < mContactEntryList.size());
-                return mCurrentPosition;
-            }
-
-            @Override
-            public String getString(int columnIndex) {
-                return String.valueOf(getInt(columnIndex));
-            }
-
-            @Override
-            public void close() {
-            }
-        };
-    }
-}
diff --git a/core/tests/coretests/src/android/pim/vcard/ImportTestResolver.java b/core/tests/coretests/src/android/pim/vcard/ImportTestResolver.java
deleted file mode 100644
index c3f6f79..0000000
--- a/core/tests/coretests/src/android/pim/vcard/ImportTestResolver.java
+++ /dev/null
@@ -1,299 +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.pim.vcard;
-
-import android.content.ContentProviderOperation;
-import android.content.ContentProviderResult;
-import android.content.ContentValues;
-import android.net.Uri;
-import android.provider.ContactsContract.Data;
-import android.provider.ContactsContract.RawContacts;
-import android.provider.ContactsContract.CommonDataKinds.Email;
-import android.provider.ContactsContract.CommonDataKinds.Event;
-import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
-import android.provider.ContactsContract.CommonDataKinds.Im;
-import android.provider.ContactsContract.CommonDataKinds.Nickname;
-import android.provider.ContactsContract.CommonDataKinds.Note;
-import android.provider.ContactsContract.CommonDataKinds.Organization;
-import android.provider.ContactsContract.CommonDataKinds.Phone;
-import android.provider.ContactsContract.CommonDataKinds.Photo;
-import android.provider.ContactsContract.CommonDataKinds.Relation;
-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.text.TextUtils;
-
-import junit.framework.TestCase;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-import java.util.SortedMap;
-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,
-                Nickname.CONTENT_ITEM_TYPE, Phone.CONTENT_ITEM_TYPE,
-                Email.CONTENT_ITEM_TYPE, StructuredPostal.CONTENT_ITEM_TYPE,
-                Im.CONTENT_ITEM_TYPE, Organization.CONTENT_ITEM_TYPE,
-                Event.CONTENT_ITEM_TYPE, Photo.CONTENT_ITEM_TYPE,
-                Note.CONTENT_ITEM_TYPE, Website.CONTENT_ITEM_TYPE,
-                Relation.CONTENT_ITEM_TYPE, Event.CONTENT_ITEM_TYPE,
-                GroupMembership.CONTENT_ITEM_TYPE));
-
-    final Map<String, Collection<ContentValues>> mMimeTypeToExpectedContentValues;
-
-    private final TestCase mTestCase;
-
-    public ImportTestProvider(TestCase testCase) {
-        mTestCase = testCase;
-        mMimeTypeToExpectedContentValues =
-            new HashMap<String, Collection<ContentValues>>();
-        for (String acceptanbleMimeType : sKnownMimeTypeSet) {
-            // Do not use HashSet since the current implementation changes the content of
-            // ContentValues after the insertion, which make the result of hashCode()
-            // changes...
-            mMimeTypeToExpectedContentValues.put(
-                    acceptanbleMimeType, new ArrayList<ContentValues>());
-        }
-    }
-
-    public void addExpectedContentValues(ContentValues expectedContentValues) {
-        final String mimeType = expectedContentValues.getAsString(Data.MIMETYPE);
-        if (!sKnownMimeTypeSet.contains(mimeType)) {
-            mTestCase.fail(String.format(
-                    "Unknow MimeType %s in the test code. Test code should be broken.",
-                    mimeType));
-        }
-
-        final Collection<ContentValues> contentValuesCollection =
-                mMimeTypeToExpectedContentValues.get(mimeType);
-        contentValuesCollection.add(expectedContentValues);
-    }
-
-    @Override
-    public ContentProviderResult[] applyBatch(
-            ArrayList<ContentProviderOperation> operations) {
-        if (operations == null) {
-            mTestCase.fail("There is no operation.");
-        }
-
-        final int size = operations.size();
-        ContentProviderResult[] fakeResultArray = new ContentProviderResult[size];
-        for (int i = 0; i < size; i++) {
-            Uri uri = Uri.withAppendedPath(RawContacts.CONTENT_URI, String.valueOf(i));
-            fakeResultArray[i] = new ContentProviderResult(uri);
-        }
-
-        for (int i = 0; i < size; i++) {
-            ContentProviderOperation operation = operations.get(i);
-            ContentValues contentValues = operation.resolveValueBackReferences(
-                    fakeResultArray, i);
-        }
-        for (int i = 0; i < size; i++) {
-            ContentProviderOperation operation = operations.get(i);
-            ContentValues actualContentValues = operation.resolveValueBackReferences(
-                    fakeResultArray, i);
-            final Uri uri = operation.getUri();
-            if (uri.equals(RawContacts.CONTENT_URI)) {
-                mTestCase.assertNull(actualContentValues.get(RawContacts.ACCOUNT_NAME));
-                mTestCase.assertNull(actualContentValues.get(RawContacts.ACCOUNT_TYPE));
-            } else if (uri.equals(Data.CONTENT_URI)) {
-                final String mimeType = actualContentValues.getAsString(Data.MIMETYPE);
-                if (!sKnownMimeTypeSet.contains(mimeType)) {
-                    mTestCase.fail(String.format(
-                            "Unknown MimeType %s. Probably added after developing this test",
-                            mimeType));
-                }
-                // Remove data meaningless in this unit tests.
-                // Specifically, Data.DATA1 - DATA7 are set to null or empty String
-                // regardless of the input, but it may change depending on how
-                // resolver-related code handles it.
-                // Here, we ignore these implementation-dependent specs and
-                // just check whether vCard importer correctly inserts rellevent data.
-                Set<String> keyToBeRemoved = new HashSet<String>();
-                for (Entry<String, Object> entry : actualContentValues.valueSet()) {
-                    Object value = entry.getValue();
-                    if (value == null || TextUtils.isEmpty(value.toString())) {
-                        keyToBeRemoved.add(entry.getKey());
-                    }
-                }
-                for (String key: keyToBeRemoved) {
-                    actualContentValues.remove(key);
-                }
-                /* for testing
-                Log.d("@@@",
-                        String.format("MimeType: %s, data: %s",
-                                mimeType, actualContentValues.toString())); */
-                // Remove RAW_CONTACT_ID entry just for safety, since we do not care
-                // how resolver-related code handles the entry in this unit test,
-                if (actualContentValues.containsKey(Data.RAW_CONTACT_ID)) {
-                    actualContentValues.remove(Data.RAW_CONTACT_ID);
-                }
-                final Collection<ContentValues> contentValuesCollection =
-                    mMimeTypeToExpectedContentValues.get(mimeType);
-                if (contentValuesCollection.isEmpty()) {
-                    mTestCase.fail("ContentValues for MimeType " + mimeType
-                            + " is not expected at all (" + actualContentValues + ")");
-                }
-                boolean checked = false;
-                for (ContentValues expectedContentValues : contentValuesCollection) {
-                    /*for testing
-                    Log.d("@@@", "expected: "
-                            + convertToEasilyReadableString(expectedContentValues));
-                    Log.d("@@@", "actual  : "
-                            + convertToEasilyReadableString(actualContentValues));*/
-                    if (equalsForContentValues(expectedContentValues,
-                            actualContentValues)) {
-                        mTestCase.assertTrue(contentValuesCollection.remove(expectedContentValues));
-                        checked = true;
-                        break;
-                    }
-                }
-                if (!checked) {
-                    final StringBuilder builder = new StringBuilder();
-                    builder.append("Unexpected: ");
-                    builder.append(convertToEasilyReadableString(actualContentValues));
-                    builder.append("\nExpected: ");
-                    for (ContentValues expectedContentValues : contentValuesCollection) {
-                        builder.append(convertToEasilyReadableString(expectedContentValues));
-                    }
-                    mTestCase.fail(builder.toString());
-                }
-            } else {
-                mTestCase.fail("Unexpected Uri has come: " + uri);
-            }
-        }  // for (int i = 0; i < size; i++) {
-        return fakeResultArray;
-    }
-
-    public void verify() {
-        StringBuilder builder = new StringBuilder();
-        for (Collection<ContentValues> contentValuesCollection :
-                mMimeTypeToExpectedContentValues.values()) {
-            for (ContentValues expectedContentValues: contentValuesCollection) {
-                builder.append(convertToEasilyReadableString(expectedContentValues));
-                builder.append("\n");
-            }
-        }
-        if (builder.length() > 0) {
-            final String failMsg =
-                "There is(are) remaining expected ContentValues instance(s): \n"
-                    + builder.toString();
-            mTestCase.fail(failMsg);
-        }
-    }
-
-    /**
-     * Utility method to print ContentValues whose content is printed with sorted keys.
-     */
-    private String convertToEasilyReadableString(ContentValues contentValues) {
-        if (contentValues == null) {
-            return "null";
-        }
-        String mimeTypeValue = "";
-        SortedMap<String, String> sortedMap = new TreeMap<String, String>();
-        for (Entry<String, Object> entry : contentValues.valueSet()) {
-            final String key = entry.getKey();
-            final Object value = entry.getValue();
-            final String valueString = (value != null ? value.toString() : null);
-            if (Data.MIMETYPE.equals(key)) {
-                mimeTypeValue = valueString;
-            } else {
-                mTestCase.assertNotNull(key);
-                sortedMap.put(key, valueString);
-            }
-        }
-        StringBuilder builder = new StringBuilder();
-        builder.append(Data.MIMETYPE);
-        builder.append('=');
-        builder.append(mimeTypeValue);
-        for (Entry<String, String> entry : sortedMap.entrySet()) {
-            final String key = entry.getKey();
-            final String value = entry.getValue();
-            builder.append(' ');
-            builder.append(key);
-            builder.append("=\"");
-            builder.append(value);
-            builder.append('"');
-        }
-        return builder.toString();
-    }
-
-    private static boolean equalsForContentValues(
-            ContentValues expected, ContentValues actual) {
-        if (expected == actual) {
-            return true;
-        } else if (expected == null || actual == null || expected.size() != actual.size()) {
-            return false;
-        }
-
-        for (Entry<String, Object> entry : expected.valueSet()) {
-            final String key = entry.getKey();
-            final Object value = entry.getValue();
-            if (!actual.containsKey(key)) {
-                return false;
-            }
-            if (value instanceof byte[]) {
-                Object actualValue = actual.get(key);
-                if (!Arrays.equals((byte[])value, (byte[])actualValue)) {
-                    return false;
-                }
-            } else if (!value.equals(actual.get(key))) {
-                    return false;
-            }
-        }
-        return true;
-    }
-}
diff --git a/core/tests/coretests/src/android/pim/vcard/LineVerifier.java b/core/tests/coretests/src/android/pim/vcard/LineVerifier.java
deleted file mode 100644
index cef15fd..0000000
--- a/core/tests/coretests/src/android/pim/vcard/LineVerifier.java
+++ /dev/null
@@ -1,65 +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.pim.vcard;
-
-import android.content.Context;
-import android.pim.vcard.VCardComposer;
-
-import junit.framework.TestCase;
-
-import java.util.ArrayList;
-
-class LineVerifier implements VCardComposer.OneEntryHandler {
-    private final TestCase mTestCase;
-    private final ArrayList<LineVerifierElem> mLineVerifierElemList;
-    private int mVCardType;
-    private int index;
-
-    public LineVerifier(TestCase testCase, int vcardType) {
-        mTestCase = testCase;
-        mLineVerifierElemList = new ArrayList<LineVerifierElem>();
-        mVCardType = vcardType;
-    }
-
-    public LineVerifierElem addLineVerifierElem() {
-        LineVerifierElem lineVerifier = new LineVerifierElem(mTestCase, mVCardType);
-        mLineVerifierElemList.add(lineVerifier);
-        return lineVerifier;
-    }
-
-    public void verify(String vcard) {
-        if (index >= mLineVerifierElemList.size()) {
-            mTestCase.fail("Insufficient number of LineVerifier (" + index + ")");
-        }
-
-        LineVerifierElem lineVerifier = mLineVerifierElemList.get(index);
-        lineVerifier.verify(vcard);
-
-        index++;
-    }
-
-    public boolean onEntryCreated(String vcard) {
-        verify(vcard);
-        return true;
-    }
-
-    public boolean onInit(Context context) {
-        return true;
-    }
-
-    public void onTerminate() {
-    }
-}
diff --git a/core/tests/coretests/src/android/pim/vcard/LineVerifierElem.java b/core/tests/coretests/src/android/pim/vcard/LineVerifierElem.java
deleted file mode 100644
index b23b29b..0000000
--- a/core/tests/coretests/src/android/pim/vcard/LineVerifierElem.java
+++ /dev/null
@@ -1,105 +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.pim.vcard;
-
-import android.pim.vcard.VCardConfig;
-import android.text.TextUtils;
-
-import junit.framework.TestCase;
-
-import java.util.ArrayList;
-import java.util.List;
-
-class LineVerifierElem {
-    private final TestCase mTestCase;
-    private final List<String> mExpectedLineList = new ArrayList<String>();
-    private final boolean mIsV30;
-
-    public LineVerifierElem(TestCase testCase, int vcardType) {
-        mTestCase = testCase;
-        mIsV30 = VCardConfig.isV30(vcardType);
-    }
-
-    public LineVerifierElem addExpected(final String line) {
-        if (!TextUtils.isEmpty(line)) {
-            mExpectedLineList.add(line);
-        }
-        return this;
-    }
-
-    public void verify(final String vcard) {
-        final String[] lineArray = vcard.split("\\r?\\n");
-        final int length = lineArray.length;
-        boolean beginExists = false;
-        boolean endExists = false;
-        boolean versionExists = false;
-
-        for (int i = 0; i < length; i++) {
-            final String line = lineArray[i];
-            if (TextUtils.isEmpty(line)) {
-                continue;
-            }
-
-            if ("BEGIN:VCARD".equalsIgnoreCase(line)) {
-                if (beginExists) {
-                    mTestCase.fail("Multiple \"BEGIN:VCARD\" line found");
-                } else {
-                    beginExists = true;
-                    continue;
-                }
-            } else if ("END:VCARD".equalsIgnoreCase(line)) {
-                if (endExists) {
-                    mTestCase.fail("Multiple \"END:VCARD\" line found");
-                } else {
-                    endExists = true;
-                    continue;
-                }
-            } else if ((mIsV30 ? "VERSION:3.0" : "VERSION:2.1").equalsIgnoreCase(line)) {
-                if (versionExists) {
-                    mTestCase.fail("Multiple VERSION line + found");
-                } else {
-                    versionExists = true;
-                    continue;
-                }
-            }
-
-            if (!beginExists) {
-                mTestCase.fail("Property other than BEGIN came before BEGIN property: "
-                        + line);
-            } else if (endExists) {
-                mTestCase.fail("Property other than END came after END property: "
-                        + line);
-            }
-
-            final int index = mExpectedLineList.indexOf(line);
-            if (index >= 0) {
-                mExpectedLineList.remove(index);
-            } else {
-                mTestCase.fail("Unexpected line: " + line);
-            }
-        }
-
-        if (!mExpectedLineList.isEmpty()) {
-            StringBuffer buffer = new StringBuffer();
-            for (String expectedLine : mExpectedLineList) {
-                buffer.append(expectedLine);
-                buffer.append("\n");
-            }
-
-            mTestCase.fail("Expected line(s) not found:" + buffer.toString());
-        }
-    }
-}
diff --git a/core/tests/coretests/src/android/pim/vcard/PropertyNode.java b/core/tests/coretests/src/android/pim/vcard/PropertyNode.java
deleted file mode 100644
index 2c1f6d2..0000000
--- a/core/tests/coretests/src/android/pim/vcard/PropertyNode.java
+++ /dev/null
@@ -1,198 +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.pim.vcard;
-
-import android.content.ContentValues;
-import android.pim.vcard.VCardEntry;
-import android.util.Log;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-/**
- * Previously used in main vCard handling code but now exists only for testing.
- *
- * 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.
- */
-public class PropertyNode {
-    public String propName;
-    public String propValue;
-    public List<String> propValue_vector;
-
-    /** Store value as byte[],after decode.
-     * Used when propValue is encoded by something like BASE64, QUOTED-PRINTABLE, etc.
-     */
-    public byte[] propValue_bytes;
-
-    /** 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"]...
-     * TODO: fix this. 
-     */
-    public ContentValues paramMap;
-
-    /** Only for TYPE=??? param store. */
-    public Set<String> paramMap_TYPE;
-
-    /** Store group values. Used only in VCard. */
-    public Set<String> propGroupSet;
-    
-    public PropertyNode() {
-        propName = "";
-        propValue = "";
-        propValue_vector = new ArrayList<String>();
-        paramMap = new ContentValues();
-        paramMap_TYPE = new HashSet<String>();
-        propGroupSet = new HashSet<String>();
-    }
-    
-    public PropertyNode(
-            String propName, String propValue, List<String> propValue_vector,
-            byte[] propValue_bytes, ContentValues paramMap, Set<String> paramMap_TYPE,
-            Set<String> propGroupSet) {
-        if (propName != null) {
-            this.propName = propName;
-        } else {
-            this.propName = "";
-        }
-        if (propValue != null) {
-            this.propValue = propValue;
-        } else {
-            this.propValue = "";
-        }
-        if (propValue_vector != null) {
-            this.propValue_vector = propValue_vector;
-        } else {
-            this.propValue_vector = new ArrayList<String>();
-        }
-        this.propValue_bytes = propValue_bytes;
-        if (paramMap != null) {
-            this.paramMap = paramMap;
-        } else {
-            this.paramMap = new ContentValues();
-        }
-        if (paramMap_TYPE != null) {
-            this.paramMap_TYPE = paramMap_TYPE;
-        } else {
-            this.paramMap_TYPE = new HashSet<String>();
-        }
-        if (propGroupSet != null) {
-            this.propGroupSet = propGroupSet;
-        } else {
-            this.propGroupSet = new HashSet<String>();
-        }
-    }
-    
-    @Override
-    public int hashCode() {
-        // vCard may contain more than one same line in one entry, while HashSet or any other
-        // library which utilize hashCode() does not honor that, so intentionally throw an
-        // Exception.
-        throw new UnsupportedOperationException(
-                "PropertyNode does not provide hashCode() implementation intentionally.");
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (!(obj instanceof PropertyNode)) {
-            return false;
-        }
-        
-        PropertyNode node = (PropertyNode)obj;
-        
-        if (propName == null || !propName.equals(node.propName)) {
-            return false;
-        } else if (!paramMap_TYPE.equals(node.paramMap_TYPE)) {
-            return false;
-        } else if (!paramMap_TYPE.equals(node.paramMap_TYPE)) {
-            return false;
-        } else if (!propGroupSet.equals(node.propGroupSet)) {
-            return false;
-        }
-
-        if (propValue_bytes != null && Arrays.equals(propValue_bytes, node.propValue_bytes)) {
-            return true;
-        } else {
-            if (!propValue.equals(node.propValue)) {
-                return false;
-            }
-
-            // The value in propValue_vector is not decoded even if it should be
-            // decoded by BASE64 or QUOTED-PRINTABLE. When the size of propValue_vector
-            // is 1, the encoded value is stored in propValue, so we do not have to
-            // check it.
-            return (propValue_vector.equals(node.propValue_vector) ||
-                    propValue_vector.size() == 1 ||
-                    node.propValue_vector.size() == 1);
-        }
-    }
-    
-    @Override
-    public String toString() {
-        StringBuilder builder = new StringBuilder();
-        builder.append("propName: ");
-        builder.append(propName);
-        builder.append(", paramMap: ");
-        builder.append(paramMap.toString());
-        builder.append(", paramMap_TYPE: [");
-        boolean first = true;
-        for (String elem : paramMap_TYPE) {
-            if (first) {
-                first = false;
-            } else {
-                builder.append(", ");
-            }
-            builder.append('"');
-            builder.append(elem);
-            builder.append('"');
-        }
-        builder.append("]");
-        if (!propGroupSet.isEmpty()) {
-            builder.append(", propGroupSet: [");
-            first = true;
-            for (String elem : propGroupSet) {
-                if (first) {
-                    first = false;
-                } else {
-                    builder.append(", ");
-                }
-                builder.append('"');
-                builder.append(elem);
-                builder.append('"');
-            }
-            builder.append("]");
-        }
-        if (propValue_vector != null && propValue_vector.size() > 1) {
-            builder.append(", propValue_vector size: ");
-            builder.append(propValue_vector.size());
-        }
-        if (propValue_bytes != null) {
-            builder.append(", propValue_bytes size: ");
-            builder.append(propValue_bytes.length);
-        }
-        builder.append(", propValue: \"");
-        builder.append(propValue);
-        builder.append("\"");
-        return builder.toString();
-    }
-}
diff --git a/core/tests/coretests/src/android/pim/vcard/PropertyNodesVerifier.java b/core/tests/coretests/src/android/pim/vcard/PropertyNodesVerifier.java
deleted file mode 100644
index cfdd074..0000000
--- a/core/tests/coretests/src/android/pim/vcard/PropertyNodesVerifier.java
+++ /dev/null
@@ -1,386 +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.pim.vcard;
-
-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.
- *
- * This class first checks whether each propertyNode in the VNode is in the
- * "ordered expected property list".
- * 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 static class TypeSet extends HashSet<String> {
-        public TypeSet(String ... array) {
-            super(Arrays.asList(array));
-        }
-    }
-
-    public static class GroupSet extends HashSet<String> {
-        public GroupSet(String ... array) {
-            super(Arrays.asList(array));
-        }
-    }
-
-    private final HashMap<String, List<PropertyNode>> mOrderedNodeMap;
-    // Intentionally use ArrayList instead of Set, assuming there may be more than one
-    // exactly same objects.
-    private final ArrayList<PropertyNode> mUnorderedNodeList;
-    private final TestCase mTestCase;
-
-    public PropertyNodesVerifierElem(TestCase testCase) {
-        mOrderedNodeMap = new HashMap<String, List<PropertyNode>>();
-        mUnorderedNodeList = new ArrayList<PropertyNode>();
-        mTestCase = testCase;
-    }
-
-    // WithOrder
-
-    public PropertyNodesVerifierElem addExpectedNodeWithOrder(String propName, String propValue) {
-        return addExpectedNodeWithOrder(propName, propValue, null, null, null, null, null);
-    }
-
-    public PropertyNodesVerifierElem addExpectedNodeWithOrder(
-            String propName, String propValue, ContentValues contentValues) {
-        return addExpectedNodeWithOrder(propName, propValue, null,
-                null, contentValues, null, null);
-    }
-
-    public PropertyNodesVerifierElem addExpectedNodeWithOrder(
-            String propName, List<String> propValueList, ContentValues contentValues) {
-        return addExpectedNodeWithOrder(propName, null, propValueList,
-                null, contentValues, null, null);
-    }
-
-    public PropertyNodesVerifierElem addExpectedNodeWithOrder(
-            String propName, String propValue, List<String> propValueList) {
-        return addExpectedNodeWithOrder(propName, propValue, propValueList, null,
-                null, null, null);
-    }
-
-    public PropertyNodesVerifierElem addExpectedNodeWithOrder(
-            String propName, List<String> propValueList) {
-        final String propValue = concatinateListWithSemiColon(propValueList);
-        return addExpectedNodeWithOrder(propName, propValue.toString(), propValueList,
-                null, null, null, null);
-    }
-
-    public PropertyNodesVerifierElem addExpectedNodeWithOrder(String propName, String propValue,
-            TypeSet paramMap_TYPE) {
-        return addExpectedNodeWithOrder(propName, propValue, null,
-                null, null, paramMap_TYPE, null);
-    }
-
-    public PropertyNodesVerifierElem addExpectedNodeWithOrder(String propName,
-            List<String> propValueList, TypeSet paramMap_TYPE) {
-        return addExpectedNodeWithOrder(propName, null, propValueList, null, null,
-                paramMap_TYPE, null);
-    }
-
-    public PropertyNodesVerifierElem addExpectedNodeWithOrder(String propName, String propValue,
-            ContentValues paramMap, TypeSet paramMap_TYPE) {
-        return addExpectedNodeWithOrder(propName, propValue, null, null,
-                paramMap, paramMap_TYPE, null);
-    }
-
-    public PropertyNodesVerifierElem addExpectedNodeWithOrder(String propName, String propValue,
-            List<String> propValueList, TypeSet paramMap_TYPE) {
-        return addExpectedNodeWithOrder(propName, propValue, propValueList, null, null,
-                paramMap_TYPE, null);
-    }
-
-    public PropertyNodesVerifierElem addExpectedNodeWithOrder(String propName, String propValue,
-            List<String> propValueList, byte[] propValue_bytes,
-            ContentValues paramMap, TypeSet paramMap_TYPE, GroupSet propGroupSet) {
-        if (propValue == null && propValueList != null) {
-            propValue = concatinateListWithSemiColon(propValueList);
-        }
-        PropertyNode propertyNode = new PropertyNode(propName,
-                propValue, propValueList, propValue_bytes,
-                paramMap, paramMap_TYPE, propGroupSet);
-        List<PropertyNode> expectedNodeList = mOrderedNodeMap.get(propName);
-        if (expectedNodeList == null) {
-            expectedNodeList = new ArrayList<PropertyNode>();
-            mOrderedNodeMap.put(propName, expectedNodeList);
-        }
-        expectedNodeList.add(propertyNode);
-        return this;
-    }
-
-    // WithoutOrder
-
-    public PropertyNodesVerifierElem addExpectedNode(String propName, String propValue) {
-        return addExpectedNode(propName, propValue, null, null, null, null, null);
-    }
-
-    public PropertyNodesVerifierElem addExpectedNode(String propName, String propValue,
-            ContentValues contentValues) {
-        return addExpectedNode(propName, propValue, null, null, contentValues, null, null);
-    }
-
-    public PropertyNodesVerifierElem addExpectedNode(String propName,
-            List<String> propValueList, ContentValues contentValues) {
-        return addExpectedNode(propName, null,
-                propValueList, null, contentValues, null, null);
-    }
-
-    public PropertyNodesVerifierElem addExpectedNode(String propName, String propValue,
-            List<String> propValueList) {
-        return addExpectedNode(propName, propValue, propValueList, null, null, null, null);
-    }
-
-    public PropertyNodesVerifierElem addExpectedNode(String propName,
-            List<String> propValueList) {
-        return addExpectedNode(propName, null, propValueList,
-                null, null, null, null);
-    }
-
-    public PropertyNodesVerifierElem addExpectedNode(String propName, String propValue,
-            TypeSet paramMap_TYPE) {
-        return addExpectedNode(propName, propValue, null, null, null, paramMap_TYPE, null);
-    }
-
-    public PropertyNodesVerifierElem addExpectedNode(String propName,
-            List<String> propValueList, TypeSet paramMap_TYPE) {
-        final String propValue = concatinateListWithSemiColon(propValueList);
-        return addExpectedNode(propName, propValue, propValueList, null, null,
-                paramMap_TYPE, null);
-    }
-
-    public PropertyNodesVerifierElem addExpectedNode(String propName, String propValue,
-            List<String> propValueList, TypeSet paramMap_TYPE) {
-        return addExpectedNode(propName, propValue, propValueList, null, null,
-                paramMap_TYPE, null);
-    }
-
-    public PropertyNodesVerifierElem addExpectedNode(String propName, String propValue,
-            ContentValues paramMap, TypeSet paramMap_TYPE) {
-        return addExpectedNode(propName, propValue, null, null,
-                paramMap, paramMap_TYPE, null);
-    }
-
-    public PropertyNodesVerifierElem addExpectedNode(String propName, String propValue,
-            List<String> propValueList, byte[] propValue_bytes,
-            ContentValues paramMap, TypeSet paramMap_TYPE, GroupSet propGroupSet) {
-        if (propValue == null && propValueList != null) {
-            propValue = concatinateListWithSemiColon(propValueList);
-        }
-        mUnorderedNodeList.add(new PropertyNode(propName, propValue,
-                propValueList, propValue_bytes, paramMap, paramMap_TYPE, propGroupSet));
-        return this;
-    }
-
-    public void verify(VNode vnode) {
-        for (PropertyNode actualNode : vnode.propList) {
-            verifyNode(actualNode.propName, actualNode);
-        }
-        if (!mOrderedNodeMap.isEmpty() || !mUnorderedNodeList.isEmpty()) {
-            List<String> expectedProps = new ArrayList<String>();
-            for (List<PropertyNode> nodes : mOrderedNodeMap.values()) {
-                for (PropertyNode node : nodes) {
-                    if (!expectedProps.contains(node.propName)) {
-                        expectedProps.add(node.propName);
-                    }
-                }
-            }
-            for (PropertyNode node : mUnorderedNodeList) {
-                if (!expectedProps.contains(node.propName)) {
-                    expectedProps.add(node.propName);
-                }
-            }
-            mTestCase.fail("Expected property " + Arrays.toString(expectedProps.toArray())
-                    + " was not found.");
-        }
-    }
-
-    private void verifyNode(final String propName, final PropertyNode actualNode) {
-        List<PropertyNode> expectedNodeList = mOrderedNodeMap.get(propName);
-        final int size = (expectedNodeList != null ? expectedNodeList.size() : 0);
-        if (size > 0) {
-            for (int i = 0; i < size; i++) {
-                PropertyNode expectedNode = expectedNodeList.get(i);
-                List<PropertyNode> expectedButDifferentValueList = new ArrayList<PropertyNode>();
-                if (expectedNode.propName.equals(propName)) {
-                    if (expectedNode.equals(actualNode)) {
-                        expectedNodeList.remove(i);
-                        if (expectedNodeList.size() == 0) {
-                            mOrderedNodeMap.remove(propName);
-                        }
-                        return;
-                    } else {
-                        expectedButDifferentValueList.add(expectedNode);
-                    }
-                }
-
-                // "actualNode" is not in ordered expected list.
-                // Try looking over unordered expected list.
-                if (tryFoundExpectedNodeFromUnorderedList(actualNode,
-                        expectedButDifferentValueList)) {
-                    return;
-                }
-
-                if (!expectedButDifferentValueList.isEmpty()) {
-                    // Same propName exists but with different value(s).
-                    failWithExpectedNodeList(propName, actualNode,
-                            expectedButDifferentValueList);
-                } else {
-                    // There's no expected node with same propName.
-                    mTestCase.fail("Unexpected property \"" + propName + "\" exists.");
-                }
-            }
-        } else {
-            List<PropertyNode> expectedButDifferentValueList =
-                new ArrayList<PropertyNode>();
-            if (tryFoundExpectedNodeFromUnorderedList(actualNode, expectedButDifferentValueList)) {
-                return;
-            } else {
-                if (!expectedButDifferentValueList.isEmpty()) {
-                    // Same propName exists but with different value(s).
-                    failWithExpectedNodeList(propName, actualNode,
-                            expectedButDifferentValueList);
-                } else {
-                    // There's no expected node with same propName.
-                    mTestCase.fail("Unexpected property \"" + propName + "\" exists.");
-                }
-            }
-        }
-    }
-
-    private String concatinateListWithSemiColon(List<String> array) {
-        StringBuffer buffer = new StringBuffer();
-        boolean first = true;
-        for (String propValueElem : array) {
-            if (first) {
-                first = false;
-            } else {
-                buffer.append(';');
-            }
-            buffer.append(propValueElem);
-        }
-
-        return buffer.toString();
-    }
-
-    private boolean tryFoundExpectedNodeFromUnorderedList(PropertyNode actualNode,
-            List<PropertyNode> expectedButDifferentValueList) {
-        final String propName = actualNode.propName;
-        int unorderedListSize = mUnorderedNodeList.size();
-        for (int i = 0; i < unorderedListSize; i++) {
-            PropertyNode unorderedExpectedNode = mUnorderedNodeList.get(i);
-            if (unorderedExpectedNode.propName.equals(propName)) {
-                if (unorderedExpectedNode.equals(actualNode)) {
-                    mUnorderedNodeList.remove(i);
-                    return true;
-                }
-                expectedButDifferentValueList.add(unorderedExpectedNode);
-            }
-        }
-        return false;
-    }
-
-    private void failWithExpectedNodeList(String propName, PropertyNode actualNode,
-            List<PropertyNode> expectedNodeList) {
-        StringBuilder builder = new StringBuilder();
-        for (PropertyNode expectedNode : expectedNodeList) {
-            builder.append("expected: ");
-            builder.append(expectedNode.toString());
-            builder.append("\n");
-        }
-        mTestCase.fail("Property \"" + propName + "\" has wrong value.\n"
-                + builder.toString()
-                + "  actual: " + actualNode.toString());
-    }
-}
diff --git a/core/tests/coretests/src/android/pim/vcard/VCardExporterTests.java b/core/tests/coretests/src/android/pim/vcard/VCardExporterTests.java
deleted file mode 100644
index 2de0464..0000000
--- a/core/tests/coretests/src/android/pim/vcard/VCardExporterTests.java
+++ /dev/null
@@ -1,969 +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.pim.vcard;
-
-import android.content.ContentValues;
-import android.pim.vcard.VCardConfig;
-import android.provider.ContactsContract.CommonDataKinds.Email;
-import android.provider.ContactsContract.CommonDataKinds.Event;
-import android.provider.ContactsContract.CommonDataKinds.Im;
-import android.provider.ContactsContract.CommonDataKinds.Nickname;
-import android.provider.ContactsContract.CommonDataKinds.Note;
-import android.provider.ContactsContract.CommonDataKinds.Organization;
-import android.provider.ContactsContract.CommonDataKinds.Phone;
-import android.provider.ContactsContract.CommonDataKinds.Photo;
-import android.provider.ContactsContract.CommonDataKinds.Relation;
-import android.provider.ContactsContract.CommonDataKinds.StructuredName;
-import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
-import android.provider.ContactsContract.CommonDataKinds.Website;
-
-import android.pim.vcard.PropertyNodesVerifierElem.TypeSet;
-
-import java.util.Arrays;
-
-/**
- * Tests for the code related to vCard exporter, inculding vCard composer.
- * This test class depends on vCard importer code, so if tests for vCard importer fail,
- * the result of this class will not be reliable.
- */
-public class VCardExporterTests extends VCardTestsBase {
-    private static final byte[] sPhotoByteArray =
-        VCardImporterTests.sPhotoByteArrayForComplicatedCase;
-
-    public void testSimpleV21() {
-        mVerifier.initForExportTest(V21);
-        mVerifier.addInputEntry().addContentValues(StructuredName.CONTENT_ITEM_TYPE)
-                .put(StructuredName.FAMILY_NAME, "Ando")
-                .put(StructuredName.GIVEN_NAME, "Roid");
-        mVerifier.addPropertyNodesVerifierElem()
-                .addExpectedNode("FN", "Roid Ando")
-                .addExpectedNode("N", "Ando;Roid;;;",
-                        Arrays.asList("Ando", "Roid", "", "", ""));
-    }
-
-    private void testStructuredNameBasic(int vcardType) {
-        final boolean isV30 = VCardConfig.isV30(vcardType);
-        mVerifier.initForExportTest(vcardType);
-        mVerifier.addInputEntry().addContentValues(StructuredName.CONTENT_ITEM_TYPE)
-                .put(StructuredName.FAMILY_NAME, "AppropriateFamilyName")
-                .put(StructuredName.GIVEN_NAME, "AppropriateGivenName")
-                .put(StructuredName.MIDDLE_NAME, "AppropriateMiddleName")
-                .put(StructuredName.PREFIX, "AppropriatePrefix")
-                .put(StructuredName.SUFFIX, "AppropriateSuffix")
-                .put(StructuredName.PHONETIC_FAMILY_NAME, "AppropriatePhoneticFamily")
-                .put(StructuredName.PHONETIC_GIVEN_NAME, "AppropriatePhoneticGiven")
-                .put(StructuredName.PHONETIC_MIDDLE_NAME, "AppropriatePhoneticMiddle");
-
-        PropertyNodesVerifierElem elem = mVerifier.addPropertyNodesVerifierElem()
-                .addExpectedNodeWithOrder("N",
-                        "AppropriateFamilyName;AppropriateGivenName;AppropriateMiddleName;"
-                        + "AppropriatePrefix;AppropriateSuffix",
-                        Arrays.asList("AppropriateFamilyName", "AppropriateGivenName",
-                                "AppropriateMiddleName", "AppropriatePrefix", "AppropriateSuffix"))
-                .addExpectedNodeWithOrder("FN",
-                        "AppropriatePrefix AppropriateGivenName "
-                        + "AppropriateMiddleName AppropriateFamilyName AppropriateSuffix")
-                .addExpectedNode("X-PHONETIC-FIRST-NAME", "AppropriatePhoneticGiven")
-                .addExpectedNode("X-PHONETIC-MIDDLE-NAME", "AppropriatePhoneticMiddle")
-                .addExpectedNode("X-PHONETIC-LAST-NAME", "AppropriatePhoneticFamily");
-
-        if (isV30) {
-            elem.addExpectedNode("SORT-STRING",
-                    "AppropriatePhoneticGiven AppropriatePhoneticMiddle "
-                    + "AppropriatePhoneticFamily");
-        }
-    }
-
-    public void testStructuredNameBasicV21() {
-        testStructuredNameBasic(V21);
-    }
-
-    public void testStructuredNameBasicV30() {
-        testStructuredNameBasic(V30);
-    }
-
-    /**
-     * Test that only "primary" StructuredName is emitted, so that our vCard file
-     * will not confuse the external importer, assuming there may be some importer
-     * which presume that there's only one property toward each of  "N", "FN", etc.
-     * Note that more than one "N", "FN", etc. properties are acceptable in vCard spec.
-     */
-    private void testStructuredNameUsePrimaryCommon(int vcardType) {
-        final boolean isV30 = (vcardType == V30);
-        mVerifier.initForExportTest(vcardType);
-        ContactEntry entry = mVerifier.addInputEntry();
-        entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
-                .put(StructuredName.FAMILY_NAME, "DoNotEmitFamilyName1")
-                .put(StructuredName.GIVEN_NAME, "DoNotEmitGivenName1")
-                .put(StructuredName.MIDDLE_NAME, "DoNotEmitMiddleName1")
-                .put(StructuredName.PREFIX, "DoNotEmitPrefix1")
-                .put(StructuredName.SUFFIX, "DoNotEmitSuffix1")
-                .put(StructuredName.PHONETIC_FAMILY_NAME, "DoNotEmitPhoneticFamily1")
-                .put(StructuredName.PHONETIC_GIVEN_NAME, "DoNotEmitPhoneticGiven1")
-                .put(StructuredName.PHONETIC_MIDDLE_NAME, "DoNotEmitPhoneticMiddle1");
-
-        // With "IS_PRIMARY=1". This is what we should use.
-        entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
-                .put(StructuredName.FAMILY_NAME, "AppropriateFamilyName")
-                .put(StructuredName.GIVEN_NAME, "AppropriateGivenName")
-                .put(StructuredName.MIDDLE_NAME, "AppropriateMiddleName")
-                .put(StructuredName.PREFIX, "AppropriatePrefix")
-                .put(StructuredName.SUFFIX, "AppropriateSuffix")
-                .put(StructuredName.PHONETIC_FAMILY_NAME, "AppropriatePhoneticFamily")
-                .put(StructuredName.PHONETIC_GIVEN_NAME, "AppropriatePhoneticGiven")
-                .put(StructuredName.PHONETIC_MIDDLE_NAME, "AppropriatePhoneticMiddle")
-                .put(StructuredName.IS_PRIMARY, 1);
-
-        // With "IS_PRIMARY=1", but we should ignore this time, since this is second, not first.
-        entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
-                .put(StructuredName.FAMILY_NAME, "DoNotEmitFamilyName2")
-                .put(StructuredName.GIVEN_NAME, "DoNotEmitGivenName2")
-                .put(StructuredName.MIDDLE_NAME, "DoNotEmitMiddleName2")
-                .put(StructuredName.PREFIX, "DoNotEmitPrefix2")
-                .put(StructuredName.SUFFIX, "DoNotEmitSuffix2")
-                .put(StructuredName.PHONETIC_FAMILY_NAME, "DoNotEmitPhoneticFamily2")
-                .put(StructuredName.PHONETIC_GIVEN_NAME, "DoNotEmitPhoneticGiven2")
-                .put(StructuredName.PHONETIC_MIDDLE_NAME, "DoNotEmitPhoneticMiddle2")
-                .put(StructuredName.IS_PRIMARY, 1);
-
-        PropertyNodesVerifierElem elem = mVerifier.addPropertyNodesVerifierElem()
-                .addExpectedNodeWithOrder("N",
-                        "AppropriateFamilyName;AppropriateGivenName;AppropriateMiddleName;"
-                        + "AppropriatePrefix;AppropriateSuffix",
-                        Arrays.asList("AppropriateFamilyName", "AppropriateGivenName",
-                                "AppropriateMiddleName", "AppropriatePrefix", "AppropriateSuffix"))
-                .addExpectedNodeWithOrder("FN",
-                        "AppropriatePrefix AppropriateGivenName "
-                        + "AppropriateMiddleName AppropriateFamilyName AppropriateSuffix")
-                .addExpectedNode("X-PHONETIC-FIRST-NAME", "AppropriatePhoneticGiven")
-                .addExpectedNode("X-PHONETIC-MIDDLE-NAME", "AppropriatePhoneticMiddle")
-                .addExpectedNode("X-PHONETIC-LAST-NAME", "AppropriatePhoneticFamily");
-
-        if (isV30) {
-            elem.addExpectedNode("SORT-STRING",
-                    "AppropriatePhoneticGiven AppropriatePhoneticMiddle "
-                    + "AppropriatePhoneticFamily");
-        }
-    }
-
-    public void testStructuredNameUsePrimaryV21() {
-        testStructuredNameUsePrimaryCommon(V21);
-    }
-
-    public void testStructuredNameUsePrimaryV30() {
-        testStructuredNameUsePrimaryCommon(V30);
-    }
-
-    /**
-     * Tests that only "super primary" StructuredName is emitted.
-     * See also the comment in {@link #testStructuredNameUsePrimaryCommon(int)}.
-     */
-    private void testStructuredNameUseSuperPrimaryCommon(int vcardType) {
-        final boolean isV30 = (vcardType == V30);
-        mVerifier.initForExportTest(vcardType);
-        ContactEntry entry = mVerifier.addInputEntry();
-        entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
-                .put(StructuredName.FAMILY_NAME, "DoNotEmitFamilyName1")
-                .put(StructuredName.GIVEN_NAME, "DoNotEmitGivenName1")
-                .put(StructuredName.MIDDLE_NAME, "DoNotEmitMiddleName1")
-                .put(StructuredName.PREFIX, "DoNotEmitPrefix1")
-                .put(StructuredName.SUFFIX, "DoNotEmitSuffix1")
-                .put(StructuredName.PHONETIC_FAMILY_NAME, "DoNotEmitPhoneticFamily1")
-                .put(StructuredName.PHONETIC_GIVEN_NAME, "DoNotEmitPhoneticGiven1")
-                .put(StructuredName.PHONETIC_MIDDLE_NAME, "DoNotEmitPhoneticMiddle1");
-
-        // With "IS_PRIMARY=1", but we should ignore this time.
-        entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
-                .put(StructuredName.FAMILY_NAME, "DoNotEmitFamilyName2")
-                .put(StructuredName.GIVEN_NAME, "DoNotEmitGivenName2")
-                .put(StructuredName.MIDDLE_NAME, "DoNotEmitMiddleName2")
-                .put(StructuredName.PREFIX, "DoNotEmitPrefix2")
-                .put(StructuredName.SUFFIX, "DoNotEmitSuffix2")
-                .put(StructuredName.PHONETIC_FAMILY_NAME, "DoNotEmitPhoneticFamily2")
-                .put(StructuredName.PHONETIC_GIVEN_NAME, "DoNotEmitPhoneticGiven2")
-                .put(StructuredName.PHONETIC_MIDDLE_NAME, "DoNotEmitPhoneticMiddle2")
-                .put(StructuredName.IS_PRIMARY, 1);
-
-        // With "IS_SUPER_PRIMARY=1". This is what we should use.
-        entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
-                .put(StructuredName.FAMILY_NAME, "AppropriateFamilyName")
-                .put(StructuredName.GIVEN_NAME, "AppropriateGivenName")
-                .put(StructuredName.MIDDLE_NAME, "AppropriateMiddleName")
-                .put(StructuredName.PREFIX, "AppropriatePrefix")
-                .put(StructuredName.SUFFIX, "AppropriateSuffix")
-                .put(StructuredName.PHONETIC_FAMILY_NAME, "AppropriatePhoneticFamily")
-                .put(StructuredName.PHONETIC_GIVEN_NAME, "AppropriatePhoneticGiven")
-                .put(StructuredName.PHONETIC_MIDDLE_NAME, "AppropriatePhoneticMiddle")
-                .put(StructuredName.IS_SUPER_PRIMARY, 1);
-
-        entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
-                .put(StructuredName.FAMILY_NAME, "DoNotEmitFamilyName3")
-                .put(StructuredName.GIVEN_NAME, "DoNotEmitGivenName3")
-                .put(StructuredName.MIDDLE_NAME, "DoNotEmitMiddleName3")
-                .put(StructuredName.PREFIX, "DoNotEmitPrefix3")
-                .put(StructuredName.SUFFIX, "DoNotEmitSuffix3")
-                .put(StructuredName.PHONETIC_FAMILY_NAME, "DoNotEmitPhoneticFamily3")
-                .put(StructuredName.PHONETIC_GIVEN_NAME, "DoNotEmitPhoneticGiven3")
-                .put(StructuredName.PHONETIC_MIDDLE_NAME, "DoNotEmitPhoneticMiddle3")
-                .put(StructuredName.IS_PRIMARY, 1);
-
-        PropertyNodesVerifierElem elem = mVerifier.addPropertyNodesVerifierElem()
-                .addExpectedNodeWithOrder("N",
-                        "AppropriateFamilyName;AppropriateGivenName;AppropriateMiddleName;"
-                        + "AppropriatePrefix;AppropriateSuffix",
-                        Arrays.asList("AppropriateFamilyName", "AppropriateGivenName",
-                                "AppropriateMiddleName", "AppropriatePrefix", "AppropriateSuffix"))
-                .addExpectedNodeWithOrder("FN",
-                        "AppropriatePrefix AppropriateGivenName "
-                        + "AppropriateMiddleName AppropriateFamilyName AppropriateSuffix")
-                .addExpectedNode("X-PHONETIC-FIRST-NAME", "AppropriatePhoneticGiven")
-                .addExpectedNode("X-PHONETIC-MIDDLE-NAME", "AppropriatePhoneticMiddle")
-                .addExpectedNode("X-PHONETIC-LAST-NAME", "AppropriatePhoneticFamily");
-
-        if (isV30) {
-            elem.addExpectedNode("SORT-STRING",
-                    "AppropriatePhoneticGiven AppropriatePhoneticMiddle"
-                    + " AppropriatePhoneticFamily");
-        }
-    }
-
-    public void testStructuredNameUseSuperPrimaryV21() {
-        testStructuredNameUseSuperPrimaryCommon(V21);
-    }
-
-    public void testStructuredNameUseSuperPrimaryV30() {
-        testStructuredNameUseSuperPrimaryCommon(V30);
-    }
-
-    public void testNickNameV30() {
-        mVerifier.initForExportTest(V30);
-        mVerifier.addInputEntry().addContentValues(Nickname.CONTENT_ITEM_TYPE)
-                .put(Nickname.NAME, "Nicky");
-
-        mVerifier.addPropertyNodesVerifierElemWithEmptyName()
-            .addExpectedNodeWithOrder("NICKNAME", "Nicky");
-    }
-
-    private void testPhoneBasicCommon(int vcardType) {
-        mVerifier.initForExportTest(vcardType);
-        mVerifier.addInputEntry().addContentValues(Phone.CONTENT_ITEM_TYPE)
-                .put(Phone.NUMBER, "1")
-                .put(Phone.TYPE, Phone.TYPE_HOME);
-        mVerifier.addPropertyNodesVerifierElemWithEmptyName()
-                .addExpectedNode("TEL", "1", new TypeSet("HOME"));
-    }
-
-    public void testPhoneBasicV21() {
-        testPhoneBasicCommon(V21);
-    }
-
-    public void testPhoneBasicV30() {
-        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.
-     */
-    private void testPhoneVariousTypeSupport(int vcardType) {
-        mVerifier.initForExportTest(vcardType);
-        ContactEntry entry = mVerifier.addInputEntry();
-        entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
-                .put(Phone.NUMBER, "10")
-                .put(Phone.TYPE, Phone.TYPE_HOME);
-        entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
-                .put(Phone.NUMBER, "20")
-                .put(Phone.TYPE, Phone.TYPE_WORK);
-        entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
-                .put(Phone.NUMBER, "30")
-                .put(Phone.TYPE, Phone.TYPE_FAX_HOME);
-        entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
-                .put(Phone.NUMBER, "40")
-                .put(Phone.TYPE, Phone.TYPE_FAX_WORK);
-        entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
-                .put(Phone.NUMBER, "50")
-                .put(Phone.TYPE, Phone.TYPE_MOBILE);
-        entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
-                .put(Phone.NUMBER, "60")
-                .put(Phone.TYPE, Phone.TYPE_PAGER);
-        entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
-                .put(Phone.NUMBER, "70")
-                .put(Phone.TYPE, Phone.TYPE_OTHER);
-        entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
-                .put(Phone.NUMBER, "80")
-                .put(Phone.TYPE, Phone.TYPE_CAR);
-        entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
-                .put(Phone.NUMBER, "90")
-                .put(Phone.TYPE, Phone.TYPE_COMPANY_MAIN);
-        entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
-                .put(Phone.NUMBER, "100")
-                .put(Phone.TYPE, Phone.TYPE_ISDN);
-        entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
-                .put(Phone.NUMBER, "110")
-                .put(Phone.TYPE, Phone.TYPE_MAIN);
-        entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
-                .put(Phone.NUMBER, "120")
-                .put(Phone.TYPE, Phone.TYPE_OTHER_FAX);
-        entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
-                .put(Phone.NUMBER, "130")
-                .put(Phone.TYPE, Phone.TYPE_TELEX);
-        entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
-                .put(Phone.NUMBER, "140")
-                .put(Phone.TYPE, Phone.TYPE_WORK_MOBILE);
-        entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
-                .put(Phone.NUMBER, "150")
-                .put(Phone.TYPE, Phone.TYPE_WORK_PAGER);
-        entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
-                .put(Phone.NUMBER, "160")
-                .put(Phone.TYPE, Phone.TYPE_MMS);
-
-        mVerifier.addPropertyNodesVerifierElemWithEmptyName()
-                .addExpectedNode("TEL", "10", new TypeSet("HOME"))
-                .addExpectedNode("TEL", "20", new TypeSet("WORK"))
-                .addExpectedNode("TEL", "30", new TypeSet("HOME", "FAX"))
-                .addExpectedNode("TEL", "40", new TypeSet("WORK", "FAX"))
-                .addExpectedNode("TEL", "50", new TypeSet("CELL"))
-                .addExpectedNode("TEL", "60", new TypeSet("PAGER"))
-                .addExpectedNode("TEL", "70", new TypeSet("VOICE"))
-                .addExpectedNode("TEL", "80", new TypeSet("CAR"))
-                .addExpectedNode("TEL", "90", new TypeSet("WORK", "PREF"))
-                .addExpectedNode("TEL", "100", new TypeSet("ISDN"))
-                .addExpectedNode("TEL", "110", new TypeSet("PREF"))
-                .addExpectedNode("TEL", "120", new TypeSet("FAX"))
-                .addExpectedNode("TEL", "130", new TypeSet("TLX"))
-                .addExpectedNode("TEL", "140", new TypeSet("WORK", "CELL"))
-                .addExpectedNode("TEL", "150", new TypeSet("WORK", "PAGER"))
-                .addExpectedNode("TEL", "160", new TypeSet("MSG"));
-    }
-
-    public void testPhoneVariousTypeSupportV21() {
-        testPhoneVariousTypeSupport(V21);
-    }
-
-    public void testPhoneVariousTypeSupportV30() {
-        testPhoneVariousTypeSupport(V30);
-    }
-
-    /**
-     * Tests that "PREF"s are emitted appropriately.
-     */
-    private void testPhonePrefHandlingCommon(int vcardType) {
-        mVerifier.initForExportTest(vcardType);
-        ContactEntry entry = mVerifier.addInputEntry();
-        entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
-                .put(Phone.NUMBER, "1")
-                .put(Phone.TYPE, Phone.TYPE_HOME);
-        entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
-                .put(Phone.NUMBER, "2")
-                .put(Phone.TYPE, Phone.TYPE_WORK)
-                .put(Phone.IS_PRIMARY, 1);
-        entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
-                .put(Phone.NUMBER, "3")
-                .put(Phone.TYPE, Phone.TYPE_FAX_HOME)
-                .put(Phone.IS_PRIMARY, 1);
-        entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
-                .put(Phone.NUMBER, "4")
-                .put(Phone.TYPE, Phone.TYPE_FAX_WORK);
-
-        mVerifier.addPropertyNodesVerifierElemWithEmptyName()
-                .addExpectedNode("TEL", "4", new TypeSet("WORK", "FAX"))
-                .addExpectedNode("TEL", "3", new TypeSet("HOME", "FAX", "PREF"))
-                .addExpectedNode("TEL", "2", new TypeSet("WORK", "PREF"))
-                .addExpectedNode("TEL", "1", new TypeSet("HOME"));
-    }
-
-    public void testPhonePrefHandlingV21() {
-        testPhonePrefHandlingCommon(V21);
-    }
-
-    public void testPhonePrefHandlingV30() {
-        testPhonePrefHandlingCommon(V30);
-    }
-
-    private void testMiscPhoneTypeHandling(int vcardType) {
-        mVerifier.initForExportTest(vcardType);
-        ContactEntry entry = mVerifier.addInputEntry();
-        entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
-                .put(Phone.NUMBER, "1")
-                .put(Phone.TYPE, Phone.TYPE_CUSTOM)
-                .put(Phone.LABEL, "Modem");
-        entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
-                .put(Phone.NUMBER, "2")
-                .put(Phone.TYPE, Phone.TYPE_CUSTOM)
-                .put(Phone.LABEL, "MSG");
-        entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
-                .put(Phone.NUMBER, "3")
-                .put(Phone.TYPE, Phone.TYPE_CUSTOM)
-                .put(Phone.LABEL, "BBS");
-        entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
-                .put(Phone.NUMBER, "4")
-                .put(Phone.TYPE, Phone.TYPE_CUSTOM)
-                .put(Phone.LABEL, "VIDEO");
-        entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
-                .put(Phone.NUMBER, "5")
-                .put(Phone.TYPE, Phone.TYPE_CUSTOM);
-        entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
-                .put(Phone.NUMBER, "6")
-                .put(Phone.TYPE, Phone.TYPE_CUSTOM)
-                .put(Phone.LABEL, "_AUTO_CELL");  // The old indicator for the type mobile.
-        entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
-                .put(Phone.NUMBER, "7")
-                .put(Phone.TYPE, Phone.TYPE_CUSTOM)
-                .put(Phone.LABEL, "\u643A\u5E2F");  // Mobile phone in Japanese Kanji
-        entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
-                .put(Phone.NUMBER, "8")
-                .put(Phone.TYPE, Phone.TYPE_CUSTOM)
-                .put(Phone.LABEL, "invalid");
-        PropertyNodesVerifierElem elem = mVerifier.addPropertyNodesVerifierElemWithEmptyName();
-        elem.addExpectedNode("TEL", "1", new TypeSet("MODEM"))
-                .addExpectedNode("TEL", "2", new TypeSet("MSG"))
-                .addExpectedNode("TEL", "3", new TypeSet("BBS"))
-                .addExpectedNode("TEL", "4", new TypeSet("VIDEO"))
-                .addExpectedNode("TEL", "5", new TypeSet("VOICE"))
-                .addExpectedNode("TEL", "6", new TypeSet("CELL"))
-                .addExpectedNode("TEL", "7", new TypeSet("CELL"))
-                .addExpectedNode("TEL", "8", new TypeSet("X-invalid"));
-    }
-
-    public void testPhoneTypeHandlingV21() {
-        testMiscPhoneTypeHandling(V21);
-    }
-
-    public void testPhoneTypeHandlingV30() {
-        testMiscPhoneTypeHandling(V30);
-    }
-
-    private void testEmailBasicCommon(int vcardType) {
-        mVerifier.initForExportTest(vcardType);
-        mVerifier.addInputEntry().addContentValues(Email.CONTENT_ITEM_TYPE)
-                .put(Email.DATA, "sample@example.com");
-        mVerifier.addPropertyNodesVerifierElemWithEmptyName()
-            .addExpectedNode("EMAIL", "sample@example.com");
-    }
-
-    public void testEmailBasicV21() {
-        testEmailBasicCommon(V21);
-    }
-
-    public void testEmailBasicV30() {
-        testEmailBasicCommon(V30);
-    }
-
-    private void testEmailVariousTypeSupportCommon(int vcardType) {
-        mVerifier.initForExportTest(vcardType);
-        ContactEntry entry = mVerifier.addInputEntry();
-        entry.addContentValues(Email.CONTENT_ITEM_TYPE)
-                .put(Email.DATA, "type_home@example.com")
-                .put(Email.TYPE, Email.TYPE_HOME);
-        entry.addContentValues(Email.CONTENT_ITEM_TYPE)
-                .put(Email.DATA, "type_work@example.com")
-                .put(Email.TYPE, Email.TYPE_WORK);
-        entry.addContentValues(Email.CONTENT_ITEM_TYPE)
-                .put(Email.DATA, "type_mobile@example.com")
-                .put(Email.TYPE, Email.TYPE_MOBILE);
-        entry.addContentValues(Email.CONTENT_ITEM_TYPE)
-                .put(Email.DATA, "type_other@example.com")
-                .put(Email.TYPE, Email.TYPE_OTHER);
-        mVerifier.addPropertyNodesVerifierElemWithEmptyName()
-                .addExpectedNode("EMAIL", "type_home@example.com", new TypeSet("HOME"))
-                .addExpectedNode("EMAIL", "type_work@example.com", new TypeSet("WORK"))
-                .addExpectedNode("EMAIL", "type_mobile@example.com", new TypeSet("CELL"))
-                .addExpectedNode("EMAIL", "type_other@example.com");
-    }
-
-    public void testEmailVariousTypeSupportV21() {
-        testEmailVariousTypeSupportCommon(V21);
-    }
-
-    public void testEmailVariousTypeSupportV30() {
-        testEmailVariousTypeSupportCommon(V30);
-    }
-
-    private void testEmailPrefHandlingCommon(int vcardType) {
-        mVerifier.initForExportTest(vcardType);
-        ContactEntry entry = mVerifier.addInputEntry();
-        entry.addContentValues(Email.CONTENT_ITEM_TYPE)
-                .put(Email.DATA, "type_home@example.com")
-                .put(Email.TYPE, Email.TYPE_HOME)
-                .put(Email.IS_PRIMARY, 1);
-        entry.addContentValues(Email.CONTENT_ITEM_TYPE)
-                .put(Email.DATA, "type_notype@example.com")
-                .put(Email.IS_PRIMARY, 1);
-
-        mVerifier.addPropertyNodesVerifierElemWithEmptyName()
-                .addExpectedNode("EMAIL", "type_notype@example.com", new TypeSet("PREF"))
-                .addExpectedNode("EMAIL", "type_home@example.com", new TypeSet("HOME", "PREF"));
-    }
-
-    public void testEmailPrefHandlingV21() {
-        testEmailPrefHandlingCommon(V21);
-    }
-
-    public void testEmailPrefHandlingV30() {
-        testEmailPrefHandlingCommon(V30);
-    }
-
-    private void testPostalAddressCommon(int vcardType) {
-        mVerifier.initForExportTest(vcardType);
-        mVerifier.addInputEntry().addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
-                .put(StructuredPostal.POBOX, "Pobox")
-                .put(StructuredPostal.NEIGHBORHOOD, "Neighborhood")
-                .put(StructuredPostal.STREET, "Street")
-                .put(StructuredPostal.CITY, "City")
-                .put(StructuredPostal.REGION, "Region")
-                .put(StructuredPostal.POSTCODE, "100")
-                .put(StructuredPostal.COUNTRY, "Country")
-                .put(StructuredPostal.FORMATTED_ADDRESS, "Formatted Address")
-                .put(StructuredPostal.TYPE, StructuredPostal.TYPE_WORK);
-        // adr-value    = 0*6(text-value ";") text-value
-        //              ; PO Box, Extended Address, Street, Locality, Region, Postal Code,
-        //              ; Country Name
-        //
-        // The NEIGHBORHOOD field is appended after the CITY field.
-        mVerifier.addPropertyNodesVerifierElemWithEmptyName()
-                .addExpectedNode("ADR",
-                        Arrays.asList("Pobox", "", "Street", "City Neighborhood",
-                                "Region", "100", "Country"), new TypeSet("WORK"));
-    }
-
-    public void testPostalAddressV21() {
-        testPostalAddressCommon(V21);
-    }
-
-    public void testPostalAddressV30() {
-        testPostalAddressCommon(V30);
-    }
-
-    private void testPostalAddressNonNeighborhood(int vcardType) {
-        mVerifier.initForExportTest(vcardType);
-        mVerifier.addInputEntry().addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
-                .put(StructuredPostal.CITY, "City");
-        mVerifier.addPropertyNodesVerifierElemWithEmptyName()
-                .addExpectedNode("ADR",
-                        Arrays.asList("", "", "", "City", "", "", ""), new TypeSet("HOME"));
-    }
-
-    public void testPostalAddressNonNeighborhoodV21() {
-        testPostalAddressNonNeighborhood(V21);
-    }
-
-    public void testPostalAddressNonNeighborhoodV30() {
-        testPostalAddressNonNeighborhood(V30);
-    }
-
-    private void testPostalAddressNonCity(int vcardType) {
-        mVerifier.initForExportTest(vcardType);
-        mVerifier.addInputEntry().addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
-                .put(StructuredPostal.NEIGHBORHOOD, "Neighborhood");
-        mVerifier.addPropertyNodesVerifierElemWithEmptyName()
-                .addExpectedNode("ADR",
-                        Arrays.asList("", "", "", "Neighborhood", "", "", ""), new TypeSet("HOME"));
-    }
-
-    public void testPostalAddressNonCityV21() {
-        testPostalAddressNonCity(V21);
-    }
-
-    public void testPostalAddressNonCityV30() {
-        testPostalAddressNonCity(V30);
-    }
-
-    private void testPostalOnlyWithFormattedAddressCommon(int vcardType) {
-        mVerifier.initForExportTest(vcardType);
-        mVerifier.addInputEntry().addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
-                .put(StructuredPostal.REGION, "")  // Must be ignored.
-                .put(StructuredPostal.FORMATTED_ADDRESS,
-                "Formatted address CA 123-334 United Statue");
-        mVerifier.addPropertyNodesVerifierElemWithEmptyName()
-                .addExpectedNodeWithOrder("ADR", ";Formatted address CA 123-334 United Statue;;;;;",
-                        Arrays.asList("", "Formatted address CA 123-334 United Statue",
-                                "", "", "", "", ""), new TypeSet("HOME"));
-    }
-
-    public void testPostalOnlyWithFormattedAddressV21() {
-        testPostalOnlyWithFormattedAddressCommon(V21);
-    }
-
-    public void testPostalOnlyWithFormattedAddressV30() {
-        testPostalOnlyWithFormattedAddressCommon(V30);
-    }
-
-    /**
-     * Tests that the vCard composer honors formatted data when it is available
-     * even when it is partial.
-     */
-    private void testPostalWithBothStructuredAndFormattedCommon(int vcardType) {
-        mVerifier.initForExportTest(vcardType);
-        mVerifier.addInputEntry().addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
-                .put(StructuredPostal.POBOX, "Pobox")
-                .put(StructuredPostal.COUNTRY, "Country")
-                .put(StructuredPostal.FORMATTED_ADDRESS,
-                        "Formatted address CA 123-334 United Statue");  // Should be ignored
-        mVerifier.addPropertyNodesVerifierElemWithEmptyName()
-                .addExpectedNode("ADR", "Pobox;;;;;;Country",
-                        Arrays.asList("Pobox", "", "", "", "", "", "Country"),
-                        new TypeSet("HOME"));
-    }
-
-    public void testPostalWithBothStructuredAndFormattedV21() {
-        testPostalWithBothStructuredAndFormattedCommon(V21);
-    }
-
-    public void testPostalWithBothStructuredAndFormattedV30() {
-        testPostalWithBothStructuredAndFormattedCommon(V30);
-    }
-
-    private void testOrganizationCommon(int vcardType) {
-        mVerifier.initForExportTest(vcardType);
-        ContactEntry entry = mVerifier.addInputEntry();
-        entry.addContentValues(Organization.CONTENT_ITEM_TYPE)
-                .put(Organization.COMPANY, "CompanyX")
-                .put(Organization.DEPARTMENT, "DepartmentY")
-                .put(Organization.TITLE, "TitleZ")
-                .put(Organization.JOB_DESCRIPTION, "Description Rambda")  // Ignored.
-                .put(Organization.OFFICE_LOCATION, "Mountain View")  // Ignored.
-                .put(Organization.PHONETIC_NAME, "PhoneticName!")  // Ignored
-                .put(Organization.SYMBOL, "(^o^)/~~");  // Ignore him (her).
-        entry.addContentValues(Organization.CONTENT_ITEM_TYPE)
-                .putNull(Organization.COMPANY)
-                .put(Organization.DEPARTMENT, "DepartmentXX")
-                .putNull(Organization.TITLE);
-        entry.addContentValues(Organization.CONTENT_ITEM_TYPE)
-                .put(Organization.COMPANY, "CompanyXYZ")
-                .putNull(Organization.DEPARTMENT)
-                .put(Organization.TITLE, "TitleXYZYX");
-        // Currently we do not use group but depend on the order.
-        mVerifier.addPropertyNodesVerifierElemWithEmptyName()
-                .addExpectedNodeWithOrder("ORG", "CompanyX;DepartmentY",
-                        Arrays.asList("CompanyX", "DepartmentY"))
-                .addExpectedNodeWithOrder("TITLE", "TitleZ")
-                .addExpectedNodeWithOrder("ORG", "DepartmentXX")
-                .addExpectedNodeWithOrder("ORG", "CompanyXYZ")
-                .addExpectedNodeWithOrder("TITLE", "TitleXYZYX");
-    }
-
-    public void testOrganizationV21() {
-        testOrganizationCommon(V21);
-    }
-
-    public void testOrganizationV30() {
-        testOrganizationCommon(V30);
-    }
-
-    private void testImVariousTypeSupportCommon(int vcardType) {
-        mVerifier.initForExportTest(vcardType);
-        ContactEntry entry = mVerifier.addInputEntry();
-        entry.addContentValues(Im.CONTENT_ITEM_TYPE)
-                .put(Im.PROTOCOL, Im.PROTOCOL_AIM)
-                .put(Im.DATA, "aim");
-        entry.addContentValues(Im.CONTENT_ITEM_TYPE)
-                .put(Im.PROTOCOL, Im.PROTOCOL_MSN)
-                .put(Im.DATA, "msn");
-        entry.addContentValues(Im.CONTENT_ITEM_TYPE)
-                .put(Im.PROTOCOL, Im.PROTOCOL_YAHOO)
-                .put(Im.DATA, "yahoo");
-        entry.addContentValues(Im.CONTENT_ITEM_TYPE)
-                .put(Im.PROTOCOL, Im.PROTOCOL_SKYPE)
-                .put(Im.DATA, "skype");
-        entry.addContentValues(Im.CONTENT_ITEM_TYPE)
-                .put(Im.PROTOCOL, Im.PROTOCOL_QQ)
-                .put(Im.DATA, "qq");
-        entry.addContentValues(Im.CONTENT_ITEM_TYPE)
-                .put(Im.PROTOCOL, Im.PROTOCOL_GOOGLE_TALK)
-                .put(Im.DATA, "google talk");
-        entry.addContentValues(Im.CONTENT_ITEM_TYPE)
-                .put(Im.PROTOCOL, Im.PROTOCOL_ICQ)
-                .put(Im.DATA, "icq");
-        entry.addContentValues(Im.CONTENT_ITEM_TYPE)
-                .put(Im.PROTOCOL, Im.PROTOCOL_JABBER)
-                .put(Im.DATA, "jabber");
-        entry.addContentValues(Im.CONTENT_ITEM_TYPE)
-                .put(Im.PROTOCOL, Im.PROTOCOL_NETMEETING)
-                .put(Im.DATA, "netmeeting");
-
-        // No determined way to express unknown type...
-        mVerifier.addPropertyNodesVerifierElemWithEmptyName()
-                .addExpectedNode("X-JABBER", "jabber")
-                .addExpectedNode("X-ICQ", "icq")
-                .addExpectedNode("X-GOOGLE-TALK", "google talk")
-                .addExpectedNode("X-QQ", "qq")
-                .addExpectedNode("X-SKYPE-USERNAME", "skype")
-                .addExpectedNode("X-YAHOO", "yahoo")
-                .addExpectedNode("X-MSN", "msn")
-                .addExpectedNode("X-NETMEETING", "netmeeting")
-                .addExpectedNode("X-AIM", "aim");
-    }
-
-    public void testImBasiV21() {
-        testImVariousTypeSupportCommon(V21);
-    }
-
-    public void testImBasicV30() {
-        testImVariousTypeSupportCommon(V30);
-    }
-
-    private void testImPrefHandlingCommon(int vcardType) {
-        mVerifier.initForExportTest(vcardType);
-        ContactEntry entry = mVerifier.addInputEntry();
-        entry.addContentValues(Im.CONTENT_ITEM_TYPE)
-                .put(Im.PROTOCOL, Im.PROTOCOL_AIM)
-                .put(Im.DATA, "aim1");
-        entry.addContentValues(Im.CONTENT_ITEM_TYPE)
-                .put(Im.PROTOCOL, Im.PROTOCOL_AIM)
-                .put(Im.DATA, "aim2")
-                .put(Im.TYPE, Im.TYPE_HOME)
-                .put(Im.IS_PRIMARY, 1);
-
-        mVerifier.addPropertyNodesVerifierElemWithEmptyName()
-                .addExpectedNode("X-AIM", "aim1")
-                .addExpectedNode("X-AIM", "aim2", new TypeSet("HOME", "PREF"));
-    }
-
-    public void testImPrefHandlingV21() {
-        testImPrefHandlingCommon(V21);
-    }
-
-    public void testImPrefHandlingV30() {
-        testImPrefHandlingCommon(V30);
-    }
-
-    private void testWebsiteCommon(int vcardType) {
-        mVerifier.initForExportTest(vcardType);
-        ContactEntry entry = mVerifier.addInputEntry();
-        entry.addContentValues(Website.CONTENT_ITEM_TYPE)
-                .put(Website.URL, "http://website.example.android.com/index.html")
-                .put(Website.TYPE, Website.TYPE_BLOG);
-        entry.addContentValues(Website.CONTENT_ITEM_TYPE)
-                .put(Website.URL, "ftp://ftp.example.android.com/index.html")
-                .put(Website.TYPE, Website.TYPE_FTP);
-
-        // We drop TYPE information since vCard (especially 3.0) does not allow us to emit it.
-        mVerifier.addPropertyNodesVerifierElemWithEmptyName()
-                .addExpectedNode("URL", "ftp://ftp.example.android.com/index.html")
-                .addExpectedNode("URL", "http://website.example.android.com/index.html");
-    }
-
-    public void testWebsiteV21() {
-        testWebsiteCommon(V21);
-    }
-
-    public void testWebsiteV30() {
-        testWebsiteCommon(V30);
-    }
-
-    private String getAndroidPropValue(final String mimeType, String value, Integer type) {
-        return getAndroidPropValue(mimeType, value, type, null);
-    }
-
-    private String getAndroidPropValue(final String mimeType, String value,
-            Integer type, String label) {
-        return (mimeType + ";" + value + ";"
-                + (type != null ? type : "") + ";"
-                + (label != null ? label : "") + ";;;;;;;;;;;;");
-    }
-
-    private void testEventCommon(int vcardType) {
-        mVerifier.initForExportTest(vcardType);
-        ContactEntry entry = mVerifier.addInputEntry();
-        entry.addContentValues(Event.CONTENT_ITEM_TYPE)
-                .put(Event.TYPE, Event.TYPE_ANNIVERSARY)
-                .put(Event.START_DATE, "1982-06-16");
-        entry.addContentValues(Event.CONTENT_ITEM_TYPE)
-                .put(Event.TYPE, Event.TYPE_BIRTHDAY)
-                .put(Event.START_DATE, "2008-10-22");
-        entry.addContentValues(Event.CONTENT_ITEM_TYPE)
-                .put(Event.TYPE, Event.TYPE_OTHER)
-                .put(Event.START_DATE, "2018-03-12");
-        entry.addContentValues(Event.CONTENT_ITEM_TYPE)
-                .put(Event.TYPE, Event.TYPE_CUSTOM)
-                .put(Event.LABEL, "The last day")
-                .put(Event.START_DATE, "When the Tower of Hanoi with 64 rings is completed.");
-        entry.addContentValues(Event.CONTENT_ITEM_TYPE)
-                .put(Event.TYPE, Event.TYPE_BIRTHDAY)
-                .put(Event.START_DATE, "2009-05-19");  // Should be ignored.
-        mVerifier.addPropertyNodesVerifierElemWithEmptyName()
-                .addExpectedNode("BDAY", "2008-10-22")
-                .addExpectedNode("X-ANDROID-CUSTOM",
-                        getAndroidPropValue(
-                                Event.CONTENT_ITEM_TYPE, "1982-06-16", Event.TYPE_ANNIVERSARY))
-                .addExpectedNode("X-ANDROID-CUSTOM",
-                        getAndroidPropValue(
-                                Event.CONTENT_ITEM_TYPE, "2018-03-12", Event.TYPE_OTHER))
-                .addExpectedNode("X-ANDROID-CUSTOM",
-                        getAndroidPropValue(
-                                Event.CONTENT_ITEM_TYPE,
-                                "When the Tower of Hanoi with 64 rings is completed.",
-                                Event.TYPE_CUSTOM, "The last day"));
-    }
-
-    public void testEventV21() {
-        testEventCommon(V21);
-    }
-
-    public void testEventV30() {
-        testEventCommon(V30);
-    }
-
-    private void testNoteCommon(int vcardType) {
-        mVerifier.initForExportTest(vcardType);
-        ContactEntry entry = mVerifier.addInputEntry();
-        entry.addContentValues(Note.CONTENT_ITEM_TYPE)
-                .put(Note.NOTE, "note1");
-        entry.addContentValues(Note.CONTENT_ITEM_TYPE)
-                .put(Note.NOTE, "note2")
-                .put(Note.IS_PRIMARY, 1);  // Just ignored.
-        mVerifier.addPropertyNodesVerifierElemWithEmptyName()
-                .addExpectedNodeWithOrder("NOTE", "note1")
-                .addExpectedNodeWithOrder("NOTE", "note2");
-    }
-
-    public void testNoteV21() {
-        testNoteCommon(V21);
-    }
-
-    public void testNoteV30() {
-        testNoteCommon(V30);
-    }
-
-    private void testPhotoCommon(int vcardType) {
-        final boolean isV30 = vcardType == V30;
-        mVerifier.initForExportTest(vcardType);
-        ContactEntry entry = mVerifier.addInputEntry();
-        entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
-                .put(StructuredName.FAMILY_NAME, "PhotoTest");
-        entry.addContentValues(Photo.CONTENT_ITEM_TYPE)
-                .put(Photo.PHOTO, sPhotoByteArray);
-
-        ContentValues contentValuesForPhoto = new ContentValues();
-        contentValuesForPhoto.put("ENCODING", (isV30 ? "b" : "BASE64"));
-        mVerifier.addPropertyNodesVerifierElem()
-                .addExpectedNode("FN", "PhotoTest")
-                .addExpectedNode("N", "PhotoTest;;;;",
-                        Arrays.asList("PhotoTest", "", "", "", ""))
-                .addExpectedNodeWithOrder("PHOTO", null, null, sPhotoByteArray,
-                        contentValuesForPhoto, new TypeSet("JPEG"), null);
-    }
-
-    public void testPhotoV21() {
-        testPhotoCommon(V21);
-    }
-
-    public void testPhotoV30() {
-        testPhotoCommon(V30);
-    }
-
-    private void testRelationCommon(int vcardType) {
-        mVerifier.initForExportTest(vcardType);
-        mVerifier.addInputEntry().addContentValues(Relation.CONTENT_ITEM_TYPE)
-                .put(Relation.TYPE, Relation.TYPE_MOTHER)
-                .put(Relation.NAME, "Ms. Mother");
-        mVerifier.addContentValuesVerifierElem().addExpected(Relation.CONTENT_ITEM_TYPE)
-                .put(Relation.TYPE, Relation.TYPE_MOTHER)
-                .put(Relation.NAME, "Ms. Mother");
-    }
-
-    public void testRelationV21() {
-        testRelationCommon(V21);
-    }
-
-    public void testRelationV30() {
-        testRelationCommon(V30);
-    }
-
-    public void testV30HandleEscape() {
-        mVerifier.initForExportTest(V30);
-        mVerifier.addInputEntry().addContentValues(StructuredName.CONTENT_ITEM_TYPE)
-                .put(StructuredName.FAMILY_NAME, "\\")
-                .put(StructuredName.GIVEN_NAME, ";")
-                .put(StructuredName.MIDDLE_NAME, ",")
-                .put(StructuredName.PREFIX, "\n")
-                .put(StructuredName.DISPLAY_NAME, "[<{Unescaped:Asciis}>]");
-        // Verifies the vCard String correctly escapes each character which must be escaped.
-        mVerifier.addLineVerifierElem()
-                .addExpected("N:\\\\;\\;;\\,;\\n;")
-                .addExpected("FN:[<{Unescaped:Asciis}>]");
-        mVerifier.addPropertyNodesVerifierElem()
-                .addExpectedNode("FN", "[<{Unescaped:Asciis}>]")
-                .addExpectedNode("N", Arrays.asList("\\", ";", ",", "\n", ""));
-    }
-
-    /**
-     * There's no "NICKNAME" property in vCard 2.1, while there is in vCard 3.0.
-     * We use Android-specific "X-ANDROID-CUSTOM" property.
-     * This test verifies the functionality.
-     */
-    public void testNickNameV21() {
-        mVerifier.initForExportTest(V21);
-        mVerifier.addInputEntry().addContentValues(Nickname.CONTENT_ITEM_TYPE)
-                .put(Nickname.NAME, "Nicky");
-        mVerifier.addPropertyNodesVerifierElemWithEmptyName()
-                .addExpectedNode("X-ANDROID-CUSTOM",
-                        Nickname.CONTENT_ITEM_TYPE + ";Nicky;;;;;;;;;;;;;;");
-        mVerifier.addContentValuesVerifierElem().addExpected(Nickname.CONTENT_ITEM_TYPE)
-                .put(Nickname.NAME, "Nicky");
-    }
-
-    public void testTolerateBrokenPhoneNumberEntryV21() {
-        mVerifier.initForExportTest(V21);
-        ContactEntry entry = mVerifier.addInputEntry();
-        entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
-                .put(Phone.TYPE, Phone.TYPE_HOME)
-                .put(Phone.NUMBER, "111-222-3333 (Miami)\n444-5555-666 (Tokyo);"
-                        + "777-888-9999 (Chicago);111-222-3333 (Miami)");
-        mVerifier.addPropertyNodesVerifierElemWithEmptyName()
-                .addExpectedNode("TEL", "111-222-3333", new TypeSet("HOME"))
-                .addExpectedNode("TEL", "444-555-5666", new TypeSet("HOME"))
-                .addExpectedNode("TEL", "777-888-9999", new TypeSet("HOME"));
-    }
-
-    private void testPickUpNonEmptyContentValuesCommon(int vcardType) {
-        mVerifier.initForExportTest(vcardType);
-        ContactEntry entry = mVerifier.addInputEntry();
-        entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
-                .put(StructuredName.IS_PRIMARY, 1);  // Empty name. Should be ignored.
-        entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
-                .put(StructuredName.FAMILY_NAME, "family1");  // Not primary. Should be ignored.
-        entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
-                .put(StructuredName.IS_PRIMARY, 1)
-                .put(StructuredName.FAMILY_NAME, "family2");  // This entry is what we want.
-        entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
-                .put(StructuredName.IS_PRIMARY, 1)
-                .put(StructuredName.FAMILY_NAME, "family3");
-        entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
-                .put(StructuredName.FAMILY_NAME, "family4");
-        mVerifier.addPropertyNodesVerifierElem()
-                .addExpectedNode("N", Arrays.asList("family2", "", "", "", ""))
-                .addExpectedNode("FN", "family2");
-    }
-
-    public void testPickUpNonEmptyContentValuesV21() {
-        testPickUpNonEmptyContentValuesCommon(V21);
-    }
-
-    public void testPickUpNonEmptyContentValuesV30() {
-        testPickUpNonEmptyContentValuesCommon(V30);
-    }
-}
diff --git a/core/tests/coretests/src/android/pim/vcard/VCardImporterTests.java b/core/tests/coretests/src/android/pim/vcard/VCardImporterTests.java
deleted file mode 100644
index 21f2254..0000000
--- a/core/tests/coretests/src/android/pim/vcard/VCardImporterTests.java
+++ /dev/null
@@ -1,1011 +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.pim.vcard;
-
-import android.content.ContentValues;
-import android.pim.vcard.VCardConfig;
-import android.provider.ContactsContract.Data;
-import android.provider.ContactsContract.CommonDataKinds.Email;
-import android.provider.ContactsContract.CommonDataKinds.Event;
-import android.provider.ContactsContract.CommonDataKinds.Note;
-import android.provider.ContactsContract.CommonDataKinds.Organization;
-import android.provider.ContactsContract.CommonDataKinds.Phone;
-import android.provider.ContactsContract.CommonDataKinds.Photo;
-import android.provider.ContactsContract.CommonDataKinds.StructuredName;
-import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
-import android.provider.ContactsContract.CommonDataKinds.Website;
-
-import com.android.frameworks.coretests.R;
-import android.pim.vcard.PropertyNodesVerifierElem.TypeSet;
-
-import java.util.Arrays;
-
-public class VCardImporterTests extends VCardTestsBase {
-    // Push data into int array at first since values like 0x80 are
-    // interpreted as int by the compiler and casting all of them is
-    // cumbersome...
-    private static final int[] sPhotoIntArrayForComplicatedCase = {
-        0xff, 0xd8, 0xff, 0xe1, 0x0a, 0x0f, 0x45, 0x78, 0x69, 0x66, 0x00,
-        0x00, 0x4d, 0x4d, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x08, 0x00, 0x0d,
-        0x01, 0x0e, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00,
-        0xaa, 0x01, 0x0f, 0x00, 0x02, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00,
-        0x00, 0xba, 0x01, 0x10, 0x00, 0x02, 0x00, 0x00, 0x00, 0x06, 0x00,
-        0x00, 0x00, 0xc2, 0x01, 0x12, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01,
-        0x00, 0x01, 0x00, 0x00, 0x01, 0x1a, 0x00, 0x05, 0x00, 0x00, 0x00,
-        0x01, 0x00, 0x00, 0x00, 0xc8, 0x01, 0x1b, 0x00, 0x05, 0x00, 0x00,
-        0x00, 0x01, 0x00, 0x00, 0x00, 0xd0, 0x01, 0x28, 0x00, 0x03, 0x00,
-        0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x01, 0x31, 0x00, 0x02,
-        0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0xd8, 0x01, 0x32, 0x00,
-        0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0xe6, 0x02, 0x13,
-        0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x82,
-        0x98, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0xfa,
-        0x87, 0x69, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01,
-        0x84, 0xc4, 0xa5, 0x00, 0x07, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x00,
-        0x01, 0x08, 0x00, 0x00, 0x04, 0x1e, 0x32, 0x30, 0x30, 0x38, 0x31,
-        0x30, 0x32, 0x39, 0x31, 0x33, 0x35, 0x35, 0x33, 0x31, 0x00, 0x00,
-        0x44, 0x6f, 0x43, 0x6f, 0x4d, 0x6f, 0x00, 0x00, 0x44, 0x39, 0x30,
-        0x35, 0x69, 0x00, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x01,
-        0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x01, 0x44, 0x39, 0x30,
-        0x35, 0x69, 0x20, 0x56, 0x65, 0x72, 0x31, 0x2e, 0x30, 0x30, 0x00,
-        0x32, 0x30, 0x30, 0x38, 0x3a, 0x31, 0x30, 0x3a, 0x32, 0x39, 0x20,
-        0x31, 0x33, 0x3a, 0x35, 0x35, 0x3a, 0x34, 0x37, 0x00, 0x20, 0x20,
-        0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
-        0x00, 0x50, 0x72, 0x69, 0x6e, 0x74, 0x49, 0x4d, 0x00, 0x30, 0x33,
-        0x30, 0x30, 0x00, 0x00, 0x00, 0x06, 0x00, 0x01, 0x00, 0x14, 0x00,
-        0x14, 0x00, 0x02, 0x01, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00,
-        0x00, 0x34, 0x01, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01,
-        0x00, 0x00, 0x00, 0x01, 0x10, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00,
-        0x11, 0x09, 0x00, 0x00, 0x27, 0x10, 0x00, 0x00, 0x0f, 0x0b, 0x00,
-        0x00, 0x27, 0x10, 0x00, 0x00, 0x05, 0x97, 0x00, 0x00, 0x27, 0x10,
-        0x00, 0x00, 0x08, 0xb0, 0x00, 0x00, 0x27, 0x10, 0x00, 0x00, 0x1c,
-        0x01, 0x00, 0x00, 0x27, 0x10, 0x00, 0x00, 0x02, 0x5e, 0x00, 0x00,
-        0x27, 0x10, 0x00, 0x00, 0x00, 0x8b, 0x00, 0x00, 0x27, 0x10, 0x00,
-        0x00, 0x03, 0xcb, 0x00, 0x00, 0x27, 0x10, 0x00, 0x00, 0x1b, 0xe5,
-        0x00, 0x00, 0x27, 0x10, 0x00, 0x28, 0x82, 0x9a, 0x00, 0x05, 0x00,
-        0x00, 0x00, 0x01, 0x00, 0x00, 0x03, 0x6a, 0x82, 0x9d, 0x00, 0x05,
-        0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x03, 0x72, 0x88, 0x22, 0x00,
-        0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x90, 0x00,
-        0x00, 0x07, 0x00, 0x00, 0x00, 0x04, 0x30, 0x32, 0x32, 0x30, 0x90,
-        0x03, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x03, 0x7a,
-        0x90, 0x04, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x03,
-        0x8e, 0x91, 0x01, 0x00, 0x07, 0x00, 0x00, 0x00, 0x04, 0x01, 0x02,
-        0x03, 0x00, 0x91, 0x02, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00,
-        0x00, 0x03, 0xa2, 0x92, 0x01, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x01,
-        0x00, 0x00, 0x03, 0xaa, 0x92, 0x02, 0x00, 0x05, 0x00, 0x00, 0x00,
-        0x01, 0x00, 0x00, 0x03, 0xb2, 0x92, 0x04, 0x00, 0x0a, 0x00, 0x00,
-        0x00, 0x01, 0x00, 0x00, 0x03, 0xba, 0x92, 0x05, 0x00, 0x05, 0x00,
-        0x00, 0x00, 0x01, 0x00, 0x00, 0x03, 0xc2, 0x92, 0x07, 0x00, 0x03,
-        0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x92, 0x08, 0x00,
-        0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x92, 0x09,
-        0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x92,
-        0x0a, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x03, 0xca,
-        0x92, 0x7c, 0x00, 0x07, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
-        0x00, 0x92, 0x86, 0x00, 0x07, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00,
-        0x03, 0xd2, 0xa0, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x04, 0x30,
-        0x31, 0x30, 0x30, 0xa0, 0x01, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01,
-        0x00, 0x01, 0x00, 0x00, 0xa0, 0x02, 0x00, 0x03, 0x00, 0x00, 0x00,
-        0x01, 0x00, 0x60, 0x00, 0x00, 0xa0, 0x03, 0x00, 0x03, 0x00, 0x00,
-        0x00, 0x01, 0x00, 0x48, 0x00, 0x00, 0xa0, 0x05, 0x00, 0x04, 0x00,
-        0x00, 0x00, 0x01, 0x00, 0x00, 0x04, 0x00, 0xa2, 0x0e, 0x00, 0x05,
-        0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x03, 0xe8, 0xa2, 0x0f, 0x00,
-        0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x03, 0xf0, 0xa2, 0x10,
-        0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0xa2,
-        0x17, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00,
-        0xa3, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x01, 0x03, 0x00, 0x00,
-        0x00, 0xa3, 0x01, 0x00, 0x07, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00,
-        0x00, 0x00, 0xa4, 0x01, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00,
-        0x00, 0x00, 0x00, 0xa4, 0x02, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01,
-        0x00, 0x00, 0x00, 0x00, 0xa4, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00,
-        0x01, 0x00, 0x00, 0x00, 0x00, 0xa4, 0x04, 0x00, 0x05, 0x00, 0x00,
-        0x00, 0x01, 0x00, 0x00, 0x03, 0xf8, 0xa4, 0x05, 0x00, 0x03, 0x00,
-        0x00, 0x00, 0x01, 0x00, 0x1d, 0x00, 0x00, 0xa4, 0x06, 0x00, 0x03,
-        0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xa4, 0x07, 0x00,
-        0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xa4, 0x08,
-        0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xa4,
-        0x09, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
-        0xa4, 0x0a, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
-        0x00, 0xa4, 0x0c, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02,
-        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x53, 0x00,
-        0x00, 0x27, 0x10, 0x00, 0x00, 0x01, 0x5e, 0x00, 0x00, 0x00, 0x64,
-        0x32, 0x30, 0x30, 0x38, 0x3a, 0x31, 0x30, 0x3a, 0x32, 0x39, 0x20,
-        0x31, 0x33, 0x3a, 0x35, 0x35, 0x3a, 0x33, 0x31, 0x00, 0x32, 0x30,
-        0x30, 0x38, 0x3a, 0x31, 0x30, 0x3a, 0x32, 0x39, 0x20, 0x31, 0x33,
-        0x3a, 0x35, 0x35, 0x3a, 0x34, 0x37, 0x00, 0x00, 0x00, 0x29, 0x88,
-        0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x02, 0xb2, 0x00, 0x00, 0x00,
-        0x64, 0x00, 0x00, 0x01, 0x5e, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00,
-        0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x25, 0x00,
-        0x00, 0x00, 0x0a, 0x00, 0x00, 0x0e, 0x92, 0x00, 0x00, 0x03, 0xe8,
-        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x32, 0x30, 0x30,
-        0x38, 0x31, 0x30, 0x32, 0x39, 0x31, 0x33, 0x35, 0x35, 0x33, 0x31,
-        0x00, 0x00, 0x20, 0x2a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x2a,
-        0xe2, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-        0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00,
-        0x04, 0x52, 0x39, 0x38, 0x00, 0x00, 0x02, 0x00, 0x07, 0x00, 0x00,
-        0x00, 0x04, 0x30, 0x31, 0x30, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00,
-        0x06, 0x01, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x06,
-        0x00, 0x00, 0x01, 0x1a, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00,
-        0x00, 0x04, 0x6c, 0x01, 0x1b, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01,
-        0x00, 0x00, 0x04, 0x74, 0x01, 0x28, 0x00, 0x03, 0x00, 0x00, 0x00,
-        0x01, 0x00, 0x02, 0x00, 0x00, 0x02, 0x01, 0x00, 0x04, 0x00, 0x00,
-        0x00, 0x01, 0x00, 0x00, 0x04, 0x7c, 0x02, 0x02, 0x00, 0x04, 0x00,
-        0x00, 0x00, 0x01, 0x00, 0x00, 0x05, 0x8b, 0x00, 0x00, 0x00, 0x00,
-        0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
-        0x48, 0x00, 0x00, 0x00, 0x01, 0xff, 0xd8, 0xff, 0xdb, 0x00, 0x84,
-        0x00, 0x20, 0x16, 0x18, 0x1c, 0x18, 0x14, 0x20, 0x1c, 0x1a, 0x1c,
-        0x24, 0x22, 0x20, 0x26, 0x30, 0x50, 0x34, 0x30, 0x2c, 0x2c, 0x30,
-        0x62, 0x46, 0x4a, 0x3a, 0x50, 0x74, 0x66, 0x7a, 0x78, 0x72, 0x66,
-        0x70, 0x6e, 0x80, 0x90, 0xb8, 0x9c, 0x80, 0x88, 0xae, 0x8a, 0x6e,
-        0x70, 0xa0, 0xda, 0xa2, 0xae, 0xbe, 0xc4, 0xce, 0xd0, 0xce, 0x7c,
-        0x9a, 0xe2, 0xf2, 0xe0, 0xc8, 0xf0, 0xb8, 0xca, 0xce, 0xc6, 0x01,
-        0x22, 0x24, 0x24, 0x30, 0x2a, 0x30, 0x5e, 0x34, 0x34, 0x5e, 0xc6,
-        0x84, 0x70, 0x84, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6,
-        0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6,
-        0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6,
-        0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6,
-        0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xff, 0xc0,
-        0x00, 0x11, 0x08, 0x00, 0x78, 0x00, 0xa0, 0x03, 0x01, 0x21, 0x00,
-        0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xff, 0xc4, 0x01, 0xa2, 0x00,
-        0x00, 0x01, 0x05, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00,
-        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05,
-        0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x10, 0x00, 0x02, 0x01, 0x03,
-        0x03, 0x02, 0x04, 0x03, 0x05, 0x05, 0x04, 0x04, 0x00, 0x00, 0x01,
-        0x7d, 0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21, 0x31,
-        0x41, 0x06, 0x13, 0x51, 0x61, 0x07, 0x22, 0x71, 0x14, 0x32, 0x81,
-        0x91, 0xa1, 0x08, 0x23, 0x42, 0xb1, 0xc1, 0x15, 0x52, 0xd1, 0xf0,
-        0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0a, 0x16, 0x17, 0x18, 0x19,
-        0x1a, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x34, 0x35, 0x36, 0x37,
-        0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a,
-        0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, 0x65,
-        0x66, 0x67, 0x68, 0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78,
-        0x79, 0x7a, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x92,
-        0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4,
-        0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6,
-        0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8,
-        0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda,
-        0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf1,
-        0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0x01, 0x00,
-        0x03, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00,
-        0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
-        0x07, 0x08, 0x09, 0x0a, 0x0b, 0x11, 0x00, 0x02, 0x01, 0x02, 0x04,
-        0x04, 0x03, 0x04, 0x07, 0x05, 0x04, 0x04, 0x00, 0x01, 0x02, 0x77,
-        0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21, 0x31, 0x06, 0x12,
-        0x41, 0x51, 0x07, 0x61, 0x71, 0x13, 0x22, 0x32, 0x81, 0x08, 0x14,
-        0x42, 0x91, 0xa1, 0xb1, 0xc1, 0x09, 0x23, 0x33, 0x52, 0xf0, 0x15,
-        0x62, 0x72, 0xd1, 0x0a, 0x16, 0x24, 0x34, 0xe1, 0x25, 0xf1, 0x17,
-        0x18, 0x19, 0x1a, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, 0x37,
-        0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a,
-        0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, 0x65,
-        0x66, 0x67, 0x68, 0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78,
-        0x79, 0x7a, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a,
-        0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3,
-        0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5,
-        0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7,
-        0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9,
-        0xda, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf2,
-        0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xff, 0xda, 0x00,
-        0x0c, 0x03, 0x01, 0x00, 0x02, 0x11, 0x03, 0x11, 0x00, 0x3f, 0x00,
-        0x14, 0x54, 0xaa, 0x2a, 0x46, 0x48, 0xa2, 0xa4, 0x55, 0xa6, 0x04,
-        0x8a, 0x29, 0xe0, 0x53, 0x10, 0xe0, 0x29, 0xc0, 0x50, 0x03, 0xb1,
-        0x46, 0x29, 0x80, 0x84, 0x52, 0x11, 0x40, 0x0d, 0x22, 0x9a, 0x45,
-        0x20, 0x23, 0x61, 0x51, 0x30, 0xa0, 0x08, 0xc8, 0xa8, 0xd8, 0x52,
-        0x02, 0x26, 0x15, 0x0b, 0x0a, 0x00, 0xb4, 0xa2, 0xa5, 0x5a, 0x00,
-        0x91, 0x45, 0x4a, 0xa2, 0x81, 0x92, 0x01, 0x4e, 0x02, 0x98, 0x87,
-        0x0a, 0x70, 0xa0, 0x07, 0x62, 0x8c, 0x50, 0x21, 0x0d, 0x25, 0x00,
-        0x34, 0x8a, 0x61, 0x14, 0x0c, 0x63, 0x0a, 0x89, 0x85, 0x00, 0x46,
-        0xd5, 0x1b, 0x52, 0x02, 0x16, 0xa8, 0x98, 0x50, 0x05, 0x94, 0xa9,
-        0x16, 0x80, 0x25, 0x5a, 0x95, 0x68, 0x18, 0xf1, 0x4f, 0x14, 0xc4,
-        0x3b, 0xb5, 0x22, 0xb6, 0x38, 0x34, 0x00, 0xe3, 0x22, 0x8e, 0xf4,
-        0x79, 0x8a, 0x7b, 0xd1, 0x71, 0x03, 0x30, 0xc7, 0x14, 0x83, 0xa5,
-        0x00, 0x06, 0x98, 0x68, 0x01, 0x8d, 0x51, 0x35, 0x03, 0x22, 0x6a,
-        0x8d, 0xa9, 0x01, 0x13, 0x54, 0x4d, 0x40, 0x13, 0xa5, 0x4a, 0x28,
-        0x02, 0x45, 0x35, 0x2a, 0x9a, 0x00, 0x78, 0x34, 0xf0, 0x69, 0x80,
-        0x34, 0x81, 0x45, 0x40, 0xce, 0x58, 0xe6, 0xa2, 0x4c, 0x06, 0xe4,
-        0xfa, 0xd1, 0x93, 0x50, 0x21, 0xca, 0xe4, 0x55, 0x84, 0x90, 0x30,
-        0xab, 0x8b, 0x18, 0xa6, 0x9a, 0x6a, 0xc4, 0x31, 0xaa, 0x26, 0xa0,
-        0x64, 0x4d, 0x51, 0xb5, 0x20, 0x23, 0x6a, 0x89, 0xa8, 0x02, 0x44,
-        0x35, 0x2a, 0x9a, 0x00, 0x95, 0x4d, 0x48, 0xa6, 0x80, 0x24, 0x53,
-        0x4e, 0xce, 0x05, 0x30, 0x2b, 0x3b, 0xee, 0x6a, 0x91, 0x5d, 0x76,
-        0x63, 0xbd, 0x65, 0x7d, 0x40, 0x66, 0x68, 0xa9, 0x02, 0x45, 0x2b,
-        0xb3, 0x9e, 0xb4, 0xc5, 0x6d, 0xad, 0x9a, 0xa0, 0x2c, 0x06, 0xc8,
-        0xcd, 0x04, 0xd6, 0xa2, 0x23, 0x63, 0x51, 0xb1, 0xa0, 0x64, 0x4d,
-        0x51, 0x93, 0x48, 0x08, 0xda, 0xa2, 0x6a, 0x00, 0x72, 0x1a, 0x99,
-        0x4d, 0x00, 0x48, 0xa6, 0xa4, 0x53, 0x4c, 0x07, 0x86, 0x03, 0xbd,
-        0x2b, 0x9c, 0xa7, 0x14, 0x98, 0x10, 0x85, 0x34, 0xe0, 0xa6, 0xb3,
-        0xb0, 0x0b, 0xb5, 0xa8, 0x0a, 0xd4, 0x58, 0x42, 0xed, 0x3e, 0x94,
-        0xd2, 0xa6, 0x8b, 0x01, 0x34, 0x44, 0xed, 0xe6, 0x9c, 0x4d, 0x6a,
-        0x80, 0x8d, 0x8d, 0x46, 0xc6, 0x80, 0x23, 0x63, 0x51, 0x9a, 0x06,
-        0x46, 0xd5, 0x13, 0x52, 0x01, 0x54, 0xd4, 0xaa, 0x68, 0x02, 0x40,
-        0x6a, 0x40, 0x78, 0xa0, 0x08, 0x59, 0xce, 0xee, 0xb5, 0x2a, 0x39,
-        0xd9, 0x59, 0xa7, 0xa8, 0x00, 0x73, 0xeb, 0x4e, 0x0e, 0x7d, 0x69,
-        0x5c, 0x05, 0xf3, 0x0f, 0xad, 0x1e, 0x61, 0xf5, 0xa7, 0x71, 0x0b,
-        0xe6, 0x35, 0x21, 0x90, 0xd3, 0xb8, 0x0e, 0x32, 0x10, 0x95, 0x10,
-        0x91, 0xb3, 0xd6, 0x9b, 0x60, 0x4b, 0x9c, 0x8a, 0x63, 0x1a, 0xb0,
-        0x18, 0x4d, 0x46, 0xc6, 0x80, 0x22, 0x6a, 0x61, 0xa4, 0x31, 0xaa,
-        0x6a, 0x55, 0x34, 0x01, 0x2a, 0x9a, 0x7e, 0x78, 0xa0, 0x08, 0x09,
-        0xf9, 0xaa, 0x58, 0xcf, 0xca, 0x6b, 0x3e, 0xa0, 0x00, 0xd3, 0x81,
-        0xa9, 0x01, 0x73, 0x46, 0x69, 0x80, 0xb9, 0xa4, 0xcd, 0x00, 0x2b,
-        0x1f, 0x92, 0xa3, 0x07, 0x9a, 0x6f, 0x70, 0x26, 0xcf, 0x14, 0xd2,
-        0x6b, 0x51, 0x0c, 0x63, 0x51, 0xb1, 0xa0, 0x08, 0xda, 0x98, 0x69,
-        0x0c, 0x8d, 0x4d, 0x4a, 0xa6, 0x80, 0x24, 0x53, 0x52, 0x03, 0xc5,
-        0x02, 0x21, 0x27, 0xe6, 0xa9, 0x23, 0x3f, 0x29, 0xac, 0xfa, 0x8c,
-        0x01, 0xe6, 0x9c, 0x0d, 0x48, 0x0a, 0x0d, 0x2e, 0x68, 0x01, 0x73,
-        0x49, 0x9a, 0x60, 0x2b, 0x1f, 0x92, 0x98, 0x3a, 0xd3, 0x7b, 0x81,
-        0x36, 0x78, 0xa6, 0x93, 0x5a, 0x88, 0x8c, 0x9a, 0x63, 0x1a, 0x00,
-        0x8c, 0xd3, 0x0d, 0x21, 0x91, 0x29, 0xa9, 0x14, 0xd0, 0x04, 0x8a,
-        0x69, 0xe0, 0xd3, 0x11, 0x1b, 0x1e, 0x6a, 0x48, 0xcf, 0xca, 0x6b,
-        0x3e, 0xa3, 0x10, 0x1a, 0x70, 0x35, 0x20, 0x38, 0x1a, 0x5c, 0xd2,
-        0x01, 0x73, 0x49, 0x9a, 0x60, 0x39, 0x8f, 0xca, 0x29, 0x8b, 0xf7,
-        0xaa, 0xba, 0x88, 0x96, 0x9a, 0x6b, 0x40, 0x18, 0xc6, 0xa3, 0x26,
-        0x80, 0x18, 0x69, 0xa6, 0x90, 0xc8, 0x14, 0xd4, 0x8a, 0x69, 0x80,
-        0xf0, 0x6a, 0x40, 0x68, 0x10, 0xbb, 0x41, 0xa7, 0xe3, 0x0b, 0xc5,
-        0x2b, 0x01, 0x10, 0xa7, 0x03, 0x59, 0x0c, 0x76, 0x69, 0x73, 0x40,
-        0x0b, 0x9a, 0x28, 0x11, 0x28, 0x19, 0x5e, 0x69, 0x02, 0x81, 0x5a,
-        0xd8, 0x00, 0xd3, 0x4d, 0x50, 0x0c, 0x6a, 0x8c, 0xd2, 0x01, 0xa6,
-        0x98, 0x69, 0x0c, 0xae, 0xa6, 0xa4, 0x06, 0x80, 0x1e, 0xa6, 0x9e,
-        0x0d, 0x31, 0x12, 0x03, 0x4f, 0x06, 0x80, 0x13, 0x60, 0x34, 0xd3,
-        0xc1, 0xa8, 0x92, 0x01, 0xf1, 0x8d, 0xdd, 0x69, 0xcc, 0xa1, 0x69,
-        0x5b, 0x4b, 0x80, 0x83, 0x93, 0x52, 0x04, 0x14, 0xe2, 0xae, 0x03,
-        0xa9, 0x0d, 0x68, 0x03, 0x4d, 0x34, 0xd0, 0x03, 0x0d, 0x30, 0xd2,
-        0x01, 0x86, 0x9a, 0x68, 0x19, 0x58, 0x1a, 0x78, 0xa4, 0x04, 0x8a,
-        0x69, 0xe0, 0xd3, 0x10, 0xe0, 0x69, 0xe0, 0xd0, 0x03, 0xc1, 0xa8,
-        0xdb, 0xad, 0x4c, 0x81, 0x12, 0x45, 0xd6, 0x9d, 0x25, 0x1d, 0x00,
-        0x6a, 0xf5, 0xa9, 0xe8, 0x80, 0x31, 0x29, 0x0d, 0x58, 0x08, 0x69,
-        0x86, 0x80, 0x1a, 0x69, 0x86, 0x90, 0x0c, 0x34, 0xd3, 0x48, 0x65,
-        0x51, 0x4f, 0x06, 0x98, 0x0f, 0x14, 0xf0, 0x68, 0x10, 0xf0, 0x69,
-        0xe0, 0xd0, 0x03, 0x81, 0xa5, 0x2b, 0x9a, 0x1a, 0xb8, 0x87, 0xa8,
-        0xdb, 0x4a, 0x46, 0x68, 0xb6, 0x80, 0x2a, 0xa8, 0x14, 0xea, 0x12,
-        0xb0, 0x05, 0x21, 0xa6, 0x02, 0x1a, 0x61, 0xa0, 0x06, 0x9a, 0x61,
-        0xa4, 0x31, 0x86, 0x9a, 0x69, 0x0c, 0xa8, 0x0d, 0x3c, 0x53, 0x01,
-        0xe2, 0x9e, 0x28, 0x10, 0xf1, 0x4e, 0x06, 0x98, 0x0f, 0x06, 0x9e,
-        0x0d, 0x02, 0x1c, 0x29, 0xc2, 0x80, 0x16, 0x96, 0x80, 0x0a, 0x4a,
-        0x00, 0x43, 0x4d, 0x34, 0x0c, 0x61, 0xa6, 0x1a, 0x40, 0x34, 0xd3,
-        0x4d, 0x21, 0x80, 0xff, 0xd9, 0xff, 0xdb, 0x00, 0x84, 0x00, 0x0a,
-        0x07, 0x07, 0x08, 0x07, 0x06, 0x0a, 0x08, 0x08, 0x08, 0x0b, 0x0a,
-        0x0a, 0x0b, 0x0e, 0x18, 0x10, 0x0e, 0x0d, 0x0d, 0x0e, 0x1d, 0x15,
-        0x16, 0x11, 0x18, 0x23, 0x1f, 0x25, 0x24, 0x22, 0x1f, 0x22, 0x21,
-        0x26, 0x2b, 0x37, 0x2f, 0x26, 0x29, 0x34, 0x29, 0x21, 0x22, 0x30,
-        0x41, 0x31, 0x34, 0x39, 0x3b, 0x3e, 0x3e, 0x3e, 0x25, 0x2e, 0x44,
-        0x49, 0x43, 0x3c, 0x48, 0x37, 0x3d, 0x3e, 0x3b, 0x01, 0x0a, 0x0b,
-        0x0b, 0x0e, 0x0d, 0x0e, 0x1c, 0x10, 0x10, 0x1c, 0x3b, 0x28, 0x22,
-        0x28, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b,
-        0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b,
-        0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b,
-        0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b,
-        0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0xff, 0xc0, 0x00, 0x11,
-        0x08, 0x00, 0x48, 0x00, 0x60, 0x03, 0x01, 0x21, 0x00, 0x02, 0x11,
-        0x01, 0x03, 0x11, 0x01, 0xff, 0xc4, 0x01, 0xa2, 0x00, 0x00, 0x01,
-        0x05, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00,
-        0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
-        0x08, 0x09, 0x0a, 0x0b, 0x10, 0x00, 0x02, 0x01, 0x03, 0x03, 0x02,
-        0x04, 0x03, 0x05, 0x05, 0x04, 0x04, 0x00, 0x00, 0x01, 0x7d, 0x01,
-        0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21, 0x31, 0x41, 0x06,
-        0x13, 0x51, 0x61, 0x07, 0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xa1,
-        0x08, 0x23, 0x42, 0xb1, 0xc1, 0x15, 0x52, 0xd1, 0xf0, 0x24, 0x33,
-        0x62, 0x72, 0x82, 0x09, 0x0a, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x25,
-        0x26, 0x27, 0x28, 0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,
-        0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x53, 0x54,
-        0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67,
-        0x68, 0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a,
-        0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x92, 0x93, 0x94,
-        0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6,
-        0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8,
-        0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca,
-        0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe1, 0xe2,
-        0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf1, 0xf2, 0xf3,
-        0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0x01, 0x00, 0x03, 0x01,
-        0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00,
-        0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
-        0x09, 0x0a, 0x0b, 0x11, 0x00, 0x02, 0x01, 0x02, 0x04, 0x04, 0x03,
-        0x04, 0x07, 0x05, 0x04, 0x04, 0x00, 0x01, 0x02, 0x77, 0x00, 0x01,
-        0x02, 0x03, 0x11, 0x04, 0x05, 0x21, 0x31, 0x06, 0x12, 0x41, 0x51,
-        0x07, 0x61, 0x71, 0x13, 0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91,
-        0xa1, 0xb1, 0xc1, 0x09, 0x23, 0x33, 0x52, 0xf0, 0x15, 0x62, 0x72,
-        0xd1, 0x0a, 0x16, 0x24, 0x34, 0xe1, 0x25, 0xf1, 0x17, 0x18, 0x19,
-        0x1a, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, 0x37, 0x38, 0x39,
-        0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x53, 0x54,
-        0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67,
-        0x68, 0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a,
-        0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x92, 0x93,
-        0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5,
-        0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7,
-        0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9,
-        0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe2,
-        0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf2, 0xf3, 0xf4,
-        0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xff, 0xda, 0x00, 0x0c, 0x03,
-        0x01, 0x00, 0x02, 0x11, 0x03, 0x11, 0x00, 0x3f, 0x00, 0x9e, 0xd2,
-        0x2e, 0x07, 0x15, 0xaf, 0x6d, 0x08, 0xe2, 0xb3, 0x45, 0x1a, 0xf6,
-        0xd0, 0x00, 0x01, 0xc5, 0x68, 0x45, 0x17, 0x4a, 0xb4, 0x22, 0xe4,
-        0x70, 0x8c, 0x74, 0xa9, 0x3c, 0xa1, 0x8e, 0x95, 0x48, 0x96, 0x31,
-        0xe2, 0x18, 0xe9, 0x55, 0xa5, 0x8c, 0x7a, 0x50, 0x05, 0x0b, 0x88,
-        0x86, 0x0f, 0x15, 0x8f, 0x75, 0x1f, 0x26, 0x93, 0x19, 0x91, 0x77,
-        0x18, 0xc1, 0xac, 0x4b, 0xc8, 0xfa, 0xd6, 0x63, 0x37, 0x6d, 0x31,
-        0xb4, 0x73, 0x5b, 0x36, 0xa0, 0x1c, 0x50, 0x80, 0xd7, 0x83, 0xa0,
-        0xab, 0xd1, 0x62, 0xad, 0x09, 0x8f, 0x17, 0x29, 0x03, 0xb2, 0xcc,
-        0xe0, 0x77, 0x14, 0xa3, 0x56, 0xb3, 0x27, 0x1e, 0x67, 0xe9, 0x52,
-        0xea, 0xc6, 0x3a, 0x36, 0x48, 0xef, 0x3d, 0x27, 0x70, 0x22, 0x60,
-        0x47, 0x52, 0x69, 0xb2, 0xe2, 0xad, 0x3b, 0xea, 0x80, 0xa3, 0x38,
-        0xe0, 0xd6, 0x3d, 0xd8, 0x1c, 0xd0, 0xca, 0x46, 0x3d, 0xd0, 0x18,
-        0x35, 0x89, 0x78, 0xa3, 0x9a, 0xcd, 0x8c, 0xd2, 0xb3, 0x93, 0x2a,
-        0x2b, 0x66, 0xd5, 0xf1, 0x8a, 0x10, 0x1a, 0xd6, 0xf2, 0x03, 0x8a,
-        0x9e, 0xe6, 0xf4, 0x5a, 0xdb, 0xef, 0xfe, 0x23, 0xc0, 0xa7, 0x27,
-        0xcb, 0x16, 0xc4, 0xcc, 0xdd, 0xe2, 0x78, 0x9a, 0x69, 0x66, 0xcc,
-        0x99, 0xe1, 0x4d, 0x47, 0xba, 0xbc, 0xd9, 0x6a, 0xee, 0x26, 0x59,
-        0x59, 0x4d, 0xac, 0x69, 0x34, 0x52, 0xe5, 0x8f, 0x55, 0xad, 0x58,
-        0xae, 0x85, 0xc4, 0x22, 0x41, 0xdf, 0xad, 0x76, 0x61, 0xe5, 0x6f,
-        0x74, 0x45, 0x69, 0xdc, 0x00, 0x79, 0xac, 0x8b, 0xa6, 0xc9, 0x35,
-        0xd4, 0x34, 0x64, 0xdc, 0x37, 0x06, 0xb1, 0xae, 0x88, 0xc1, 0xac,
-        0xd8, 0xc9, 0x2c, 0xa6, 0xe0, 0x73, 0x5b, 0x36, 0xf3, 0x74, 0xe6,
-        0x84, 0x05, 0xe3, 0xa9, 0x47, 0x6a, 0x14, 0xb6, 0x49, 0x3d, 0x85,
-        0x3a, 0xee, 0xee, 0x2b, 0xa8, 0xe2, 0x6f, 0x30, 0x81, 0xe9, 0x8a,
-        0xca, 0xa4, 0xe2, 0xd3, 0x8b, 0x01, 0xb1, 0xf9, 0x04, 0x7f, 0xaf,
-        0x23, 0xf0, 0xa9, 0x54, 0x41, 0x9c, 0xfd, 0xa3, 0xf4, 0xae, 0x65,
-        0x18, 0xf7, 0x25, 0x8a, 0xe2, 0x02, 0x38, 0xb8, 0xfd, 0x2a, 0x7b,
-        0x5b, 0xa8, 0x6d, 0x6d, 0x5d, 0x9a, 0x5d, 0xcb, 0xbb, 0xd2, 0xb6,
-        0xa6, 0xa3, 0x19, 0x5e, 0xe2, 0x03, 0x7b, 0x1d, 0xc2, 0x17, 0x8d,
-        0xb8, 0xac, 0xfb, 0x89, 0x39, 0x35, 0xd6, 0x9a, 0x6a, 0xe8, 0x66,
-        0x55, 0xcb, 0xf5, 0xac, 0x7b, 0x96, 0xeb, 0x50, 0xc6, 0x88, 0x6d,
-        0x66, 0xe9, 0xcd, 0x6c, 0xdb, 0x4f, 0xd3, 0x9a, 0x00, 0x2f, 0xe6,
-        0xf9, 0xa3, 0xe7, 0xb5, 0x4a, 0x93, 0x7f, 0xa2, 0xc6, 0x73, 0xdc,
-        0xd7, 0x15, 0x55, 0xef, 0x48, 0x7d, 0x09, 0x52, 0x6e, 0x3a, 0xd4,
-        0xab, 0x2f, 0xbd, 0x61, 0x16, 0x0c, 0x73, 0x49, 0xc5, 0x24, 0x92,
-        0x7f, 0xa2, 0x63, 0xfd, 0xaa, 0xd6, 0x2f, 0x71, 0x0e, 0xb1, 0x93,
-        0xf7, 0x2d, 0xf5, 0xa4, 0x9e, 0x4e, 0xb5, 0xdd, 0x4b, 0xf8, 0x68,
-        0x4c, 0xcb, 0xb9, 0x93, 0xad, 0x65, 0xce, 0xd9, 0x26, 0xa9, 0x8d,
-        0x19, 0xf6, 0xf2, 0xf4, 0xe6, 0xb5, 0xad, 0xe7, 0xc6, 0x39, 0xa0,
-        0x18, 0xeb, 0xc9, 0x77, 0x6c, 0x35, 0x2a, 0x4b, 0xfe, 0x8a, 0x9c,
-        0xff, 0x00, 0x11, 0xae, 0x3a, 0x8b, 0xde, 0x61, 0xd0, 0x9e, 0x39,
-        0xb8, 0xeb, 0x53, 0xac, 0xb9, 0xae, 0x5b, 0x00, 0xf3, 0x27, 0x14,
-        0x92, 0xc9, 0xfe, 0x8a, 0x3f, 0xde, 0x35, 0xac, 0x3a, 0x88, 0x92,
-        0xcd, 0xb1, 0x6e, 0x7d, 0xcd, 0x32, 0x67, 0xeb, 0xcd, 0x7a, 0x14,
-        0xfe, 0x04, 0x26, 0x66, 0xce, 0xf9, 0x26, 0xb3, 0xe6, 0x6e, 0xb4,
-        0xd9, 0x48, 0xc8, 0x82, 0x4e, 0x07, 0x35, 0xa7, 0x6f, 0x2f, 0x02,
-        0x9a, 0x06, 0x5f, 0x8c, 0xa4, 0x83, 0x0e, 0x32, 0x2a, 0x69, 0xe3,
-        0xdd, 0x12, 0x08, 0x97, 0x85, 0xec, 0x2a, 0x2a, 0x42, 0xf1, 0x76,
-        0x26, 0xe4, 0x6a, 0x59, 0x0e, 0x18, 0x10, 0x6a, 0xd2, 0x89, 0x02,
-        0x6e, 0x2a, 0x71, 0xeb, 0x5c, 0x1c, 0x8c, 0xa6, 0x48, 0xbb, 0xdc,
-        0x61, 0x41, 0x35, 0x72, 0x28, 0x87, 0xd9, 0xf6, 0x4a, 0xb9, 0xe7,
-        0x38, 0xae, 0x8c, 0x3d, 0x36, 0xdd, 0xde, 0xc4, 0xb0, 0x21, 0x51,
-        0x76, 0xa8, 0xc0, 0xaa, 0x93, 0x31, 0xe6, 0xbb, 0x2d, 0x65, 0x61,
-        0x19, 0xd3, 0x1e, 0xb5, 0x46, 0x5a, 0x96, 0x5a, 0x30, 0xa0, 0x7e,
-        0x05, 0x69, 0x5b, 0xc9, 0xc6, 0x28, 0x40, 0xcd, 0x08, 0x64, 0x3c,
-        0x73, 0x57, 0xe1, 0x94, 0xf1, 0xcd, 0x5a, 0x21, 0x8c, 0xb9, 0x63,
-        0xe7, 0x67, 0x1d, 0xab, 0x40, 0xb1, 0xfb, 0x00, 0x1d, 0xf0, 0x2b,
-        0x99, 0x2d, 0x66, 0x3e, 0x88, 0x75, 0x81, 0x3f, 0x31, 0xf6, 0xab,
-        0x64, 0xd6, 0xb4, 0x17, 0xee, 0xd0, 0x9e, 0xe4, 0x32, 0x1a, 0xa7,
-        0x31, 0xad, 0x18, 0x14, 0x26, 0xef, 0x54, 0xa5, 0xa8, 0x65, 0xa3,
-        0x9c, 0x81, 0xfa, 0x56, 0x8c, 0x2d, 0xce, 0x68, 0x40, 0xcb, 0xf1,
-        0x37, 0xbd, 0x5e, 0x85, 0xea, 0xd1, 0x0c, 0xbb, 0x19, 0x56, 0x23,
-        0x20, 0x1f, 0xad, 0x5c, 0x42, 0x08, 0x03, 0xb5, 0x55, 0x91, 0x04,
-        0xc9, 0x80, 0x38, 0x00, 0x0a, 0x71, 0x34, 0x6c, 0x32, 0x27, 0xe9,
-        0x55, 0x25, 0x15, 0x2c, 0x68, 0xa3, 0x30, 0xeb, 0x54, 0xa5, 0x15,
-        0x0c, 0xd1, 0x00, 0xff, 0xd9};
-
-    /* package */ static final byte[] sPhotoByteArrayForComplicatedCase;
-
-    static {
-        final int length = sPhotoIntArrayForComplicatedCase.length;
-        sPhotoByteArrayForComplicatedCase = new byte[length];
-        for (int i = 0; i < length; i++) {
-            sPhotoByteArrayForComplicatedCase[i] = (byte)sPhotoIntArrayForComplicatedCase[i];
-        }
-    }
-
-    public void testV21SimpleCase1_Parsing() {
-        mVerifier.initForImportTest(V21, R.raw.v21_simple_1);
-        mVerifier.addPropertyNodesVerifierElem()
-                .addExpectedNodeWithOrder("N", "Ando;Roid;", Arrays.asList("Ando", "Roid", ""));
-    }
-
-    public void testV21SimpleCase1_Type_Generic() {
-        mVerifier.initForImportTest(VCardConfig.VCARD_TYPE_V21_GENERIC_UTF8, R.raw.v21_simple_1);
-        mVerifier.addContentValuesVerifierElem()
-                .addExpected(StructuredName.CONTENT_ITEM_TYPE)
-                        .put(StructuredName.FAMILY_NAME, "Ando")
-                        .put(StructuredName.GIVEN_NAME, "Roid")
-                        .put(StructuredName.DISPLAY_NAME, "Roid Ando");
-    }
-
-    public void testV21SimpleCase1_Type_Japanese() {
-        mVerifier.initForImportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE_SJIS, R.raw.v21_simple_1);
-        mVerifier.addContentValuesVerifierElem()
-                .addExpected(StructuredName.CONTENT_ITEM_TYPE)
-                        .put(StructuredName.FAMILY_NAME, "Ando")
-                        .put(StructuredName.GIVEN_NAME, "Roid")
-                        // If name-related strings only contains printable Ascii,
-                        // the order is remained to be US's:
-                        // "Prefix Given Middle Family Suffix"
-                        .put(StructuredName.DISPLAY_NAME, "Roid Ando");
-    }
-
-    public void testV21SimpleCase2() {
-        mVerifier.initForImportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE_SJIS, R.raw.v21_simple_2);
-        mVerifier.addContentValuesVerifierElem()
-                .addExpected(StructuredName.CONTENT_ITEM_TYPE)
-                        .put(StructuredName.DISPLAY_NAME, "Ando Roid");
-    }
-
-    public void testV21SimpleCase3() {
-        mVerifier.initForImportTest(V21, R.raw.v21_simple_3);
-        mVerifier.addContentValuesVerifierElem()
-                .addExpected(StructuredName.CONTENT_ITEM_TYPE)
-                        .put(StructuredName.FAMILY_NAME, "Ando")
-                        .put(StructuredName.GIVEN_NAME, "Roid")
-                        // "FN" field should be prefered since it should contain the original
-                        // order intended by the author of the file.
-                        .put(StructuredName.DISPLAY_NAME, "Ando Roid");
-    }
-
-    /**
-     * Tests ';' is properly handled by VCardParser implementation.
-     */
-    public void testV21BackslashCase_Parsing() {
-        mVerifier.initForImportTest(V21, R.raw.v21_backslash);
-        mVerifier.addPropertyNodesVerifierElem()
-                .addExpectedNodeWithOrder("VERSION", "2.1")
-                .addExpectedNodeWithOrder("N", ";A;B\\;C\\;;D;:E;\\\\;",
-                        Arrays.asList("", "A;B\\", "C\\;", "D", ":E", "\\\\", ""))
-                .addExpectedNodeWithOrder("FN", "A;B\\C\\;D:E\\\\");
-        
-    }
-
-    /**
-     * Tests ContactStruct correctly ignores redundant fields in "N" property values and
-     * inserts name related data.
-     */
-    public void testV21BackslashCase() {
-        mVerifier.initForImportTest(V21, R.raw.v21_backslash);
-        mVerifier.addContentValuesVerifierElem()
-                .addExpected(StructuredName.CONTENT_ITEM_TYPE)
-                        // FAMILY_NAME is empty and removed in this test...
-                        .put(StructuredName.GIVEN_NAME, "A;B\\")
-                        .put(StructuredName.MIDDLE_NAME, "C\\;")
-                        .put(StructuredName.PREFIX, "D")
-                        .put(StructuredName.SUFFIX, ":E")
-                        .put(StructuredName.DISPLAY_NAME, "A;B\\C\\;D:E\\\\");
-    }
-
-    public void testOrgBeforTitle() {
-        mVerifier.initForImportTest(V21, R.raw.v21_org_before_title);
-        ContentValuesVerifierElem elem = mVerifier.addContentValuesVerifierElem();
-        elem.addExpected(StructuredName.CONTENT_ITEM_TYPE)
-                .put(StructuredName.DISPLAY_NAME, "Normal Guy");
-        elem.addExpected(Organization.CONTENT_ITEM_TYPE)
-                .put(Organization.COMPANY, "Company")
-                .put(Organization.DEPARTMENT, "Organization Devision Room Sheet No.")
-                .put(Organization.TITLE, "Excellent Janitor")
-                .put(Organization.TYPE, Organization.TYPE_WORK);
-    }
-
-    public void testTitleBeforOrg() {
-        mVerifier.initForImportTest(V21, R.raw.v21_title_before_org);
-        ContentValuesVerifierElem elem = mVerifier.addContentValuesVerifierElem();
-        elem.addExpected(StructuredName.CONTENT_ITEM_TYPE)
-                .put(StructuredName.DISPLAY_NAME, "Nice Guy");
-        elem.addExpected(Organization.CONTENT_ITEM_TYPE)
-                .put(Organization.COMPANY, "Marverous")
-                .put(Organization.DEPARTMENT, "Perfect Great Good Bad Poor")
-                .put(Organization.TITLE, "Cool Title")
-                .put(Organization.TYPE, Organization.TYPE_WORK);
-    }
-
-    /**
-     * Verifies that vCard importer correctly interpret "PREF" attribute to IS_PRIMARY.
-     * The data contain three cases: one "PREF", no "PREF" and multiple "PREF", in each type.
-     */
-    public void testV21PrefToIsPrimary() {
-        mVerifier.initForImportTest(V21, R.raw.v21_pref_handling);
-        ContentValuesVerifierElem elem = mVerifier.addContentValuesVerifierElem();
-        elem.addExpected(StructuredName.CONTENT_ITEM_TYPE)
-                .put(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE)
-                .put(StructuredName.DISPLAY_NAME, "Smith");
-        elem.addExpected(Phone.CONTENT_ITEM_TYPE)
-                .put(Phone.NUMBER, "1")
-                .put(Phone.TYPE, Phone.TYPE_HOME);
-        elem.addExpected(Phone.CONTENT_ITEM_TYPE)
-                .put(Phone.NUMBER, "2")
-                .put(Phone.TYPE, Phone.TYPE_WORK)
-                .put(Phone.IS_PRIMARY, 1);
-        elem.addExpected(Phone.CONTENT_ITEM_TYPE)
-                .put(Phone.NUMBER, "3")
-                .put(Phone.TYPE, Phone.TYPE_ISDN);
-        elem.addExpected(Email.CONTENT_ITEM_TYPE)
-                .put(Email.DATA, "test@example.com")
-                .put(Email.TYPE, Email.TYPE_HOME)
-                .put(Email.IS_PRIMARY, 1);
-        elem.addExpected(Email.CONTENT_ITEM_TYPE)
-                .put(Email.DATA, "test2@examination.com")
-                .put(Email.TYPE, Email.TYPE_MOBILE)
-                .put(Email.IS_PRIMARY, 1);
-        elem.addExpected(Organization.CONTENT_ITEM_TYPE)
-                .put(Organization.COMPANY, "Company")
-                .put(Organization.TITLE, "Engineer")
-                .put(Organization.TYPE, Organization.TYPE_WORK);
-        elem.addExpected(Organization.CONTENT_ITEM_TYPE)
-                .put(Organization.COMPANY, "Mystery")
-                .put(Organization.TITLE, "Blogger")
-                .put(Organization.TYPE, Organization.TYPE_WORK);
-        elem.addExpected(Organization.CONTENT_ITEM_TYPE)
-                .put(Organization.COMPANY, "Poetry")
-                .put(Organization.TITLE, "Poet")
-                .put(Organization.TYPE, Organization.TYPE_WORK);
-    }
-
-    /**
-     * Tests all the properties in a complicated vCard are correctly parsed by the VCardParser.
-     */
-    public void testV21ComplicatedCase_Parsing() {
-        mVerifier.initForImportTest(V21, R.raw.v21_complicated);
-        mVerifier.addPropertyNodesVerifierElem()
-                .addExpectedNodeWithOrder("VERSION", "2.1")
-                .addExpectedNodeWithOrder("N", "Gump;Forrest;Hoge;Pos;Tao",
-                        Arrays.asList("Gump", "Forrest", "Hoge", "Pos", "Tao"))
-                .addExpectedNodeWithOrder("FN", "Joe Due")
-                .addExpectedNodeWithOrder("ORG", "Gump Shrimp Co.;Sales Dept.;Manager;Fish keeper",
-                        Arrays.asList("Gump Shrimp Co.", "Sales Dept.;Manager", "Fish keeper"))
-                .addExpectedNodeWithOrder("ROLE", "Fish Cake Keeper!")
-                .addExpectedNodeWithOrder("TITLE", "Shrimp Man")
-                .addExpectedNodeWithOrder("X-CLASS", "PUBLIC")
-                .addExpectedNodeWithOrder("TEL", "(111) 555-1212", new TypeSet("WORK", "VOICE"))
-                .addExpectedNodeWithOrder("TEL", "(404) 555-1212", new TypeSet("HOME", "VOICE"))
-                .addExpectedNodeWithOrder("TEL", "0311111111", new TypeSet("CELL"))
-                .addExpectedNodeWithOrder("TEL", "0322222222", new TypeSet("VIDEO"))
-                .addExpectedNodeWithOrder("TEL", "0333333333", new TypeSet("VOICE"))
-                .addExpectedNodeWithOrder("ADR",
-                        ";;100 Waters Edge;Baytown;LA;30314;United States of America",
-                        Arrays.asList("", "", "100 Waters Edge", "Baytown",
-                                "LA", "30314", "United States of America"),
-                                null, null, new TypeSet("WORK"), null)
-                .addExpectedNodeWithOrder("LABEL",
-                        "100 Waters Edge\r\nBaytown, LA 30314\r\nUnited  States of America",
-                        null, null, mContentValuesForQP, new TypeSet("WORK"), null)
-                .addExpectedNodeWithOrder("ADR",
-                        ";;42 Plantation St.;Baytown;LA;30314;United States of America",
-                        Arrays.asList("", "", "42 Plantation St.", "Baytown",
-                                "LA", "30314", "United States of America"), null, null,
-                                new TypeSet("HOME"), null)
-                .addExpectedNodeWithOrder("LABEL",
-                        "42 Plantation St.\r\nBaytown, LA 30314\r\nUnited  States of America",
-                        null, null, mContentValuesForQP,
-                        new TypeSet("HOME"), null)
-                .addExpectedNodeWithOrder("EMAIL", "forrestgump@walladalla.com",
-                        new TypeSet("PREF", "INTERNET"))
-                .addExpectedNodeWithOrder("EMAIL", "cell@example.com", new TypeSet("CELL"))
-                .addExpectedNodeWithOrder("NOTE", "The following note is the example from RFC 2045.")
-                .addExpectedNodeWithOrder("NOTE",
-                        "Now's the time for all folk to come to the aid of their country.",
-                        null, null, mContentValuesForQP, null, null)
-                .addExpectedNodeWithOrder("PHOTO", null,
-                        null, sPhotoByteArrayForComplicatedCase, mContentValuesForBase64V21,
-                        new TypeSet("JPEG"), null)
-                .addExpectedNodeWithOrder("X-ATTRIBUTE", "Some String")
-                .addExpectedNodeWithOrder("BDAY", "19800101")
-                .addExpectedNodeWithOrder("GEO", "35.6563854,139.6994233")
-                .addExpectedNodeWithOrder("URL", "http://www.example.com/")
-                .addExpectedNodeWithOrder("REV", "20080424T195243Z");
-    }
-
-    /**
-     * Checks ContactStruct correctly inserts values in a complicated vCard
-     * into ContentResolver.
-     */
-    public void testV21ComplicatedCase() {
-        mVerifier.initForImportTest(V21, R.raw.v21_complicated);
-        ContentValuesVerifierElem elem = mVerifier.addContentValuesVerifierElem();
-        elem.addExpected(StructuredName.CONTENT_ITEM_TYPE)
-                .put(StructuredName.FAMILY_NAME, "Gump")
-                .put(StructuredName.GIVEN_NAME, "Forrest")
-                .put(StructuredName.MIDDLE_NAME, "Hoge")
-                .put(StructuredName.PREFIX, "Pos")
-                .put(StructuredName.SUFFIX, "Tao")
-                .put(StructuredName.DISPLAY_NAME, "Joe Due");
-        elem.addExpected(Organization.CONTENT_ITEM_TYPE)
-                .put(Organization.TYPE, Organization.TYPE_WORK)
-                .put(Organization.COMPANY, "Gump Shrimp Co.")
-                .put(Organization.DEPARTMENT, "Sales Dept.;Manager Fish keeper")
-                .put(Organization.TITLE, "Shrimp Man");
-        elem.addExpected(Phone.CONTENT_ITEM_TYPE)
-                .put(Phone.TYPE, Phone.TYPE_WORK)
-                // Phone number is expected to be formated with NAMP format in default.
-                .put(Phone.NUMBER, "111-555-1212");
-        elem.addExpected(Phone.CONTENT_ITEM_TYPE)
-                .put(Phone.TYPE, Phone.TYPE_HOME)
-                .put(Phone.NUMBER, "404-555-1212");
-        elem.addExpected(Phone.CONTENT_ITEM_TYPE)
-                .put(Phone.TYPE, Phone.TYPE_MOBILE)
-                .put(Phone.NUMBER, "031-111-1111");
-        elem.addExpected(Phone.CONTENT_ITEM_TYPE)
-                .put(Phone.TYPE, Phone.TYPE_CUSTOM)
-                .put(Phone.LABEL, "VIDEO")
-                .put(Phone.NUMBER, "032-222-2222");
-        elem.addExpected(Phone.CONTENT_ITEM_TYPE)
-                .put(Phone.TYPE, Phone.TYPE_CUSTOM)
-                .put(Phone.LABEL, "VOICE")
-                .put(Phone.NUMBER, "033-333-3333");
-        elem.addExpected(StructuredPostal.CONTENT_ITEM_TYPE)
-                .put(StructuredPostal.TYPE, StructuredPostal.TYPE_WORK)
-                .put(StructuredPostal.COUNTRY, "United States of America")
-                .put(StructuredPostal.POSTCODE, "30314")
-                .put(StructuredPostal.REGION, "LA")
-                .put(StructuredPostal.CITY, "Baytown")
-                .put(StructuredPostal.STREET, "100 Waters Edge")
-                .put(StructuredPostal.FORMATTED_ADDRESS,
-                        "100 Waters Edge Baytown LA 30314 United States of America");
-        elem.addExpected(StructuredPostal.CONTENT_ITEM_TYPE)
-                .put(StructuredPostal.TYPE, StructuredPostal.TYPE_HOME)
-                .put(StructuredPostal.COUNTRY, "United States of America")
-                .put(StructuredPostal.POSTCODE, "30314")
-                .put(StructuredPostal.REGION, "LA")
-                .put(StructuredPostal.CITY, "Baytown")
-                .put(StructuredPostal.STREET, "42 Plantation St.")
-                .put(StructuredPostal.FORMATTED_ADDRESS,
-                        "42 Plantation St. Baytown LA 30314 United States of America");
-        elem.addExpected(Email.CONTENT_ITEM_TYPE)
-                // "TYPE=INTERNET" -> TYPE_CUSTOM + the label "INTERNET"
-                .put(Email.TYPE, Email.TYPE_CUSTOM)
-                .put(Email.LABEL, "INTERNET")
-                .put(Email.DATA, "forrestgump@walladalla.com")
-                .put(Email.IS_PRIMARY, 1);
-        elem.addExpected(Email.CONTENT_ITEM_TYPE)
-                .put(Email.TYPE, Email.TYPE_MOBILE)
-                .put(Email.DATA, "cell@example.com");
-        elem.addExpected(Note.CONTENT_ITEM_TYPE)
-                .put(Note.NOTE, "The following note is the example from RFC 2045.");
-        elem.addExpected(Note.CONTENT_ITEM_TYPE)
-                .put(Note.NOTE,
-                        "Now's the time for all folk to come to the aid of their country.");
-        elem.addExpected(Photo.CONTENT_ITEM_TYPE)
-                // No information about its image format can be inserted.
-                .put(Photo.PHOTO, sPhotoByteArrayForComplicatedCase);
-        elem.addExpected(Event.CONTENT_ITEM_TYPE)
-                .put(Event.START_DATE, "19800101")
-                .put(Event.TYPE, Event.TYPE_BIRTHDAY);
-        elem.addExpected(Website.CONTENT_ITEM_TYPE)
-                .put(Website.URL, "http://www.example.com/")
-                .put(Website.TYPE, Website.TYPE_HOMEPAGE);
-    }
-
-    public void testV30Simple_Parsing() {
-        mVerifier.initForImportTest(V30, R.raw.v30_simple);
-        mVerifier.addPropertyNodesVerifierElem()
-                .addExpectedNodeWithOrder("VERSION", "3.0")
-                .addExpectedNodeWithOrder("FN", "And Roid")
-                .addExpectedNodeWithOrder("N", "And;Roid;;;", Arrays.asList("And", "Roid", "", "", ""))
-                .addExpectedNodeWithOrder("ORG", "Open;Handset; Alliance",
-                        Arrays.asList("Open", "Handset", " Alliance"))
-                .addExpectedNodeWithOrder("SORT-STRING", "android")
-                .addExpectedNodeWithOrder("TEL", "0300000000", new TypeSet("PREF", "VOICE"))
-                .addExpectedNodeWithOrder("CLASS", "PUBLIC")
-                .addExpectedNodeWithOrder("X-GNO", "0")
-                .addExpectedNodeWithOrder("X-GN", "group0")
-                .addExpectedNodeWithOrder("X-REDUCTION", "0")
-                .addExpectedNodeWithOrder("REV", "20081031T065854Z");
-    }
-
-    public void testV30Simple() {
-        mVerifier.initForImportTest(V30, R.raw.v30_simple);
-        ContentValuesVerifierElem elem = mVerifier.addContentValuesVerifierElem();
-        elem.addExpected(StructuredName.CONTENT_ITEM_TYPE)
-                .put(StructuredName.FAMILY_NAME, "And")
-                .put(StructuredName.GIVEN_NAME, "Roid")
-                .put(StructuredName.DISPLAY_NAME, "And Roid")
-                .put(StructuredName.PHONETIC_GIVEN_NAME, "android");
-        elem.addExpected(Organization.CONTENT_ITEM_TYPE)
-                .put(Organization.COMPANY, "Open")
-                .put(Organization.DEPARTMENT, "Handset  Alliance")
-                .put(Organization.TYPE, Organization.TYPE_WORK);
-        elem.addExpected(Phone.CONTENT_ITEM_TYPE)
-                .put(Phone.TYPE, Phone.TYPE_CUSTOM)
-                .put(Phone.LABEL, "VOICE")
-                .put(Phone.NUMBER, "030-000-0000")
-                .put(Phone.IS_PRIMARY, 1);
-    }
-
-    public void testV21Japanese1_Parsing() {
-        // 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.addPropertyNodesVerifierElem()
-                .addExpectedNodeWithOrder("VERSION", "2.1", null, null, null, null, null)
-                .addExpectedNodeWithOrder("N", "\u5B89\u85E4\u30ED\u30A4\u30C9;;;;",
-                        Arrays.asList("\u5B89\u85E4\u30ED\u30A4\u30C9", "", "", "", ""),
-                        null, mContentValuesForSJis, null, null)
-                .addExpectedNodeWithOrder("SOUND",
-                        "\uFF71\uFF9D\uFF84\uFF9E\uFF73\uFF9B\uFF72\uFF84\uFF9E;;;;",
-                        null, null, mContentValuesForSJis,
-                        new TypeSet("X-IRMC-N"), null)
-                .addExpectedNodeWithOrder("TEL", "0300000000", null, null, null,
-                        new TypeSet("VOICE", "PREF"), null);
-    }
-
-    private void testV21Japanese1Common(int resId, int vcardType, boolean japanese) {
-        mVerifier.initForImportTest(vcardType, resId);
-        ContentValuesVerifierElem elem = mVerifier.addContentValuesVerifierElem();
-        elem.addExpected(StructuredName.CONTENT_ITEM_TYPE)
-                .put(StructuredName.FAMILY_NAME, "\u5B89\u85E4\u30ED\u30A4\u30C9")
-                .put(StructuredName.DISPLAY_NAME, "\u5B89\u85E4\u30ED\u30A4\u30C9")
-                // While vCard parser does not split "SOUND" property values,
-                // ContactStruct care it.
-                .put(StructuredName.PHONETIC_GIVEN_NAME,
-                        "\uFF71\uFF9D\uFF84\uFF9E\uFF73\uFF9B\uFF72\uFF84\uFF9E");
-        elem.addExpected(Phone.CONTENT_ITEM_TYPE)
-                // Phone number formatting is different.
-                .put(Phone.NUMBER, (japanese ? "03-0000-0000" : "030-000-0000"))
-                .put(Phone.TYPE, Phone.TYPE_CUSTOM)
-                .put(Phone.LABEL, "VOICE")
-                .put(Phone.IS_PRIMARY, 1);
-    }
-
-    /**
-     * Verifies vCard with Japanese can be parsed correctly with
-     * {@link android.pim.vcard.VCardConfig#VCARD_TYPE_V21_GENERIC_UTF8}.
-     */
-    public void testV21Japanese1_Type_Generic_Utf8() {
-        testV21Japanese1Common(
-                R.raw.v21_japanese_1, VCardConfig.VCARD_TYPE_V21_GENERIC_UTF8, false);
-    }
-
-    /**
-     * Verifies vCard with Japanese can be parsed correctly with
-     * {@link android.pim.vcard.VCardConfig#VCARD_TYPE_V21_JAPANESE_SJIS}.
-     */
-    public void testV21Japanese1_Type_Japanese_Sjis() {
-        testV21Japanese1Common(
-                R.raw.v21_japanese_1, VCardConfig.VCARD_TYPE_V21_JAPANESE_SJIS, true);
-    }
-
-    /**
-     * Verifies vCard with Japanese can be parsed correctly with
-     * {@link android.pim.vcard.VCardConfig#VCARD_TYPE_V21_JAPANESE_UTF8}.
-     * 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);
-    }
-
-    public void testV21Japanese2_Parsing() {
-        mVerifier.initForImportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE_SJIS,
-                R.raw.v21_japanese_2);
-        mVerifier.addPropertyNodesVerifierElem()
-                .addExpectedNodeWithOrder("VERSION", "2.1")
-                .addExpectedNodeWithOrder("N", "\u5B89\u85E4;\u30ED\u30A4\u30C9\u0031;;;",
-                        Arrays.asList("\u5B89\u85E4", "\u30ED\u30A4\u30C9\u0031",
-                                "", "", ""),
-                        null, mContentValuesForSJis, null, null)
-                .addExpectedNodeWithOrder("FN", "\u5B89\u85E4\u0020\u30ED\u30A4\u30C9\u0020\u0031",
-                        null, null, mContentValuesForSJis, null, null)
-                .addExpectedNodeWithOrder("SOUND",
-                        "\uFF71\uFF9D\uFF84\uFF9E\uFF73;\uFF9B\uFF72\uFF84\uFF9E\u0031;;;",
-                        null, null, mContentValuesForSJis,
-                        new TypeSet("X-IRMC-N"), null)
-                .addExpectedNodeWithOrder("ADR",
-                        ";\u6771\u4EAC\u90FD\u6E0B\u8C37\u533A\u685C" +
-                        "\u4E18\u753A\u0032\u0036\u002D\u0031\u30BB" +
-                        "\u30EB\u30EA\u30A2\u30F3\u30BF\u30EF\u30FC\u0036" +
-                        "\u968E;;;;150-8512;",
-                        Arrays.asList("",
-                                "\u6771\u4EAC\u90FD\u6E0B\u8C37\u533A\u685C" +
-                                "\u4E18\u753A\u0032\u0036\u002D\u0031\u30BB" +
-                                "\u30EB\u30EA\u30A2\u30F3\u30BF\u30EF\u30FC" +
-                                "\u0036\u968E", "", "", "", "150-8512", ""),
-                        null, mContentValuesForQPAndSJis, new TypeSet("HOME"), null)
-                .addExpectedNodeWithOrder("NOTE", "\u30E1\u30E2", null, null,
-                        mContentValuesForQPAndSJis, null, null);
-    }
-
-    public void testV21Japanese2_Type_Generic_Utf8() {
-        mVerifier.initForImportTest(V21, R.raw.v21_japanese_2);
-        ContentValuesVerifierElem elem = mVerifier.addContentValuesVerifierElem();
-        elem.addExpected(StructuredName.CONTENT_ITEM_TYPE)
-                .put(StructuredName.FAMILY_NAME, "\u5B89\u85E4")
-                .put(StructuredName.GIVEN_NAME, "\u30ED\u30A4\u30C9\u0031")
-                .put(StructuredName.DISPLAY_NAME,
-                        "\u5B89\u85E4\u0020\u30ED\u30A4\u30C9\u0020\u0031")
-                // ContactStruct should correctly split "SOUND" property into several elements,
-                // even though VCardParser side does not care it.
-                .put(StructuredName.PHONETIC_FAMILY_NAME, "\uFF71\uFF9D\uFF84\uFF9E\uFF73")
-                .put(StructuredName.PHONETIC_GIVEN_NAME, "\uFF9B\uFF72\uFF84\uFF9E\u0031");
-        elem.addExpected(StructuredPostal.CONTENT_ITEM_TYPE)
-                .put(StructuredPostal.POSTCODE, "150-8512")
-                .put(StructuredPostal.STREET,
-                        "\u6771\u4EAC\u90FD\u6E0B\u8C37\u533A\u685C" +
-                        "\u4E18\u753A\u0032\u0036\u002D\u0031\u30BB" +
-                        "\u30EB\u30EA\u30A2\u30F3\u30BF\u30EF\u30FC" +
-                        "\u0036\u968E")
-                .put(StructuredPostal.FORMATTED_ADDRESS,
-                        "\u6771\u4EAC\u90FD\u6E0B\u8C37\u533A\u685C" +
-                        "\u4E18\u753A\u0032\u0036\u002D\u0031\u30BB" +
-                        "\u30EB\u30EA\u30A2\u30F3\u30BF\u30EF\u30FC" +
-                        "\u0036\u968E 150-8512")
-                .put(StructuredPostal.TYPE, StructuredPostal.TYPE_HOME);
-        elem.addExpected(Note.CONTENT_ITEM_TYPE)
-                .put(Note.NOTE, "\u30E1\u30E2");
-    }
-
-    public void testV21MultipleEntryCase_Parse() {
-        mVerifier.initForImportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE_SJIS,
-                R.raw.v21_multiple_entry);
-        mVerifier.addPropertyNodesVerifierElem()
-                .addExpectedNodeWithOrder("VERSION", "2.1")
-                .addExpectedNodeWithOrder("N", "\u5B89\u85E4\u30ED\u30A4\u30C9\u0033;;;;",
-                        Arrays.asList("\u5B89\u85E4\u30ED\u30A4\u30C9\u0033", "", "", "", ""),
-                        null, mContentValuesForSJis, null, null)
-                .addExpectedNodeWithOrder("SOUND",
-                        "\uFF71\uFF9D\uFF84\uFF9E\uFF73\uFF9B\uFF72\uFF84\uFF9E\u0033;;;;",
-                        null, null, mContentValuesForSJis,
-                        new TypeSet("X-IRMC-N"), null)
-                .addExpectedNodeWithOrder("TEL", "9", new TypeSet("X-NEC-SECRET"))
-                .addExpectedNodeWithOrder("TEL", "10", new TypeSet("X-NEC-HOTEL"))
-                .addExpectedNodeWithOrder("TEL", "11", new TypeSet("X-NEC-SCHOOL"))
-                .addExpectedNodeWithOrder("TEL", "12", new TypeSet("FAX", "HOME"));
-        mVerifier.addPropertyNodesVerifierElem()
-                .addExpectedNodeWithOrder("VERSION", "2.1")
-                .addExpectedNodeWithOrder("N", "\u5B89\u85E4\u30ED\u30A4\u30C9\u0034;;;;",
-                        Arrays.asList("\u5B89\u85E4\u30ED\u30A4\u30C9\u0034", "", "", "", ""),
-                        null, mContentValuesForSJis, null, null)
-                .addExpectedNodeWithOrder("SOUND",
-                        "\uFF71\uFF9D\uFF84\uFF9E\uFF73\uFF9B\uFF72\uFF84\uFF9E\u0034;;;;",
-                        null, null, mContentValuesForSJis,
-                        new TypeSet("X-IRMC-N"), null)
-                .addExpectedNodeWithOrder("TEL", "13", new TypeSet("MODEM"))
-                .addExpectedNodeWithOrder("TEL", "14", new TypeSet("PAGER"))
-                .addExpectedNodeWithOrder("TEL", "15", new TypeSet("X-NEC-FAMILY"))
-                .addExpectedNodeWithOrder("TEL", "16", new TypeSet("X-NEC-GIRL"));
-        mVerifier.addPropertyNodesVerifierElem()
-                .addExpectedNodeWithOrder("VERSION", "2.1")
-                .addExpectedNodeWithOrder("N", "\u5B89\u85E4\u30ED\u30A4\u30C9\u0035;;;;",
-                        Arrays.asList("\u5B89\u85E4\u30ED\u30A4\u30C9\u0035", "", "", "", ""),
-                        null, mContentValuesForSJis, null, null)
-                .addExpectedNodeWithOrder("SOUND",
-                        "\uFF71\uFF9D\uFF84\uFF9E\uFF73\uFF9B\uFF72\uFF84\uFF9E\u0035;;;;",
-                        null, null, mContentValuesForSJis,
-                        new TypeSet("X-IRMC-N"), null)
-                .addExpectedNodeWithOrder("TEL", "17", new TypeSet("X-NEC-BOY"))
-                .addExpectedNodeWithOrder("TEL", "18", new TypeSet("X-NEC-FRIEND"))
-                .addExpectedNodeWithOrder("TEL", "19", new TypeSet("X-NEC-PHS"))
-                .addExpectedNodeWithOrder("TEL", "20", new TypeSet("X-NEC-RESTAURANT"));
-    }
-
-    public void testV21MultipleEntryCase() {
-        mVerifier.initForImportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE_SJIS,
-                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")
-                .put(StructuredName.DISPLAY_NAME, "\u5B89\u85E4\u30ED\u30A4\u30C9\u0033")
-                .put(StructuredName.PHONETIC_GIVEN_NAME,
-                        "\uFF71\uFF9D\uFF84\uFF9E\uFF73\uFF9B\uFF72\uFF84\uFF9E\u0033");
-        elem.addExpected(Phone.CONTENT_ITEM_TYPE)
-                .put(Phone.TYPE, Phone.TYPE_CUSTOM)
-                .put(Phone.LABEL, "NEC-SECRET")
-                .put(Phone.NUMBER, "9");
-        elem.addExpected(Phone.CONTENT_ITEM_TYPE)
-                .put(Phone.TYPE, Phone.TYPE_CUSTOM)
-                .put(Phone.LABEL, "NEC-HOTEL")
-                .put(Phone.NUMBER, "10");
-        elem.addExpected(Phone.CONTENT_ITEM_TYPE)
-                .put(Phone.TYPE, Phone.TYPE_CUSTOM)
-                .put(Phone.LABEL, "NEC-SCHOOL")
-                .put(Phone.NUMBER, "11");
-        elem.addExpected(Phone.CONTENT_ITEM_TYPE)
-                .put(Phone.TYPE, Phone.TYPE_FAX_HOME)
-                .put(Phone.NUMBER, "12");
-
-        elem = mVerifier.addContentValuesVerifierElem();
-        elem.addExpected(StructuredName.CONTENT_ITEM_TYPE)
-                .put(StructuredName.FAMILY_NAME, "\u5B89\u85E4\u30ED\u30A4\u30C9\u0034")
-                .put(StructuredName.DISPLAY_NAME, "\u5B89\u85E4\u30ED\u30A4\u30C9\u0034")
-                .put(StructuredName.PHONETIC_GIVEN_NAME,
-                        "\uFF71\uFF9D\uFF84\uFF9E\uFF73\uFF9B\uFF72\uFF84\uFF9E\u0034");
-        elem.addExpected(Phone.CONTENT_ITEM_TYPE)
-                .put(Phone.TYPE, Phone.TYPE_CUSTOM)
-                .put(Phone.LABEL, "MODEM")
-                .put(Phone.NUMBER, "13");
-        elem.addExpected(Phone.CONTENT_ITEM_TYPE)
-                .put(Phone.TYPE, Phone.TYPE_PAGER)
-                .put(Phone.NUMBER, "14");
-        elem.addExpected(Phone.CONTENT_ITEM_TYPE)
-                .put(Phone.TYPE, Phone.TYPE_CUSTOM)
-                .put(Phone.LABEL, "NEC-FAMILY")
-                .put(Phone.NUMBER, "15");
-        elem.addExpected(Phone.CONTENT_ITEM_TYPE)
-                .put(Phone.TYPE, Phone.TYPE_CUSTOM)
-                .put(Phone.LABEL, "NEC-GIRL")
-                .put(Phone.NUMBER, "16");
-
-        elem = mVerifier.addContentValuesVerifierElem();
-        elem.addExpected(StructuredName.CONTENT_ITEM_TYPE)
-                .put(StructuredName.FAMILY_NAME, "\u5B89\u85E4\u30ED\u30A4\u30C9\u0035")
-                .put(StructuredName.DISPLAY_NAME, "\u5B89\u85E4\u30ED\u30A4\u30C9\u0035")
-                .put(StructuredName.PHONETIC_GIVEN_NAME,
-                        "\uFF71\uFF9D\uFF84\uFF9E\uFF73\uFF9B\uFF72\uFF84\uFF9E\u0035");
-        elem.addExpected(Phone.CONTENT_ITEM_TYPE)
-                .put(Phone.TYPE, Phone.TYPE_CUSTOM)
-                .put(Phone.LABEL, "NEC-BOY")
-                .put(Phone.NUMBER, "17");
-        elem.addExpected(Phone.CONTENT_ITEM_TYPE)
-                .put(Phone.TYPE, Phone.TYPE_CUSTOM)
-                .put(Phone.LABEL, "NEC-FRIEND")
-                .put(Phone.NUMBER, "18");
-        elem.addExpected(Phone.CONTENT_ITEM_TYPE)
-                .put(Phone.TYPE, Phone.TYPE_CUSTOM)
-                .put(Phone.LABEL, "NEC-PHS")
-                .put(Phone.NUMBER, "19");
-        elem.addExpected(Phone.CONTENT_ITEM_TYPE)
-                .put(Phone.TYPE, Phone.TYPE_CUSTOM)
-                .put(Phone.LABEL, "NEC-RESTAURANT")
-                .put(Phone.NUMBER, "20");
-    }
-
-    public void testIgnoreAgentV21_Parse() {
-        mVerifier.initForImportTest(V21, R.raw.v21_winmo_65);
-        ContentValues contentValuesForValue = new ContentValues();
-        contentValuesForValue.put("VALUE", "DATE");
-        mVerifier.addPropertyNodesVerifierElem()
-                .addExpectedNodeWithOrder("VERSION", "2.1")
-                .addExpectedNodeWithOrder("N", Arrays.asList("Example", "", "", "", ""))
-                .addExpectedNodeWithOrder("FN", "Example")
-                .addExpectedNodeWithOrder("ANNIVERSARY", "20091010", contentValuesForValue)
-                .addExpectedNodeWithOrder("AGENT", "")
-                .addExpectedNodeWithOrder("X-CLASS", "PUBLIC")
-                .addExpectedNodeWithOrder("X-REDUCTION", "")
-                .addExpectedNodeWithOrder("X-NO", "");
-    }
-
-    public void testIgnoreAgentV21() {
-        mVerifier.initForImportTest(V21, R.raw.v21_winmo_65);
-        ContentValuesVerifier verifier = new ContentValuesVerifier();
-        ContentValuesVerifierElem elem = mVerifier.addContentValuesVerifierElem();
-        elem.addExpected(StructuredName.CONTENT_ITEM_TYPE)
-                .put(StructuredName.FAMILY_NAME, "Example")
-                .put(StructuredName.DISPLAY_NAME, "Example");
-    }
-
-    public void testTolerateInvalidCommentLikeLineV21() {
-        mVerifier.initForImportTest(V21, R.raw.v21_invalid_comment_line);
-        ContentValuesVerifierElem elem = mVerifier.addContentValuesVerifierElem();
-        elem.addExpected(StructuredName.CONTENT_ITEM_TYPE)
-                .put(StructuredName.GIVEN_NAME, "Conference Call")
-                .put(StructuredName.DISPLAY_NAME, "Conference Call");
-        elem.addExpected(Note.CONTENT_ITEM_TYPE)
-                .put(Note.NOTE, "This is an (sharp ->#<- sharp) example. "
-                        + "This message must NOT be ignored.");
-    }
-
-    public void testPagerV30_Parse() {
-        mVerifier.initForImportTest(V30, R.raw.v30_comma_separated);
-        mVerifier.addPropertyNodesVerifierElem()
-                .addExpectedNodeWithOrder("VERSION", "3.0")
-                .addExpectedNodeWithOrder("N", Arrays.asList("F", "G", "M", "", ""))
-                .addExpectedNodeWithOrder("TEL", "6101231234@pagersample.com",
-                        new TypeSet("WORK", "MSG", "PAGER"));
-    }
-
-    public void testPagerV30() {
-        mVerifier.initForImportTest(V30, R.raw.v30_comma_separated);
-        ContentValuesVerifierElem elem = mVerifier.addContentValuesVerifierElem();
-        elem.addExpected(StructuredName.CONTENT_ITEM_TYPE)
-                .put(StructuredName.FAMILY_NAME, "F")
-                .put(StructuredName.MIDDLE_NAME, "M")
-                .put(StructuredName.GIVEN_NAME, "G")
-                .put(StructuredName.DISPLAY_NAME, "G M F");
-        elem.addExpected(Phone.CONTENT_ITEM_TYPE)
-                .put(Phone.TYPE, Phone.TYPE_PAGER)
-                .put(Phone.NUMBER, "6101231234@pagersample.com");
-    }
-}
diff --git a/core/tests/coretests/src/android/pim/vcard/VCardJapanizationTests.java b/core/tests/coretests/src/android/pim/vcard/VCardJapanizationTests.java
deleted file mode 100644
index 5b60342..0000000
--- a/core/tests/coretests/src/android/pim/vcard/VCardJapanizationTests.java
+++ /dev/null
@@ -1,434 +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.pim.vcard;
-
-import android.content.ContentValues;
-import android.pim.vcard.VCardConfig;
-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 {
-    private void testNameUtf8Common(int vcardType) {
-        mVerifier.initForExportTest(vcardType);
-        ContactEntry entry = mVerifier.addInputEntry();
-        entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
-                .put(StructuredName.FAMILY_NAME, "\u3075\u308B\u3069")
-                .put(StructuredName.GIVEN_NAME, "\u3091\u308A\u304B")
-                .put(StructuredName.MIDDLE_NAME, "B")
-                .put(StructuredName.PREFIX, "Dr.")
-                .put(StructuredName.SUFFIX, "Ph.D");
-        ContentValues contentValues =
-            (VCardConfig.isV30(vcardType) ? null : mContentValuesForQPAndUtf8);
-        mVerifier.addPropertyNodesVerifierElem()
-                .addExpectedNode("FN", "Dr. \u3075\u308B\u3069 B \u3091\u308A\u304B Ph.D",
-                        contentValues)
-                .addExpectedNode("N", "\u3075\u308B\u3069;\u3091\u308A\u304B;B;Dr.;Ph.D",
-                        Arrays.asList(
-                                "\u3075\u308B\u3069", "\u3091\u308A\u304B", "B", "Dr.", "Ph.D"),
-                                null, contentValues, null, null);
-    }
-
-    public void testNameUtf8V21() {
-        testNameUtf8Common(VCardConfig.VCARD_TYPE_V21_JAPANESE_UTF8);
-    }
-
-    public void testNameUtf8V30() {
-        testNameUtf8Common(VCardConfig.VCARD_TYPE_V30_JAPANESE_UTF8);
-    }
-
-    public void testNameShiftJis() {
-        mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_V30_JAPANESE_SJIS);
-        ContactEntry entry = mVerifier.addInputEntry();
-        entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
-                .put(StructuredName.FAMILY_NAME, "\u3075\u308B\u3069")
-                .put(StructuredName.GIVEN_NAME, "\u3091\u308A\u304B")
-                .put(StructuredName.MIDDLE_NAME, "B")
-                .put(StructuredName.PREFIX, "Dr.")
-                .put(StructuredName.SUFFIX, "Ph.D");
-
-        mVerifier.addPropertyNodesVerifierElem()
-                .addExpectedNode("FN", "Dr. \u3075\u308B\u3069 B \u3091\u308A\u304B Ph.D",
-                        mContentValuesForSJis)
-                .addExpectedNode("N", "\u3075\u308B\u3069;\u3091\u308A\u304B;B;Dr.;Ph.D",
-                        Arrays.asList(
-                                "\u3075\u308B\u3069", "\u3091\u308A\u304B", "B", "Dr.", "Ph.D"),
-                                null, mContentValuesForSJis, null, null);
-    }
-
-    /**
-     * DoCoMo phones require all name elements should be in "family name" field.
-     */
-    public void testNameDoCoMo() {
-        mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO);
-        ContactEntry entry = mVerifier.addInputEntry();
-        entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
-                .put(StructuredName.FAMILY_NAME, "\u3075\u308B\u3069")
-                .put(StructuredName.GIVEN_NAME, "\u3091\u308A\u304B")
-                .put(StructuredName.MIDDLE_NAME, "B")
-                .put(StructuredName.PREFIX, "Dr.")
-                .put(StructuredName.SUFFIX, "Ph.D");
-
-        final String fullName = "Dr. \u3075\u308B\u3069 B \u3091\u308A\u304B Ph.D";
-        mVerifier.addPropertyNodesVerifierElem()
-                .addExpectedNode("N", fullName + ";;;;",
-                        Arrays.asList(fullName, "", "", "", ""),
-                        null, mContentValuesForSJis, null, null)
-                .addExpectedNode("FN", fullName, mContentValuesForSJis)
-                .addExpectedNode("SOUND", ";;;;", new TypeSet("X-IRMC-N"))
-                .addExpectedNode("TEL", "", new TypeSet("HOME"))
-                .addExpectedNode("EMAIL", "", new TypeSet("HOME"))
-                .addExpectedNode("ADR", "", new TypeSet("HOME"))
-                .addExpectedNode("X-CLASS", "PUBLIC")
-                .addExpectedNode("X-REDUCTION", "")
-                .addExpectedNode("X-NO", "")
-                .addExpectedNode("X-DCM-HMN-MODE", "");
-    }
-
-    private void testPhoneticNameCommon(int vcardType) {
-        mVerifier.initForExportTest(vcardType);
-        ContactEntry entry = mVerifier.addInputEntry();
-        entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
-                .put(StructuredName.PHONETIC_FAMILY_NAME, "\u3084\u307E\u3060")
-                .put(StructuredName.PHONETIC_MIDDLE_NAME, "\u30DF\u30C9\u30EB\u30CD\u30FC\u30E0")
-                .put(StructuredName.PHONETIC_GIVEN_NAME, "\u305F\u308D\u3046");
-
-        final ContentValues contentValues =
-            (VCardConfig.usesShiftJis(vcardType) ?
-                    (VCardConfig.isV30(vcardType) ? mContentValuesForSJis :
-                            mContentValuesForQPAndSJis) :
-                    (VCardConfig.isV30(vcardType) ? null : mContentValuesForQPAndUtf8));
-        PropertyNodesVerifierElem elem = mVerifier.addPropertyNodesVerifierElemWithEmptyName();
-        elem.addExpectedNode("X-PHONETIC-LAST-NAME", "\u3084\u307E\u3060",
-                        contentValues)
-                .addExpectedNode("X-PHONETIC-MIDDLE-NAME",
-                        "\u30DF\u30C9\u30EB\u30CD\u30FC\u30E0",
-                        contentValues)
-                .addExpectedNode("X-PHONETIC-FIRST-NAME", "\u305F\u308D\u3046",
-                        contentValues);
-        if (VCardConfig.isV30(vcardType)) {
-            elem.addExpectedNode("SORT-STRING",
-                    "\u3084\u307E\u3060 \u30DF\u30C9\u30EB\u30CD\u30FC\u30E0 \u305F\u308D\u3046",
-                    contentValues);
-        }
-        ContentValuesBuilder builder = mVerifier.addContentValuesVerifierElem()
-                .addExpected(StructuredName.CONTENT_ITEM_TYPE);
-        builder.put(StructuredName.PHONETIC_FAMILY_NAME, "\u3084\u307E\u3060")
-                .put(StructuredName.PHONETIC_MIDDLE_NAME, "\u30DF\u30C9\u30EB\u30CD\u30FC\u30E0")
-                .put(StructuredName.PHONETIC_GIVEN_NAME, "\u305F\u308D\u3046")
-                .put(StructuredName.DISPLAY_NAME,
-                        "\u3084\u307E\u3060 \u30DF\u30C9\u30EB\u30CD\u30FC\u30E0 " +
-                        "\u305F\u308D\u3046");
-    }
-
-    public void testPhoneticNameForJapaneseV21Utf8() {
-        testPhoneticNameCommon(VCardConfig.VCARD_TYPE_V21_JAPANESE_UTF8);
-    }
-
-    public void testPhoneticNameForJapaneseV21Sjis() {
-        testPhoneticNameCommon(VCardConfig.VCARD_TYPE_V21_JAPANESE_SJIS);
-    }
-
-    public void testPhoneticNameForJapaneseV30Utf8() {
-        testPhoneticNameCommon(VCardConfig.VCARD_TYPE_V30_JAPANESE_UTF8);
-    }
-
-    public void testPhoneticNameForJapaneseV30SJis() {
-        testPhoneticNameCommon(VCardConfig.VCARD_TYPE_V30_JAPANESE_SJIS);
-    }
-
-    public void testPhoneticNameForMobileV21_1() {
-        mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE_MOBILE);
-        ContactEntry entry = mVerifier.addInputEntry();
-        entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
-                .put(StructuredName.PHONETIC_FAMILY_NAME, "\u3084\u307E\u3060")
-                .put(StructuredName.PHONETIC_MIDDLE_NAME, "\u30DF\u30C9\u30EB\u30CD\u30FC\u30E0")
-                .put(StructuredName.PHONETIC_GIVEN_NAME, "\u305F\u308D\u3046");
-
-        mVerifier.addPropertyNodesVerifierElem()
-                .addExpectedNode("SOUND",
-                        "\uFF94\uFF8F\uFF80\uFF9E \uFF90\uFF84\uFF9E\uFF99\uFF88\uFF70\uFF91 " +
-                        "\uFF80\uFF9B\uFF73;;;;",
-                        mContentValuesForSJis, new TypeSet("X-IRMC-N"));
-        ContentValuesBuilder builder = mVerifier.addContentValuesVerifierElem()
-                .addExpected(StructuredName.CONTENT_ITEM_TYPE);
-        builder.put(StructuredName.PHONETIC_FAMILY_NAME, "\uFF94\uFF8F\uFF80\uFF9E")
-                .put(StructuredName.PHONETIC_MIDDLE_NAME,
-                        "\uFF90\uFF84\uFF9E\uFF99\uFF88\uFF70\uFF91")
-                .put(StructuredName.PHONETIC_GIVEN_NAME, "\uFF80\uFF9B\uFF73")
-                .put(StructuredName.DISPLAY_NAME,
-                        "\uFF94\uFF8F\uFF80\uFF9E \uFF90\uFF84\uFF9E\uFF99\uFF88\uFF70\uFF91 " +
-                        "\uFF80\uFF9B\uFF73");
-    }
-
-    public void testPhoneticNameForMobileV21_2() {
-        mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE_MOBILE);
-        ContactEntry entry = mVerifier.addInputEntry();
-        entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
-                .put(StructuredName.PHONETIC_FAMILY_NAME, "\u3084\u307E\u3060")
-                .put(StructuredName.PHONETIC_GIVEN_NAME, "\u305F\u308D\u3046");
-
-        mVerifier.addPropertyNodesVerifierElem()
-                .addExpectedNode("SOUND", "\uFF94\uFF8F\uFF80\uFF9E \uFF80\uFF9B\uFF73;;;;",
-                                mContentValuesForSJis, new TypeSet("X-IRMC-N"));
-        ContentValuesBuilder builder = mVerifier.addContentValuesVerifierElem()
-                .addExpected(StructuredName.CONTENT_ITEM_TYPE);
-        builder.put(StructuredName.PHONETIC_FAMILY_NAME, "\uFF94\uFF8F\uFF80\uFF9E")
-                .put(StructuredName.PHONETIC_GIVEN_NAME, "\uFF80\uFF9B\uFF73")
-                .put(StructuredName.DISPLAY_NAME, "\uFF94\uFF8F\uFF80\uFF9E \uFF80\uFF9B\uFF73");
-    }
-
-    private void testPostalAddressWithJapaneseCommon(int vcardType) {
-        mVerifier.initForExportTest(vcardType);
-        ContactEntry entry = mVerifier.addInputEntry();
-        entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
-                .put(StructuredPostal.POBOX, "\u79C1\u66F8\u7BB107")
-                .put(StructuredPostal.STREET, "\u96DB\u898B\u6CA2\u6751")
-                .put(StructuredPostal.CITY, "\u9E7F\u9AA8\u5E02")
-                .put(StructuredPostal.REGION, "\u00D7\u00D7\u770C")
-                .put(StructuredPostal.POSTCODE, "494-1313")
-                .put(StructuredPostal.COUNTRY, "\u65E5\u672C")
-                .put(StructuredPostal.FORMATTED_ADDRESS,
-                        "\u3053\u3093\u306A\u3068\u3053\u308D\u3092\u898B"
-                        + "\u308B\u306A\u3093\u3066\u6687\u4EBA\u3067\u3059\u304B\uFF1F")
-                .put(StructuredPostal.TYPE, StructuredPostal.TYPE_CUSTOM)
-                .put(StructuredPostal.LABEL, "\u304A\u3082\u3061\u304B\u3048\u308A");
-
-        ContentValues contentValues = (VCardConfig.usesShiftJis(vcardType) ?
-                (VCardConfig.isV30(vcardType) ? mContentValuesForSJis :
-                    mContentValuesForQPAndSJis) :
-                (VCardConfig.isV30(vcardType) ? mContentValuesForUtf8 :
-                    mContentValuesForQPAndUtf8));
-
-        PropertyNodesVerifierElem elem = mVerifier.addPropertyNodesVerifierElemWithEmptyName();
-        // LABEL must be ignored in vCard 2.1. As for vCard 3.0, the current behavior is
-        // same as that in vCard 3.0, which can be changed in the future.
-        elem.addExpectedNode("ADR", Arrays.asList("\u79C1\u66F8\u7BB107",
-                "", "\u96DB\u898B\u6CA2\u6751", "\u9E7F\u9AA8\u5E02", "\u00D7\u00D7\u770C",
-                "494-1313", "\u65E5\u672C"),
-                contentValues);
-        mVerifier.addContentValuesVerifierElem().addExpected(StructuredPostal.CONTENT_ITEM_TYPE)
-                .put(StructuredPostal.POBOX, "\u79C1\u66F8\u7BB107")
-                .put(StructuredPostal.STREET, "\u96DB\u898B\u6CA2\u6751")
-                .put(StructuredPostal.CITY, "\u9E7F\u9AA8\u5E02")
-                .put(StructuredPostal.REGION, "\u00D7\u00D7\u770C")
-                .put(StructuredPostal.POSTCODE, "494-1313")
-                .put(StructuredPostal.COUNTRY, "\u65E5\u672C")
-                .put(StructuredPostal.FORMATTED_ADDRESS,
-                        "\u65E5\u672C 494-1313 \u00D7\u00D7\u770C \u9E7F\u9AA8\u5E02 " +
-                        "\u96DB\u898B\u6CA2\u6751 " + "\u79C1\u66F8\u7BB107")
-                .put(StructuredPostal.TYPE, StructuredPostal.TYPE_HOME);
-    }
-    public void testPostalAddresswithJapaneseV21() {
-        testPostalAddressWithJapaneseCommon(VCardConfig.VCARD_TYPE_V21_JAPANESE_SJIS);
-    }
-
-    /**
-     * Verifies that only one address field is emitted toward DoCoMo phones.
-     * Prefered type must (should?) be: HOME > WORK > OTHER > CUSTOM
-     */
-    public void testPostalAdrressForDoCoMo_1() {
-        mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO);
-        ContactEntry entry = mVerifier.addInputEntry();
-        entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
-                .put(StructuredPostal.TYPE, StructuredPostal.TYPE_WORK)
-                .put(StructuredPostal.POBOX, "1");
-        entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
-                .put(StructuredPostal.TYPE, StructuredPostal.TYPE_OTHER)
-                .put(StructuredPostal.POBOX, "2");
-        entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
-                .put(StructuredPostal.TYPE, StructuredPostal.TYPE_HOME)
-                .put(StructuredPostal.POBOX, "3");
-        entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
-                .put(StructuredPostal.TYPE, StructuredPostal.TYPE_CUSTOM)
-                .put(StructuredPostal.LABEL, "custom")
-                .put(StructuredPostal.POBOX, "4");
-
-        mVerifier.addPropertyNodesVerifierElemWithEmptyName()
-                .addExpectedNode("TEL", "", new TypeSet("HOME"))
-                .addExpectedNode("EMAIL", "", new TypeSet("HOME"))
-                .addExpectedNode("X-CLASS", "PUBLIC")
-                .addExpectedNode("X-REDUCTION", "")
-                .addExpectedNode("X-NO", "")
-                .addExpectedNode("X-DCM-HMN-MODE", "")
-                .addExpectedNode("ADR",
-                        Arrays.asList("3", "", "", "", "", "", ""), new TypeSet("HOME"));
-    }
-
-    public void testPostalAdrressForDoCoMo_2() {
-        mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO);
-        ContactEntry entry = mVerifier.addInputEntry();
-        entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
-                .put(StructuredPostal.TYPE, StructuredPostal.TYPE_OTHER)
-                .put(StructuredPostal.POBOX, "1");
-        entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
-                .put(StructuredPostal.TYPE, StructuredPostal.TYPE_WORK)
-                .put(StructuredPostal.POBOX, "2");
-        entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
-                .put(StructuredPostal.TYPE, StructuredPostal.TYPE_CUSTOM)
-                .put(StructuredPostal.LABEL, "custom")
-                .put(StructuredPostal.POBOX, "3");
-
-        mVerifier.addPropertyNodesVerifierElemWithEmptyName()
-                .addExpectedNode("TEL", "", new TypeSet("HOME"))
-                .addExpectedNode("EMAIL", "", new TypeSet("HOME"))
-                .addExpectedNode("X-CLASS", "PUBLIC")
-                .addExpectedNode("X-REDUCTION", "")
-                .addExpectedNode("X-NO", "")
-                .addExpectedNode("X-DCM-HMN-MODE", "")
-                .addExpectedNode("ADR",
-                        Arrays.asList("2", "", "", "", "", "", ""), new TypeSet("WORK"));
-    }
-
-    public void testPostalAdrressForDoCoMo_3() {
-        mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO);
-        ContactEntry entry = mVerifier.addInputEntry();
-        entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
-                .put(StructuredPostal.TYPE, StructuredPostal.TYPE_CUSTOM)
-                .put(StructuredPostal.LABEL, "custom1")
-                .put(StructuredPostal.POBOX, "1");
-        entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
-                .put(StructuredPostal.TYPE, StructuredPostal.TYPE_OTHER)
-                .put(StructuredPostal.POBOX, "2");
-        entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
-                .put(StructuredPostal.TYPE, StructuredPostal.TYPE_CUSTOM)
-                .put(StructuredPostal.LABEL, "custom2")
-                .put(StructuredPostal.POBOX, "3");
-
-        mVerifier.addPropertyNodesVerifierElemWithEmptyName()
-                .addExpectedNode("TEL", "", new TypeSet("HOME"))
-                .addExpectedNode("EMAIL", "", new TypeSet("HOME"))
-                .addExpectedNode("X-CLASS", "PUBLIC")
-                .addExpectedNode("X-REDUCTION", "")
-                .addExpectedNode("X-NO", "")
-                .addExpectedNode("X-DCM-HMN-MODE", "")
-                .addExpectedNode("ADR", Arrays.asList("2", "", "", "", "", "", ""));
-    }
-
-    /**
-     * Verifies the vCard exporter tolerates null TYPE.
-     */
-    public void testPostalAdrressForDoCoMo_4() {
-        mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO);
-        ContactEntry entry = mVerifier.addInputEntry();
-        entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
-                .put(StructuredPostal.POBOX, "1");
-        entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
-                .put(StructuredPostal.TYPE, StructuredPostal.TYPE_OTHER)
-                .put(StructuredPostal.POBOX, "2");
-        entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
-                .put(StructuredPostal.TYPE, StructuredPostal.TYPE_HOME)
-                .put(StructuredPostal.POBOX, "3");
-        entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
-                .put(StructuredPostal.TYPE, StructuredPostal.TYPE_WORK)
-                .put(StructuredPostal.POBOX, "4");
-        entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
-                .put(StructuredPostal.POBOX, "5");
-
-        mVerifier.addPropertyNodesVerifierElemWithEmptyName()
-                .addExpectedNode("TEL", "", new TypeSet("HOME"))
-                .addExpectedNode("EMAIL", "", new TypeSet("HOME"))
-                .addExpectedNode("X-CLASS", "PUBLIC")
-                .addExpectedNode("X-REDUCTION", "")
-                .addExpectedNode("X-NO", "")
-                .addExpectedNode("X-DCM-HMN-MODE", "")
-                .addExpectedNode("ADR",
-                        Arrays.asList("3", "", "", "", "", "", ""), new TypeSet("HOME"));
-    }
-
-    private void testJapanesePhoneNumberCommon(int vcardType) {
-        mVerifier.initForExportTest(vcardType);
-        ContactEntry entry = mVerifier.addInputEntry();
-        entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
-                .put(Phone.NUMBER, "0312341234")
-                .put(Phone.TYPE, Phone.TYPE_HOME);
-        entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
-                .put(Phone.NUMBER, "09012341234")
-                .put(Phone.TYPE, Phone.TYPE_MOBILE);
-        mVerifier.addPropertyNodesVerifierElemWithEmptyName()
-                .addExpectedNode("TEL", "03-1234-1234", new TypeSet("HOME"))
-                .addExpectedNode("TEL", "090-1234-1234", new TypeSet("CELL"));
-    }
-
-    public void testJapanesePhoneNumberV21_1() {
-        testJapanesePhoneNumberCommon(VCardConfig.VCARD_TYPE_V21_JAPANESE_UTF8);
-    }
-
-    public void testJapanesePhoneNumberV30() {
-        testJapanesePhoneNumberCommon(VCardConfig.VCARD_TYPE_V30_JAPANESE_UTF8);
-    }
-
-    public void testJapanesePhoneNumberDoCoMo() {
-        mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO);
-        ContactEntry entry = mVerifier.addInputEntry();
-        entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
-                .put(Phone.NUMBER, "0312341234")
-                .put(Phone.TYPE, Phone.TYPE_HOME);
-        entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
-                .put(Phone.NUMBER, "09012341234")
-                .put(Phone.TYPE, Phone.TYPE_MOBILE);
-        mVerifier.addPropertyNodesVerifierElemWithEmptyName()
-                .addExpectedNode("EMAIL", "", new TypeSet("HOME"))
-                .addExpectedNode("X-CLASS", "PUBLIC")
-                .addExpectedNode("X-REDUCTION", "")
-                .addExpectedNode("X-NO", "")
-                .addExpectedNode("X-DCM-HMN-MODE", "")
-                .addExpectedNode("ADR", "", new TypeSet("HOME"))
-                .addExpectedNode("TEL", "03-1234-1234", new TypeSet("HOME"))
-                .addExpectedNode("TEL", "090-1234-1234", new TypeSet("CELL"));
-    }
-
-    public void testNoteDoCoMo() {
-        mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO);
-        ContactEntry entry = mVerifier.addInputEntry();
-        entry.addContentValues(Note.CONTENT_ITEM_TYPE)
-                .put(Note.NOTE, "note1");
-        entry.addContentValues(Note.CONTENT_ITEM_TYPE)
-                .put(Note.NOTE, "note2");
-        entry.addContentValues(Note.CONTENT_ITEM_TYPE)
-                .put(Note.NOTE, "note3");
-
-        // More than one note fields must be aggregated into one note.
-        mVerifier.addPropertyNodesVerifierElemWithEmptyName()
-                .addExpectedNode("TEL", "", new TypeSet("HOME"))
-                .addExpectedNode("EMAIL", "", new TypeSet("HOME"))
-                .addExpectedNode("X-CLASS", "PUBLIC")
-                .addExpectedNode("X-REDUCTION", "")
-                .addExpectedNode("X-NO", "")
-                .addExpectedNode("X-DCM-HMN-MODE", "")
-                .addExpectedNode("ADR", "", new TypeSet("HOME"))
-                .addExpectedNode("NOTE", "note1\nnote2\nnote3", mContentValuesForQP);
-    }
-
-    public void testAndroidCustomV21() {
-        mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_V21_GENERIC_UTF8);
-        mVerifier.addInputEntry().addContentValues(Nickname.CONTENT_ITEM_TYPE)
-                .put(Nickname.NAME, "\u304D\u3083\u30FC\u30A8\u30C3\u30C1\u30FC");
-        mVerifier.addPropertyNodesVerifierElemWithEmptyName()
-                .addExpectedNode("X-ANDROID-CUSTOM",
-                        Arrays.asList(Nickname.CONTENT_ITEM_TYPE,
-                                "\u304D\u3083\u30FC\u30A8\u30C3\u30C1\u30FC",
-                                "", "", "", "", "", "", "", "", "", "", "", "", "", ""),
-                        mContentValuesForQPAndUtf8);
-    }
-}
diff --git a/core/tests/coretests/src/android/pim/vcard/VCardTestsBase.java b/core/tests/coretests/src/android/pim/vcard/VCardTestsBase.java
deleted file mode 100644
index 0857e0c..0000000
--- a/core/tests/coretests/src/android/pim/vcard/VCardTestsBase.java
+++ /dev/null
@@ -1,169 +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.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.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;
-
-    // Do not modify these during tests.
-    protected final ContentValues mContentValuesForQP;
-    protected final ContentValues mContentValuesForSJis;
-    protected final ContentValues mContentValuesForUtf8;
-    protected final ContentValues mContentValuesForQPAndSJis;
-    protected final ContentValues mContentValuesForQPAndUtf8;
-    protected final ContentValues mContentValuesForBase64V21;
-    protected final ContentValues mContentValuesForBase64V30;
-
-    protected VCardVerifier mVerifier;
-    private boolean mSkipVerification;
-
-    public VCardTestsBase() {
-        super();
-        mContentValuesForQP = new ContentValues();
-        mContentValuesForQP.put("ENCODING", "QUOTED-PRINTABLE");
-        mContentValuesForSJis = new ContentValues();
-        mContentValuesForSJis.put("CHARSET", "SHIFT_JIS");
-        mContentValuesForUtf8 = new ContentValues();
-        mContentValuesForUtf8.put("CHARSET", "UTF-8");
-        mContentValuesForQPAndSJis = new ContentValues();
-        mContentValuesForQPAndSJis.put("ENCODING", "QUOTED-PRINTABLE");
-        mContentValuesForQPAndSJis.put("CHARSET", "SHIFT_JIS");
-        mContentValuesForQPAndUtf8 = new ContentValues();
-        mContentValuesForQPAndUtf8.put("ENCODING", "QUOTED-PRINTABLE");
-        mContentValuesForQPAndUtf8.put("CHARSET", "UTF-8");
-        mContentValuesForBase64V21 = new ContentValues();
-        mContentValuesForBase64V21.put("ENCODING", "BASE64");
-        mContentValuesForBase64V30 = new ContentValues();
-        mContentValuesForBase64V30.put("ENCODING", "b");
-    }
-
-    @Override
-    public void testAndroidTestCaseSetupProperly() {
-        super.testAndroidTestCaseSetupProperly();
-        mSkipVerification = true;
-    }
-
-    @Override
-    public void setUp() throws Exception{
-        super.setUp();
-        mVerifier = new VCardVerifier(this);
-        mSkipVerification = false;
-    }
-
-    @Override
-    public void tearDown() throws Exception {
-        super.tearDown();
-        if (!mSkipVerification) {
-            mVerifier.verify();
-        }
-    }
-}
diff --git a/core/tests/coretests/src/android/pim/vcard/VCardUtilsTests.java b/core/tests/coretests/src/android/pim/vcard/VCardUtilsTests.java
deleted file mode 100644
index 59299f9..0000000
--- a/core/tests/coretests/src/android/pim/vcard/VCardUtilsTests.java
+++ /dev/null
@@ -1,85 +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.pim.vcard;
-
-import android.pim.vcard.VCardUtils;
-
-import junit.framework.TestCase;
-
-import java.util.List;
-
-public class VCardUtilsTests extends TestCase {
-    public void testContainsOnlyPrintableAscii() {
-        assertTrue(VCardUtils.containsOnlyPrintableAscii((String)null));
-        assertTrue(VCardUtils.containsOnlyPrintableAscii((String[])null));
-        assertTrue(VCardUtils.containsOnlyPrintableAscii((List<String>)null));
-        assertTrue(VCardUtils.containsOnlyPrintableAscii(""));
-        assertTrue(VCardUtils.containsOnlyPrintableAscii("abcdefghijklmnopqrstuvwxyz"));
-        assertTrue(VCardUtils.containsOnlyPrintableAscii("ABCDEFGHIJKLMNOPQRSTUVWXYZ"));
-        StringBuilder builder = new StringBuilder();
-        for (int i = 0x20; i < 0x7F; i++) {
-            builder.append((char)i);
-        }
-        assertTrue(VCardUtils.containsOnlyPrintableAscii(builder.toString()));
-        assertTrue(VCardUtils.containsOnlyPrintableAscii("\r\n"));
-        assertFalse(VCardUtils.containsOnlyPrintableAscii("\u0019"));
-        assertFalse(VCardUtils.containsOnlyPrintableAscii("\u007F"));
-    }
-
-    public void testContainsOnlyNonCrLfPrintableAscii() {
-        assertTrue(VCardUtils.containsOnlyNonCrLfPrintableAscii((String)null));
-        assertTrue(VCardUtils.containsOnlyNonCrLfPrintableAscii((String[])null));
-        assertTrue(VCardUtils.containsOnlyNonCrLfPrintableAscii((List<String>)null));
-        assertTrue(VCardUtils.containsOnlyNonCrLfPrintableAscii(""));
-        assertTrue(VCardUtils.containsOnlyNonCrLfPrintableAscii("abcdefghijklmnopqrstuvwxyz"));
-        assertTrue(VCardUtils.containsOnlyNonCrLfPrintableAscii("ABCDEFGHIJKLMNOPQRSTUVWXYZ"));
-        StringBuilder builder = new StringBuilder();
-        for (int i = 0x20; i < 0x7F; i++) {
-            builder.append((char)i);
-        }
-        assertTrue(VCardUtils.containsOnlyNonCrLfPrintableAscii(builder.toString()));
-        assertFalse(VCardUtils.containsOnlyNonCrLfPrintableAscii("\u0019"));
-        assertFalse(VCardUtils.containsOnlyNonCrLfPrintableAscii("\u007F"));
-        assertFalse(VCardUtils.containsOnlyNonCrLfPrintableAscii("\r"));
-        assertFalse(VCardUtils.containsOnlyNonCrLfPrintableAscii("\n"));
-    }
-
-    public void testContainsOnlyAlphaDigitHyphen() {
-        assertTrue(VCardUtils.containsOnlyAlphaDigitHyphen((String)null));
-        assertTrue(VCardUtils.containsOnlyAlphaDigitHyphen((String[])null));
-        assertTrue(VCardUtils.containsOnlyAlphaDigitHyphen((List<String>)null));
-        assertTrue(VCardUtils.containsOnlyAlphaDigitHyphen(""));
-        assertTrue(VCardUtils.containsOnlyNonCrLfPrintableAscii("abcdefghijklmnopqrstuvwxyz"));
-        assertTrue(VCardUtils.containsOnlyNonCrLfPrintableAscii("ABCDEFGHIJKLMNOPQRSTUVWXYZ"));
-        assertTrue(VCardUtils.containsOnlyNonCrLfPrintableAscii("0123456789-"));
-        for (int i = 0; i < 0x30; i++) {
-            if (i == 0x2D) {  // -
-                continue;
-            }
-            assertFalse(VCardUtils.containsOnlyAlphaDigitHyphen(String.valueOf((char)i)));
-        }
-        for (int i = 0x3A; i < 0x41; i++) {
-            assertFalse(VCardUtils.containsOnlyAlphaDigitHyphen(String.valueOf((char)i)));
-        }
-        for (int i = 0x5B; i < 0x61; i++) {
-            assertFalse(VCardUtils.containsOnlyAlphaDigitHyphen(String.valueOf((char)i)));
-        }
-        for (int i = 0x7B; i < 0x100; i++) {
-            assertFalse(VCardUtils.containsOnlyAlphaDigitHyphen(String.valueOf((char)i)));
-        }
-    }
-}
diff --git a/core/tests/coretests/src/android/pim/vcard/VCardVerifier.java b/core/tests/coretests/src/android/pim/vcard/VCardVerifier.java
deleted file mode 100644
index bfc3158..0000000
--- a/core/tests/coretests/src/android/pim/vcard/VCardVerifier.java
+++ /dev/null
@@ -1,306 +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.pim.vcard;
-
-import android.content.ContentProvider;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.EntityIterator;
-import android.net.Uri;
-import android.pim.vcard.VCardComposer;
-import android.pim.vcard.VCardConfig;
-import android.pim.vcard.VCardEntryConstructor;
-import android.pim.vcard.VCardInterpreter;
-import android.pim.vcard.VCardInterpreterCollection;
-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 android.test.mock.MockContext;
-
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.lang.reflect.Method;
-import java.util.Arrays;
-
-/* package */ class CustomMockContext extends MockContext {
-    final ContentResolver mResolver;
-    public CustomMockContext(ContentResolver resolver) {
-        mResolver = resolver;
-    }
-
-    @Override
-    public ContentResolver getContentResolver() {
-        return mResolver;
-    }
-}
-
-/* package */ class VCardVerifier {
-    private class VCardVerifierInternal implements VCardComposer.OneEntryHandler {
-        public boolean onInit(Context context) {
-            return true;
-        }
-        public boolean onEntryCreated(String vcard) {
-            verifyOneVCard(vcard);
-            return true;
-        }
-        public void onTerminate() {
-        }
-    }
-
-    private final AndroidTestCase mTestCase;
-    private final VCardVerifierInternal mVCardVerifierInternal;
-    private int mVCardType;
-    private boolean mIsV30;
-    private boolean mIsDoCoMo;
-
-    // Only one of them must be non-empty.
-    private ExportTestResolver mExportTestResolver;
-    private InputStream mInputStream;
-
-    // To allow duplication, use list instead of set.
-    // When null, we don't need to do the verification.
-    private PropertyNodesVerifier mPropertyNodesVerifier;
-    private LineVerifier mLineVerifier;
-    private ContentValuesVerifier mContentValuesVerifier;
-    private boolean mInitialized;
-    private boolean mVerified = false;
-
-    public VCardVerifier(AndroidTestCase androidTestCase) {
-        mTestCase = androidTestCase;
-        mVCardVerifierInternal = new VCardVerifierInternal();
-        mExportTestResolver = null;
-        mInputStream = null;
-        mInitialized = false;
-        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;
-    }
-
-    public void initForImportTest(int vcardType, int resId) {
-        if (mInitialized) {
-            mTestCase.fail("Already initialized");
-        }
-        mVCardType = vcardType;
-        mIsV30 = VCardConfig.isV30(vcardType);
-        mIsDoCoMo = VCardConfig.isDoCoMo(vcardType);
-        setInputResourceId(resId);
-        mInitialized = true;
-    }
-
-    private void setInputResourceId(int resId) {
-        InputStream inputStream = mTestCase.getContext().getResources().openRawResource(resId);
-        if (inputStream == null) {
-            mTestCase.fail("Wrong resId: " + resId);
-        }
-        setInputStream(inputStream);
-    }
-
-    private void setInputStream(InputStream inputStream) {
-        if (mExportTestResolver != null) {
-            mTestCase.fail("addInputEntry() is called.");
-        } else if (mInputStream != null) {
-            mTestCase.fail("InputStream is already set");
-        }
-        mInputStream = inputStream;
-    }
-
-    public ContactEntry addInputEntry() {
-        if (!mInitialized) {
-            mTestCase.fail("Not initialized");
-        }
-        if (mInputStream != null) {
-            mTestCase.fail("setInputStream is called");
-        }
-        return mExportTestResolver.addInputContactEntry();
-    }
-
-    public PropertyNodesVerifierElem addPropertyNodesVerifierElem() {
-        if (!mInitialized) {
-            mTestCase.fail("Not initialized");
-        }
-        if (mPropertyNodesVerifier == null) {
-            mPropertyNodesVerifier = new PropertyNodesVerifier(mTestCase);
-        }
-        PropertyNodesVerifierElem elem =
-                mPropertyNodesVerifier.addPropertyNodesVerifierElem();
-        elem.addExpectedNodeWithOrder("VERSION", (mIsV30 ? "3.0" : "2.1"));
-
-        return elem;
-    }
-
-    public PropertyNodesVerifierElem addPropertyNodesVerifierElemWithEmptyName() {
-        if (!mInitialized) {
-            mTestCase.fail("Not initialized");
-        }
-        PropertyNodesVerifierElem elem = addPropertyNodesVerifierElem();
-        if (mIsV30) {
-            elem.addExpectedNodeWithOrder("N", "").addExpectedNodeWithOrder("FN", "");
-        } else if (mIsDoCoMo) {
-            elem.addExpectedNodeWithOrder("N", "");
-        }
-        return elem;
-    }
-
-    public LineVerifierElem addLineVerifierElem() {
-        if (!mInitialized) {
-            mTestCase.fail("Not initialized");
-        }
-        if (mLineVerifier == null) {
-            mLineVerifier = new LineVerifier(mTestCase, mVCardType);
-        }
-        return mLineVerifier.addLineVerifierElem();
-    }
-
-    public ContentValuesVerifierElem addContentValuesVerifierElem() {
-        if (!mInitialized) {
-            mTestCase.fail("Not initialized");
-        }
-        if (mContentValuesVerifier == null) {
-            mContentValuesVerifier = new ContentValuesVerifier();
-        }
-
-        return mContentValuesVerifier.addElem(mTestCase);
-    }
-
-    private void verifyOneVCard(final String vcard) {
-        // Log.d("@@@", vcard);
-        final VCardInterpreter builder;
-        if (mContentValuesVerifier != null) {
-            final VNodeBuilder vnodeBuilder = mPropertyNodesVerifier;
-            final VCardEntryConstructor vcardDataBuilder =
-                    new VCardEntryConstructor(mVCardType);
-            vcardDataBuilder.addEntryHandler(mContentValuesVerifier);
-            if (mPropertyNodesVerifier != null) {
-                builder = new VCardInterpreterCollection(Arrays.asList(
-                        mPropertyNodesVerifier, vcardDataBuilder));
-            } else {
-                builder = vnodeBuilder;
-            }
-        } else {
-            if (mPropertyNodesVerifier != null) {
-                builder = mPropertyNodesVerifier;
-            } else {
-                return;
-            }
-        }
-
-        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));
-        } catch (IOException e) {
-            mTestCase.fail("Unexpected IOException: " + e.getMessage());
-        } catch (VCardException e) {
-            mTestCase.fail("Unexpected VCardException: " + e.getMessage());
-        } finally {
-            if (is != null) {
-                try {
-                    is.close();
-                } catch (IOException e) {
-                }
-            }
-        }
-    }
-
-    public void verify() {
-        if (!mInitialized) {
-            mTestCase.fail("Not initialized.");
-        }
-        if (mVerified) {
-            mTestCase.fail("verify() was called twice.");
-        }
-        if (mInputStream != null) {
-            try {
-                verifyForImportTest();
-            } catch (IOException e) {
-                mTestCase.fail("IOException was thrown: " + e.getMessage());
-            } catch (VCardException e) {
-                mTestCase.fail("VCardException was thrown: " + e.getMessage());
-            }
-        } else if (mExportTestResolver != null){
-            verifyForExportTest();
-        } else {
-            mTestCase.fail("No input is determined");
-        }
-        mVerified = true;
-    }
-
-    private void verifyForImportTest() throws IOException, VCardException {
-        if (mLineVerifier != null) {
-            mTestCase.fail("Not supported now.");
-        }
-        if (mContentValuesVerifier != null) {
-            mContentValuesVerifier.verify(mInputStream, mVCardType);
-        }
-    }
-
-    public static EntityIterator mockGetEntityIteratorMethod(
-            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);
-    }
-
-    private Method getMockGetEntityIteratorMethod()
-            throws SecurityException, NoSuchMethodException {
-        return this.getClass().getMethod("mockGetEntityIteratorMethod",
-                ContentResolver.class, Uri.class, String.class, String[].class, String.class);
-    }
-
-    private void verifyForExportTest() {
-       final VCardComposer composer =
-            new VCardComposer(new CustomMockContext(mExportTestResolver), mVCardType);
-        composer.addHandler(mLineVerifier);
-        composer.addHandler(mVCardVerifierInternal);
-        if (!composer.init(VCardComposer.CONTACTS_TEST_CONTENT_URI, null, null, null)) {
-            mTestCase.fail("init() failed. Reason: " + composer.getErrorReason());
-        }
-        mTestCase.assertFalse(composer.isAfterLast());
-        try {
-            while (!composer.isAfterLast()) {
-                try {
-                    final Method mockGetEntityIteratorMethod = getMockGetEntityIteratorMethod();
-                    mTestCase.assertTrue(
-                            composer.createOneEntry(getMockGetEntityIteratorMethod()));
-                } catch (Exception e) {
-                    e.printStackTrace();
-                    mTestCase.fail();
-                }
-            }
-        } finally {
-            composer.terminate();
-        }
-    }
-}
diff --git a/core/tests/coretests/src/android/pim/vcard/VNode.java b/core/tests/coretests/src/android/pim/vcard/VNode.java
deleted file mode 100644
index 79f10dc..0000000
--- a/core/tests/coretests/src/android/pim/vcard/VNode.java
+++ /dev/null
@@ -1,30 +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.pim.vcard;
-
-import java.util.ArrayList;
-
-/**
- * Previously used in main vCard handling code but now exists only for testing.
- */
-public class VNode {
-    public String VName;
-
-    public ArrayList<PropertyNode> propList = new ArrayList<PropertyNode>();
-
-    /** 0:parse over. 1:parsing. */
-    public int parseStatus = 1;
-}
diff --git a/core/tests/coretests/src/android/pim/vcard/VNodeBuilder.java b/core/tests/coretests/src/android/pim/vcard/VNodeBuilder.java
deleted file mode 100644
index 0e6c325..0000000
--- a/core/tests/coretests/src/android/pim/vcard/VNodeBuilder.java
+++ /dev/null
@@ -1,314 +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.pim.vcard;
-
-import android.content.ContentValues;
-import android.pim.vcard.VCardInterpreter;
-import android.pim.vcard.VCardConfig;
-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;
-import java.nio.charset.Charset;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Store the parse result to custom datastruct: VNode, 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.
- */
-public 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.
-     */
-    private String mTargetCharset;
-    
-    private boolean mStrictLineBreakParsing;
-    
-    public VNodeBuilder() {
-        this(VCardConfig.DEFAULT_CHARSET, TARGET_CHARSET, false);
-    }
-
-    public VNodeBuilder(String charset, boolean strictLineBreakParsing) {
-        this(null, charset, strictLineBreakParsing);
-    }
-    
-    /**
-     * @hide sourceCharset is temporal. 
-     */
-    public VNodeBuilder(String sourceCharset, String targetCharset,
-            boolean strictLineBreakParsing) {
-        if (sourceCharset != null) {
-            mSourceCharset = sourceCharset;
-        } else {
-            mSourceCharset = VCardConfig.DEFAULT_CHARSET;
-        }
-        if (targetCharset != null) {
-            mTargetCharset = targetCharset;
-        } else {
-            mTargetCharset = TARGET_CHARSET;
-        }
-        mStrictLineBreakParsing = strictLineBreakParsing;
-    }
-
-    public void start() {
-    }
-
-    public void end() {
-    }
-
-    // Note: I guess that this code assumes the Record may nest like this:
-    // START:VPOS
-    // ...
-    // START:VPOS2
-    // ...
-    // END:VPOS2
-    // ...
-    // END:VPOS
-    //
-    // However the following code has a bug.
-    // When error occurs after calling startRecord(), the entry which is probably
-    // the cause of the error remains to be in vNodeList, while endRecord() is not called.
-    //
-    // I leave this code as is since I'm not familiar with vcalendar specification.
-    // But I believe we should refactor this code in the future.
-    // Until this, the last entry has to be removed when some error occurs.
-    public void startEntry() {
-        VNode vnode = new VNode();
-        vnode.parseStatus = 1;
-        vnode.VName = "VCARD";
-        // I feel this should be done in endRecord(), but it cannot be done because of
-        // the reason above.
-        vNodeList.add(vnode);
-        mNodeListPos = vNodeList.size() - 1;
-        mCurrentVNode = vNodeList.get(mNodeListPos);
-    }
-
-    public void endEntry() {
-        VNode endNode = vNodeList.get(mNodeListPos);
-        endNode.parseStatus = 0;
-        while(mNodeListPos > 0){
-            mNodeListPos--;
-            if((vNodeList.get(mNodeListPos)).parseStatus == 1)
-                break;
-        }
-        mCurrentVNode = vNodeList.get(mNodeListPos);
-    }
-
-    public void startProperty() {
-        mCurrentPropNode = new PropertyNode();
-    }
-
-    public void endProperty() {
-        mCurrentVNode.propList.add(mCurrentPropNode);
-    }
-    
-    public void propertyName(String name) {
-        mCurrentPropNode.propName = name;
-    }
-
-    // Used only in VCard.
-    public void propertyGroup(String group) {
-        mCurrentPropNode.propGroupSet.add(group);
-    }
-    
-    public void propertyParamType(String type) {
-        mCurrentParamType = type;
-    }
-
-    public void propertyParamValue(String value) {
-        if (mCurrentParamType == null ||
-                mCurrentParamType.equalsIgnoreCase("TYPE")) {
-            mCurrentPropNode.paramMap_TYPE.add(value);
-        } else {
-            mCurrentPropNode.paramMap.put(mCurrentParamType, value);
-        }
-
-        mCurrentParamType = null;
-    }
-
-    private String encodeString(String originalString, String targetCharset) {
-        if (mSourceCharset.equalsIgnoreCase(targetCharset)) {
-            return originalString;
-        }
-        Charset charset = Charset.forName(mSourceCharset);
-        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()];
-        byteBuffer.get(bytes);
-        try {
-            return new String(bytes, targetCharset);
-        } catch (UnsupportedEncodingException e) {
-            Log.e(LOG_TAG, "Failed to encode: charset=" + targetCharset);
-            return null;
-        }
-    }
-    
-    private String handleOneValue(String value, String targetCharset, String encoding) {
-        if (encoding != null) {
-            encoding = encoding.toUpperCase();
-            if (encoding.equals("BASE64") || encoding.equals("B")) {
-                // Assume BASE64 is used only when the number of values is 1.
-                mCurrentPropNode.propValue_bytes =
-                    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);
-                }
-            }
-            // Unknown encoding. Fall back to default.
-        }
-        return encodeString(value, targetCharset);
-    }
-    
-    public void propertyValues(List<String> values) {
-        if (values == null || values.size() == 0) {
-            mCurrentPropNode.propValue_bytes = null;
-            mCurrentPropNode.propValue_vector.clear();
-            mCurrentPropNode.propValue_vector.add("");
-            mCurrentPropNode.propValue = "";
-            return;
-        }
-        
-        ContentValues paramMap = mCurrentPropNode.paramMap;
-        
-        String targetCharset = CharsetUtils.nameForDefaultVendor(paramMap.getAsString("CHARSET")); 
-        String encoding = paramMap.getAsString("ENCODING"); 
-        
-        if (targetCharset == null || targetCharset.length() == 0) {
-            targetCharset = mTargetCharset;
-        }
-        
-        for (String value : values) {
-            mCurrentPropNode.propValue_vector.add(
-                    handleOneValue(value, targetCharset, encoding));
-        }
-
-        mCurrentPropNode.propValue = listToString(mCurrentPropNode.propValue_vector);
-    }
-
-    private String listToString(List<String> list){
-        int size = list.size();
-        if (size > 1) {
-            StringBuilder typeListB = new StringBuilder();
-            for (String type : list) {
-                typeListB.append(type).append(";");
-            }
-            int len = typeListB.length();
-            if (len > 0 && typeListB.charAt(len - 1) == ';') {
-                return typeListB.substring(0, len - 1);
-            }
-            return typeListB.toString();
-        } else if (size == 1) {
-            return list.get(0);
-        } else {
-            return "";
-        }
-    }
-    
-    public String getResult(){
-        return null;
-    }
-}
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/util/PatternsTest.java b/core/tests/coretests/src/android/util/PatternsTest.java
index b90c97b..aad3fe1 100644
--- a/core/tests/coretests/src/android/util/PatternsTest.java
+++ b/core/tests/coretests/src/android/util/PatternsTest.java
@@ -68,6 +68,13 @@
         t = Patterns.WEB_URL.matcher("xn--fsqu00a.xn--0zwm56d").matches();
         assertTrue("Valid URL", t);
 
+        // Url for testing top level Arabic country code domain in Punycode:
+        //   http://xn--4gbrim.xn----rmckbbajlc6dj7bxne2c.xn--wgbh1c/ar/default.aspx
+        t = Patterns.WEB_URL.matcher("http://xn--4gbrim.xn----rmckbbajlc6dj7bxne2c.xn--wgbh1c/ar/default.aspx").matches();
+        assertTrue("Valid URL", t);
+        t = Patterns.WEB_URL.matcher("xn--4gbrim.xn----rmckbbajlc6dj7bxne2c.xn--wgbh1c/ar/default.aspx").matches();
+        assertTrue("Valid URL", t);
+
         // Internationalized URL.
         t = Patterns.WEB_URL.matcher("http://\uD604\uAE08\uC601\uC218\uC99D.kr").matches();
         assertTrue("Valid URL", t);
@@ -109,7 +116,7 @@
         t = Patterns.DOMAIN_NAME.matcher("mail.example.com").matches();
         assertTrue("Valid domain", t);
 
-        t = Patterns.WEB_URL.matcher("google.me").matches();
+        t = Patterns.DOMAIN_NAME.matcher("google.me").matches();
         assertTrue("Valid domain", t);
 
         // Internationalized domains.
@@ -118,6 +125,14 @@
 
         t = Patterns.DOMAIN_NAME.matcher("__+&42.xer").matches();
         assertFalse("Invalid domain", t);
+
+        // Obsolete domain .yu
+        t = Patterns.DOMAIN_NAME.matcher("test.yu").matches();
+        assertFalse("Obsolete country code top level domain", t);
+
+        // Testing top level Arabic country code domain in Punycode:
+        t = Patterns.DOMAIN_NAME.matcher("xn--4gbrim.xn----rmckbbajlc6dj7bxne2c.xn--wgbh1c").matches();
+        assertTrue("Valid domain", t);
     }
 
     @SmallTest
diff --git a/core/tests/coretests/src/android/webkit/ZoomManagerTest.java b/core/tests/coretests/src/android/webkit/ZoomManagerTest.java
new file mode 100644
index 0000000..1c9defe
--- /dev/null
+++ b/core/tests/coretests/src/android/webkit/ZoomManagerTest.java
@@ -0,0 +1,127 @@
+/*
+ * 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.webkit;
+
+import android.test.AndroidTestCase;
+
+public class ZoomManagerTest extends AndroidTestCase {
+
+    private ZoomManager zoomManager;
+
+    @Override
+    public void setUp() {
+        WebView webView = new WebView(this.getContext());
+        CallbackProxy callbackProxy = new CallbackProxy(this.getContext(), webView);
+        zoomManager = new ZoomManager(webView, callbackProxy);
+
+        zoomManager.init(1.00f);
+    }
+
+    public void testInit() {
+        testInit(0.01f);
+        testInit(1.00f);
+        testInit(1.25f);
+    }
+
+    private void testInit(float density) {
+        zoomManager.init(density);
+        actualScaleTest(density);
+        defaultScaleTest(density);
+        assertEquals(zoomManager.getDefaultMaxZoomScale(), zoomManager.getMaxZoomScale());
+        assertEquals(zoomManager.getDefaultMinZoomScale(), zoomManager.getMinZoomScale());
+        assertEquals(density, zoomManager.getTextWrapScale());
+    }
+
+    public void testUpdateDefaultZoomDensity() {
+        // test the basic case where the actual values are equal to the defaults
+        testUpdateDefaultZoomDensity(0.01f);
+        testUpdateDefaultZoomDensity(1.00f);
+        testUpdateDefaultZoomDensity(1.25f);
+    }
+
+    private void testUpdateDefaultZoomDensity(float density) {
+        zoomManager.updateDefaultZoomDensity(density);
+        defaultScaleTest(density);
+    }
+
+    public void testUpdateDefaultZoomDensityWithSmallMinZoom() {
+        // test the case where the minZoomScale has changed to be < the default
+        float newDefaultScale = 1.50f;
+        float minZoomScale = ZoomManager.DEFAULT_MIN_ZOOM_SCALE_FACTOR * newDefaultScale;
+        WebViewCore.ViewState minViewState = new WebViewCore.ViewState();
+        minViewState.mMinScale = minZoomScale - 0.1f;
+        zoomManager.updateZoomRange(minViewState, 0, 0);
+        zoomManager.updateDefaultZoomDensity(newDefaultScale);
+        defaultScaleTest(newDefaultScale);
+    }
+
+    public void testUpdateDefaultZoomDensityWithLargeMinZoom() {
+        // test the case where the minZoomScale has changed to be > the default
+        float newDefaultScale = 1.50f;
+        float minZoomScale = ZoomManager.DEFAULT_MIN_ZOOM_SCALE_FACTOR * newDefaultScale;
+        WebViewCore.ViewState minViewState = new WebViewCore.ViewState();
+        minViewState.mMinScale = minZoomScale + 0.1f;
+        zoomManager.updateZoomRange(minViewState, 0, 0);
+        zoomManager.updateDefaultZoomDensity(newDefaultScale);
+        defaultScaleTest(newDefaultScale);
+    }
+
+    public void testUpdateDefaultZoomDensityWithSmallMaxZoom() {
+        // test the case where the maxZoomScale has changed to be < the default
+        float newDefaultScale = 1.50f;
+        float maxZoomScale = ZoomManager.DEFAULT_MAX_ZOOM_SCALE_FACTOR * newDefaultScale;
+        WebViewCore.ViewState maxViewState = new WebViewCore.ViewState();
+        maxViewState.mMaxScale = maxZoomScale - 0.1f;
+        zoomManager.updateZoomRange(maxViewState, 0, 0);
+        zoomManager.updateDefaultZoomDensity(newDefaultScale);
+        defaultScaleTest(newDefaultScale);
+    }
+
+    public void testUpdateDefaultZoomDensityWithLargeMaxZoom() {
+        // test the case where the maxZoomScale has changed to be > the default
+        float newDefaultScale = 1.50f;
+        float maxZoomScale = ZoomManager.DEFAULT_MAX_ZOOM_SCALE_FACTOR * newDefaultScale;
+        WebViewCore.ViewState maxViewState = new WebViewCore.ViewState();
+        maxViewState.mMaxScale = maxZoomScale + 0.1f;
+        zoomManager.updateZoomRange(maxViewState, 0, 0);
+        zoomManager.updateDefaultZoomDensity(newDefaultScale);
+        defaultScaleTest(newDefaultScale);
+    }
+
+    public void testComputeScaleWithLimits() {
+        final float maxScale = zoomManager.getMaxZoomScale();
+        final float minScale = zoomManager.getMinZoomScale();
+        assertTrue(maxScale > minScale);
+        assertEquals(maxScale, zoomManager.computeScaleWithLimits(maxScale));
+        assertEquals(maxScale, zoomManager.computeScaleWithLimits(maxScale + .01f));
+        assertEquals(minScale, zoomManager.computeScaleWithLimits(minScale));
+        assertEquals(minScale, zoomManager.computeScaleWithLimits(minScale - .01f));
+    }
+
+    private void actualScaleTest(float actualScale) {
+        assertEquals(actualScale, zoomManager.getScale());
+        assertEquals(1 / actualScale, zoomManager.getInvScale());
+    }
+
+    private void defaultScaleTest(float defaultScale) {
+        final float maxDefault = ZoomManager.DEFAULT_MAX_ZOOM_SCALE_FACTOR * defaultScale;
+        final float minDefault = ZoomManager.DEFAULT_MIN_ZOOM_SCALE_FACTOR * defaultScale;
+        assertEquals(defaultScale, zoomManager.getDefaultScale());
+        assertEquals(1 / defaultScale, zoomManager.getInvDefaultScale());
+        assertEquals(maxDefault, zoomManager.getDefaultMaxZoomScale());
+        assertEquals(minDefault, zoomManager.getDefaultMinZoomScale());
+    }
+}
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 c74c853..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));
-    }
-
-    @LargeTest
-    public void testContextMenus() {
-        ExpandableListTester tester = new ExpandableListTester(mExpandableListView, this);
-        tester.testContextMenus();
-    }
-
-    @LargeTest
-    public void testConvertionBetweenFlatAndPacked() {
-        ExpandableListTester tester = new ExpandableListTester(mExpandableListView, this);
-        tester.testConvertionBetweenFlatAndPackedOnGroups();
-        tester.testConvertionBetweenFlatAndPackedOnChildren();
-    }
-
-    @LargeTest
-    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/core/tests/hosttests/src/android/content/pm/PackageManagerHostTestUtils.java b/core/tests/hosttests/src/android/content/pm/PackageManagerHostTestUtils.java
index b225c37..38191b0 100644
--- a/core/tests/hosttests/src/android/content/pm/PackageManagerHostTestUtils.java
+++ b/core/tests/hosttests/src/android/content/pm/PackageManagerHostTestUtils.java
@@ -130,7 +130,11 @@
         RemoteAndroidTestRunner testRunner = new RemoteAndroidTestRunner(
                 pkgName, mDevice);
         CollectingTestRunListener listener = new CollectingTestRunListener();
-        testRunner.run(listener);
+        try {
+            testRunner.run(listener);
+        } catch (IOException ioe) {
+            Log.w(LOG_TAG, "encountered IOException " + ioe);
+        }
         return listener;
     }
 
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index 7322e6c..0e5df8c 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -58,6 +58,10 @@
         <group gid="sdcard_rw" />
     </permission>
 
+    <permission name="android.permission.ACCESS_USB" >
+        <group gid="usb" />
+    </permission>
+
     <!-- The group that /cache belongs to, linked to the permission
          set on the applications that can access /cache -->
     <permission name="android.permission.ACCESS_CACHE_FILESYSTEM" >
diff --git a/docs/html/guide/topics/resources/string-resource.jd b/docs/html/guide/topics/resources/string-resource.jd
index 81c5d55..2db38f1 100644
--- a/docs/html/guide/topics/resources/string-resource.jd
+++ b/docs/html/guide/topics/resources/string-resource.jd
@@ -12,8 +12,8 @@
     <dd>XML resource that provides a single string.</dd>
   <dt><a href="#StringArray">String Array</a></dt>
     <dd>XML resource that provides an array of strings.</dd>
-  <dt><a href="#Plurals">Plurals</a></dt>
-    <dd>XML resource that carries different strings for different pluralizations
+  <dt><a href="#Plurals">Quantity Strings (Plurals)</a></dt>
+    <dd>XML resource that carries different strings for different quantities
     of the same word or phrase.</dd>
 </dl>
 
@@ -218,13 +218,30 @@
 
 
 
-<h2 id="Plurals">Plurals</h2>
+<h2 id="Plurals">Quantity Strings (Plurals)</h2>
 
-<p>A pair of strings that each provide a different plural form of the same word or phrase,
-which you can collectively reference from the application. When you request the plurals
-resource using a method such as {@link android.content.res.Resources#getQuantityString(int,int)
-getQuantityString()}, you must pass a "count", which will determine the plural form you
-require and return that string to you.</p>
+<p>Different languages have different rules for grammatical agreement with quantity. In English,
+for example, the quantity 1 is a special case. We write "1 book", but for any other quantity we'd
+write "<i>n</i> books". This distinction between singular and plural is very common, but other
+languages make finer distinctions. The full set supported by Android is <code>zero</code>,
+<code>one</code>, <code>two</code>, <code>few</code>, <code>many</code>, and <code>other</code>.
+
+<p>The rules for deciding which case to use for a given language and quantity can be very complex,
+so Android provides you with methods such as
+{@link android.content.res.Resources#getQuantityString(int,int) getQuantityString()} to select
+the appropriate resource for you.
+
+<p>Note that the selection is made based on grammatical necessity. A string for <code>zero</code>
+in English will be ignored even if the quantity is 0, because 0 isn't grammatically different
+from 2, or any other number except 1 ("zero books", "one book", "two books", et cetera).
+Don't be misled either by the fact that, say, <code>two</code> sounds like it could only apply to
+the quantity 2: a language may require that 2, 12, 102 (et cetera) are all treated like one
+another but differently to other quantities. Rely on your translator to know what distinctions
+their language actually insists upon.
+
+<p>It's often possible to avoid quantity strings by using quantity-neutral formulations such as
+"Books: 1". This will make your life and your translators' lives easier, if it's a style that's
+in keeping with your application.
 
 <p class="note"><strong>Note:</strong> A plurals collection is a simple resource that is
 referenced using the value provided in the {@code name} attribute (not the name of the XML
@@ -251,7 +268,7 @@
     &lt;<a href="#plurals-element">plurals</a>
         name="<em>plural_name</em>">
         &lt;<a href="#plurals-item-element">item</a>
-            quantity=["one" | "other"]
+            quantity=["zero" | "one" | "two" | "few" | "many" | "other"]
             &gt;<em>text_string</em>&lt;/item>
     &lt;/plurals>
 &lt;/resources>
@@ -285,16 +302,27 @@
       <p class="caps">attributes:</p>
       <dl class="atn-list">
         <dt><code>quantity</code></dt>
-        <dd><em>Keyword</em>. A value indicating the case in which this string should be used. Valid
-values:
+        <dd><em>Keyword</em>. A value indicating when this string should be used. Valid
+values, with non-exhaustive examples in parentheses:
           <table>
             <tr><th>Value</th><th>Description</th></tr>
             <tr>
-              <td>{@code one}</td><td>When there is one (a singular string).</td>
+              <td>{@code zero}</td><td>When the language requires special treatment of the number 0 (as in Arabic).</td>
             </tr>
             <tr>
-              <td>{@code other}</td><td>When the quantity is anything other than one (a plural
-string, but also used when the count is zero).</td>
+              <td>{@code one}</td><td>When the language requires special treatment of numbers like one (as with the number 1 in English and most other languages; in Russian, any number ending in 1 but not ending in 11 is in this class).</td>
+            </tr>
+            <tr>
+              <td>{@code two}</td><td>When the language requires special treatment of numbers like two (as in Welsh).</td>
+            </tr>
+            <tr>
+              <td>{@code few}</td><td>When the language requires special treatment of "small" numbers (as with 2, 3, and 4 in Czech; or numbers ending 2, 3, or 4 but not 12, 13, or 14 in Polish).</td>
+            </tr>
+            <tr>
+              <td>{@code many}</td><td>When the language requires special treatment of "large" numbers (as with numbers ending 11-99 in Maltese).</td>
+            </tr>
+            <tr>
+              <td>{@code other}</td><td>When the language does not require special treatment of the given quantity.</td>
             </tr>
           </table>
         </dd>
@@ -315,6 +343,17 @@
     &lt;/plurals>
 &lt;/resources>
 </pre>
+    <p>XML file saved at {@code res/values-pl/strings.xml}:</p>
+<pre>
+&lt;?xml version="1.0" encoding="utf-8"?>
+&lt;resources>
+    &lt;plurals name="numberOfSongsAvailable">
+        &lt;item quantity="one">Znaleziono jedn&#x0105; piosenk&#x0119;.&lt;/item>
+        &lt;item quantity="few">Znaleziono %d piosenki.&lt;/item>
+        &lt;item quantity="other">Znaleziono %d piosenek.&lt;/item>
+    &lt;/plurals>
+&lt;/resources>
+</pre>
     <p>Java code:</p>
 <pre>
 int count = getNumberOfsongsAvailable();
diff --git a/docs/html/guide/topics/testing/testing_android.jd b/docs/html/guide/topics/testing/testing_android.jd
index 46ba769..9ace1cb 100755
--- a/docs/html/guide/topics/testing/testing_android.jd
+++ b/docs/html/guide/topics/testing/testing_android.jd
@@ -23,9 +23,9 @@
         <li>
           <a href="#MockObjects">Mock object classes</a>
         </li>
-            <li>
-                <a href="#InstrumentationTestRunner">Instrumentation Test Runner</a>
-            </li>
+        <li>
+          <a href="#InstrumentationTestRunner">Instrumentation Test Runner</a>
+        </li>
       </ol>
     </li>
     <li>
@@ -64,7 +64,8 @@
   <h2>Related Tutorials</h2>
     <ol>
         <li>
-            <a href="{@docRoot}resources/tutorials/testing/helloandroid_test.html">Hello, Testing</a>
+            <a href="{@docRoot}resources/tutorials/testing/helloandroid_test.html">
+            Hello, Testing</a>
         </li>
         <li>
             <a href="{@docRoot}resources/tutorials/testing/activity_test.html">Activity Testing</a>
@@ -73,10 +74,12 @@
   <h2>See Also</h2>
       <ol>
         <li>
-          <a href="{@docRoot}guide/developing/testing/testing_eclipse.html">Testing in Eclipse, with ADT</a>
+          <a href="{@docRoot}guide/developing/testing/testing_eclipse.html">
+          Testing in Eclipse, with ADT</a>
         </li>
         <li>
-          <a href="{@docRoot}guide/developing/testing/testing_otheride.html">Testing in Other IDEs</a>
+          <a href="{@docRoot}guide/developing/testing/testing_otheride.html">
+          Testing in Other IDEs</a>
         </li>
       </ol>
   </div>
@@ -118,45 +121,55 @@
 in the same process. </p>
 
 <p>Your test application is linked to the application under test by means of an
-<a
-href="{@docRoot}guide/topics/manifest/instrumentation-element.html"><code>&lt;instrumentation&gt;</code></a>
-element in the test application's manifest file. The attributes of the element
-specify the package name of the application under test and also tell Android how
-to run the test application. Instrumentation is described in more detail in the
-section <a href="#InstrumentationTestRunner">Instrumentation Test
-Runner</a>.</p>
+    <a
+      href="{@docRoot}guide/topics/manifest/instrumentation-element.html">
+      <code>&lt;instrumentation&gt;</code></a> element in the test application's manifest file. 
+      The attributes of the element specify the package name of the application under test and also 
+      tell Android how to run the test application. Instrumentation is described in more detail 
+      in the section <a href="#InstrumentationTestRunner">Instrumentation Test Runner</a>.
+</p>
 
 <p>The following diagram summarizes the Android testing environment:</p>
 
 <img src="{@docRoot}images/testing/android_test_framework.png"/>
 
-<p>In Android, test applications are themselves Android applications, so you
-write them in much the same way as the application you are testing. The SDK
-tools help you create a main application project and its test project at the same
-time. You can run Android tests within Eclipse with ADT or from the command
-line. Eclipse with ADT provides an extensive set of tools for creating tests,
-running them, and viewing their results. You can also use the <code>adb</code>
-tool to run tests, or use a built-in Ant target.</p>
+<p>
+    In Android, test applications are themselves Android applications, so you
+    write them in much the same way as the application you are testing. The SDK
+    tools help you create a main application project and its test project at the same
+    time. You can run Android tests within Eclipse with ADT or from the command
+    line. Eclipse with ADT provides an extensive set of tools for creating tests,
+    running them, and viewing their results. You can also use the <code>adb</code>
+    tool to run tests, or use a built-in Ant target.
+</p>
 
-<p>To learn how to set up and run tests in Eclipse, please refer to <a
-href="{@docRoot}guide/developing/testing/testing_eclipse.html">Testing in
-Eclipse, with ADT</a>. If you're not working in Eclipse, refer to <a
-href="{@docRoot}guide/developing/testing/testing_otheride.html">Testing in Other
-IDEs</a>.</p>
+<p>
+    To learn how to set up and run tests in Eclipse, please refer to <a
+    href="{@docRoot}guide/developing/testing/testing_eclipse.html">Testing in
+    Eclipse, with ADT</a>. If you're not working in Eclipse, refer to <a
+    href="{@docRoot}guide/developing/testing/testing_otheride.html">Testing in Other
+    IDEs</a>.
+</p>
 
-<p>If you want a step-by-step introduction to Android testing, try one of the
-testing tutorials:</p>
+<p>
+    If you want a step-by-step introduction to Android testing, try one of the
+    testing tutorials:
+</p>
 
 <ul>
-  <li>The <a
-href="{@docRoot}resources/tutorials/testing/helloandroid_test.html">Hello,
-Testing</a> tutorial introduces basic testing concepts and procedures in the
-context of the Hello, World application.</li>
-  <li>The <a
-href="{@docRoot}resources/tutorials/testing/activity_test.html">Activity
-Testing</a> tutorial is an excellent follow-up to the Hello, Testing tutorial.
-It guides you through a more complex testing scenario that you develop against a
-more realistic application.</li>
+    <li>
+        The <a
+        href="{@docRoot}resources/tutorials/testing/helloandroid_test.html">Hello,
+        Testing</a> tutorial introduces basic testing concepts and procedures in the
+        context of the Hello, World application.
+    </li>
+    <li>
+        The <a
+        href="{@docRoot}resources/tutorials/testing/activity_test.html">Activity
+        Testing</a> tutorial is an excellent follow-up to the Hello, Testing tutorial.
+        It guides you through a more complex testing scenario that you develop against a
+        more realistic application.
+    </li>
 </ul>
 
 <h2 id="TestAPI">The Testing API</h2>
@@ -166,120 +179,156 @@
     a powerful instrumentation framework that lets your tests access the state and runtime objects
     of the application under tests.
 </p>
-<p>The sections below describe the major components of the testing API available in Android.</p>
+<p>
+    The sections below describe the major components of the testing API available in Android.
+</p>
 <h3 id="Extensions">JUnit test case classes</h3>
 <p>
-  Some of the classes in the testing API extend the JUnit {@link junit.framework.TestCase TestCase} but do not use the instrumentation framework. These classes
-  contain methods for accessing system objects such as the Context of the application under test. With this Context, you can look at its resources, files, databases,
-  and so forth. The base class is {@link android.test.AndroidTestCase}, but you usually use a subclass associated with a particular component.
+  Some of the classes in the testing API extend the JUnit {@link junit.framework.TestCase TestCase} 
+  but do not use the instrumentation framework. These classes contain methods for accessing system 
+  objects such as the Context of the application under test. With this Context, you can look at its 
+  resources, files, databases, and so forth. The base class is {@link android.test.AndroidTestCase},
+  but you usually use a subclass associated with a particular component.
 <p>
   The subclasses are:
 </p>
   <ul>
     <li>
-      {@link android.test.ApplicationTestCase} - A class for testing an entire application. It allows you to inject a mock Context into the application,
-      set up initial test parameters before the application starts, and examine the application after it finishes but before it is destroyed.
+      {@link android.test.ApplicationTestCase} - A class for testing an entire application. 
+      It allows you to inject a mock Context into the application, set up initial test parameters 
+      before the application starts, and examine the application after it finishes but before it 
+      is destroyed.
     </li>
     <li>
-      {@link android.test.ProviderTestCase2} - A class for isolated testing of a single {@link android.content.ContentProvider}. Since it is restricted to using a
-      {@link android.test.mock.MockContentResolver} for the provider, and it injects an {@link android.test.IsolatedContext}, your provider testing is isolated
+      {@link android.test.ProviderTestCase2} - A class for isolated testing of a single 
+      {@link android.content.ContentProvider}. Since it is restricted to using a
+       {@link android.test.mock.MockContentResolver} for the provider, and it injects an 
+       {@link android.test.IsolatedContext}, your provider testing is isolated
       from the rest of the OS.
     </li>
     <li>
-      {@link android.test.ServiceTestCase} - a class for isolated testing of a single {@link android.app.Service}. You can inject a mock Context or
-      mock Application (or both), or let Android provide you a full Context and a {@link android.test.mock.MockApplication}.
+      {@link android.test.ServiceTestCase} - a class for isolated testing of a single 
+      {@link android.app.Service}. You can inject a mock Context or mock Application (or both), or 
+      let Android provide you a full Context and a {@link android.test.mock.MockApplication}.
     </li>
   </ul>
 <h3 id="Instrumentation">Instrumentation test case classes</h3>
 <p>
-  The API for testing activities extends the JUnit {@link junit.framework.TestCase TestCase} class and also uses the instrumentation framework. With instrumentation,
-  Android can automate UI testing by sending events to the application under test, precisely control the start of an activity, and monitor the state of the
-  activity during its life cycle.
+  The API for testing activities extends the JUnit {@link junit.framework.TestCase TestCase} class 
+  and also uses the instrumentation framework. With instrumentation, Android can automate UI 
+  testing by sending events to the application under test, precisely control the start of an 
+  activity, and monitor the state of the activity during its life cycle.
 </p>
 <p>
-  The base class is {@link android.test.InstrumentationTestCase}. All of its subclasses have the ability to send a keystroke or touch event to the UI of the application
-  under test. The subclasses can also inject a mock Intent.
-  The subclasses are:
+  The base class is {@link android.test.InstrumentationTestCase}. All of its subclasses have 
+  the ability to send a keystroke or touch event to the UI of the application
+  under test. The subclasses can also inject a mock Intent. The subclasses are:
 </p>
   <ul>
     <li>
       {@link android.test.ActivityTestCase} - A base class for activity test classes.
     </li>
     <li>
-      {@link android.test.SingleLaunchActivityTestCase} - A convenience class for testing a single activity.
-      It invokes {@link junit.framework.TestCase#setUp() setUp()} and {@link junit.framework.TestCase#tearDown() tearDown()} only
-      once, instead of once per method call. Use it when all of your test methods run against the same activity.
+      {@link android.test.SingleLaunchActivityTestCase} - A convenience class for 
+      testing a single activity. It invokes {@link junit.framework.TestCase#setUp() setUp()} and 
+      {@link junit.framework.TestCase#tearDown() tearDown()} only once, instead of once per 
+      method call. Use it when all of your test methods run against the same activity.
     </li>
     <li>
-      {@link android.test.SyncBaseInstrumentation} - A class that tests synchronization of a content provider. It uses instrumentation to cancel and disable
-      existing synchronizations before starting the test synchronization.
+      {@link android.test.SyncBaseInstrumentation} - A class that tests synchronization of a 
+      content provider. It uses instrumentation to cancel and disable existing synchronizations 
+      before starting the test synchronization.
     </li>
     <li>
-      {@link android.test.ActivityUnitTestCase} - This class does an isolated test of a single activity. With it, you can inject a mock context or application, or both.
-      It is intended for doing unit tests of an activity, and is the activity equivalent of the test classes described in <a href="#Extensions">JUnit test case classes</a>.
-      <p> Unlike the other instrumentation classes, this test class cannot inject a mock Intent.</p>
+      {@link android.test.ActivityUnitTestCase} - This class does an isolated test of a single 
+      activity. With it, you can inject a mock context or application, or both.
+      It is intended for doing unit tests of an activity, and is the activity equivalent of the 
+      test classes described in <a href="#Extensions">JUnit test case classes</a>.
+      <p>
+        Unlike the other instrumentation classes, this test class cannot inject a mock Intent.
+      </p>
     </li>
     <li>
-      {@link android.test.ActivityInstrumentationTestCase2} - This class tests a single activity within the normal system environment.
-      You cannot inject a mock Context, but you can inject mock Intents. Also, you can run a test method on the UI thread (the main thread of the application under test),
-      which allows you to send key and touch events to the application UI.
+        {@link android.test.ActivityInstrumentationTestCase2} - This class tests a single activity 
+        within the normal system environment. You cannot inject a mock Context, but you can inject 
+        mock Intents. Also, you can run a test method on the UI thread (the main thread of the 
+        application under test), which allows you to send key and touch events to the 
+        application UI.
     </li>
   </ul>
 <h3 id="Assert">Assert classes</h3>
 <p>
-  Android also extends the JUnit {@link junit.framework.Assert} class that is the basis of <code>assert()</code> calls in tests.
-  There are two extensions to this class, {@link android.test.MoreAsserts} and {@link android.test.ViewAsserts}:
+  Android also extends the JUnit {@link junit.framework.Assert} class that is the basis of 
+  <code>assert()</code> calls in tests. There are two extensions to this class, 
+  {@link android.test.MoreAsserts} and {@link android.test.ViewAsserts}:
 </p>
 <ul>
   <li>
-    The <code>MoreAsserts</code> class contains more powerful assertions such as {@link android.test.MoreAsserts#assertContainsRegex} that does regular expression matching.
+    The <code>MoreAsserts</code> class contains more powerful assertions such as 
+    {@link android.test.MoreAsserts#assertContainsRegex} that does regular expression matching.
   </li>
   <li>
-    The {@link android.test.ViewAsserts} class contains useful assertions about Android Views, such as {@link android.test.ViewAsserts#assertHasScreenCoordinates} that tests if a View has a particular X and Y
-    position on the visible screen. These asserts simplify testing of geometry and alignment in the UI.
+    The {@link android.test.ViewAsserts} class contains useful assertions about Android Views, 
+    such as {@link android.test.ViewAsserts#assertHasScreenCoordinates} that tests if a View has a 
+    particular X and Y position on the visible screen. These asserts simplify testing of geometry 
+    and alignment in the UI.
   </li>
 </ul>
 <h3 id="MockObjects">Mock object classes</h3>
   <p>
-    Android has convenience classes for creating mock system objects such as applications, contexts, content resolvers, and resources. Android also provides
-    methods in some test classes for creating mock Intents. Use these mocks to facilitate dependency injection, since they are easier to use than creating their
-    real counterparts. These convenience classes are found in {@link android.test} and {@link android.test.mock}. They are:
+    Android has convenience classes for creating mock system objects such as applications, contexts,
+    content resolvers, and resources. Android also provides methods in some test classes for 
+    creating mock Intents. Use these mocks to facilitate dependency injection, since they are 
+    easier to use than creating their real counterparts. These convenience classes are found in 
+    {@link android.test} and {@link android.test.mock}. They are:
   </p>
     <ul>
       <li>
-        {@link android.test.IsolatedContext} - Mocks a Context so that the application using it runs in isolation.
-        At the same time, it has enough stub code to satisfy OS code that tries to communicate with contexts. This class is useful in unit testing.
+        {@link android.test.IsolatedContext} - Mocks a Context so that the application using it 
+        runs in isolation. At the same time, it has enough stub code to satisfy OS code that tries 
+        to communicate with contexts. This class is useful in unit testing.
       </li>
       <li>
-        {@link android.test.RenamingDelegatingContext} - Delegates most context functions to an existing, normal context while changing the default file and database
-        names in the context. Use this to test file and database operations with a normal system context, using test names.
+        {@link android.test.RenamingDelegatingContext} - Delegates most context functions to an 
+        existing, normal context while changing the default file and database
+        names in the context. Use this to test file and database operations with a normal system 
+        context, using test names.
       </li>
       <li>
-        {@link android.test.mock.MockApplication}, {@link android.test.mock.MockContentResolver}, {@link android.test.mock.MockContext},
-        {@link android.test.mock.MockDialogInterface}, {@link android.test.mock.MockPackageManager},
-        {@link android.test.mock.MockResources} - Classes that create mock Android system objects for use in testing. They expose only those methods that are
-        useful in managing the object. The default implementations of these methods simply throw an Exception. You are expected to extend the classes and
-        override any methods that are called by the application under test.
+        {@link android.test.mock.MockApplication}, {@link android.test.mock.MockContentResolver}, 
+        {@link android.test.mock.MockContext}, {@link android.test.mock.MockDialogInterface}, 
+        {@link android.test.mock.MockPackageManager}, {@link android.test.mock.MockResources} - 
+        Classes that create mock Android system objects for use in testing. They expose only those 
+        methods that are useful in managing the object. The default implementations of these methods
+        simply throw an Exception. You are expected to extend the classes and override any methods '
+        that are called by the application under test.
       </li>
     </ul>
 <h3 id="InstrumentationTestRunner">Instrumentation Test Runner</h3>
 <p>
   Android provides a custom class for running tests with instrumentation called called
   {@link android.test.InstrumentationTestRunner}. This class
-  controls of the application under test, runs the test application and the main application in the same process, and routes
-  test output to the appropriate place. Using instrumentation is key to the ability of <code>InstrumentationTestRunner</code> to control the entire test
-  environment at runtime. Notice that you use this test runner even if your test class does not itself use instrumentation.
+  controls of the application under test, runs the test application and the main application in the 
+  same process, and routes test output to the appropriate place. Using instrumentation is key to the
+  ability of <code>InstrumentationTestRunner</code> to control the entire test
+  environment at runtime. Notice that you use this test runner even if your test class does not 
+  itself use instrumentation.
 </p>
 <p>
-  When you run a test application, you first run a system utility called Activity Manager. Activity Manager uses the instrumentation framework to start and control the test runner, which in turn uses instrumentation to shut down any running instances
-  of the main application, starts the test application, and then starts the main application in the same process. This allows various aspects of the test application to work directly with the main application.
+  When you run a test application, you first run a system utility called Activity Manager. Activity 
+  Manager uses the instrumentation framework to start and control the test runner, which in turn 
+  uses instrumentation to shut down any running instances of the main application, starts the test 
+  application, and then starts the main application in the same process. This allows various 
+  aspects of the test application to work directly with the main application.
 </p>
 <p>
-  If you are developing in Eclipse, the ADT plugin assists you in the setup of <code>InstrumentationTestRunner</code> or other test runners.
-  The plugin UI prompts you to specify the test runner class to use, as well as the package name of the application under test.
-  The plugin then adds an <code>&lt;instrumentation&gt;</code> element with appropriate attributes to the manifest file of the test application.
-  Eclipse with ADT automatically starts a test application under the control of Activity Manager using instrumentation,
-  and redirects the test output to the Eclipse window's JUnit view.
+  If you are developing in Eclipse, the ADT plugin assists you in the setup of 
+  <code>InstrumentationTestRunner</code> or other test runners. The plugin UI prompts you to specify 
+  the test runner class to use, as well as the package name of the application under test.
+  The plugin then adds an <code>&lt;instrumentation&gt;</code> element with appropriate attributes 
+  to the manifest file of the test application. Eclipse with ADT automatically starts a test 
+  application under the control of Activity Manager using instrumentation, and redirects the test 
+  output to the Eclipse window's JUnit view.
 </p>
 <p>
   If you prefer working from the command line, you can use Ant and the <code>android</code>
@@ -289,68 +338,105 @@
 </p>
 <h2 id="TestEnviroment">Working in the Test Environment</h2>
 <p>
-    The tests for an Android application are contained in a test application, which itself is an Android application. A test application resides in a separate Android project that has the
-    same files and directories as a regular Android application. The test project is linked to the project of the application it tests
-    (known as the application under test) by its manifest file.
+    The tests for an Android application are contained in a test application, which itself is an 
+    Android application. A test application resides in a separate Android project that has the
+    same files and directories as a regular Android application. The test project is linked to the 
+    project of the application it tests (known as the application under test) by its manifest file.
 </p>
 <p>
     Each test application contains one or more test case classes based on an Android class for a
-    particular type of component. The test case class contains methods that define tests on some part of the application under test. When you run the test application, Android
-    starts it, loads the application under test into the same process, and then invokes each method in the test case class.
+    particular type of component. The test case class contains methods that define tests on some 
+    part of the application under test. When you run the test application, Android
+    starts it, loads the application under test into the same process, and then invokes each method 
+    in the test case class.
 </p>
 <p>
-    The tools and procedures you use with testing depend on the development environment you are using. If you use Eclipse, then the ADT plug in for Eclipse provides tools that
-    allow you to develop and run tests entirely within Eclipse. This is documented in the topic <a href="{@docRoot}guide/developing/testing/testing_eclipse.html">Testing in Eclipse, with ADT</a>.
-    If you use another development environment, then you use Android's command-line tools, as documented in the topic <a href="{@docRoot}guide/developing/testing/testing_otheride.html">Testing in Other IDEs</a>.
+    The tools and procedures you use with testing depend on the development environment you are 
+    using. If you use Eclipse, then the ADT plug in for Eclipse provides tools that allow you to 
+    develop and run tests entirely within Eclipse. This is documented in the topic 
+    <a href="{@docRoot}guide/developing/testing/testing_eclipse.html">
+    Testing in Eclipse, with ADT</a>. If you use another development environment, then you use 
+    Android's command-line tools, as documented in the topic 
+    <a href="{@docRoot}guide/developing/testing/testing_otheride.html">Testing in Other IDEs</a>.
 </p>
 <h3 id="TestProjects">Working with test projects</h3>
 <p>
-    To start testing an Android application, you create a test project for it using Android tools. The tools create the project directory and the files and subdirectories needed.
-    The tools also create a manifest file that links the application in the test project to the application under test. The procedure for creating a test project in Eclipse with
-    ADT is documented in <a href="{@docRoot}guide/developing/testing/testing_eclipse.html">Testing in Eclipse, with ADT</a>. The procedure for creating a test project for use with development
-    tools other than Eclipse is documented in <a href="{@docRoot}guide/developing/testing/testing_otheride.html">Testing in Other IDEs</a>.
+    To start testing an Android application, you create a test project for it using Android tools. 
+    The tools create the project directory and the files and subdirectories needed.
+    The tools also create a manifest file that links the application in the test project to the 
+    application under test. The procedure for creating a test project in Eclipse with ADT is 
+    documented in <a href="{@docRoot}guide/developing/testing/testing_eclipse.html">
+    Testing in Eclipse, with ADT</a>. The procedure for creating a test project for use with 
+    development tools other than Eclipse is documented in 
+    <a href="{@docRoot}guide/developing/testing/testing_otheride.html">Testing in Other IDEs</a>.
 </p>
 <h3 id="TestClasses">Working with test case classes</h3>
 <p>
-    A test application contains one or more test case classes that extend an Android test case class. You choose a test case class based on the type of Android component you are testing and the
-    tests you are doing. A test application can test different components, but each test case class is designed to test a single type of component.
-    The Android test case classes are described in the section <a href="#TestAPI">The Testing API</a>.
+    A test application contains one or more test case classes that extend an Android test case 
+    class. You choose a test case class based on the type of Android component you are testing and 
+    the tests you are doing. A test application can test different components, but each test case 
+    class is designed to test a single type of component. The Android test case classes are 
+    described in the section <a href="#TestAPI">The Testing API</a>.
 </p>
 <p>
-    Some Android components have more than one associated test case class. In this case, you choose among the available classes based on the type of tests you want to do. For activities,
-    for example, you have the choice of either {@link android.test.ActivityInstrumentationTestCase2} or {@link android.test.ActivityUnitTestCase}.
+    Some Android components have more than one associated test case class. In this case, you choose 
+    among the available classes based on the type of tests you want to do. For activities, for 
+    example, you have the choice of either {@link android.test.ActivityInstrumentationTestCase2} or 
+    {@link android.test.ActivityUnitTestCase}.
 <p>
-    <code>ActivityInstrumentationTestCase2</code> is designed to do functional testing, so it tests activities in a normal system infrastructure. You can inject mocked Intents, but not
+    <code>ActivityInstrumentationTestCase2</code> is designed to do functional testing, so it tests 
+    activities in a normal system infrastructure. You can inject mocked Intents, but not
     mocked Contexts. In general, you can't mock dependencies for the activity under test.
 </p>
 <p>
-    In comparison, <code>ActivityUnitTestCase</code> is designed for unit testing, so it tests activities in an isolated system infrastructure. You can inject mocked or wrappered dependencies for
-    the activity under test, particularly mocked Contexts. On the other hand, when you use this test case class the activity under test runs in isolation and can't interact with other activities.
+    In comparison, <code>ActivityUnitTestCase</code> is designed for unit testing, so it tests 
+    activities in an isolated system infrastructure. You can inject mocked or wrappered 
+    dependencies for the activity under test, particularly mocked Contexts. On the other hand, 
+    when you use this test case class the activity under test runs in isolation and can't interact 
+    with other activities.
 </p>
 <p>
-    As a rule of thumb, if you wanted to test an activity's interaction with the rest of Android, you would use <code>ActivityInstrumentationTestCase2</code>. If you wanted to do regression testing
-    on an activity, you would use <code>ActivityUnitTestCase</code>.
+    As a rule of thumb, if you wanted to test an activity's interaction with the rest of Android, 
+    you would use <code>ActivityInstrumentationTestCase2</code>. If you wanted to do regression 
+    testing on an activity, you would use <code>ActivityUnitTestCase</code>.
 </p>
 <h3 id="Tests">Working with test methods</h3>
 <p>
-    Each test case class provides methods that you use to set up the test environment and control the application under test. For example, all test case classes provide the JUnit {@link junit.framework.TestCase#setUp() setUp()}
-    method that you can override to set up fixtures. In addition, you add methods to the class to define individual tests. Each method you add is run once each time you run the test application. If you override the <code>setUp()</code>
-    method, it runs before each of your methods. Similarly, the JUnit {@link junit.framework.TestCase#tearDown() tearDown()} method is run once after each of your methods.
+    Each test case class provides methods that you use to set up the test environment and control 
+    the application under test. For example, all test case classes provide the JUnit 
+    {@link junit.framework.TestCase#setUp() setUp()} method that you can override to set up 
+    fixtures. In addition, you add methods to the class to define individual tests. Each method you
+    add is run once each time you run the test application. If you override the <code>setUp()</code>
+    method, it runs before each of your methods. Similarly, the JUnit 
+    {@link junit.framework.TestCase#tearDown() tearDown()} method is run once after each of 
+    your methods.
 </p>
 <p>
-    The test case classes give you substantial control over starting and stopping components. For this reason, you have to specifically tell Android to start a component before you run tests against it. For example, you use the
-    {@link android.test.ActivityInstrumentationTestCase2#getActivity()} method to start the activity under test. You can call this method once during the entire test case, or once for each test method. You can even destroy the
-    activity under test by calling its {@link android.app.Activity#finish()} method and then restart it with <code>getActivity()</code> within a single test method.
+    The test case classes give you substantial control over starting and stopping components. For 
+    this reason, you have to specifically tell Android to start a component before you run tests 
+    against it. For example, you use the
+    {@link android.test.ActivityInstrumentationTestCase2#getActivity()} method to start the activity
+    under test. You can call this method once during the entire test case, or once for each test 
+    method. You can even destroy the activity under test by calling its 
+    {@link android.app.Activity#finish()} method and then restart it with 
+    <code>getActivity()</code> within a single test method.
 </p>
 <h3 id="RunTests">Running tests and seeing the results</h3>
 <p>
-    To run your tests, you build your test project and then run the test application using the system utility Activity Manager with instrumentation. You provide to Activity Manager the name of the test runner (usually
-    {@link android.test.InstrumentationTestRunner}) you specified for your application; the name includes both your test application's package name and the test runner class name. Activity Manager loads and starts your
-    test application, kills any instances of the application under test, loads an instance of the application under test into the same process as the test application, and then passes control to the first test case
-    class in your test application. The test runner then takes control of the tests, running each of your test methods against the application under test until all the methods in all the classes have been run.
+    To run your tests, you build your test project and then run the test application using the 
+    system utility Activity Manager with instrumentation. You provide to Activity Manager the name 
+    of the test runner (usually {@link android.test.InstrumentationTestRunner}) you specified for 
+    your application; the name includes both your test application's package name and the test 
+    runner class name. Activity Manager loads and starts your test application, kills any instances
+    of the application under test, loads an instance of the application under test into the same 
+    process as the test application, and then passes control to the first test case class in your 
+    test application. The test runner then takes control of the tests, running each of your test 
+    methods against the application under test until all the methods in all the classes have been 
+    run.
 </p>
 <p>
-    If you run a test within Eclipse with ADT, the output appears in a new JUnit view pane. If you run a test from the command line, the output goes to STDOUT.
+    If you run a test within Eclipse with ADT, the output appears in a new JUnit view pane. If you 
+    run a test from the command line, the output goes to STDOUT.
 </p>
 <h2 id="TestAreas">What to Test</h2>
 <p>
@@ -359,58 +445,64 @@
 </p>
   <ul>
     <li>
-      Activity lifecycle events: You should test that your activities handle lifecycle events correctly. For example
-      an activity should respond to pause or destroy events by saving its state. Remember that even a change in screen orientation
-      causes the current activity to be destroyed, so you should test that accidental device movements don't accidentally lose the
+      Activity lifecycle events: You should test that your activities handle lifecycle events 
+      correctly. For example, an activity should respond to pause or destroy events by saving its 
+      state. Remember that even a change in screen orientation causes the current activity to be 
+      destroyed, so you should test that accidental device movements don't accidentally lose the
       application state.
     </li>
     <li>
-      Database operations: You should ensure that database operations correctly handle changes to the application's state.
-      To do this, use mock objects from the package {@link android.test.mock android.test.mock}.
+      Database operations: You should ensure that database operations correctly handle changes to 
+      the application's state. To do this, use mock objects from the package 
+      {@link android.test.mock android.test.mock}.
     </li>
     <li>
-        Screen sizes and resolutions: Before you publish your application, make sure to test it on all of the
-        screen sizes and densities on which you want it to run. You can test the application on multiple sizes and densities using
-        AVDs, or you can test your application directly on the devices that you are targeting. For more information, see
-        the topic <a href="{@docRoot}guide/practices/screens_support.html">Supporting Multiple Screens</a>.
+        Screen sizes and resolutions: Before you publish your application, make sure to test it on 
+        all of the screen sizes and densities on which you want it to run. You can test the 
+        application on multiple sizes and densities using AVDs, or you can test your application 
+        directly on the devices that you are targeting. For more information, see the topic 
+        <a href="{@docRoot}guide/practices/screens_support.html">Supporting Multiple Screens</a>.
     </li>
   </ul>
 <p>
-  When possible, you should run these tests on an actual device. If this is not possible, you can
-  use the <a href="{@docRoot}guide/developing/tools/emulator.html">Android Emulator</a> with
-  <a href="{@docRoot}guide/developing/tools/avd.html">Android Virtual Devices</a> configured for
-  the hardware, screens, and versions you want to test.
+    When possible, you should run these tests on an actual device. If this is not possible, you can
+    use the <a href="{@docRoot}guide/developing/tools/emulator.html">Android Emulator</a> with
+    <a href="{@docRoot}guide/developing/tools/avd.html">Android Virtual Devices</a> configured for
+    the hardware, screens, and versions you want to test.
 </p>
 <h2 id="UITesting">Appendix: UI Testing Notes</h2>
 <p>
-  The following sections have tips for testing the UI of your Android application, specifically
-  to help you handle actions that run in the UI thread, touch screen and keyboard events, and home
-  screen unlock during testing.
+    The following sections have tips for testing the UI of your Android application, specifically
+    to help you handle actions that run in the UI thread, touch screen and keyboard events, and home
+    screen unlock during testing.
 </p>
 <h3 id="RunOnUIThread">Testing on the UI thread</h3>
 <p>
-  An application's activities run on the application's <strong>UI thread</strong>. Once the
-  UI is instantiated, for example in the activity's <code>onCreate()</code> method, then all
-  interactions with the UI must run in the UI thread. When you run the application normally, it
-  has access to the thread and does not have to do anything special.
+    An application's activities run on the application's <strong>UI thread</strong>. Once the
+    UI is instantiated, for example in the activity's <code>onCreate()</code> method, then all
+    interactions with the UI must run in the UI thread. When you run the application normally, it
+    has access to the thread and does not have to do anything special.
 </p>
 <p>
-  This changes when you run tests against the application. With instrumentation-based classes,
-  you can invoke methods against the UI of the application under test. The other test classes don't allow this.
-  To run an entire test method on the UI thread, you can annotate the thread with <code>@UIThreadTest</code>.
-  Notice that this will run <em>all</em> of the method statements on the UI thread.  Methods that do not interact with the UI
-  are not allowed; for example, you can't invoke <code>Instrumentation.waitForIdleSync()</code>.
+    This changes when you run tests against the application. With instrumentation-based classes,
+    you can invoke methods against the UI of the application under test. The other test classes 
+    don't allow this. To run an entire test method on the UI thread, you can annotate the thread 
+    with <code>@UIThreadTest</code>. Notice that this will run <em>all</em> of the method statements 
+    on the UI thread.  Methods that do not interact with the UI are not allowed; for example, you 
+    can't invoke <code>Instrumentation.waitForIdleSync()</code>.
 </p>
 <p>
-  To run a subset of a test method on the UI thread, create an anonymous class of type
-  <code>Runnable</code>, put the statements you want in the <code>run()</code> method, and instantiate a new
-  instance of the class as a parameter to the method <code><em>appActivity</em>.runOnUiThread()</code>, where
-  <code><em>appActivity</em></code> is the instance of the app you are testing.
+    To run a subset of a test method on the UI thread, create an anonymous class of type
+    <code>Runnable</code>, put the statements you want in the <code>run()</code> method, and 
+    instantiate a new instance of the class as a parameter to the method 
+    <code><em>appActivity</em>.runOnUiThread()</code>, where <code><em>appActivity</em></code> is 
+    the instance of the app you are testing.
 </p>
 <p>
-  For example, this code instantiates an activity to test, requests focus (a UI action) for the Spinner displayed
-  by the activity, and then sends a key to it. Notice that the calls to <code>waitForIdleSync</code> and <code>sendKeys</code>
-  aren't allowed to run on the UI thread:</p>
+    For example, this code instantiates an activity to test, requests focus (a UI action) for the 
+    Spinner displayed by the activity, and then sends a key to it. Notice that the calls to 
+    <code>waitForIdleSync</code> and <code>sendKeys</code> aren't allowed to run on the UI thread:
+</p>
 <pre>
   private MyActivity mActivity; // MyActivity is the class name of the app under test
   private Spinner mSpinner;
@@ -449,31 +541,36 @@
 
 <h3 id="NotouchMode">Turning off touch mode</h3>
 <p>
-  To control the emulator or a device with key events you send from your tests, you must turn off
-  touch mode. If you do not do this, the key events are ignored.
+    To control the emulator or a device with key events you send from your tests, you must turn off
+    touch mode. If you do not do this, the key events are ignored.
 </p>
 <p>
-  To turn off touch mode, you invoke <code>ActivityInstrumentationTestCase2.setActivityTouchMode(false)</code>
-  <em>before</em> you call <code>getActivity()</code> to start the activity. You must invoke the method in a test method
-  that is <em>not</em> running on the UI thread. For this reason, you can't invoke the touch mode method
-  from a test method that is annotated with <code>@UIThread</code>. Instead, invoke the touch mode method from <code>setUp()</code>.
+    To turn off touch mode, you invoke 
+    <code>ActivityInstrumentationTestCase2.setActivityTouchMode(false)</code>
+    <em>before</em> you call <code>getActivity()</code> to start the activity. You must invoke the 
+    method in a test method that is <em>not</em> running on the UI thread. For this reason, you 
+    can't invoke the touch mode method from a test method that is annotated with 
+    <code>@UIThread</code>. Instead, invoke the touch mode method from <code>setUp()</code>.
 </p>
 <h3 id="UnlockDevice">Unlocking the emulator or device</h3>
 <p>
-  You may find that UI tests don't work if the emulator's or device's home screen is disabled with the keyguard pattern.
-  This is because the application under test can't receive key events sent by <code>sendKeys()</code>. The best
-  way to avoid this is to start your emulator or device first and then disable the keyguard for the home screen.
+    You may find that UI tests don't work if the emulator's or device's home screen is disabled with 
+    the keyguard pattern. This is because the application under test can't receive key events sent '
+    by <code>sendKeys()</code>. The best way to avoid this is to start your emulator or device 
+    first and then disable the keyguard for the home screen.
 </p>
 <p>
-  You can also explicitly disable the keyguard. To do this,
-  you need to add a permission in the manifest file (<code>AndroidManifest.xml</code>) and
-  then disable the keyguard in your application under test. Note, though, that you either have to remove this before
-  you publish your application, or you have to disable it programmatically in the published app.
+    You can also explicitly disable the keyguard. To do this,
+    you need to add a permission in the manifest file (<code>AndroidManifest.xml</code>) and
+    then disable the keyguard in your application under test. Note, though, that you either have to 
+    remove this before you publish your application, or you have to disable it programmatically in 
+    the published app.
 </p>
 <p>
-  To add the the permission, add the element <code>&lt;uses-permission android:name="android.permission.DISABLE_KEYGUARD"/&gt;</code>
-  as a child of the <code>&lt;manifest&gt;</code> element. To disable the KeyGuard, add the following code
-  to the <code>onCreate()</code> method of activities you intend to test:
+    To add the the permission, add the element 
+    <code>&lt;uses-permission android:name="android.permission.DISABLE_KEYGUARD"/&gt;</code>
+    as a child of the <code>&lt;manifest&gt;</code> element. To disable the KeyGuard, add the 
+    following code to the <code>onCreate()</code> method of activities you intend to test:
 </p>
 <pre>
   mKeyGuardManager = (KeyguardManager) getSystemService(KEYGUARD_SERVICE);
@@ -483,7 +580,8 @@
 <p>where <code><em>activity_classname</em></code> is the class name of the activity.</p>
 <h3 id="UITestTroubleshooting">Troubleshooting UI tests</h3>
 <p>
-  This section lists some of the common test failures you may encounter in UI testing, and their causes:
+    This section lists some of the common test failures you may encounter in UI testing, and their 
+    causes:
 </p>
 <dl>
     <dt><code>WrongThreadException</code></dt>
@@ -491,27 +589,33 @@
       <p><strong>Problem:</strong></p>
       For a failed test, the Failure Trace contains the following error message:
       <code>
-        android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
+        android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created 
+        a view hierarchy can touch its views.
       </code>
       <p><strong>Probable Cause:</strong></p>
-        This error is common if you tried to send UI events to the UI thread from outside the UI thread. This commonly happens if you send UI events
-        from the test application, but you don't use the <code>@UIThread</code> annotation or the <code>runOnUiThread()</code> method. The test method tried to interact with the UI outside the UI thread.
+        This error is common if you tried to send UI events to the UI thread from outside the UI 
+        thread. This commonly happens if you send UI events from the test application, but you don't
+        use the <code>@UIThread</code> annotation or the <code>runOnUiThread()</code> method. The 
+        test method tried to interact with the UI outside the UI thread.
       <p><strong>Suggested Resolution:</strong></p>
-        Run the interaction on the UI thread. Use a test class that provides instrumentation. See the previous section <a href="#RunOnUIThread">Testing on the UI Thread</a>
+        Run the interaction on the UI thread. Use a test class that provides instrumentation. See 
+        the previous section <a href="#RunOnUIThread">Testing on the UI Thread</a>
         for more details.
     </dd>
     <dt><code>java.lang.RuntimeException</code></dt>
     <dd>
       <p><strong>Problem:</strong></p>
-      For a failed test, the Failure Trace contains the following error message:
+        For a failed test, the Failure Trace contains the following error message:
       <code>
         java.lang.RuntimeException: This method can not be called from the main application thread
       </code>
       <p><strong>Probable Cause:</strong></p>
-        This error is common if your test method is annotated with <code>@UiThreadTest</code> but then tries to
-        do something outside the UI thread or tries to invoke <code>runOnUiThread()</code>.
+        This error is common if your test method is annotated with <code>@UiThreadTest</code> but 
+        then tries to do something outside the UI thread or tries to invoke 
+        <code>runOnUiThread()</code>.
       <p><strong>Suggested Resolution:</strong></p>
-        Remove the <code>@UiThreadTest</code> annotation, remove the <code>runOnUiThread()</code> call, or re-factor your tests.
+        Remove the <code>@UiThreadTest</code> annotation, remove the <code>runOnUiThread()</code> 
+        call, or re-factor your tests.
     </dd>
 </dl>
 
diff --git a/docs/html/resources/browser.jd b/docs/html/resources/browser.jd
new file mode 100644
index 0000000..8a08769
--- /dev/null
+++ b/docs/html/resources/browser.jd
@@ -0,0 +1,49 @@
+page.title=Technical Resources
+@jd:body
+
+<style type="text/css">
+  {@literal @import} "{@docRoot}assets/android-developer-resource-browser.css";
+</style>
+
+<script type="text/javascript" src="{@docRoot}assets/android-developer-resource-browser.js"></script>
+<script type="text/javascript" src="{@docRoot}assets/microtemplate.js"></script>
+
+<div>
+  <p style="display: none; float: right">Filter: <input id="resource-browser-keyword-filter"/></p>
+  <p id="resource-browser-search-params">Showing all technical resources:</p>
+</div>
+
+<noscript>
+  <p class="note"><strong>Error:</strong>
+    You must have JavaScript enabled to view this page. Resources are also
+    available offline in the SDK.
+  </p>
+</noscript>
+
+<div id="resource-browser-results">
+  <div class="no-results">No results.</div>
+</div>
+
+<script type="text/html" id="tmpl_resource_browser_result">
+<div class="result">
+  <h3>
+    <% if ('external' in tagsHash) { %><strong>External: </strong> <% } %>
+    <a href="<%= path %>"><%= title.en %></a>
+    <% if ('new' in tagsHash) { %><span class="new">new!</span> <% } %>
+  </h3>
+  <p class="resource-meta"><%
+    var __g = ['', ''];
+    if ('article' in tagsHash) {
+      __g = ['Article', 'about'];
+    } else if ('tutorial' in tagsHash) {
+      __g = ['Tutorial', 'on'];
+    } else if ('sample' in tagsHash) {
+      __g = ['Sample', 'for'];
+    } else if ('video' in tagsHash) {
+      __g = ['Video', 'about'];
+    }
+  %>
+  <%= __g[0] %><% if (topicsHtml) { %> <%= __g[1] %><% } %> <%= topicsHtml %></p>
+  <p><%= description.en %></p>
+</div>
+</script>
diff --git a/docs/html/resources/index.jd b/docs/html/resources/index.jd
index 1668721..9055868 100644
--- a/docs/html/resources/index.jd
+++ b/docs/html/resources/index.jd
@@ -1,38 +1,90 @@
 page.title=Developer Resources
 @jd:body
 
+<style type="text/css">
+  #resource-list-table td {
+    border: 0;
+    padding: 0 24px;
+    width: 33%;
+    max-width: 250px;
+    border-right: 1px solid #ddd;
+  }
+
+  #resource-list-table td.last {
+    border-right: 0;
+    padding-right: 0;
+  }
+</style>
+
 <p>
-This section provides technical articles, tutorials, sample code, and other
+This section provides articles, tutorials, sample code, and other
 information to help you quickly implement the features you want in your
-application.
+application. To return to this page later, just click the "Resources"
+tab while any Resources page is loaded.
 </p>
 
+<h2>Technical Resources</h2>
+
+<table id="resource-list-table">
+<tr>
+  <td>
+    <a href="{@docRoot}resources/browser.html?tag=sample">
+      <img src="{@docRoot}assets/images/resource-big-sample.png"/>
+    </a>
+    <h3><a href="{@docRoot}resources/browser.html?tag=sample">
+      Sample Code
+    </a></h3>
+    <p>Fully-functioning sample applications that you can build and run
+    to learn about how Android works. Feel free to reuse any of the code or
+    techniques in the samples.</p>
+  </td>
+  <td>
+    <a href="{@docRoot}resources/browser.html?tag=article">
+      <img src="{@docRoot}assets/images/resource-big-article.png"/>
+    </a>
+    <h3><a href="{@docRoot}resources/browser.html?tag=article">
+      Articles
+    </a></h3>
+    <p>Focused discussions about Android development subjects, including
+    optimizations, tips, interesting implementations, "how-tos",
+    and so on.</p>
+  </td>
+  <td>
+    <a href="{@docRoot}resources/browser.html?tag=tutorial">
+      <img src="{@docRoot}assets/images/resource-big-tutorial.png"/>
+    </a>
+    <h3><a href="{@docRoot}resources/browser.html?tag=tutorial">
+      Tutorials
+    </a></h3>
+    <p>Step-by-step instructions demonstrating how to build an Android application
+    that has the specific features you want.</p>
+  </td>
+  <!-- <td class="last">
+    <a href="{@docRoot}resources/browser.html?tag=video">
+      <img src="{@docRoot}assets/images/resource-big-video.png"/>
+    </a>
+    <h3><a href="{@docRoot}resources/browser.html?tag=video">
+      Videos &amp; Screencasts
+    </a></h3>
+    <p>Videos and presentation slides from developer events, along with
+    screencasts to walk you through common Android development
+    workflows.</p>
+  </td> -->
+</tr>
+</table>
+
+<h2>Other Resources</h2>
+
 <dl>
-<dt><b>Technical Articles</b></dt>
-<dd>Focused discussions about Android development subjects, including
-optimizations, tips, interesting implementations,
-and so on. Most of the articles provide "how-to" instructions for adding
-features or functionality to your app. The articles are drawn from posts to the
-Android Developers Blog.
-</dd>
-
-<dt><b>Tutorials</b></dt>
-<dd>Step-by-step instructions demonstrating how to build an Android application
-that has the specific features you want. </dd>
-
-<dt><b>Sample Code</b></dt>
-<dd>Fully-functioning sample applications that you can look at or build and run,
-to learn about how Android works. Feel free to reuse any of the code or
-techniques that you find in the samples!</dd>
-
 <dt><b>Community</b></dt>
 <dd>Links to the Android discussion groups and information about other ways to
 collaborate with other developers. </dd>
 
+<dt><b>Device Dashboard</b></dt>
+<dd>Device distribution data, grouped by various dimensions such as screen size
+and Android platform version. </dd>
+
 <dt><b>More</b></dt>
 <dd>Quick development tips, troubleshooting information, and frequently asked
 questions (FAQs). </dd>
 </dl>
-
-<p>To return to this page later, just click the "Resources" tab while any
-Resources page is loaded. </p>
\ No newline at end of file
diff --git a/docs/html/resources/resources-data.js b/docs/html/resources/resources-data.js
new file mode 100644
index 0000000..d06b695
--- /dev/null
+++ b/docs/html/resources/resources-data.js
@@ -0,0 +1,632 @@
+var ANDROID_TAGS = {
+  type: {
+    'article': 'Article',
+    'tutorial': 'Tutorial',
+    'sample': 'Sample',
+    'video': 'Video',
+    'library': 'Code Library'
+  },
+  topic: {
+    'accessibility': 'Accessibility',
+    'accountsync': 'Accounts &amp; Sync',
+    'bestpractice': 'Best Practices',
+    'communication': 'Communication',
+    'compatibility': 'Compatibility',
+    'data': 'Data Access',
+    'drawing': 'Canvas Drawing',
+    'gamedev': 'Game Development',
+    'gl': 'OpenGL ES',
+    'input': 'Input Methods',
+    'intent': 'Intents',
+    'layout': 'Layouts/Views',
+    'media': 'Multimedia',
+    'newfeature': 'New Features',
+    'performance': 'Performance',
+    'search': 'Search',
+    'testing': 'Testing',
+    'ui': 'User Interface',
+    'web': 'Web Content'
+  },
+  misc: {
+    'external': 'External',
+    'new': 'New'
+  }
+};
+
+var ANDROID_RESOURCES = [
+
+//////////////////////////
+/// TECHNICAL ARTICLES ///
+//////////////////////////
+
+  {
+    tags: ['article', 'performance', 'bestpractice'],
+    path: 'articles/avoiding-memory-leaks.html',
+    title: {
+      en: 'Avoiding Memory Leaks'
+    },
+    description: {
+      en: 'Mobile devices often have limited memory, and memory leaks can cause your application to waste this valuable resource without your knowledge. This article provides tips to help you avoid common causes of memory leaks on the Android platform.'
+    }
+  },
+  {
+    tags: ['article', 'compatibility'],
+    path: 'articles/backward-compatibility.html',
+    title: {
+      en: 'Backward Compatibility'
+    },
+    description: {
+      en: 'The Android platform strives to ensure backwards compatibility. However, sometimes you want to use new features which aren\'t supported on older platforms. This article discusses strategies for selectively using these features based on availability, allowing you to keep your applications portable across a wide range of devices.'
+    }
+  },
+  {
+    tags: ['article', 'intent'],
+    path: 'articles/can-i-use-this-intent.html',
+    title: {
+      en: 'Can I Use this Intent?'
+    },
+    description: {
+      en: 'Android offers a very powerful and yet easy-to-use message type called an intent. You can use intents to turn applications into high-level libraries and make code modular and reusable. While it is nice to be able to make use of a loosely coupled API, there is no guarantee that the intent you send will be received by another application. This article describes a technique you can use to find out whether the system contains any application capable of responding to the intent you want to use.'
+    }
+  },
+  {
+    tags: ['article', 'input'],
+    path: 'articles/creating-input-method.html',
+    title: {
+      en: 'Creating an Input Method'
+    },
+    description: {
+      en: 'Input Method Editors (IMEs) provide the mechanism for entering text into text fields and other Views. Android devices come bundled with at least one IME, but users can install additional IMEs. This article covers the basics of developing an IME for the Android platform.'
+    }
+  },
+  {
+    tags: ['article', 'drawing', 'ui'],
+    path: 'articles/drawable-mutations.html',
+    title: {
+      en: 'Drawable Mutations'
+    },
+    description: {
+      en: 'Drawables are pluggable drawing containers that allow applications to display graphics. This article explains some common pitfalls when trying to modify the properties of multiple Drawables.'
+    }
+  },
+  {
+    tags: ['article', 'bestpractice', 'ui'],
+    path: 'articles/faster-screen-orientation-change.html',
+    title: {
+      en: 'Faster Screen Orientation Change'
+    },
+    description: {
+      en: 'When an Android device changes its orientation, the default behavior is to automatically restart the current activity with a new configuration. However, this can become a bottleneck in applications that access a large amount of external data. This article discusses how to gracefully handle this situation without resorting to manually processing configuration changes.'
+    }
+  },
+  {
+    tags: ['article', 'compatibility'],
+    path: 'articles/future-proofing.html',
+    title: {
+      en: 'Future-Proofing Your Apps'
+    },
+    description: {
+      en: 'A collection of common sense advice to help you ensure that your applications don\'t break when new versions of the Android platform are released.'
+    }
+  },
+  {
+    tags: ['article', 'input'],
+    path: 'articles/gestures.html',
+    title: {
+      en: 'Gestures'
+    },
+    description: {
+      en: 'Touch screens allow users to perform gestures, such as tapping, dragging, flinging, or sliding, to perform various actions. The gestures API enables your application to recognize even complicated gestures with ease. This article explains how to integrate this API into an application.'
+    }
+  },
+  {
+    tags: ['article', 'gamedev', 'gl'],
+    path: 'articles/glsurfaceview.html',
+    title: {
+      en: 'Introducing GLSurfaceView'
+    },
+    description: {
+      en: 'This article provides an overview of GLSurfaceView, a class that makes it easy to implement 2D or 3D OpenGL rendering inside of an Android application.'
+    }
+  },
+  {
+    tags: ['article', 'ui', 'layout'],
+    path: 'articles/layout-tricks-reuse.html',
+    title: {
+      en: 'Layout Tricks: Creating Reusable UI Components'
+    },
+    description: {
+      en: 'Learn how to combine multiple standard UI widgets into a single high-level component, which can be reused throughout your application.'
+    }
+  },
+  {
+    tags: ['article', 'layout', 'ui', 'performance', 'bestpractice'],
+    path: 'articles/layout-tricks-efficiency.html',
+    title: {
+      en: 'Layout Tricks: Creating Efficient Layouts'
+    },
+    description: {
+      en: 'Learn how to optimize application layouts as this article walks you through converting a LinearLayout into a RelativeLayout, and analyzes the resulting implications on performance.'
+    }
+  },
+  {
+    tags: ['article', 'layout', 'ui', 'performance', 'bestpractice'],
+    path: 'articles/layout-tricks-stubs.html',
+    title: {
+      en: 'Layout Tricks: Using ViewStubs'
+    },
+    description: {
+      en: 'Learn about using ViewStubs inside an application\'s layout in order to inflate rarely used UI elements, without the performance implications which would otherwise be caused by using the <code>&lt;include&gt;</code> tag.'
+    }
+  },
+  {
+    tags: ['article', 'layout', 'ui', 'performance', 'bestpractice'],
+    path: 'articles/layout-tricks-merge.html',
+    title: {
+      en: 'Layout Tricks: Merging Layouts'
+    },
+    description: {
+      en: 'Learn how to use the <code>&lt;merge&gt;</code> tag in your XML layouts in order to avoid unnecessary levels of hierarchy within an application\'s view tree.'
+    }
+  },
+  {
+    tags: ['article', 'ui', 'performance'],
+    path: 'articles/listview-backgrounds.html',
+    title: {
+      en: 'ListView Backgrounds: An Optimization'
+    },
+    description: {
+      en: 'ListViews are very popular widgets within the Android framework. This article describes some of the optimizations used by the ListView widget, and how to avoid some common issues that this causes when trying to use a custom background.'
+    }
+  },
+  {
+    tags: ['article', 'ui', 'newfeature'],
+    path: 'articles/live-folders.html',
+    title: {
+      en: 'Live Folders'
+    },
+    description: {
+      en: 'Live Folders allow users to display any source of data on their home screen without launching an application. This article discusses how to export an application\'s data in a format suitable for display inside of a live folder.'
+    }
+  },
+  {
+    tags: ['article', 'ui', 'newfeature'],
+    path: 'articles/live-wallpapers.html',
+    title: {
+      en: 'Live Wallpapers'
+    },
+    description: {
+      en: 'Live wallpapers are richer, animated, interactive backgrounds that users can display in their home screens. Learn how to create a live wallpaper and bundle it in an application that users can install on their devices.'
+    }
+  },
+  {
+    tags: ['article', 'input'],
+    path: 'articles/on-screen-inputs.html',
+    title: {
+      en: 'Onscreen Input Methods'
+    },
+    description: {
+      en: 'The Input Method Framework (IMF) allows users to take advantage of on-screen input methods, such as software keyboards. This article provides an overview of Input Method Editors (IMEs) and how applications interact with them.'
+    }
+  },
+  {
+    tags: ['article', 'performance', 'bestpractice'],
+    path: 'articles/painless-threading.html',
+    title: {
+      en: 'Painless Threading'
+    },
+    description: {
+      en: 'This article discusses the threading model used by Android applications and how applications can ensure best UI performance by spawning worker threads to handle long-running operations, rather than handling them in the main thread. The article also explains the API that your application can use to interact with Android UI toolkit components running on the main thread and spawn managed worker threads.'
+    }
+  },
+  {
+    tags: ['article', 'ui', 'search'],
+    path: 'articles/qsb.html',
+    title: {
+      en: 'Quick Search Box'
+    },
+    description: {
+      en: 'Quick Search Box (QSB) is a powerful, system-wide search framework. QSB makes it possible for users to quickly and easily find what they\'re looking for, both on their devices and on the web. This article discusses how to work with the QSB framework to add new search results for an installed application.'
+    }
+  },
+  {
+    tags: ['article', 'ui'],
+    path: 'articles/touch-mode.html',
+    title: {
+      en: 'Touch Mode'
+    },
+    description: {
+      en: 'This article explains the touch mode, one of the most important principles of Android\'s UI toolkit. Whenever a user interacts with a device\'s touch screen, the system enters touch mode. While simple in concept, there are important implications touch mode that are often overlooked.'
+    }
+  },
+  {
+    tags: ['article', 'performance', 'bestpractice'],
+    path: 'articles/track-mem.html',
+    title: {
+      en: 'Tracking Memory Allocations'
+    },
+    description: {
+      en: 'This article discusses how to use the Allocation Tracker tool to observe memory allocations and avoid performance problems that would otherwise be caused by ignoring the effect of Dalvik\'s garbage collector.'
+    }
+  },
+  {
+    tags: ['article', 'newfeature'],
+    path: 'articles/ui-1.5.html',
+    title: {
+      en: 'UI Framework Changes in Android 1.5'
+    },
+    description: {
+      en: 'Explore the UI changes that were introduced in Android 1.5, compared with the UI provided in Android 1.0 and 1.1.'
+    }
+  },
+  {
+    tags: ['article', 'newfeature'],
+    path: 'articles/ui-1.6.html',
+    title: {
+      en: 'UI Framework Changes in Android 1.6'
+    },
+    description: {
+      en: 'Explore the UI changes that were introduced in Android 1.6, compared with the UI provided in Android 1.5. In particular, this article discusses changes to RelativeLayouts and click listeners.'
+    }
+  },
+  {
+    tags: ['article', 'ui', 'bestpractice'],
+    path: 'articles/timed-ui-updates.html',
+    title: {
+      en: 'Updating the UI from a Timer'
+    },
+    description: {
+      en: 'Learn about how to use Handlers as a more efficient replacement for java.util.Timer on the Android platform.'
+    }
+  },
+  {
+    tags: ['article', 'ui', 'accessibility'],
+    path: 'articles/tts.html',
+    title: {
+      en: 'Using Text-to-Speech'
+    },
+    description: {
+      en: 'The text-to-speech API lets your application "speak" to users, in any of several languages. This article provides an overview of the TTS API and how you use to add speech capabilities to your application.'
+    }
+  },
+  {
+    tags: ['article', 'ui', 'web'],
+    path: 'articles/using-webviews.html',
+    title: {
+      en: 'Using WebViews'
+    },
+    description: {
+      en: 'WebViews allow an application to dynamically display HTML and execute JavaScript, without relinquishing control to a separate browser application. This article introduces the WebView classes and provides a sample application that demonstrates its use.'
+    }
+  },
+  {
+    tags: ['article', 'ui'],
+    path: 'articles/wikinotes-linkify.html',
+    title: {
+      en: 'WikiNotes: Linkify your Text!'
+    },
+    description: {
+      en: 'This article introduces WikiNotes for Android, part of the Apps for Android project. It covers the use of Linkify to turn ordinary text views into richer, link-oriented content that causes Android intents to fire when a link is selected.'
+    }
+  },
+  {
+    tags: ['article', 'intent'],
+    path: 'articles/wikinotes-intents.html',
+    title: {
+      en: 'WikiNotes: Routing Intents'
+    },
+    description: {
+      en: 'This article illustrates how an application, in this case the WikiNotes sample app, can use intents to route various types of linked text to the application that handles that type of data. For example, an app can use intents to route a linked telephone number to a dialer app and a web URL to a browser.'
+    }
+  },
+  {
+    tags: ['article', 'ui', 'performance'],
+    path: 'articles/window-bg-speed.html',
+    title: {
+      en: 'Window Backgrounds & UI Speed'
+    },
+    description: {
+      en: 'Some Android applications need to squeeze every bit of performance out of the UI toolkit and there are many ways to do so. In this article, you will discover how to speed up the drawing and the perceived startup time of your activities. Both of these techniques rely on a single feature, the window\'s background drawable.'
+    }
+  },
+  {
+    tags: ['article', 'performance', 'bestpractice'],
+    path: 'articles/zipalign.html',
+    title: {
+      en: 'Zipalign: an Easy Optimization'
+    },
+    description: {
+      en: 'The Android SDK includes a tool called zipalign that optimizes the way an application is packaged. Running zipalign against your application enables Android to interact with it more efficiently at run time and thus has the potential to make it and the overall system run faster. This article provides a high-level overview of the zipalign tool and its use.'
+    }
+  },
+
+///////////////////
+/// SAMPLE CODE ///
+///////////////////
+
+  {
+    tags: ['sample', 'layout', 'ui'],
+    path: 'samples/ApiDemos/index.html',
+    title: {
+      en: 'API Demos'
+    },
+    description: {
+      en: 'A variety of small applications that demonstrate an extensive collection of framework topics.'
+    }
+  },
+  {
+    tags: ['sample', 'data', 'newfeature', 'accountsync', 'new'],
+    path: 'samples/BackupRestore/index.html',
+    title: {
+      en: 'Backup and Restore'
+    },
+    description: {
+      en: 'Illustrates a few different approaches that an application developer can take when integrating with the Android Backup Manager using the BackupAgent API introduced in Android 2.2.'
+    }
+  },
+  {
+    tags: ['sample', 'communication'],
+    path: 'samples/BluetoothChat/index.html',
+    title: {
+      en: 'Bluetooth Chat'
+    },
+    description: {
+      en: 'An application for two-way text messaging over Bluetooth.'
+    }
+  },
+  {
+    tags: ['sample', 'accountsync'],
+    path: 'samples/BusinessCard/index.html',
+    title: {
+      en: 'BusinessCard'
+    },
+    description: {
+      en: 'An application that demonstrates how to launch the built-in contact picker from within an activity. This sample also uses reflection to ensure that the correct version of the contacts API is used, depending on which API level the application is running under.'
+    }
+  },
+  {
+    tags: ['sample', 'accountsync'],
+    path: 'samples/ContactManager/index.html',
+    title: {
+      en: 'Contact Manager'
+    },
+    description: {
+      en: 'An application that demonstrates how to query the system contacts provider  using the <code>ContactsContract</code> API, as well as insert contacts into a specific account.'
+    }
+  },
+  {
+    tags: ['sample'],
+    path: 'samples/Home/index.html',
+    title: {
+      en: 'Home'
+    },
+    description: {
+      en: 'A home screen replacement application.'
+    }
+  },
+  {
+    tags: ['sample', 'gamedev', 'media'],
+    path: 'samples/JetBoy/index.html',
+    title: {
+      en: 'JetBoy'
+    },
+    description: {
+      en: 'A game that demonstrates the SONiVOX JET interactive music technology, with <code><a href="/reference/android/media/JetPlayer.html">JetPlayer</a></code>.'
+    }
+  },
+  {
+    tags: ['sample', 'ui', 'newfeature'],
+    path: 'samples/CubeLiveWallpaper/index.html',
+    title: {
+      en: 'Live Wallpaper'
+    },
+    description: {
+      en: 'An application that demonstrates how to create a live wallpaper and  bundle it in an application that users can install on their devices.'
+    }
+  },
+  {
+    tags: ['sample', 'gamedev', 'media'],
+    path: 'samples/LunarLander/index.html',
+    title: {
+      en: 'Lunar Lander'
+    },
+    description: {
+      en: 'A classic Lunar Lander game.'
+    }
+  },
+  {
+    tags: ['sample', 'ui', 'bestpractice', 'layout'],
+    path: 'samples/MultiResolution/index.html',
+    title: {
+      en: 'Multiple Resolutions'
+    },
+    description: {
+      en: 'A sample application that shows how to use resource directory qualifiers to provide different resources for different screen configurations.'
+    }
+  },
+  {
+    tags: ['sample', 'data'],
+    path: 'samples/NotePad/index.html',
+    title: {
+      en: 'Note Pad'
+    },
+    description: {
+      en: 'An application for saving notes. Similar (but not identical) to the <a href="/resources/tutorials/notepad/index.html">Notepad tutorial</a>.'
+    }
+  },
+  {
+    tags: ['sample', 'accountsync'],
+    path: 'samples/SampleSyncAdapter/index.html',
+    title: {
+      en: 'SampleSyncAdapter'
+    },
+    description: {
+      en: 'Demonstrates how an application can communicate with a cloud-based service and synchronize its data with data stored locally in a content provider. The sample uses two related parts of the Android framework &mdash; the account manager and the synchronization manager (through a sync adapter).'
+    }
+  },
+  {
+    tags: ['sample', 'ui', 'search', 'new'],
+    path: 'samples/SearchableDictionary/index.html',
+    title: {
+      en: 'Searchable Dictionary v2'
+    },
+    description: {
+      en: 'A sample application that demonstrates Android\'s search framework, including how to provide search suggestions for Quick Search Box.'
+    }
+  },
+  {
+    tags: ['sample', 'layout', 'ui'],
+    path: 'samples/Snake/index.html',
+    title: {
+      en: 'Snake'
+    },
+    description: {
+      en: 'An implementation of the classic game "Snake."'
+    }
+  },
+  {
+    tags: ['sample', 'testing', 'new'],
+    path: 'samples/Spinner/index.html',
+    title: {
+      en: 'Spinner'
+    },
+    description: {
+      en: 'A simple application that serves as an application under test for the SpinnerTest example.'
+    }
+  },
+  {
+    tags: ['sample', 'testing', 'new'],
+    path: 'samples/SpinnerTest/index.html',
+    title: {
+      en: 'SpinnerTest'
+    },
+    description: {
+      en: 'The test application for the Activity Testing tutorial. It tests the Spinner example application.'
+    }
+  },
+  {
+    tags: ['sample', 'newfeature', 'new'],
+    path: 'samples/TicTacToeLib/index.html',
+    title: {
+      en: 'TicTacToeLib'
+    },
+    description: {
+      en: 'An example of an Android library project, a type of project that lets you store and manage shared code and resources in one place, then make them available to your other Android applications.'
+    }
+  },
+  {
+    tags: ['sample', 'newfeature', 'new'],
+    path: 'samples/TicTacToeMain/index.html',
+    title: {
+      en: 'TicTacToeMain'
+    },
+    description: {
+      en: 'Demonstrates how an application can make use of shared code and resources stored in an Android library project.'
+    }
+  },
+  {
+    tags: ['sample', 'input'],
+    path: 'samples/SoftKeyboard/index.html',
+    title: {
+      en: 'Soft Keyboard'
+    },
+    description: {
+      en: 'An example of writing an input method for a software keyboard.'
+    }
+  },
+  {
+    tags: ['sample', 'ui'],
+    path: 'samples/Wiktionary/index.html',
+    title: {
+      en: 'Wiktionary'
+    },
+    description: {
+      en: 'An example of creating interactive widgets for display on the Android home screen.'
+    }
+  },
+  {
+    tags: ['sample', 'ui'],
+    path: 'samples/WiktionarySimple/index.html',
+    title: {
+      en: 'Wiktionary (Simplified)'
+    },
+    description: {
+      en: 'A simple Android home screen widgets example.'
+    }
+  },
+  {
+    tags: ['sample', 'layout', 'new'],
+    path: 'samples/XmlAdapters/index.html',
+    title: {
+      en: 'XML Adapters'
+    },
+    description: {
+      en: 'Binding data to views using XML Adapters examples.'
+    }
+  },
+
+/////////////////
+/// TUTORIALS ///
+/////////////////
+
+  {
+    tags: ['tutorial'],
+    path: 'tutorials/hello-world.html',
+    title: {
+      en: 'Hello World'
+    },
+    description: {
+      en: 'Beginning basic application development with the Android SDK.'
+    }
+  },
+  {
+    tags: ['tutorial', 'ui', 'layout'],
+    path: 'tutorials/views/index.html',
+    title: {
+      en: 'Hello Views'
+    },
+    description: {
+      en: 'A walk-through of the various types of layouts and views available in the Android SDK.'
+    }
+  },
+  {
+    tags: ['tutorial', 'ui', 'bestpractice'],
+    path: 'tutorials/localization/index.html',
+    title: {
+      en: 'Hello Localization'
+    },
+    description: {
+      en: 'The basics of localizing your applications for multiple languages and locales.'
+    }
+  },
+  {
+    tags: ['tutorial', 'data'],
+    path: 'tutorials/notepad/index.html',
+    title: {
+      en: 'Notepad Tutorial'
+    },
+    description: {
+      en: 'A multi-part tutorial discussing intermediate-level concepts such as data access.'
+    }
+  },
+  {
+    tags: ['tutorial', 'testing', 'new'],
+    path: 'tutorials/testing/helloandroid_test.html',
+    title: {
+      en: 'Hello Testing'
+    },
+    description: {
+      en: 'A basic introduction to the Android testing framework.'
+    }
+  },
+  {
+    tags: ['tutorial', 'testing', 'new'],
+    path: 'tutorials/testing/activity_test.html',
+    title: {
+      en: 'Activity Testing'
+    },
+    description: {
+      en: 'A more advanced demonstration of the Android testing framework and tools.'
+    }
+  }
+];
diff --git a/docs/html/resources/resources_toc.cs b/docs/html/resources/resources_toc.cs
index 52689b6..a1711b5 100644
--- a/docs/html/resources/resources_toc.cs
+++ b/docs/html/resources/resources_toc.cs
@@ -1,5 +1,56 @@
 <ul>
   <li>
+    <h2><span class="en">Technical Resources</span>
+    </h2>
+    <ul>
+      <li class="toggle-list">
+        <div><a href="<?cs var:toroot ?>resources/browser.html?tag=sample">
+            <span class="en">Sample Code</span>
+            <span class="de" style="display:none">Beispielcode</span>
+            <span class="es" style="display:none">Código de ejemplo</span>
+            <span class="fr" style="display:none">Exemple de code</span>
+            <span class="it" style="display:none">Codice di esempio</span>
+            <span class="ja" style="display:none">サンプル コード</span>
+            <span class="zh-CN" style="display:none"></span>
+            <span class="zh-TW" style="display:none"></span>
+          </a></div>
+        <ul id="devdoc-nav-sample-list">
+          <li><a href="<?cs var:toroot ?>resources/samples/get.html">
+                <span class="en">Getting the Samples</span>
+              </a></li>
+        </ul>
+      </li>
+      <li class="toggle-list">
+        <div><a href="<?cs var:toroot ?>resources/browser.html?tag=article">
+               <span class="en">Articles</span>
+             </a></div>
+        <ul id="devdoc-nav-article-list">
+        </ul>
+      </li>
+      <li class="toggle-list">
+        <div><a href="<?cs var:toroot ?>resources/browser.html?tag=tutorial">
+               <span class="en">Tutorials</span>
+               <span class="de" style="display:none">Lernprogramme</span>
+               <span class="es" style="display:none">Tutoriales</span>
+               <span class="fr" style="display:none">Didacticiels</span>
+               <span class="it" style="display:none">Esercitazioni</span>
+               <span class="ja" style="display:none">チュートリアル</span>
+               <span class="zh-CN" style="display:none"></span>
+               <span class="zh-TW" style="display:none"></span>
+             </a></div>
+        <ul id="devdoc-nav-tutorial-list">
+        </ul>
+      </li>
+      <li class="toggle-list">
+        <div><a href="<?cs var:toroot ?>resources/topics.html">
+               <span class="en">Topics</span>
+             </a></div>
+        <ul id="devdoc-nav-topic-list">
+        </ul>
+      </li>
+    </ul>
+  </li>
+  <li>
     <h2><span class="en">Community</span>
                <span style="display:none" class="de"></span>
                <span style="display:none" class="es">Comunidad</span>
@@ -34,241 +85,6 @@
   </li><?cs
   /if
 ?>
-
-  <li>
-    <h2><span class="en">Technical Articles</span>
-    </h2>
-    <ul>
-      <li class="toggle-list">
-        <div><a href="<?cs var:toroot ?>resources/articles/index.html">
-               <span class="en">List of Articles</span>
-             </a></div>
-        <ul>
-        <li><a href="<?cs var:toroot ?>resources/articles/avoiding-memory-leaks.html">
-                <span class="en">Avoiding Memory Leaks</span>
-                </a></li>
-        <li><a href="<?cs var:toroot ?>resources/articles/backward-compatibility.html">
-                <span class="en">Backward Compatibility</span>
-                </a></li>
-        <li><a href="<?cs var:toroot ?>resources/articles/can-i-use-this-intent.html">
-                <span class="en">Can I Use this Intent?</span>
-                </a></li>
-        <li><a href="<?cs var:toroot ?>resources/articles/creating-input-method.html">
-                <span class="en">Creating an Input Method</span>
-                </a></li>
-        <li><a href="<?cs var:toroot ?>resources/articles/drawable-mutations.html">
-                <span class="en">Drawable Mutations</span>
-                </a></li>
-        <li><a href="<?cs var:toroot ?>resources/articles/faster-screen-orientation-change.html">
-                <span class="en">Faster Screen Orientation Change</span>
-                </a></li>
-        <li><a href="<?cs var:toroot ?>resources/articles/future-proofing.html">
-                <span class="en">Future-Proofing Your Apps</span>
-                </a></li>
-        <li><a href="<?cs var:toroot ?>resources/articles/gestures.html">
-                <span class="en">Gestures</span>
-                </a></li>
-        <li><a href="<?cs var:toroot ?>resources/articles/glsurfaceview.html">
-                <span class="en">Introducing GLSurfaceView</span>
-                </a></li>
-        <li><a href="<?cs var:toroot ?>resources/articles/layout-tricks-reuse.html">
-                <span class="en">Layout Tricks: Reusing </span>
-                </a></li>
-        <li><a href="<?cs var:toroot ?>resources/articles/layout-tricks-efficiency.html">
-                <span class="en">Layout Tricks: Efficiency</span>
-                </a></li>
-        <li><a href="<?cs var:toroot ?>resources/articles/layout-tricks-stubs.html">
-                <span class="en">Layout Tricks: ViewStubs </span>
-                </a></li>
-        <li><a href="<?cs var:toroot ?>resources/articles/layout-tricks-merge.html">
-                <span class="en">Layout Tricks: Merging </span>
-                </a></li>
-        <li><a href="<?cs var:toroot ?>resources/articles/listview-backgrounds.html">
-                <span class="en">ListView Backgrounds</span>
-                </a></li>
-        <li><a href="<?cs var:toroot ?>resources/articles/live-folders.html">
-                <span class="en">Live Folders</span>
-                </a></li>
-        <li><a href="<?cs var:toroot ?>resources/articles/live-wallpapers.html">
-                <span class="en">Live Wallpapers</span>
-                </a></li>
-        <li><a href="<?cs var:toroot ?>resources/articles/on-screen-inputs.html">
-                <span class="en">Onscreen Input Methods</span>
-                </a></li>
-        <li><a href="<?cs var:toroot ?>resources/articles/painless-threading.html">
-                <span class="en">Painless Threading</span>
-                </a></li>
-        <li><a href="<?cs var:toroot ?>resources/articles/qsb.html">
-                <span class="en">Quick Search Box</span>
-                </a></li>
-        <li><a href="<?cs var:toroot ?>resources/articles/speech-input.html">
-                <span class="en">Speech Input</span>
-                </a></li>
-        <li><a href="<?cs var:toroot ?>resources/articles/touch-mode.html">
-                <span class="en">Touch Mode</span>
-                </a></li>
-        <li><a href="<?cs var:toroot ?>resources/articles/track-mem.html">
-                <span class="en">Tracking Memory Allocations</span>
-                </a></li>
-        <li><a href="<?cs var:toroot ?>resources/articles/ui-1.5.html">
-                <span class="en">UI Framework Changes in Android 1.5</span>
-                </a></li>
-        <li><a href="<?cs var:toroot ?>resources/articles/ui-1.6.html">
-                <span class="en">UI Framework Changes in Android 1.6</span>
-                </a></li>
-        <li><a href="<?cs var:toroot ?>resources/articles/timed-ui-updates.html">
-                <span class="en">Updating the UI from a Timer</span>
-                </a></li>
-        <li><a href="<?cs var:toroot ?>resources/articles/tts.html">
-                <span class="en">Using Text-to-Speech</span>
-                </a></li>
-        <li><a href="<?cs var:toroot ?>resources/articles/contacts.html">
-                <span class="en">Using the Contacts API</span>
-                </a></li>
-        <li><a href="<?cs var:toroot ?>resources/articles/using-webviews.html">
-                <span class="en">Using WebViews</span>
-                </a></li>
-        <li><a href="<?cs var:toroot ?>resources/articles/wikinotes-linkify.html">
-                <span class="en">WikiNotes: Linkify your Text!</span>
-                </a></li>
-        <li><a href="<?cs var:toroot ?>resources/articles/wikinotes-intents.html">
-                <span class="en">WikiNotes: Routing Intents</span>
-                </a></li>
-        <li><a href="<?cs var:toroot ?>resources/articles/window-bg-speed.html">
-                <span class="en">Window Backgrounds &amp; UI Speed</span>
-                </a></li>
-        <li><a href="<?cs var:toroot ?>resources/articles/zipalign.html">
-                <span class="en">Zipalign: An Easy Optimization</span>
-                </a></li>
-        </ul>
-      </li>
-    </ul>
-   </li>
-
-  <li>
-    <h2><span class="en">Tutorials</span>
-               <span class="de" style="display:none">Lernprogramme</span>
-               <span class="es" style="display:none">Tutoriales</span>
-               <span class="fr" style="display:none">Didacticiels</span>
-               <span class="it" style="display:none">Esercitazioni</span>
-               <span class="ja" style="display:none">チュートリアル</span>
-               <span class="zh-CN" style="display:none"></span>
-               <span class="zh-TW" style="display:none"></span>
-    </h2>
-    <ul>
-      <li><a href="<?cs var:toroot ?>resources/tutorials/hello-world.html">
-            <span class="en">Hello World</span>
-          </a></li>
-      <li><a href="<?cs var:toroot ?>resources/tutorials/views/index.html">
-            <span class="en">Hello Views</span>
-          </a></li>
-      <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>
-
-
-  <li>
-    <h2><span class="en">Sample Code</span>
-               <span class="de" style="display:none">Beispielcode</span>
-               <span class="es" style="display:none">Código de ejemplo</span>
-               <span class="fr" style="display:none">Exemple de code</span>
-               <span class="it" style="display:none">Codice di esempio</span>
-               <span class="ja" style="display:none">サンプル コード</span>
-               <span class="zh-CN" style="display:none"></span>
-               <span class="zh-TW" style="display:none"></span>
-    </h2>
-    <ul>
-      <li><a href="<?cs var:toroot ?>resources/samples/get.html">
-            <span class="en">Getting the Samples</span>
-          </a></li>
-      <li class="toggle-list">
-        <div><a href="<?cs var:toroot ?>resources/samples/index.html">
-               <span class="en">List of Samples</span>
-             </a></div>
-        <ul>
-          <li><a href="<?cs var:toroot ?>resources/samples/ApiDemos/index.html">
-                <span class="en">API Demos</span>
-              </a></li>
-          <li><a href="<?cs var:toroot ?>resources/samples/BackupRestore/index.html">
-                <span class="en">Backup and Restore</span>
-              </a> <span class="new">new!</span></li>
-          <li><a href="<?cs var:toroot ?>resources/samples/BluetoothChat/index.html">
-                <span class="en">Bluetooth Chat</span>
-              </a></li>
-          <li><a href="<?cs var:toroot ?>resources/samples/BusinessCard/index.html">
-                <span class="en">Business Card</span>
-              </a></li>
-          <li><a href="<?cs var:toroot ?>resources/samples/ContactManager/index.html">
-                <span class="en">Contact Manager</span>
-              </a></li>
-          <li><a href="<?cs var:toroot ?>resources/samples/Home/index.html">
-                <span class="en">Home</span>
-              </a></li>
-          <li><a href="<?cs var:toroot ?>resources/samples/JetBoy/index.html">
-                <span class="en">JetBoy</span>
-              </a></li>
-          <li><a href="<?cs var:toroot ?>resources/samples/CubeLiveWallpaper/index.html">
-                <span class="en">Live Wallpaper</span>
-                </a></li>
-          <li><a href="<?cs var:toroot ?>resources/samples/LunarLander/index.html">
-                <span class="en">Lunar Lander</span>
-              </a></li>
-          <li><a href="<?cs var:toroot ?>resources/samples/MultiResolution/index.html">
-                <span class="en">Multiple Resolutions</span>
-              </a></li>
-          <li><a href="<?cs var:toroot ?>resources/samples/NotePad/index.html">
-                <span class="en">Note Pad</span>
-              </a></li>
-          <li><a href="<?cs var:toroot ?>resources/samples/SampleSyncAdapter/index.html">
-                <span class="en">Sample Sync Adapter</span>
-              </a></li>
-          <li><a href="<?cs var:toroot ?>resources/samples/SearchableDictionary/index.html">
-                <span class="en">Searchable Dictionary v2</span>
-              </a> <span class="new">new!</span></li>
-          <li><a href="<?cs var:toroot ?>resources/samples/Snake/index.html">
-                <span class="en">Snake</span>
-              </a></li>
-          <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/TicTacToeLib/index.html">
-                <span class="en">TicTacToeLib</span>
-                </a> <span class="new">new!</span></li>
-          <li><a href="<?cs var:toroot ?>resources/samples/TicTacToeMain/index.html">
-                <span class="en">TicTacToeMain</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>
-          <li><a href="<?cs var:toroot ?>resources/samples/WiktionarySimple/index.html">
-                <span class="en">Wiktionary (Simplified)</span>
-              </a></li>
-        </ul>
-      </li>
-    </ul>
-  </li>
-
-
-
   <li>
     <h2><span class="en">More</span>
     </h2>
@@ -297,8 +113,6 @@
      </li>
     </ul>
   </li>
-
-
 </ul>
 
 <script type="text/javascript">
diff --git a/docs/html/resources/samples/images/XmlPhotosAdapter.png b/docs/html/resources/samples/images/XmlPhotosAdapter.png
new file mode 100644
index 0000000..c018d54
--- /dev/null
+++ b/docs/html/resources/samples/images/XmlPhotosAdapter.png
Binary files differ
diff --git a/docs/html/resources/samples/images/XmlRssReader.png b/docs/html/resources/samples/images/XmlRssReader.png
new file mode 100644
index 0000000..00f841b
--- /dev/null
+++ b/docs/html/resources/samples/images/XmlRssReader.png
Binary files differ
diff --git a/docs/html/resources/topics.jd b/docs/html/resources/topics.jd
new file mode 100644
index 0000000..b5960ff
--- /dev/null
+++ b/docs/html/resources/topics.jd
@@ -0,0 +1,72 @@
+page.title=Technical Resource Topics
+@jd:body
+
+<style type="text/css">
+  #resource-topic-table td {
+    border: 0;
+    padding: 0;
+    margin: 0;
+    padding-left: 1em;
+    width: 18em;
+  }
+
+  #resource-topic-table ul {
+    padding: 0;
+    margin: 0;
+  }
+
+  #resource-topic-table li {
+    list-style: none;
+    margin: 0;
+    padding: 0;
+  }
+</style>
+
+<p>
+You can browse the list of technical resources by topic by clicking on the
+links below. Over time, as more topics are added, they will be added to the
+list below.
+</p>
+
+<noscript>
+  <p class="note"><strong>Error:</strong>
+    You must have JavaScript enabled to view this page. Resources are also
+    available offline in the SDK.
+  </p>
+</noscript>
+
+<table id="resource-topic-table">
+  <tr></tr>
+</table>
+
+<script type="text/javascript">
+<!-- 
+(function() {
+  var topics = [];
+  for (var topic in ANDROID_TAGS['topic']) {
+    topics.push({name:topic,title:ANDROID_TAGS['topic'][topic]});
+  }
+  topics.sort(function(x,y){ return (x.title < y.title) ? -1 : 1; });
+  var topicParent = null;
+  for (var i = 0; i < topics.length; i++) {
+    if (topicParent == null || i % 10 == 0) {
+      // create a new column
+      topicParent = $('ul', $('<td><ul>').appendTo('#resource-topic-table tr'));
+    }
+
+    topicParent.append(
+        $('<li>').append(
+          $('<h3>').append(
+            $('<a>')
+              .attr('href', toRoot + "resources/browser.html?tag=" + topics[i].name)
+              .append($('<span>')
+                .addClass('en')
+                .html(topics[i].title)
+              )
+            )
+          )
+        );
+  }
+})();
+//-->
+</script>
\ No newline at end of file
diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java
index 7ca3741..537dd3a 100644
--- a/graphics/java/android/graphics/Bitmap.java
+++ b/graphics/java/android/graphics/Bitmap.java
@@ -35,9 +35,13 @@
      */
     public static final int DENSITY_NONE = 0;
     
-    // Note:  mNativeBitmap is used by FaceDetector_jni.cpp
-    // Don't change/rename without updating FaceDetector_jni.cpp
-    private final int mNativeBitmap;
+    /**
+     * Note:  mNativeBitmap is used by FaceDetector_jni.cpp
+     * Don't change/rename without updating FaceDetector_jni.cpp
+     * 
+     * @hide
+     */
+    public final int mNativeBitmap;
 
     private final boolean mIsMutable;
     private byte[] mNinePatchChunk;   // may be null
@@ -172,6 +176,19 @@
     }
 
     /**
+     * Returns the generation ID of this bitmap. The generation ID changes
+     * whenever the bitmap is modified. This can be used as an efficient way to
+     * check if a bitmap has changed.
+     * 
+     * @return The current generation ID for this bitmap.
+     * 
+     * @hide
+     */
+    public int getGenerationId() {
+        return nativeGenerationId(mNativeBitmap);
+    }
+    
+    /**
      * This is called by methods that want to throw an exception if the bitmap
      * has already been recycled.
      */
@@ -1041,6 +1058,7 @@
     private static native void nativeCopyPixelsToBuffer(int nativeBitmap,
                                                         Buffer dst);
     private static native void nativeCopyPixelsFromBuffer(int nb, Buffer src);
+    private static native int nativeGenerationId(int nativeBitmap);
 
     private static native Bitmap nativeCreateFromParcel(Parcel p);
     // returns true on success
diff --git a/graphics/java/android/graphics/BitmapFactory.java b/graphics/java/android/graphics/BitmapFactory.java
index 2313f4c..320fc4d 100644
--- a/graphics/java/android/graphics/BitmapFactory.java
+++ b/graphics/java/android/graphics/BitmapFactory.java
@@ -69,8 +69,11 @@
          * the decoder will try to pick the best matching config based on the
          * system's screen depth, and characteristics of the original image such
          * as if it has per-pixel alpha (requiring a config that also does).
+         * 
+         * The configuration is set to {@link android.graphics.Bitmap.Config#ARGB_8888}
+         * by default.
          */
-        public Bitmap.Config inPreferredConfig;
+        public Bitmap.Config inPreferredConfig = Bitmap.Config.ARGB_8888;
 
         /**
          * If dither is true, the decoder will attempt to dither the decoded
@@ -81,7 +84,7 @@
         /**
          * The pixel density to use for the bitmap.  This will always result
          * in the returned bitmap having a density set for it (see
-         * {@link Bitmap#setDensity(int) Bitmap.setDensity(int)).  In addition,
+         * {@link Bitmap#setDensity(int) Bitmap.setDensity(int))}.  In addition,
          * if {@link #inScaled} is set (which it is by default} and this
          * density does not match {@link #inTargetDensity}, then the bitmap
          * will be scaled to the target density before being returned.
@@ -507,9 +510,7 @@
      *
      * @param is The input stream that holds the raw data to be decoded into a
      *           bitmap.
-     * @return The decoded bitmap, or null if the image data could not be
-     *         decoded, or, if opts is non-null, if opts requested only the
-     *         size be returned (in opts.outWidth and opts.outHeight)
+     * @return The decoded bitmap, or null if the image data could not be decoded.
      */
     public static Bitmap decodeStream(InputStream is) {
         return decodeStream(is, null, null);
diff --git a/graphics/java/android/graphics/BitmapShader.java b/graphics/java/android/graphics/BitmapShader.java
index 612b0ab..4c92942 100644
--- a/graphics/java/android/graphics/BitmapShader.java
+++ b/graphics/java/android/graphics/BitmapShader.java
@@ -16,11 +16,11 @@
 
 package android.graphics;
 
+/**
+ * Shader used to draw a bitmap as a texture. The bitmap can be repeated or
+ * mirrored by setting the tiling mode.
+ */
 public class BitmapShader extends Shader {
-
-    // we hold on just for the GC, since our native counterpart is using it
-    private Bitmap mBitmap;
-
     /**
      * Call this to create a new shader that will draw with a bitmap.
      *
@@ -29,13 +29,13 @@
      * @param tileY             The tiling mode for y to draw the bitmap in.
      */
     public BitmapShader(Bitmap bitmap, TileMode tileX, TileMode tileY) {
-        mBitmap = bitmap;
-        native_instance = nativeCreate(bitmap.ni(),
-                                       tileX.nativeInt, tileY.nativeInt);
+        final int b = bitmap.ni();
+        native_instance = nativeCreate(b, tileX.nativeInt, tileY.nativeInt);
+        native_shader = nativePostCreate(native_instance, b, tileX.nativeInt, tileY.nativeInt);
     }
 
-    private static native int nativeCreate(int native_bitmap,
-                                           int shaderTileModeX,
-                                           int shaderTileModeY);    
+    private static native int nativeCreate(int native_bitmap, int shaderTileModeX,
+            int shaderTileModeY);
+    private static native int nativePostCreate(int native_shader, int native_bitmap,
+            int shaderTileModeX, int shaderTileModeY);
 }
-
diff --git a/graphics/java/android/graphics/Canvas.java b/graphics/java/android/graphics/Canvas.java
index 76cde73..77a1930 100644
--- a/graphics/java/android/graphics/Canvas.java
+++ b/graphics/java/android/graphics/Canvas.java
@@ -16,11 +16,10 @@
 
 package android.graphics;
 
-import android.text.TextUtils;
-import android.text.SpannedString;
-import android.text.SpannableString;
 import android.text.GraphicsOperations;
-import android.util.DisplayMetrics;
+import android.text.SpannableString;
+import android.text.SpannedString;
+import android.text.TextUtils;
 
 import javax.microedition.khronos.opengles.GL;
 
@@ -46,19 +45,37 @@
     private GL      mGL;        // if not null, mBitmap must be null
     
     // optional field set by the caller
-    private DrawFilter  mDrawFilter;
+    private DrawFilter mDrawFilter;
 
-    // Package-scoped for quick access.
-    /*package*/ int mDensity = Bitmap.DENSITY_NONE;
+    /**
+     * @hide
+     */
+    protected int mDensity = Bitmap.DENSITY_NONE;
 
-    // Used to determine when compatibility scaling is in effect.
-    private int mScreenDensity = Bitmap.DENSITY_NONE;
+    /**
+     * Used to determine when compatibility scaling is in effect.
+     * 
+     * @hide
+     */
+    protected int mScreenDensity = Bitmap.DENSITY_NONE;
     
     // Used by native code
     @SuppressWarnings({"UnusedDeclaration"})
     private int         mSurfaceFormat;
 
     /**
+     * Flag for drawTextRun indicating left-to-right run direction.
+     * @hide
+     */
+    public static final int DIRECTION_LTR = 0;
+    
+    /**
+     * Flag for drawTextRun indicating right-to-left run direction.
+     * @hide
+     */
+    public static final int DIRECTION_RTL = 1;
+    
+    /**
      * Construct an empty raster canvas. Use setBitmap() to specify a bitmap to
      * draw into.  The initial target density is {@link Bitmap#DENSITY_NONE};
      * this will typically be replaced when a target bitmap is set for the
@@ -106,17 +123,36 @@
      * 
      * <p>The initial target density of the canvas is the same as the initial
      * density of bitmaps as per {@link Bitmap#getDensity() Bitmap.getDensity()}.
+     * 
+     * @deprecated This constructor is not supported and should not be invoked.
      */
+    @Deprecated
     public Canvas(GL gl) {
         mNativeCanvas = initGL();
         mGL = gl;
         mDensity = Bitmap.getDefaultDensity();
     }
+
+    /**
+     * Indicates whether this Canvas uses hardware acceleration.
+     * 
+     * Note that this method does not define what type of hardware acceleration
+     * may or may not be used.
+     * 
+     * @return True if drawing operations are hardware accelerated,
+     *         false otherwise.
+     */
+    public boolean isHardwareAccelerated() {
+        return mGL != null;
+    }
     
     /**
      * Return the GL object associated with this canvas, or null if it is not
      * backed by GL.
+     * 
+     * @deprecated This method is not supported and should not be invoked.
      */
+    @Deprecated
     public GL getGL() {
         return mGL;
     }
@@ -125,7 +161,10 @@
      * Call this to free up OpenGL resources that may be cached or allocated
      * on behalf of the Canvas. Any subsequent drawing with a GL-backed Canvas
      * will have to recreate those resources.
+     * 
+     * @deprecated This method is not supported and should not be invoked.
      */
+    @Deprecated
     public static void freeGlCaches() {
         freeCaches();
     }
@@ -157,9 +196,12 @@
      * Set the viewport dimensions if this canvas is GL based. If it is not,
      * this method is ignored and no exception is thrown.
      *
-     *  @param width    The width of the viewport
-     *  @param height   The height of the viewport
+     *  @param width The width of the viewport
+     *  @param height The height of the viewport
+     * 
+     * @deprecated This method is not supported and should not be invoked.
      */
+    @Deprecated
     public void setViewport(int width, int height) {
         if (mGL != null) {
             nativeSetViewport(mNativeCanvas, width, height);
@@ -377,8 +419,8 @@
      *
      * @param sx The amount to scale in X
      * @param sy The amount to scale in Y
-     * @param px The x-coord for the pivot point (unchanged by the rotation)
-     * @param py The y-coord for the pivot point (unchanged by the rotation)
+     * @param px The x-coord for the pivot point (unchanged by the scale)
+     * @param py The y-coord for the pivot point (unchanged by the scale)
      */
     public final void scale(float sx, float sy, float px, float py) {
         translate(px, py);
@@ -621,7 +663,11 @@
         EdgeType(int nativeInt) {
             this.nativeInt = nativeInt;
         }
-        final int nativeInt;
+
+        /**
+         * @hide
+         */
+        public final int nativeInt;
     }
 
     /**
@@ -958,6 +1004,21 @@
     }
 
     /**
+     * Draws the specified bitmap as an N-patch (most often, a 9-patches.)
+     *
+     * Note: Only supported by hardware accelerated canvas at the moment.
+     *
+     * @param bitmap The bitmap to draw as an N-patch
+     * @param chunks The patches information (matches the native struct Res_png_9patch)
+     * @param dst The destination rectangle.
+     * @param paint The paint to draw the bitmap with. may be null
+     * 
+     * @hide
+     */
+    public void drawPatch(Bitmap bitmap, byte[] chunks, RectF dst, Paint paint) {
+    }    
+    
+    /**
      * Draw the specified bitmap, with its top/left corner at (x,y), using
      * the specified paint, transformed by the current matrix.
      * 
@@ -1246,8 +1307,8 @@
             (text.length - index - count)) < 0) {
             throw new IndexOutOfBoundsException();
         }
-        native_drawText(mNativeCanvas, text, index, count, x, y,
-                        paint.mNativePaint);
+        native_drawText(mNativeCanvas, text, index, count, x, y, paint.mBidiFlags,
+                paint.mNativePaint);
     }
 
     /**
@@ -1259,7 +1320,10 @@
      * @param y     The y-coordinate of the origin of the text being drawn
      * @param paint The paint used for the text (e.g. color, size, style)
      */
-    public native void drawText(String text, float x, float y, Paint paint);
+    public void drawText(String text, float x, float y, Paint paint) {
+        native_drawText(mNativeCanvas, text, 0, text.length(), x, y, paint.mBidiFlags,
+                paint.mNativePaint);
+    }
 
     /**
      * Draw the text, with origin at (x,y), using the specified paint.
@@ -1277,8 +1341,8 @@
         if ((start | end | (end - start) | (text.length() - end)) < 0) {
             throw new IndexOutOfBoundsException();
         }
-        native_drawText(mNativeCanvas, text, start, end, x, y,
-                        paint.mNativePaint);
+        native_drawText(mNativeCanvas, text, start, end, x, y, paint.mBidiFlags,
+                paint.mNativePaint);
     }
 
     /**
@@ -1299,16 +1363,108 @@
         if (text instanceof String || text instanceof SpannedString ||
             text instanceof SpannableString) {
             native_drawText(mNativeCanvas, text.toString(), start, end, x, y,
-                            paint.mNativePaint);
-        }
-        else if (text instanceof GraphicsOperations) {
+                            paint.mBidiFlags, paint.mNativePaint);
+        } else if (text instanceof GraphicsOperations) {
             ((GraphicsOperations) text).drawText(this, start, end, x, y,
                                                      paint);
-        }
-        else {
+        } else {
             char[] buf = TemporaryBuffer.obtain(end - start);
             TextUtils.getChars(text, start, end, buf, 0);
-            drawText(buf, 0, end - start, x, y, paint);
+            native_drawText(mNativeCanvas, buf, 0, end - start, x, y, 
+                    paint.mBidiFlags, paint.mNativePaint);
+            TemporaryBuffer.recycle(buf);
+        }
+    }
+
+    /**
+     * Render a run of all LTR or all RTL text, with shaping. This does not run
+     * bidi on the provided text, but renders it as a uniform right-to-left or
+     * left-to-right run, as indicated by dir. Alignment of the text is as
+     * determined by the Paint's TextAlign value.
+     * 
+     * @param text the text to render
+     * @param index the start of the text to render
+     * @param count the count of chars to render
+     * @param contextIndex the start of the context for shaping.  Must be
+     *         no greater than index.
+     * @param contextCount the number of characters in the context for shaping.
+     *         ContexIndex + contextCount must be no less than index
+     *         + count.
+     * @param x the x position at which to draw the text
+     * @param y the y position at which to draw the text
+     * @param dir the run direction, either {@link #DIRECTION_LTR} or
+     *         {@link #DIRECTION_RTL}.
+     * @param paint the paint
+     * @hide
+     */
+    public void drawTextRun(char[] text, int index, int count,
+            int contextIndex, int contextCount, float x, float y, int dir,
+            Paint paint) {
+
+        if (text == null) {
+            throw new NullPointerException("text is null");
+        }
+        if (paint == null) {
+            throw new NullPointerException("paint is null");
+        }
+        if ((index | count | text.length - index - count) < 0) {
+            throw new IndexOutOfBoundsException();
+        }
+        if (dir != DIRECTION_LTR && dir != DIRECTION_RTL) {
+            throw new IllegalArgumentException("unknown dir: " + dir);
+        }
+
+        native_drawTextRun(mNativeCanvas, text, index, count,
+                contextIndex, contextCount, x, y, dir, paint.mNativePaint);
+    }
+
+    /**
+     * Render a run of all LTR or all RTL text, with shaping. This does not run
+     * bidi on the provided text, but renders it as a uniform right-to-left or
+     * left-to-right run, as indicated by dir. Alignment of the text is as
+     * determined by the Paint's TextAlign value.
+     *
+     * @param text the text to render
+     * @param start the start of the text to render. Data before this position
+     *            can be used for shaping context.
+     * @param end the end of the text to render. Data at or after this
+     *            position can be used for shaping context.
+     * @param x the x position at which to draw the text
+     * @param y the y position at which to draw the text
+     * @param dir the run direction, either 0 for LTR or 1 for RTL.
+     * @param paint the paint
+     * @hide
+     */
+    public void drawTextRun(CharSequence text, int start, int end,
+            int contextStart, int contextEnd, float x, float y, int dir,
+            Paint paint) {
+
+        if (text == null) {
+            throw new NullPointerException("text is null");
+        }
+        if (paint == null) {
+            throw new NullPointerException("paint is null");
+        }
+        if ((start | end | end - start | text.length() - end) < 0) {
+            throw new IndexOutOfBoundsException();
+        }
+
+        int flags = dir == 0 ? 0 : 1;
+
+        if (text instanceof String || text instanceof SpannedString ||
+                text instanceof SpannableString) {
+            native_drawTextRun(mNativeCanvas, text.toString(), start, end,
+                    contextStart, contextEnd, x, y, flags, paint.mNativePaint);
+        } else if (text instanceof GraphicsOperations) {
+            ((GraphicsOperations) text).drawTextRun(this, start, end,
+                    contextStart, contextEnd, x, y, flags, paint);
+        } else {
+            int contextLen = contextEnd - contextStart;
+            int len = end - start;
+            char[] buf = TemporaryBuffer.obtain(contextLen);
+            TextUtils.getChars(text, contextStart, contextEnd, buf, 0);
+            native_drawTextRun(mNativeCanvas, buf, start - contextStart, len,
+                    0, contextLen, x, y, flags, paint.mNativePaint);
             TemporaryBuffer.recycle(buf);
         }
     }
@@ -1368,7 +1524,7 @@
         }
         native_drawTextOnPath(mNativeCanvas, text, index, count,
                               path.ni(), hOffset, vOffset,
-                              paint.mNativePaint);
+                              paint.mBidiFlags, paint.mNativePaint);
     }
 
     /**
@@ -1388,7 +1544,8 @@
                                float vOffset, Paint paint) {
         if (text.length() > 0) {
             native_drawTextOnPath(mNativeCanvas, text, path.ni(),
-                                  hOffset, vOffset, paint.mNativePaint);
+                                  hOffset, vOffset, paint.mBidiFlags,
+                                  paint.mNativePaint);
         }
     }
 
@@ -1432,6 +1589,7 @@
         restore();
     }
     
+    @Override
     protected void finalize() throws Throwable {
         super.finalize();
         // If the constructor threw an exception before setting mNativeCanvas, the native finalizer
@@ -1555,10 +1713,19 @@
     
     private static native void native_drawText(int nativeCanvas, char[] text,
                                                int index, int count, float x,
-                                               float y, int paint);
+                                               float y, int flags, int paint);
     private static native void native_drawText(int nativeCanvas, String text,
                                                int start, int end, float x,
-                                               float y, int paint);
+                                               float y, int flags, int paint);
+
+    private static native void native_drawTextRun(int nativeCanvas, String text,
+            int start, int end, int contextStart, int contextEnd,
+            float x, float y, int flags, int paint);
+
+    private static native void native_drawTextRun(int nativeCanvas, char[] text,
+            int start, int count, int contextStart, int contextCount,
+            float x, float y, int flags, int paint);
+
     private static native void native_drawPosText(int nativeCanvas,
                                                   char[] text, int index,
                                                   int count, float[] pos,
@@ -1570,11 +1737,13 @@
                                                      char[] text, int index,
                                                      int count, int path,
                                                      float hOffset,
-                                                     float vOffset, int paint);
+                                                     float vOffset, int bidiFlags,
+                                                     int paint);
     private static native void native_drawTextOnPath(int nativeCanvas,
                                                      String text, int path,
-                                                     float hOffset,
-                                                     float vOffset, int paint);
+                                                     float hOffset, 
+                                                     float vOffset, 
+                                                     int flags, int paint);
     private static native void native_drawPicture(int nativeCanvas,
                                                   int nativePicture);
     private static native void finalizer(int nativeCanvas);
diff --git a/graphics/java/android/graphics/ColorFilter.java b/graphics/java/android/graphics/ColorFilter.java
index 76f2c7f..e5cf830 100644
--- a/graphics/java/android/graphics/ColorFilter.java
+++ b/graphics/java/android/graphics/ColorFilter.java
@@ -23,12 +23,20 @@
 
 
 public class ColorFilter {
+    int native_instance;
+
+    /**
+     * @hide
+     */
+    public int nativeColorFilter;
 
     protected void finalize() throws Throwable {
-        finalizer(native_instance);
+        try {
+            super.finalize();
+        } finally {
+            finalizer(native_instance, nativeColorFilter);
+        }
     }
 
-    private static native void finalizer(int native_instance);
-
-    int native_instance;
+    private static native void finalizer(int native_instance, int nativeColorFilter);
 }
diff --git a/graphics/java/android/graphics/ColorMatrixColorFilter.java b/graphics/java/android/graphics/ColorMatrixColorFilter.java
index 5d73cff..245c615 100644
--- a/graphics/java/android/graphics/ColorMatrixColorFilter.java
+++ b/graphics/java/android/graphics/ColorMatrixColorFilter.java
@@ -25,7 +25,9 @@
      *               is constructed will not be reflected in the filter.
      */
     public ColorMatrixColorFilter(ColorMatrix matrix) {
-        native_instance = nativeColorMatrixFilter(matrix.getArray());
+        final float[] colorMatrix = matrix.getArray();
+        native_instance = nativeColorMatrixFilter(colorMatrix);
+        nativeColorFilter = nColorMatrixFilter(colorMatrix);
     }
 
     /**
@@ -40,7 +42,9 @@
             throw new ArrayIndexOutOfBoundsException();
         }
         native_instance = nativeColorMatrixFilter(array);
+        nativeColorFilter = nColorMatrixFilter(array);
     }
 
     private static native int nativeColorMatrixFilter(float[] array);
+    private static native int nColorMatrixFilter(float[] array);
 }
diff --git a/graphics/java/android/graphics/ComposeShader.java b/graphics/java/android/graphics/ComposeShader.java
index a06d30b..9b57ea4 100644
--- a/graphics/java/android/graphics/ComposeShader.java
+++ b/graphics/java/android/graphics/ComposeShader.java
@@ -30,7 +30,9 @@
     */
     public ComposeShader(Shader shaderA, Shader shaderB, Xfermode mode) {
         native_instance = nativeCreate1(shaderA.native_instance, shaderB.native_instance,
-                                        (mode != null) ? mode.native_instance : 0);
+                (mode != null) ? mode.native_instance : 0);
+        native_shader = nativePostCreate1(native_instance, shaderA.native_shader,
+                shaderB.native_shader, (mode != null) ? mode.native_instance : 0);
     }
 
     /** Create a new compose shader, given shaders A, B, and a combining PorterDuff mode.
@@ -42,10 +44,17 @@
     */
     public ComposeShader(Shader shaderA, Shader shaderB, PorterDuff.Mode mode) {
         native_instance = nativeCreate2(shaderA.native_instance, shaderB.native_instance,
-                                        mode.nativeInt);
+                mode.nativeInt);
+        native_shader = nativePostCreate2(native_instance, shaderA.native_shader,
+                shaderB.native_shader, mode.nativeInt);
     }
 
-    private static native int nativeCreate1(int native_shaderA, int native_shaderB, int native_mode);
-    private static native int nativeCreate2(int native_shaderA, int native_shaderB, int porterDuffMode);
+    private static native int nativeCreate1(int native_shaderA, int native_shaderB,
+            int native_mode);
+    private static native int nativeCreate2(int native_shaderA, int native_shaderB,
+            int porterDuffMode);
+    private static native int nativePostCreate1(int native_shader, int native_skiaShaderA,
+            int native_skiaShaderB, int native_mode);
+    private static native int nativePostCreate2(int native_shader, int native_skiaShaderA,
+            int native_skiaShaderB, int porterDuffMode);
 }
-
diff --git a/graphics/java/android/graphics/LightingColorFilter.java b/graphics/java/android/graphics/LightingColorFilter.java
index 5562389..715ce86 100644
--- a/graphics/java/android/graphics/LightingColorFilter.java
+++ b/graphics/java/android/graphics/LightingColorFilter.java
@@ -30,7 +30,9 @@
      */
     public LightingColorFilter(int mul, int add) {
         native_instance = native_CreateLightingFilter(mul, add);
+        nativeColorFilter = nCreateLightingFilter(mul, add);
     }
 
     private static native int native_CreateLightingFilter(int mul, int add);
+    private static native int nCreateLightingFilter(int mul, int add);
 }
diff --git a/graphics/java/android/graphics/LinearGradient.java b/graphics/java/android/graphics/LinearGradient.java
index e3db105..82ed199 100644
--- a/graphics/java/android/graphics/LinearGradient.java
+++ b/graphics/java/android/graphics/LinearGradient.java
@@ -17,7 +17,6 @@
 package android.graphics;
 
 public class LinearGradient extends Shader {
-
 	/**	Create a shader that draws a linear gradient along a line.
         @param x0           The x-coordinate for the start of the gradient line
         @param y0           The y-coordinate for the start of the gradient line
@@ -38,6 +37,8 @@
             throw new IllegalArgumentException("color and position arrays must be of equal length");
         }
         native_instance = nativeCreate1(x0, y0, x1, y1, colors, positions, tile.nativeInt);
+        native_shader = nativePostCreate1(native_instance, x0, y0, x1, y1, colors, positions,
+                tile.nativeInt);
     }
 
 	/**	Create a shader that draws a linear gradient along a line.
@@ -52,12 +53,16 @@
 	public LinearGradient(float x0, float y0, float x1, float y1,
                           int color0, int color1, TileMode tile) {
         native_instance = nativeCreate2(x0, y0, x1, y1, color0, color1, tile.nativeInt);
+        native_shader = nativePostCreate2(native_instance, x0, y0, x1, y1, color0, color1,
+                tile.nativeInt);
     }
 
-
-	private static native int nativeCreate1(float x0, float y0, float x1, float y1,
-                                            int colors[], float positions[], int tileMode);
-	private static native int nativeCreate2(float x0, float y0, float x1, float y1,
-                                            int color0, int color1, int tileMode);
+	private native int nativeCreate1(float x0, float y0, float x1, float y1,
+            int colors[], float positions[], int tileMode);
+	private native int nativeCreate2(float x0, float y0, float x1, float y1,
+            int color0, int color1, int tileMode);
+    private native int nativePostCreate1(int native_shader, float x0, float y0, float x1, float y1,
+            int colors[], float positions[], int tileMode);
+    private native int nativePostCreate2(int native_shader, float x0, float y0, float x1, float y1,
+            int color0, int color1, int tileMode);
 }
-
diff --git a/graphics/java/android/graphics/Matrix.java b/graphics/java/android/graphics/Matrix.java
index f549900..b336995 100644
--- a/graphics/java/android/graphics/Matrix.java
+++ b/graphics/java/android/graphics/Matrix.java
@@ -37,7 +37,10 @@
     public static final int MPERSP_1 = 7;   //!< use with getValues/setValues
     public static final int MPERSP_2 = 8;   //!< use with getValues/setValues
 
-    /* package */ int native_instance;
+    /**
+     * @hide
+     */
+    public int native_instance;
 
     /**
      * Create an identity matrix
diff --git a/graphics/java/android/graphics/NinePatch.java b/graphics/java/android/graphics/NinePatch.java
index 88dfd67..df6feba 100644
--- a/graphics/java/android/graphics/NinePatch.java
+++ b/graphics/java/android/graphics/NinePatch.java
@@ -35,6 +35,12 @@
  * </p>
  */
 public class NinePatch {
+    private final Bitmap mBitmap;
+    private final byte[] mChunk;
+    private Paint mPaint;
+    private String mSrcName;  // Useful for debugging
+    private final RectF mRect = new RectF();
+    
     /** 
      * Create a drawable projection from a bitmap to nine patches.
      *
@@ -74,10 +80,14 @@
      * @param location  Where to draw the bitmap.
      */
     public void draw(Canvas canvas, RectF location) {
-        nativeDraw(canvas.mNativeCanvas, location,
-                   mBitmap.ni(), mChunk,
-                   mPaint != null ? mPaint.mNativePaint : 0,
-                   canvas.mDensity, mBitmap.mDensity);
+        if (!canvas.isHardwareAccelerated()) {
+            nativeDraw(canvas.mNativeCanvas, location,
+                       mBitmap.ni(), mChunk,
+                       mPaint != null ? mPaint.mNativePaint : 0,
+                       canvas.mDensity, mBitmap.mDensity);
+        } else {
+            canvas.drawPatch(mBitmap, mChunk, location, null);
+        }
     }
     
     /** 
@@ -87,10 +97,15 @@
      * @param location  Where to draw the bitmap.
      */
     public void draw(Canvas canvas, Rect location) {
-        nativeDraw(canvas.mNativeCanvas, location,
-                mBitmap.ni(), mChunk,
-                mPaint != null ? mPaint.mNativePaint : 0,
-                canvas.mDensity, mBitmap.mDensity);
+        if (!canvas.isHardwareAccelerated()) {
+            nativeDraw(canvas.mNativeCanvas, location,
+                        mBitmap.ni(), mChunk,
+                        mPaint != null ? mPaint.mNativePaint : 0,
+                        canvas.mDensity, mBitmap.mDensity);
+        } else {
+            mRect.set(location);
+            canvas.drawPatch(mBitmap, mChunk, mRect, null);
+        }
     }
 
     /** 
@@ -101,9 +116,14 @@
      * @param paint     The Paint to draw through.
      */
     public void draw(Canvas canvas, Rect location, Paint paint) {
-        nativeDraw(canvas.mNativeCanvas, location,
-                mBitmap.ni(), mChunk, paint != null ? paint.mNativePaint : 0,
-                canvas.mDensity, mBitmap.mDensity);
+        if (!canvas.isHardwareAccelerated()) {
+            nativeDraw(canvas.mNativeCanvas, location,
+                    mBitmap.ni(), mChunk, paint != null ? paint.mNativePaint : 0,
+                    canvas.mDensity, mBitmap.mDensity);
+        } else {
+            mRect.set(location);
+            canvas.drawPatch(mBitmap, mChunk, mRect, paint);
+        }
     }
 
     /**
@@ -133,11 +153,6 @@
     
     public native static boolean isNinePatchChunk(byte[] chunk);
 
-    private final Bitmap mBitmap;
-    private final byte[] mChunk;
-    private Paint        mPaint;
-    private String       mSrcName;  // Useful for debugging
-
     private static native void validateNinePatchChunk(int bitmap, byte[] chunk);
     private static native void nativeDraw(int canvas_instance, RectF loc, int bitmap_instance,
                                           byte[] c, int paint_instance_or_null,
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index 3e3f87b..6349cb3 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -16,10 +16,10 @@
 
 package android.graphics;
 
-import android.text.TextUtils;
+import android.text.GraphicsOperations;
 import android.text.SpannableString;
 import android.text.SpannedString;
-import android.text.GraphicsOperations;
+import android.text.TextUtils;
 
 /**
  * The Paint class holds the style and color information about how to draw
@@ -27,7 +27,11 @@
  */
 public class Paint {
 
-    /*package*/ int     mNativePaint;
+    /**
+     * @hide
+     */
+    public int mNativePaint;
+
     private ColorFilter mColorFilter;
     private MaskFilter  mMaskFilter;
     private PathEffect  mPathEffect;
@@ -39,6 +43,10 @@
     private boolean     mHasCompatScaling;
     private float       mCompatScaling;
     private float       mInvCompatScaling;
+    /**
+     * @hide
+     */
+    public  int         mBidiFlags = BIDI_DEFAULT_LTR;
     
     private static final Style[] sStyleArray = {
         Style.FILL, Style.STROKE, Style.FILL_AND_STROKE
@@ -76,8 +84,116 @@
     private static final int DEFAULT_PAINT_FLAGS = DEV_KERN_TEXT_FLAG;
 
     /**
-     * The Style specifies if the primitive being drawn is filled,
-     * stroked, or both (in the same color). The default is FILL.
+     * Bidi flag to set LTR paragraph direction.
+     * 
+     * @hide
+     */
+    public static final int BIDI_LTR = 0x0;
+
+    /**
+     * Bidi flag to set RTL paragraph direction.
+     * 
+     * @hide
+     */
+    public static final int BIDI_RTL = 0x1;
+
+    /**
+     * Bidi flag to detect paragraph direction via heuristics, defaulting to
+     * LTR.
+     * 
+     * @hide
+     */
+    public static final int BIDI_DEFAULT_LTR = 0x2;
+
+    /**
+     * Bidi flag to detect paragraph direction via heuristics, defaulting to
+     * RTL.
+     * 
+     * @hide
+     */
+    public static final int BIDI_DEFAULT_RTL = 0x3;
+
+    /**
+     * Bidi flag to override direction to all LTR (ignore bidi).
+     * 
+     * @hide
+     */
+    public static final int BIDI_FORCE_LTR = 0x4;
+
+    /**
+     * Bidi flag to override direction to all RTL (ignore bidi).
+     * 
+     * @hide
+     */
+    public static final int BIDI_FORCE_RTL = 0x5;
+
+    /**
+     * Maximum Bidi flag value.
+     * @hide
+     */
+    private static final int BIDI_MAX_FLAG_VALUE = BIDI_FORCE_RTL;
+
+    /**
+     * Mask for bidi flags.
+     * @hide
+     */
+    private static final int BIDI_FLAG_MASK = 0x7;
+
+    /**
+     * Flag for getTextRunAdvances indicating left-to-right run direction.
+     * @hide
+     */
+    public static final int DIRECTION_LTR = 0;
+
+    /**
+     * Flag for getTextRunAdvances indicating right-to-left run direction.
+     * @hide
+     */
+    public static final int DIRECTION_RTL = 1;
+
+    /**
+     * Option for getTextRunCursor to compute the valid cursor after
+     * offset or the limit of the context, whichever is less.
+     * @hide
+     */
+    public static final int CURSOR_AFTER = 0;
+
+    /**
+     * Option for getTextRunCursor to compute the valid cursor at or after
+     * the offset or the limit of the context, whichever is less.
+     * @hide
+     */
+    public static final int CURSOR_AT_OR_AFTER = 1;
+
+     /**
+     * Option for getTextRunCursor to compute the valid cursor before
+     * offset or the start of the context, whichever is greater.
+     * @hide
+     */
+    public static final int CURSOR_BEFORE = 2;
+
+   /**
+     * Option for getTextRunCursor to compute the valid cursor at or before
+     * offset or the start of the context, whichever is greater.
+     * @hide
+     */
+    public static final int CURSOR_AT_OR_BEFORE = 3;
+
+    /**
+     * Option for getTextRunCursor to return offset if the cursor at offset
+     * is valid, or -1 if it isn't.
+     * @hide
+     */
+    public static final int CURSOR_AT = 4;
+
+    /**
+     * Maximum cursor option value.
+     */
+    private static final int CURSOR_OPT_MAX_VALUE = CURSOR_AT;
+
+    /**
+     * The Style specifies if the primitive being drawn is filled, stroked, or
+     * both (in the same color). The default is FILL.
      */
     public enum Style {
         /**
@@ -93,7 +209,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);
         
@@ -208,6 +326,7 @@
         mHasCompatScaling = paint.mHasCompatScaling;
         mCompatScaling = paint.mCompatScaling;
         mInvCompatScaling = paint.mInvCompatScaling;
+        mBidiFlags = paint.mBidiFlags;
     }
 
     /** Restores the paint to its default settings. */
@@ -216,6 +335,7 @@
         setFlags(DEFAULT_PAINT_FLAGS);
         mHasCompatScaling = false;
         mCompatScaling = mInvCompatScaling = 1;
+        mBidiFlags = BIDI_DEFAULT_LTR;
     }
     
     /**
@@ -238,6 +358,7 @@
             mHasCompatScaling = src.mHasCompatScaling;
             mCompatScaling    = src.mCompatScaling;
             mInvCompatScaling = src.mInvCompatScaling;
+            mBidiFlags      = src.mBidiFlags;
         }
     }
 
@@ -252,10 +373,33 @@
             mInvCompatScaling = 1.0f/factor;
         }
     }
-    
+
+    /**
+     * Return the bidi flags on the paint.
+     * 
+     * @return the bidi flags on the paint
+     * @hide
+     */
+    public int getBidiFlags() {
+        return mBidiFlags;
+    }
+
+    /**
+     * Set the bidi flags on the paint.
+     * @hide
+     */
+    public void setBidiFlags(int flags) {
+        // only flag value is the 3-bit BIDI control setting
+        flags &= BIDI_FLAG_MASK;
+        if (flags > BIDI_MAX_FLAG_VALUE) {
+            throw new IllegalArgumentException("unknown bidi flag: " + flags);
+        }
+        mBidiFlags = flags;
+    }
+
     /**
      * Return the paint's flags. Use the Flag enum to test flag values.
-     *
+     * 
      * @return the paint's flags (see enums ending in _Flag for bit masks)
      */
     public native int getFlags();
@@ -787,15 +931,14 @@
     }
     
     /**
-     * Temporary API to expose layer drawing. This draws a shadow layer below
-     * the main layer, with the specified offset and color, and blur radius.
-     * If radius is 0, then the shadow layer is removed.
+     * This draws a shadow layer below the main layer, with the specified
+     * offset and color, and blur radius. If radius is 0, then the shadow
+     * layer is removed.
      */
-    public native void setShadowLayer(float radius, float dx, float dy,
-                                      int color);
+    public native void setShadowLayer(float radius, float dx, float dy, int color);
 
     /**
-     * Temporary API to clear the shadow layer.
+     * Clear the shadow layer.
      */
     public void clearShadowLayer() {
         setShadowLayer(0, 0, 0, 0);
@@ -1232,10 +1375,10 @@
         }
 
         char[] buf = TemporaryBuffer.obtain(end - start);
-    	TextUtils.getChars(text, start, end, buf, 0);
-    	int result = getTextWidths(buf, 0, end - start, widths);
+        TextUtils.getChars(text, start, end, buf, 0);
+        int result = getTextWidths(buf, 0, end - start, widths);
         TemporaryBuffer.recycle(buf);
-    	return result;
+        return result;
     }
 
     /**
@@ -1282,6 +1425,284 @@
     }
 
     /**
+     * Convenience overload that takes a char array instead of a
+     * String.
+     *
+     * @see #getTextRunAdvances(String, int, int, int, int, int, float[], int)
+     * @hide
+     */
+    public float getTextRunAdvances(char[] chars, int index, int count,
+            int contextIndex, int contextCount, int flags, float[] advances,
+            int advancesIndex) {
+
+        if ((index | count | contextIndex | contextCount | advancesIndex
+                | (index - contextIndex)
+                | ((contextIndex + contextCount) - (index + count))
+                | (chars.length - (contextIndex + contextCount))
+                | (advances == null ? 0 :
+                    (advances.length - (advancesIndex + count)))) < 0) {
+            throw new IndexOutOfBoundsException();
+        }
+        if (flags != DIRECTION_LTR && flags != DIRECTION_RTL) {
+            throw new IllegalArgumentException("unknown flags value: " + flags);
+        }
+
+        if (!mHasCompatScaling) {
+            return native_getTextRunAdvances(mNativePaint, chars, index, count,
+                    contextIndex, contextCount, flags, advances, advancesIndex);
+        }
+
+        final float oldSize = getTextSize();
+        setTextSize(oldSize * mCompatScaling);
+        float res = native_getTextRunAdvances(mNativePaint, chars, index, count,
+                contextIndex, contextCount, flags, advances, advancesIndex);
+        setTextSize(oldSize);
+
+        if (advances != null) {
+            for (int i = advancesIndex, e = i + count; i < e; i++) {
+                advances[i] *= mInvCompatScaling;
+            }
+        }
+        return res * mInvCompatScaling; // assume errors are not significant
+    }
+
+    /**
+     * Convenience overload that takes a CharSequence instead of a
+     * String.
+     *
+     * @see #getTextRunAdvances(String, int, int, int, int, int, float[], int)
+     * @hide
+     */
+    public float getTextRunAdvances(CharSequence text, int start, int end,
+            int contextStart, int contextEnd, int flags, float[] advances,
+            int advancesIndex) {
+
+        if (text instanceof String) {
+            return getTextRunAdvances((String) text, start, end,
+                    contextStart, contextEnd, flags, advances, advancesIndex);
+        }
+        if (text instanceof SpannedString ||
+            text instanceof SpannableString) {
+            return getTextRunAdvances(text.toString(), start, end,
+                    contextStart, contextEnd, flags, advances, advancesIndex);
+        }
+        if (text instanceof GraphicsOperations) {
+            return ((GraphicsOperations) text).getTextRunAdvances(start, end,
+                    contextStart, contextEnd, flags, advances, advancesIndex, this);
+        }
+
+        int contextLen = contextEnd - contextStart;
+        int len = end - start;
+        char[] buf = TemporaryBuffer.obtain(contextLen);
+        TextUtils.getChars(text, start, end, buf, 0);
+        float result = getTextRunAdvances(buf, start - contextStart, len,
+                0, contextLen, flags, advances, advancesIndex);
+        TemporaryBuffer.recycle(buf);
+        return result;
+    }
+
+    /**
+     * Returns the total advance width for the characters in the run
+     * between start and end, and if advances is not null, the advance
+     * assigned to each of these characters (java chars).
+     *
+     * <p>The trailing surrogate in a valid surrogate pair is assigned
+     * an advance of 0.  Thus the number of returned advances is
+     * always equal to count, not to the number of unicode codepoints
+     * represented by the run.
+     *
+     * <p>In the case of conjuncts or combining marks, the total
+     * advance is assigned to the first logical character, and the
+     * following characters are assigned an advance of 0.
+     *
+     * <p>This generates the sum of the advances of glyphs for
+     * characters in a reordered cluster as the width of the first
+     * logical character in the cluster, and 0 for the widths of all
+     * other characters in the cluster.  In effect, such clusters are
+     * treated like conjuncts.
+     *
+     * <p>The shaping bounds limit the amount of context available
+     * outside start and end that can be used for shaping analysis.
+     * These bounds typically reflect changes in bidi level or font
+     * metrics across which shaping does not occur.
+     *
+     * @param text the text to measure
+     * @param start the index of the first character to measure
+     * @param end the index past the last character to measure
+     * @param contextStart the index of the first character to use for shaping context,
+     * must be <= start
+     * @param contextEnd the index past the last character to use for shaping context,
+     * must be >= end
+     * @param flags the flags to control the advances, either {@link #DIRECTION_LTR}
+     * or {@link #DIRECTION_RTL}
+     * @param advances array to receive the advances, must have room for all advances,
+     * can be null if only total advance is needed
+     * @param advancesIndex the position in advances at which to put the
+     * advance corresponding to the character at start
+     * @return the total advance
+     *
+     * @hide
+     */
+    public float getTextRunAdvances(String text, int start, int end, int contextStart,
+            int contextEnd, int flags, float[] advances, int advancesIndex) {
+
+        if ((start | end | contextStart | contextEnd | advancesIndex | (end - start)
+                | (start - contextStart) | (contextEnd - end)
+                | (text.length() - contextEnd)
+                | (advances == null ? 0 :
+                    (advances.length - advancesIndex - (end - start)))) < 0) {
+            throw new IndexOutOfBoundsException();
+        }
+        if (flags != DIRECTION_LTR && flags != DIRECTION_RTL) {
+            throw new IllegalArgumentException("unknown flags value: " + flags);
+        }
+
+        if (!mHasCompatScaling) {
+            return native_getTextRunAdvances(mNativePaint, text, start, end,
+                    contextStart, contextEnd, flags, advances, advancesIndex);
+        }
+
+        final float oldSize = getTextSize();
+        setTextSize(oldSize * mCompatScaling);
+        float totalAdvance = native_getTextRunAdvances(mNativePaint, text, start, end,
+                contextStart, contextEnd, flags, advances, advancesIndex);
+        setTextSize(oldSize);
+
+        if (advances != null) {
+            for (int i = advancesIndex, e = i + (end - start); i < e; i++) {
+                advances[i] *= mInvCompatScaling;
+            }
+        }
+        return totalAdvance * mInvCompatScaling; // assume errors are insignificant
+    }
+
+    /**
+     * Returns the next cursor position in the run.  This avoids placing the
+     * cursor between surrogates, between characters that form conjuncts,
+     * between base characters and combining marks, or within a reordering
+     * cluster.
+     *
+     * <p>ContextStart and offset are relative to the start of text.
+     * The context is the shaping context for cursor movement, generally
+     * the bounds of the metric span enclosing the cursor in the direction of
+     * movement.
+     *
+     * <p>If cursorOpt is {@link #CURSOR_AT} and the offset is not a valid
+     * cursor position, this returns -1.  Otherwise this will never return a
+     * value before contextStart or after contextStart + contextLength.
+     *
+     * @param text the text
+     * @param contextStart the start of the context
+     * @param contextLength the length of the context
+     * @param flags either {@link #DIRECTION_RTL} or {@link #DIRECTION_LTR}
+     * @param offset the cursor position to move from
+     * @param cursorOpt how to move the cursor, one of {@link #CURSOR_AFTER},
+     * {@link #CURSOR_AT_OR_AFTER}, {@link #CURSOR_BEFORE},
+     * {@link #CURSOR_AT_OR_BEFORE}, or {@link #CURSOR_AT}
+     * @return the offset of the next position, or -1
+     * @hide
+     */
+    public int getTextRunCursor(char[] text, int contextStart, int contextLength,
+            int flags, int offset, int cursorOpt) {
+        int contextEnd = contextStart + contextLength;
+        if (((contextStart | contextEnd | offset | (contextEnd - contextStart)
+                | (offset - contextStart) | (contextEnd - offset)
+                | (text.length - contextEnd) | cursorOpt) < 0)
+                || cursorOpt > CURSOR_OPT_MAX_VALUE) {
+            throw new IndexOutOfBoundsException();
+        }
+
+        return native_getTextRunCursor(mNativePaint, text,
+                contextStart, contextLength, flags, offset, cursorOpt);
+    }
+
+    /**
+     * Returns the next cursor position in the run.  This avoids placing the
+     * cursor between surrogates, between characters that form conjuncts,
+     * between base characters and combining marks, or within a reordering
+     * cluster.
+     *
+     * <p>ContextStart, contextEnd, and offset are relative to the start of
+     * text.  The context is the shaping context for cursor movement, generally
+     * the bounds of the metric span enclosing the cursor in the direction of
+     * movement.
+     *
+     * <p>If cursorOpt is {@link #CURSOR_AT} and the offset is not a valid
+     * cursor position, this returns -1.  Otherwise this will never return a
+     * value before contextStart or after contextEnd.
+     *
+     * @param text the text
+     * @param contextStart the start of the context
+     * @param contextEnd the end of the context
+     * @param flags either {@link #DIRECTION_RTL} or {@link #DIRECTION_LTR}
+     * @param offset the cursor position to move from
+     * @param cursorOpt how to move the cursor, one of {@link #CURSOR_AFTER},
+     * {@link #CURSOR_AT_OR_AFTER}, {@link #CURSOR_BEFORE},
+     * {@link #CURSOR_AT_OR_BEFORE}, or {@link #CURSOR_AT}
+     * @return the offset of the next position, or -1
+     * @hide
+     */
+    public int getTextRunCursor(CharSequence text, int contextStart,
+           int contextEnd, int flags, int offset, int cursorOpt) {
+
+        if (text instanceof String || text instanceof SpannedString ||
+                text instanceof SpannableString) {
+            return getTextRunCursor(text.toString(), contextStart, contextEnd,
+                    flags, offset, cursorOpt);
+        }
+        if (text instanceof GraphicsOperations) {
+            return ((GraphicsOperations) text).getTextRunCursor(
+                    contextStart, contextEnd, flags, offset, cursorOpt, this);
+        }
+
+        int contextLen = contextEnd - contextStart;
+        char[] buf = TemporaryBuffer.obtain(contextLen);
+        TextUtils.getChars(text, contextStart, contextEnd, buf, 0);
+        int result = getTextRunCursor(buf, 0, contextLen, flags, offset, cursorOpt);
+        TemporaryBuffer.recycle(buf);
+        return result;
+    }
+
+    /**
+     * Returns the next cursor position in the run.  This avoids placing the
+     * cursor between surrogates, between characters that form conjuncts,
+     * between base characters and combining marks, or within a reordering
+     * cluster.
+     *
+     * <p>ContextStart, contextEnd, and offset are relative to the start of
+     * text.  The context is the shaping context for cursor movement, generally
+     * the bounds of the metric span enclosing the cursor in the direction of
+     * movement.
+     *
+     * <p>If cursorOpt is {@link #CURSOR_AT} and the offset is not a valid
+     * cursor position, this returns -1.  Otherwise this will never return a
+     * value before contextStart or after contextEnd.
+     *
+     * @param text the text
+     * @param contextStart the start of the context
+     * @param contextEnd the end of the context
+     * @param flags either {@link #DIRECTION_RTL} or {@link #DIRECTION_LTR}
+     * @param offset the cursor position to move from
+     * @param cursorOpt how to move the cursor, one of {@link #CURSOR_AFTER},
+     * {@link #CURSOR_AT_OR_AFTER}, {@link #CURSOR_BEFORE},
+     * {@link #CURSOR_AT_OR_BEFORE}, or {@link #CURSOR_AT}
+     * @return the offset of the next position, or -1
+     * @hide
+     */
+    public int getTextRunCursor(String text, int contextStart, int contextEnd,
+            int flags, int offset, int cursorOpt) {
+        if (((contextStart | contextEnd | offset | (contextEnd - contextStart)
+                | (offset - contextStart) | (contextEnd - offset)
+                | (text.length() - contextEnd) | cursorOpt) < 0)
+                || cursorOpt > CURSOR_OPT_MAX_VALUE) {
+            throw new IndexOutOfBoundsException();
+        }
+
+        return native_getTextRunCursor(mNativePaint, text,
+                contextStart, contextEnd, flags, offset, cursorOpt);
+    }
+
+    /**
      * Return the path (outline) for the specified text.
      * Note: just like Canvas.drawText, this will respect the Align setting in
      * the paint.
@@ -1299,7 +1720,8 @@
         if ((index | count) < 0 || index + count > text.length) {
             throw new ArrayIndexOutOfBoundsException();
         }
-        native_getTextPath(mNativePaint, text, index, count, x, y, path.ni());
+        native_getTextPath(mNativePaint, mBidiFlags, text, index, count, x, y, 
+                path.ni());
     }
 
     /**
@@ -1320,7 +1742,8 @@
         if ((start | end | (end - start) | (text.length() - end)) < 0) {
             throw new IndexOutOfBoundsException();
         }
-        native_getTextPath(mNativePaint, text, start, end, x, y, path.ni());
+        native_getTextPath(mNativePaint, mBidiFlags, text, start, end, x, y, 
+                path.ni());
     }
     
     /**
@@ -1404,9 +1827,22 @@
                             char[] text, int index, int count, float[] widths);
     private static native int native_getTextWidths(int native_object,
                             String text, int start, int end, float[] widths);
-    private static native void native_getTextPath(int native_object,
+
+    private static native float native_getTextRunAdvances(int native_object,
+            char[] text, int index, int count, int contextIndex, int contextCount,
+            int flags, float[] advances, int advancesIndex);
+    private static native float native_getTextRunAdvances(int native_object,
+            String text, int start, int end, int contextStart, int contextEnd,
+            int flags, float[] advances, int advancesIndex);
+
+    private native int native_getTextRunCursor(int native_object, char[] text,
+            int contextStart, int contextLength, int flags, int offset, int cursorOpt);
+    private native int native_getTextRunCursor(int native_object, String text,
+            int contextStart, int contextEnd, int flags, int offset, int cursorOpt);
+
+    private static native void native_getTextPath(int native_object, int bidiFlags,
                 char[] text, int index, int count, float x, float y, int path);
-    private static native void native_getTextPath(int native_object,
+    private static native void native_getTextPath(int native_object, int bidiFlags,
                 String text, int start, int end, float x, float y, int path);
     private static native void nativeGetStringBounds(int nativePaint,
                                 String text, int start, int end, Rect bounds);
@@ -1414,4 +1850,3 @@
                                 char[] text, int index, int count, Rect bounds);
     private static native void finalizer(int nativePaint);
 }
-
diff --git a/graphics/java/android/graphics/PorterDuff.java b/graphics/java/android/graphics/PorterDuff.java
index 3904234..2ef1662 100644
--- a/graphics/java/android/graphics/PorterDuff.java
+++ b/graphics/java/android/graphics/PorterDuff.java
@@ -53,11 +53,18 @@
         /** [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;
         }
-        final int nativeInt;
+
+        /**
+         * @hide
+         */
+        public final int nativeInt;
     }
 }
diff --git a/graphics/java/android/graphics/PorterDuffColorFilter.java b/graphics/java/android/graphics/PorterDuffColorFilter.java
index 06724bd..b02dab1 100644
--- a/graphics/java/android/graphics/PorterDuffColorFilter.java
+++ b/graphics/java/android/graphics/PorterDuffColorFilter.java
@@ -25,10 +25,10 @@
      * @param mode           The porter-duff mode that is applied
      */
     public PorterDuffColorFilter(int srcColor, PorterDuff.Mode mode) {
-        native_instance = native_CreatePorterDuffFilter(srcColor,
-                                                        mode.nativeInt);
+        native_instance = native_CreatePorterDuffFilter(srcColor, mode.nativeInt);
+        nativeColorFilter = nCreatePorterDuffFilter(srcColor, mode.nativeInt);
     }
 
-    private static native int native_CreatePorterDuffFilter(int srcColor,
-                                                            int porterDuffMode);
+    private static native int native_CreatePorterDuffFilter(int srcColor, int porterDuffMode);
+    private static native int nCreatePorterDuffFilter(int srcColor, int porterDuffMode);
 }
diff --git a/graphics/java/android/graphics/Rect.java b/graphics/java/android/graphics/Rect.java
index 98ffb8b..7830224 100644
--- a/graphics/java/android/graphics/Rect.java
+++ b/graphics/java/android/graphics/Rect.java
@@ -75,6 +75,7 @@
         bottom = r.bottom;
     }
 
+    @Override
     public boolean equals(Object obj) {
         Rect r = (Rect) obj;
         if (r != null) {
@@ -84,6 +85,7 @@
         return false;
     }
 
+    @Override
     public String toString() {
         StringBuilder sb = new StringBuilder(32);
         sb.append("Rect("); sb.append(left); sb.append(", ");
@@ -351,7 +353,7 @@
      * rectangle, return true and set this rectangle to that intersection,
      * otherwise return false and do not change this rectangle. No check is
      * performed to see if either rectangle is empty. Note: To just test for
-     * intersection, use intersects()
+     * intersection, use {@link #intersects(Rect, Rect)}.
      *
      * @param left The left side of the rectangle being intersected with this
      *             rectangle
@@ -445,7 +447,7 @@
     /**
      * Returns true iff the two specified rectangles intersect. In no event are
      * either of the rectangles modified. To record the intersection,
-     * use intersect() or setIntersect().
+     * use {@link #intersect(Rect)} or {@link #setIntersect(Rect, Rect)}.
      *
      * @param a The first rectangle being tested for intersection
      * @param b The second rectangle being tested for intersection
diff --git a/graphics/java/android/graphics/Region.java b/graphics/java/android/graphics/Region.java
index 2b080aa..489ef83 100644
--- a/graphics/java/android/graphics/Region.java
+++ b/graphics/java/android/graphics/Region.java
@@ -33,7 +33,11 @@
         Op(int nativeInt) {
             this.nativeInt = nativeInt;
         }
-        final int nativeInt;
+
+        /**
+         * @hide
+         */
+        public final int nativeInt;
     }
 
     /** Create an empty region
diff --git a/graphics/java/android/graphics/Shader.java b/graphics/java/android/graphics/Shader.java
index ae0304e..b397662 100644
--- a/graphics/java/android/graphics/Shader.java
+++ b/graphics/java/android/graphics/Shader.java
@@ -23,9 +23,16 @@
  * drawn with that paint will get its color(s) from the shader.
  */
 public class Shader {
-
-    // this is set by subclasses, but don't make it public
-    /* package */ int native_instance;
+    /**
+     * This is set by subclasses, but don't make it public.
+     * 
+     * @hide 
+     */
+    public int native_instance;
+    /**
+     * @hide
+     */
+    public int native_shader;
 
     public enum TileMode {
         /**
@@ -64,17 +71,20 @@
      * @param localM The shader's new local matrix, or null to specify identity
      */
     public void setLocalMatrix(Matrix localM) {
-        nativeSetLocalMatrix(native_instance,
-                             localM != null ? localM.native_instance : 0);
+        nativeSetLocalMatrix(native_instance, native_shader, localM.native_instance);
     }
 
     protected void finalize() throws Throwable {
-        nativeDestructor(native_instance);
+        try {
+            super.finalize();
+        } finally {
+            nativeDestructor(native_instance, native_shader);
+        }
     }
 
-    private static native void nativeDestructor(int native_shader);
+    private static native void nativeDestructor(int native_shader, int native_skiaShader);
     private static native boolean nativeGetLocalMatrix(int native_shader,
-                                                       int matrix_instance);
+            int matrix_instance); 
     private static native void nativeSetLocalMatrix(int native_shader,
-                                                    int matrix_instance);
+            int native_skiaShader, int matrix_instance);
 }
diff --git a/graphics/java/android/graphics/TemporaryBuffer.java b/graphics/java/android/graphics/TemporaryBuffer.java
index 1d7fe01..c5b8143 100644
--- a/graphics/java/android/graphics/TemporaryBuffer.java
+++ b/graphics/java/android/graphics/TemporaryBuffer.java
@@ -18,9 +18,11 @@
 
 import com.android.internal.util.ArrayUtils;
 
-/* package */ class TemporaryBuffer
-{
-    /* package */ static char[] obtain(int len) {
+/**
+ * @hide
+ */
+public class TemporaryBuffer {
+    public static char[] obtain(int len) {
         char[] buf;
 
         synchronized (TemporaryBuffer.class) {
@@ -28,15 +30,15 @@
             sTemp = null;
         }
 
-        if (buf == null || buf.length < len)
+        if (buf == null || buf.length < len) {
             buf = new char[ArrayUtils.idealCharArraySize(len)];
+        }
 
         return buf;
     }
 
-    /* package */ static void recycle(char[] temp) {
-        if (temp.length > 1000)
-            return;
+    public static void recycle(char[] temp) {
+        if (temp.length > 1000) return;
 
         synchronized (TemporaryBuffer.class) {
             sTemp = temp;
diff --git a/graphics/java/android/graphics/drawable/Drawable.java b/graphics/java/android/graphics/drawable/Drawable.java
index 3125321..7b2d9d7 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;
@@ -749,6 +760,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")) {
@@ -770,7 +783,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());
             }
@@ -805,6 +818,9 @@
         return null;
     }
 
+    /**
+     * Inflate this Drawable from an XML resource.
+     */
     public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs)
             throws XmlPullParserException, IOException {
 
@@ -813,6 +829,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 {
@@ -820,12 +842,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();
         /**
@@ -844,6 +881,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/LayerDrawable.java b/graphics/java/android/graphics/drawable/LayerDrawable.java
index 8047dd4..501cca9 100644
--- a/graphics/java/android/graphics/drawable/LayerDrawable.java
+++ b/graphics/java/android/graphics/drawable/LayerDrawable.java
@@ -266,6 +266,7 @@
      */
     public boolean setDrawableByLayerId(int id, Drawable drawable) {
         final ChildDrawable[] layers = mLayerState.mChildren;
+        drawable.setCallback(this);
         
         for (int i = mLayerState.mNum - 1; i >= 0; i--) {
             if (layers[i].mId == id) {
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/graphics/drawable/NinePatchDrawable.java b/graphics/java/android/graphics/drawable/NinePatchDrawable.java
index bad94fb..9a98d53 100644
--- a/graphics/java/android/graphics/drawable/NinePatchDrawable.java
+++ b/graphics/java/android/graphics/drawable/NinePatchDrawable.java
@@ -175,16 +175,9 @@
             dest.bottom = Bitmap.scaleFromDensity(src.bottom, sdensity, tdensity);
         }
     }
-    
-    // overrides
 
     @Override
     public void draw(Canvas canvas) {
-        if (false) {
-            float[] pts = new float[2];
-            canvas.getMatrix().mapPoints(pts);
-            Log.v("9patch", "Drawing 9-patch @ " + pts[0] + "," + pts[1] + ": " + getBounds());
-        }
         mNinePatch.draw(canvas, getBounds(), mPaint);
     }
 
diff --git a/graphics/java/android/renderscript/Allocation.java b/graphics/java/android/renderscript/Allocation.java
index 17c0778..46f3eae 100644
--- a/graphics/java/android/renderscript/Allocation.java
+++ b/graphics/java/android/renderscript/Allocation.java
@@ -40,6 +40,22 @@
         mType = t;
     }
 
+    Allocation(int id, RenderScript rs) {
+        super(rs);
+        mID = id;
+    }
+
+    @Override
+    void updateFromNative() {
+        mRS.validate();
+        mName = mRS.nGetName(mID);
+        int typeID = mRS.nAllocationGetType(mID);
+        if(typeID != 0) {
+            mType = new Type(typeID, mRS);
+            mType.updateFromNative();
+        }
+    }
+
     public Type getType() {
         return mType;
     }
@@ -76,10 +92,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.");
@@ -366,6 +402,21 @@
         Bitmap b = BitmapFactory.decodeResource(res, id, mBitmapOptions);
         return createFromBitmapBoxed(rs, b, dstFmt, genMips);
     }
+
+    static public Allocation createFromString(RenderScript rs, String str)
+        throws IllegalArgumentException {
+        byte[] allocArray = null;
+        try {
+            allocArray = str.getBytes("UTF-8");
+            Allocation alloc = Allocation.createSized(rs, Element.U8(rs), allocArray.length);
+            alloc.data(allocArray);
+            return alloc;
+        }
+        catch (Exception e) {
+            Log.e("rs", "could not convert string to utf-8");
+        }
+        return null;
+    }
 }
 
 
diff --git a/graphics/java/android/renderscript/BaseObj.java b/graphics/java/android/renderscript/BaseObj.java
index 002fc78..28675dc 100644
--- a/graphics/java/android/renderscript/BaseObj.java
+++ b/graphics/java/android/renderscript/BaseObj.java
@@ -81,5 +81,10 @@
         mRS.nObjDestroy(mID);
     }
 
+    // If an object came from an a3d file, java fields need to be
+    // created with objects from the native layer
+    void updateFromNative() {
+    }
+
 }
 
diff --git a/graphics/java/android/renderscript/Byte2.java b/graphics/java/android/renderscript/Byte2.java
new file mode 100644
index 0000000..95cf88c
--- /dev/null
+++ b/graphics/java/android/renderscript/Byte2.java
@@ -0,0 +1,37 @@
+/*
+ * 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.renderscript;
+
+import java.lang.Math;
+import android.util.Log;
+
+
+/**
+ * @hide
+ *
+ **/
+public class Byte2 {
+    public Byte2() {
+    }
+
+    public byte x;
+    public byte y;
+}
+
+
+
+
diff --git a/graphics/java/android/renderscript/Byte3.java b/graphics/java/android/renderscript/Byte3.java
new file mode 100644
index 0000000..a6c0ca9
--- /dev/null
+++ b/graphics/java/android/renderscript/Byte3.java
@@ -0,0 +1,38 @@
+/*
+ * 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.renderscript;
+
+import java.lang.Math;
+import android.util.Log;
+
+
+/**
+ * @hide
+ *
+ **/
+public class Byte3 {
+    public Byte3() {
+    }
+
+    public byte x;
+    public byte y;
+    public byte z;
+}
+
+
+
+
diff --git a/graphics/java/android/renderscript/Byte4.java b/graphics/java/android/renderscript/Byte4.java
new file mode 100644
index 0000000..a5bfc61
--- /dev/null
+++ b/graphics/java/android/renderscript/Byte4.java
@@ -0,0 +1,38 @@
+/*
+ * 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.renderscript;
+
+import java.lang.Math;
+import android.util.Log;
+
+
+/**
+ * @hide
+ *
+ **/
+public class Byte4 {
+    public Byte4() {
+    }
+
+    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..5d2a059 100644
--- a/graphics/java/android/renderscript/Element.java
+++ b/graphics/java/android/renderscript/Element.java
@@ -17,6 +17,7 @@
 package android.renderscript;
 
 import java.lang.reflect.Field;
+import android.util.Log;
 
 /**
  * @hide
@@ -47,20 +48,22 @@
         UNSIGNED_32 (10, 4),
         //UNSIGNED_64 (11, 8),
 
-        UNSIGNED_5_6_5 (12, 2),
-        UNSIGNED_5_5_5_1 (13, 2),
-        UNSIGNED_4_4_4_4 (14, 2),
+        BOOLEAN(12, 1),
 
-        RS_ELEMENT (15, 4),
-        RS_TYPE (16, 4),
-        RS_ALLOCATION (17, 4),
-        RS_SAMPLER (18, 4),
-        RS_SCRIPT (19, 4),
-        RS_MESH (20, 4),
-        RS_PROGRAM_FRAGMENT (21, 4),
-        RS_PROGRAM_VERTEX (22, 4),
-        RS_PROGRAM_RASTER (23, 4),
-        RS_PROGRAM_STORE (24, 4);
+        UNSIGNED_5_6_5 (13, 2),
+        UNSIGNED_5_5_5_1 (14, 2),
+        UNSIGNED_4_4_4_4 (15, 2),
+
+        RS_ELEMENT (16, 4),
+        RS_TYPE (17, 4),
+        RS_ALLOCATION (18, 4),
+        RS_SAMPLER (19, 4),
+        RS_SCRIPT (20, 4),
+        RS_MESH (21, 4),
+        RS_PROGRAM_FRAGMENT (22, 4),
+        RS_PROGRAM_VERTEX (23, 4),
+        RS_PROGRAM_RASTER (24, 4),
+        RS_PROGRAM_STORE (25, 4);
 
         int mID;
         int mSize;
@@ -72,12 +75,6 @@
 
     public enum DataKind {
         USER (0),
-        COLOR (1),
-        POSITION (2),
-        TEXTURE (3),
-        NORMAL (4),
-        INDEX (5),
-        POINT_SIZE(6),
 
         PIXEL_L (7),
         PIXEL_A (8),
@@ -91,41 +88,133 @@
         }
     }
 
-    public static Element USER_U8(RenderScript rs) {
-        if(rs.mElement_USER_U8 == null) {
-            rs.mElement_USER_U8 = createUser(rs, DataType.UNSIGNED_8);
+    public static Element BOOLEAN(RenderScript rs) {
+        if(rs.mElement_BOOLEAN == null) {
+            rs.mElement_BOOLEAN = createUser(rs, DataType.BOOLEAN);
         }
-        return rs.mElement_USER_U8;
+        return rs.mElement_BOOLEAN;
     }
 
-    public static Element USER_I8(RenderScript rs) {
-        if(rs.mElement_USER_I8 == null) {
-            rs.mElement_USER_I8 = createUser(rs, DataType.SIGNED_8);
+    public static Element U8(RenderScript rs) {
+        if(rs.mElement_U8 == null) {
+            rs.mElement_U8 = createUser(rs, DataType.UNSIGNED_8);
         }
-        return rs.mElement_USER_I8;
+        return rs.mElement_U8;
     }
 
-    public static Element USER_U32(RenderScript rs) {
-        if(rs.mElement_USER_U32 == null) {
-            rs.mElement_USER_U32 = createUser(rs, DataType.UNSIGNED_32);
+    public static Element I8(RenderScript rs) {
+        if(rs.mElement_I8 == null) {
+            rs.mElement_I8 = createUser(rs, DataType.SIGNED_8);
         }
-        return rs.mElement_USER_U32;
+        return rs.mElement_I8;
     }
 
-    public static Element USER_I32(RenderScript rs) {
-        if(rs.mElement_USER_I32 == null) {
-            rs.mElement_USER_I32 = createUser(rs, DataType.SIGNED_32);
+    public static Element U16(RenderScript rs) {
+        if(rs.mElement_U16 == null) {
+            rs.mElement_U16 = createUser(rs, DataType.UNSIGNED_16);
         }
-        return rs.mElement_USER_I32;
+        return rs.mElement_U16;
     }
 
-    public static Element USER_F32(RenderScript rs) {
-        if(rs.mElement_USER_F32 == null) {
-            rs.mElement_USER_F32 = createUser(rs, DataType.FLOAT_32);
+    public static Element I16(RenderScript rs) {
+        if(rs.mElement_I16 == null) {
+            rs.mElement_I16 = createUser(rs, DataType.SIGNED_16);
         }
-        return rs.mElement_USER_F32;
+        return rs.mElement_I16;
     }
 
+    public static Element U32(RenderScript rs) {
+        if(rs.mElement_U32 == null) {
+            rs.mElement_U32 = createUser(rs, DataType.UNSIGNED_32);
+        }
+        return rs.mElement_U32;
+    }
+
+    public static Element I32(RenderScript rs) {
+        if(rs.mElement_I32 == null) {
+            rs.mElement_I32 = createUser(rs, DataType.SIGNED_32);
+        }
+        return rs.mElement_I32;
+    }
+
+    public static Element F32(RenderScript rs) {
+        if(rs.mElement_F32 == null) {
+            rs.mElement_F32 = createUser(rs, DataType.FLOAT_32);
+        }
+        return rs.mElement_F32;
+    }
+
+    public static Element ELEMENT(RenderScript rs) {
+        if(rs.mElement_ELEMENT == null) {
+            rs.mElement_ELEMENT = createUser(rs, DataType.RS_ELEMENT);
+        }
+        return rs.mElement_ELEMENT;
+    }
+
+    public static Element TYPE(RenderScript rs) {
+        if(rs.mElement_TYPE == null) {
+            rs.mElement_TYPE = createUser(rs, DataType.RS_TYPE);
+        }
+        return rs.mElement_TYPE;
+    }
+
+    public static Element ALLOCATION(RenderScript rs) {
+        if(rs.mElement_ALLOCATION == null) {
+            rs.mElement_ALLOCATION = createUser(rs, DataType.RS_ALLOCATION);
+        }
+        return rs.mElement_ALLOCATION;
+    }
+
+    public static Element SAMPLER(RenderScript rs) {
+        if(rs.mElement_SAMPLER == null) {
+            rs.mElement_SAMPLER = createUser(rs, DataType.RS_SAMPLER);
+        }
+        return rs.mElement_SAMPLER;
+    }
+
+    public static Element SCRIPT(RenderScript rs) {
+        if(rs.mElement_SCRIPT == null) {
+            rs.mElement_SCRIPT = createUser(rs, DataType.RS_SCRIPT);
+        }
+        return rs.mElement_SCRIPT;
+    }
+
+    public static Element MESH(RenderScript rs) {
+        if(rs.mElement_MESH == null) {
+            rs.mElement_MESH = createUser(rs, DataType.RS_MESH);
+        }
+        return rs.mElement_MESH;
+    }
+
+    public static Element PROGRAM_FRAGMENT(RenderScript rs) {
+        if(rs.mElement_PROGRAM_FRAGMENT == null) {
+            rs.mElement_PROGRAM_FRAGMENT = createUser(rs, DataType.RS_PROGRAM_FRAGMENT);
+        }
+        return rs.mElement_PROGRAM_FRAGMENT;
+    }
+
+    public static Element PROGRAM_VERTEX(RenderScript rs) {
+        if(rs.mElement_PROGRAM_VERTEX == null) {
+            rs.mElement_PROGRAM_VERTEX = createUser(rs, DataType.RS_PROGRAM_VERTEX);
+        }
+        return rs.mElement_PROGRAM_VERTEX;
+    }
+
+    public static Element PROGRAM_RASTER(RenderScript rs) {
+        if(rs.mElement_PROGRAM_RASTER == null) {
+            rs.mElement_PROGRAM_RASTER = createUser(rs, DataType.RS_PROGRAM_RASTER);
+        }
+        return rs.mElement_PROGRAM_RASTER;
+    }
+
+    public static Element PROGRAM_STORE(RenderScript rs) {
+        if(rs.mElement_PROGRAM_STORE == null) {
+            rs.mElement_PROGRAM_STORE = createUser(rs, DataType.RS_PROGRAM_STORE);
+        }
+        return rs.mElement_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);
@@ -168,54 +257,34 @@
         return rs.mElement_RGBA_8888;
     }
 
-    public static Element INDEX_16(RenderScript rs) {
-        if(rs.mElement_INDEX_16 == null) {
-            rs.mElement_INDEX_16 = createIndex(rs);
+    public static Element F32_2(RenderScript rs) {
+        if(rs.mElement_FLOAT_2 == null) {
+            rs.mElement_FLOAT_2 = createVector(rs, DataType.FLOAT_32, 2);
         }
-        return rs.mElement_INDEX_16;
+        return rs.mElement_FLOAT_2;
     }
 
-    public static Element ATTRIB_POSITION_2(RenderScript rs) {
-        if(rs.mElement_POSITION_2 == null) {
-            rs.mElement_POSITION_2 = createAttrib(rs, DataType.FLOAT_32, DataKind.POSITION, 2);
+    public static Element F32_3(RenderScript rs) {
+        if(rs.mElement_FLOAT_3 == null) {
+            rs.mElement_FLOAT_3 = createVector(rs, DataType.FLOAT_32, 3);
         }
-        return rs.mElement_POSITION_2;
+        return rs.mElement_FLOAT_3;
     }
 
-    public static Element ATTRIB_POSITION_3(RenderScript rs) {
-        if(rs.mElement_POSITION_3 == null) {
-            rs.mElement_POSITION_3 = createAttrib(rs, DataType.FLOAT_32, DataKind.POSITION, 3);
+    public static Element F32_4(RenderScript rs) {
+        if(rs.mElement_FLOAT_4 == null) {
+            rs.mElement_FLOAT_4 = createVector(rs, DataType.FLOAT_32, 4);
         }
-        return rs.mElement_POSITION_3;
+        return rs.mElement_FLOAT_4;
     }
 
-    public static Element ATTRIB_TEXTURE_2(RenderScript rs) {
-        if(rs.mElement_TEXTURE_2 == null) {
-            rs.mElement_TEXTURE_2 = createAttrib(rs, DataType.FLOAT_32, DataKind.TEXTURE, 2);
+    public static Element U8_4(RenderScript rs) {
+        if(rs.mElement_UCHAR_4 == null) {
+            rs.mElement_UCHAR_4 = createVector(rs, DataType.UNSIGNED_8, 4);
         }
-        return rs.mElement_TEXTURE_2;
+        return rs.mElement_UCHAR_4;
     }
 
-    public static Element ATTRIB_NORMAL_3(RenderScript rs) {
-        if(rs.mElement_NORMAL_3 == null) {
-            rs.mElement_NORMAL_3 = createAttrib(rs, DataType.FLOAT_32, DataKind.NORMAL, 3);
-        }
-        return rs.mElement_NORMAL_3;
-    }
-
-    public static Element ATTRIB_COLOR_U8_4(RenderScript rs) {
-        if(rs.mElement_COLOR_U8_4 == null) {
-            rs.mElement_COLOR_U8_4 = createAttrib(rs, DataType.UNSIGNED_8, DataKind.COLOR, 4);
-        }
-        return rs.mElement_COLOR_U8_4;
-    }
-
-    public static Element ATTRIB_COLOR_F32_4(RenderScript rs) {
-        if(rs.mElement_COLOR_F32_4 == null) {
-            rs.mElement_COLOR_F32_4 = createAttrib(rs, DataType.FLOAT_32, DataKind.COLOR, 4);
-        }
-        return rs.mElement_COLOR_F32_4;
-    }
 
     Element(RenderScript rs, Element[] e, String[] n) {
         super(rs);
@@ -240,33 +309,49 @@
         mID = rs.nElementCreate(dt.mID, dk.mID, norm, size);
     }
 
+    Element(RenderScript rs, int id) {
+        super(rs);
+        mID = id;
+    }
+
+    @Override
+    void updateFromNative() {
+
+        // we will pack mType; mKind; mNormalized; mVectorSize; NumSubElements
+        int[] dataBuffer = new int[5];
+        mRS.nElementGetNativeData(mID, dataBuffer);
+        for (DataType dt: DataType.values()) {
+            if(dt.mID == dataBuffer[0]){
+                mType = dt;
+            }
+        }
+        for (DataKind dk: DataKind.values()) {
+            if(dk.mID == dataBuffer[1]){
+                mKind = dk;
+            }
+        }
+
+        mNormalized = dataBuffer[2] == 1 ? true : false;
+        mVectorSize = dataBuffer[3];
+        int numSubElements = dataBuffer[4];
+        if(numSubElements > 0) {
+            mElements = new Element[numSubElements];
+            mElementNames = new String[numSubElements];
+
+            int[] subElementIds = new int[numSubElements];
+            mRS.nElementGetSubElements(mID, subElementIds, mElementNames);
+            for(int i = 0; i < numSubElements; i ++) {
+                mElements[i] = new Element(mRS, subElementIds[i]);
+                mElements[i].updateFromNative();
+            }
+        }
+
+    }
+
     public void destroy() throws IllegalStateException {
         super.destroy();
     }
 
-    public static Element createFromClass(RenderScript rs, Class c) {
-        rs.validate();
-        Field[] fields = c.getFields();
-        Builder b = new Builder(rs);
-
-        for(Field f: fields) {
-            Class fc = f.getType();
-            if(fc == int.class) {
-                b.add(createUser(rs, DataType.SIGNED_32), f.getName());
-            } else if(fc == short.class) {
-                b.add(createUser(rs, DataType.SIGNED_16), f.getName());
-            } else if(fc == byte.class) {
-                b.add(createUser(rs, DataType.SIGNED_8), f.getName());
-            } else if(fc == float.class) {
-                b.add(createUser(rs, DataType.FLOAT_32), f.getName());
-            } else {
-                throw new IllegalArgumentException("Unkown field type");
-            }
-        }
-        return b.create();
-    }
-
-
     /////////////////////////////////////////
     public static Element createUser(RenderScript rs, DataType dt) {
         return new Element(rs, dt, DataKind.USER, false, 1);
@@ -279,59 +364,6 @@
         return new Element(rs, dt, DataKind.USER, false, size);
     }
 
-    public static Element createIndex(RenderScript rs) {
-        return new Element(rs, DataType.UNSIGNED_16, DataKind.INDEX, false, 1);
-    }
-
-    public static Element createAttrib(RenderScript rs, DataType dt, DataKind dk, int size) {
-        if (!(dt == DataType.FLOAT_32 ||
-              dt == DataType.UNSIGNED_8 ||
-              dt == DataType.UNSIGNED_16 ||
-              dt == DataType.UNSIGNED_32 ||
-              dt == DataType.SIGNED_8 ||
-              dt == DataType.SIGNED_16 ||
-              dt == DataType.SIGNED_32)) {
-            throw new IllegalArgumentException("Unsupported DataType");
-        }
-
-        if (!(dk == DataKind.COLOR ||
-              dk == DataKind.POSITION ||
-              dk == DataKind.TEXTURE ||
-              dk == DataKind.NORMAL ||
-              dk == DataKind.POINT_SIZE ||
-              dk == DataKind.USER)) {
-            throw new IllegalArgumentException("Unsupported DataKind");
-        }
-
-        if (dk == DataKind.COLOR &&
-            ((dt != DataType.FLOAT_32 && dt != DataType.UNSIGNED_8) ||
-             size < 3 || size > 4)) {
-            throw new IllegalArgumentException("Bad combo");
-        }
-        if (dk == DataKind.POSITION && (size < 1 || size > 4)) {
-            throw new IllegalArgumentException("Bad combo");
-        }
-        if (dk == DataKind.TEXTURE &&
-            (dt != DataType.FLOAT_32 || size < 1 || size > 4)) {
-            throw new IllegalArgumentException("Bad combo");
-        }
-        if (dk == DataKind.NORMAL &&
-            (dt != DataType.FLOAT_32 || size != 3)) {
-            throw new IllegalArgumentException("Bad combo");
-        }
-        if (dk == DataKind.POINT_SIZE &&
-            (dt != DataType.FLOAT_32 || size != 1)) {
-            throw new IllegalArgumentException("Bad combo");
-        }
-
-        boolean norm = false;
-        if (dk == DataKind.COLOR && dt == DataType.UNSIGNED_8) {
-            norm = true;
-        }
-
-        return new Element(rs, dt, dk, norm, size);
-    }
-
     public static Element createPixel(RenderScript rs, DataType dt, DataKind dk) {
         if (!(dk == DataKind.PIXEL_L ||
               dk == DataKind.PIXEL_A ||
diff --git a/graphics/java/android/renderscript/FieldPacker.java b/graphics/java/android/renderscript/FieldPacker.java
index b26e47d..f03b51c 100644
--- a/graphics/java/android/renderscript/FieldPacker.java
+++ b/graphics/java/android/renderscript/FieldPacker.java
@@ -33,21 +33,28 @@
         }
     }
 
-    void reset() {
+    public void reset() {
         mPos = 0;
     }
+    public void reset(int i) {
+        mPos = i;
+    }
 
-    void addI8(byte v) {
+    public void skip(int i) {
+        mPos += i;
+    }
+
+    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 +62,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 +74,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 +90,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 +101,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 +116,139 @@
         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 void addBoolean(boolean v) {
+        addI8((byte)(v ? 1 : 0));
+    }
+
+    public final byte[] getData() {
         return mData;
     }
 
diff --git a/graphics/java/android/renderscript/FileA3D.java b/graphics/java/android/renderscript/FileA3D.java
new file mode 100644
index 0000000..302a5f4
--- /dev/null
+++ b/graphics/java/android/renderscript/FileA3D.java
@@ -0,0 +1,218 @@
+/*
+ * 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.
+ */
+
+package android.renderscript;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import android.content.res.Resources;
+import android.content.res.AssetManager;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.util.Log;
+import android.util.TypedValue;
+
+/**
+ * @hide
+ *
+ **/
+public class FileA3D extends BaseObj {
+
+    public enum ClassID {
+
+        UNKNOWN,
+        MESH,
+        TYPE,
+        ELEMENT,
+        ALLOCATION,
+        PROGRAM_VERTEX,
+        PROGRAM_RASTER,
+        PROGRAM_FRAGMENT,
+        PROGRAM_STORE,
+        SAMPLER,
+        ANIMATION,
+        LIGHT,
+        ADAPTER_1D,
+        ADAPTER_2D,
+        SCRIPT_C;
+
+        public static ClassID toClassID(int intID) {
+            return ClassID.values()[intID];
+        }
+    }
+
+    // Read only class with index entries
+    public static class IndexEntry {
+        RenderScript mRS;
+        int mIndex;
+        int mID;
+        String mName;
+        ClassID mClassID;
+        BaseObj mLoadedObj;
+
+        public String getName() {
+            return mName;
+        }
+
+        public ClassID getClassID() {
+            return mClassID;
+        }
+
+        public BaseObj getObject() {
+            mRS.validate();
+            BaseObj obj = internalCreate(mRS, this);
+            return obj;
+        }
+
+        static synchronized BaseObj internalCreate(RenderScript rs, IndexEntry entry) {
+            if(entry.mLoadedObj != null) {
+                return entry.mLoadedObj;
+            }
+
+            if(entry.mClassID == ClassID.UNKNOWN) {
+                return null;
+            }
+
+            int objectID = rs.nFileA3DGetEntryByIndex(entry.mID, entry.mIndex);
+            if(objectID == 0) {
+                return null;
+            }
+
+            switch (entry.mClassID) {
+            case MESH:
+                entry.mLoadedObj = new Mesh(objectID, rs);
+                break;
+            case TYPE:
+                entry.mLoadedObj = new Type(objectID, rs);
+                break;
+            case ELEMENT:
+                entry.mLoadedObj = null;
+                break;
+            case ALLOCATION:
+                entry.mLoadedObj = null;
+                break;
+            case PROGRAM_VERTEX:
+                entry.mLoadedObj = new ProgramVertex(objectID, rs);
+                break;
+            case PROGRAM_RASTER:
+                break;
+            case PROGRAM_FRAGMENT:
+                break;
+            case PROGRAM_STORE:
+                break;
+            case SAMPLER:
+                break;
+            case ANIMATION:
+                break;
+            case LIGHT:
+                break;
+            case ADAPTER_1D:
+                break;
+            case ADAPTER_2D:
+                break;
+            case SCRIPT_C:
+                break;
+            }
+
+            entry.mLoadedObj.updateFromNative();
+
+            return entry.mLoadedObj;
+        }
+
+        IndexEntry(RenderScript rs, int index, int id, String name, ClassID classID) {
+            mRS = rs;
+            mIndex = index;
+            mID = id;
+            mName = name;
+            mClassID = classID;
+            mLoadedObj = null;
+        }
+    }
+
+    IndexEntry[] mFileEntries;
+
+    FileA3D(int id, RenderScript rs) {
+        super(rs);
+        mID = id;
+    }
+
+    private void initEntries() {
+        int numFileEntries = mRS.nFileA3DGetNumIndexEntries(mID);
+        if(numFileEntries <= 0) {
+            return;
+        }
+
+        mFileEntries = new IndexEntry[numFileEntries];
+        int[] ids = new int[numFileEntries];
+        String[] names = new String[numFileEntries];
+
+        mRS.nFileA3DGetIndexEntries(mID, numFileEntries, ids, names);
+
+        for(int i = 0; i < numFileEntries; i ++) {
+            mFileEntries[i] = new IndexEntry(mRS, i, mID, names[i], ClassID.toClassID(ids[i]));
+        }
+    }
+
+    public int getNumIndexEntries() {
+        if(mFileEntries == null) {
+            return 0;
+        }
+        return mFileEntries.length;
+    }
+
+    public IndexEntry getIndexEntry(int index) {
+        if(getNumIndexEntries() == 0 || index < 0 || index >= mFileEntries.length) {
+            return null;
+        }
+        return mFileEntries[index];
+    }
+
+    static public FileA3D createFromResource(RenderScript rs, Resources res, int id)
+        throws IllegalArgumentException {
+
+        rs.validate();
+        InputStream is = null;
+        try {
+            final TypedValue value = new TypedValue();
+            is = res.openRawResource(id, value);
+
+            int asset = ((AssetManager.AssetInputStream) is).getAssetInt();
+
+            int fileId = rs.nFileA3DCreateFromAssetStream(asset);
+
+            if(fileId == 0) {
+                throw new IllegalStateException("Load failed.");
+            }
+            FileA3D fa3d = new FileA3D(fileId, rs);
+            fa3d.initEntries();
+            return fa3d;
+
+        } catch (Exception e) {
+            // Ignore
+        } finally {
+            if (is != null) {
+                try {
+                    is.close();
+                } catch (IOException e) {
+                    // Ignore
+                }
+            }
+        }
+
+        return null;
+    }
+}
diff --git a/graphics/java/android/renderscript/Float2.java b/graphics/java/android/renderscript/Float2.java
new file mode 100644
index 0000000..8fea91f
--- /dev/null
+++ b/graphics/java/android/renderscript/Float2.java
@@ -0,0 +1,37 @@
+/*
+ * 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.renderscript;
+
+import java.lang.Math;
+import android.util.Log;
+
+
+/**
+ * @hide
+ *
+ **/
+public class Float2 {
+    public Float2() {
+    }
+
+    public float x;
+    public float y;
+}
+
+
+
+
diff --git a/graphics/java/android/renderscript/Float3.java b/graphics/java/android/renderscript/Float3.java
new file mode 100644
index 0000000..9d9e406
--- /dev/null
+++ b/graphics/java/android/renderscript/Float3.java
@@ -0,0 +1,38 @@
+/*
+ * 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.renderscript;
+
+import java.lang.Math;
+import android.util.Log;
+
+
+/**
+ * @hide
+ *
+ **/
+public class Float3 {
+    public Float3() {
+    }
+
+    public float x;
+    public float y;
+    public float z;
+}
+
+
+
+
diff --git a/graphics/java/android/renderscript/Float4.java b/graphics/java/android/renderscript/Float4.java
new file mode 100644
index 0000000..a703e80
--- /dev/null
+++ b/graphics/java/android/renderscript/Float4.java
@@ -0,0 +1,38 @@
+/*
+ * 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.renderscript;
+
+import java.lang.Math;
+import android.util.Log;
+
+
+/**
+ * @hide
+ *
+ **/
+public class Float4 {
+    public Float4() {
+    }
+
+    public float x;
+    public float y;
+    public float z;
+    public float w;
+}
+
+
+
diff --git a/graphics/java/android/renderscript/Font.java b/graphics/java/android/renderscript/Font.java
new file mode 100644
index 0000000..41f8827
--- /dev/null
+++ b/graphics/java/android/renderscript/Font.java
@@ -0,0 +1,59 @@
+/*
+ * 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.
+ */
+
+package android.renderscript;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import android.content.res.Resources;
+import android.content.res.AssetManager;
+import android.util.Log;
+import android.util.TypedValue;
+
+/**
+ * @hide
+ *
+ **/
+public class Font extends BaseObj {
+
+    Font(int id, RenderScript rs) {
+        super(rs);
+        mID = id;
+    }
+
+    static public Font create(RenderScript rs, Resources res, String fileName, int size)
+        throws IllegalArgumentException {
+
+        rs.validate();
+        try {
+            int dpi = res.getDisplayMetrics().densityDpi;
+            int fontId = rs.nFontCreateFromFile(fileName, size, dpi);
+
+            if(fontId == 0) {
+                throw new IllegalStateException("Load loading a font");
+            }
+            Font rsFont = new Font(fontId, rs);
+
+            return rsFont;
+
+        } catch (Exception e) {
+            // Ignore
+        }
+
+        return null;
+    }
+}
diff --git a/graphics/java/android/renderscript/Int2.java b/graphics/java/android/renderscript/Int2.java
new file mode 100644
index 0000000..56e2fe9
--- /dev/null
+++ b/graphics/java/android/renderscript/Int2.java
@@ -0,0 +1,37 @@
+/*
+ * 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.renderscript;
+
+import java.lang.Math;
+import android.util.Log;
+
+
+/**
+ * @hide
+ *
+ **/
+public class Int2 {
+    public Int2() {
+    }
+
+    public int x;
+    public int y;
+}
+
+
+
+
diff --git a/graphics/java/android/renderscript/Int3.java b/graphics/java/android/renderscript/Int3.java
new file mode 100644
index 0000000..1b27509
--- /dev/null
+++ b/graphics/java/android/renderscript/Int3.java
@@ -0,0 +1,38 @@
+/*
+ * 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.renderscript;
+
+import java.lang.Math;
+import android.util.Log;
+
+
+/**
+ * @hide
+ *
+ **/
+public class Int3 {
+    public Int3() {
+    }
+
+    public int x;
+    public int y;
+    public int z;
+}
+
+
+
+
diff --git a/graphics/java/android/renderscript/Int4.java b/graphics/java/android/renderscript/Int4.java
new file mode 100644
index 0000000..3d6f3f5
--- /dev/null
+++ b/graphics/java/android/renderscript/Int4.java
@@ -0,0 +1,38 @@
+/*
+ * 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.renderscript;
+
+import java.lang.Math;
+import android.util.Log;
+
+
+/**
+ * @hide
+ *
+ **/
+public class Int4 {
+    public Int4() {
+    }
+
+    public int x;
+    public int y;
+    public int z;
+    public int w;
+}
+
+
+
diff --git a/graphics/java/android/renderscript/Long2.java b/graphics/java/android/renderscript/Long2.java
new file mode 100644
index 0000000..11ead2f
--- /dev/null
+++ b/graphics/java/android/renderscript/Long2.java
@@ -0,0 +1,37 @@
+/*
+ * 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.renderscript;
+
+import java.lang.Math;
+import android.util.Log;
+
+
+/**
+ * @hide
+ *
+ **/
+public class Long2 {
+    public Long2() {
+    }
+
+    public long x;
+    public long y;
+}
+
+
+
+
diff --git a/graphics/java/android/renderscript/Long3.java b/graphics/java/android/renderscript/Long3.java
new file mode 100644
index 0000000..1604532
--- /dev/null
+++ b/graphics/java/android/renderscript/Long3.java
@@ -0,0 +1,38 @@
+/*
+ * 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.renderscript;
+
+import java.lang.Math;
+import android.util.Log;
+
+
+/**
+ * @hide
+ *
+ **/
+public class Long3 {
+    public Long3() {
+    }
+
+    public long x;
+    public long y;
+    public long z;
+}
+
+
+
+
diff --git a/graphics/java/android/renderscript/Long4.java b/graphics/java/android/renderscript/Long4.java
new file mode 100644
index 0000000..2fd2747
--- /dev/null
+++ b/graphics/java/android/renderscript/Long4.java
@@ -0,0 +1,38 @@
+/*
+ * 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.renderscript;
+
+import java.lang.Math;
+import android.util.Log;
+
+
+/**
+ * @hide
+ *
+ **/
+public class Long4 {
+    public Long4() {
+    }
+
+    public long x;
+    public long y;
+    public long z;
+    public long w;
+}
+
+
+
diff --git a/graphics/java/android/renderscript/Matrix2f.java b/graphics/java/android/renderscript/Matrix2f.java
index 4b5e61b..99d23db 100644
--- a/graphics/java/android/renderscript/Matrix2f.java
+++ b/graphics/java/android/renderscript/Matrix2f.java
@@ -51,6 +51,57 @@
         System.arraycopy(mMat, 0, src, 0, 4);
     }
 
+    public void loadRotate(float rot) {
+        float c, s;
+        rot *= (float)(java.lang.Math.PI / 180.0f);
+        c = (float)java.lang.Math.cos(rot);
+        s = (float)java.lang.Math.sin(rot);
+        mMat[0] = c;
+        mMat[1] = -s;
+        mMat[2] = s;
+        mMat[3] = c;
+    }
+
+    public void loadScale(float x, float y) {
+        loadIdentity();
+        mMat[0] = x;
+        mMat[3] = y;
+    }
+    public void loadMultiply(Matrix2f lhs, Matrix2f rhs) {
+        for (int i=0 ; i<2 ; i++) {
+            float ri0 = 0;
+            float ri1 = 0;
+            for (int j=0 ; j<2 ; j++) {
+                float rhs_ij = rhs.get(i,j);
+                ri0 += lhs.get(j,0) * rhs_ij;
+                ri1 += lhs.get(j,1) * rhs_ij;
+            }
+            set(i,0, ri0);
+            set(i,1, ri1);
+        }
+    }
+
+    public void multiply(Matrix2f rhs) {
+        Matrix2f tmp = new Matrix2f();
+        tmp.loadMultiply(this, rhs);
+        load(tmp);
+    }
+    public void rotate(float rot) {
+        Matrix2f tmp = new Matrix2f();
+        tmp.loadRotate(rot);
+        multiply(tmp);
+    }
+    public void scale(float x, float y) {
+        Matrix2f tmp = new Matrix2f();
+        tmp.loadScale(x, y);
+        multiply(tmp);
+    }
+    public void transpose() {
+        float temp = mMat[1];
+        mMat[1] = mMat[2];
+        mMat[2] = temp;
+    }
+
     final float[] mMat;
 }
 
diff --git a/graphics/java/android/renderscript/Matrix3f.java b/graphics/java/android/renderscript/Matrix3f.java
index 19d7b43..961bc5d 100644
--- a/graphics/java/android/renderscript/Matrix3f.java
+++ b/graphics/java/android/renderscript/Matrix3f.java
@@ -57,6 +57,124 @@
         System.arraycopy(mMat, 0, src, 0, 9);
     }
 
+    public void loadRotate(float rot, float x, float y, float z) {
+        float c, s;
+        rot *= (float)(java.lang.Math.PI / 180.0f);
+        c = (float)java.lang.Math.cos(rot);
+        s = (float)java.lang.Math.sin(rot);
+
+        float len = (float)java.lang.Math.sqrt(x*x + y*y + z*z);
+        if (!(len != 1)) {
+            float recipLen = 1.f / len;
+            x *= recipLen;
+            y *= recipLen;
+            z *= recipLen;
+        }
+        float nc = 1.0f - c;
+        float xy = x * y;
+        float yz = y * z;
+        float zx = z * x;
+        float xs = x * s;
+        float ys = y * s;
+        float zs = z * s;
+        mMat[0] = x*x*nc +  c;
+        mMat[3] =  xy*nc - zs;
+        mMat[6] =  zx*nc + ys;
+        mMat[1] =  xy*nc + zs;
+        mMat[4] = y*y*nc +  c;
+        mMat[9] =  yz*nc - xs;
+        mMat[2] =  zx*nc - ys;
+        mMat[6] =  yz*nc + xs;
+        mMat[8] = z*z*nc +  c;
+    }
+
+    public void loadRotate(float rot) {
+        float c, s;
+        rot *= (float)(java.lang.Math.PI / 180.0f);
+        c = (float)java.lang.Math.cos(rot);
+        s = (float)java.lang.Math.sin(rot);
+        mMat[0] = c;
+        mMat[1] = -s;
+        mMat[3] = s;
+        mMat[4] = c;
+    }
+
+    public void loadScale(float x, float y) {
+        loadIdentity();
+        mMat[0] = x;
+        mMat[4] = y;
+    }
+
+    public void loadScale(float x, float y, float z) {
+        loadIdentity();
+        mMat[0] = x;
+        mMat[4] = y;
+        mMat[8] = z;
+    }
+
+    public void loadTranslate(float x, float y) {
+        loadIdentity();
+        mMat[6] = x;
+        mMat[7] = y;
+    }
+
+    public void loadMultiply(Matrix3f lhs, Matrix3f rhs) {
+        for (int i=0 ; i<3 ; i++) {
+            float ri0 = 0;
+            float ri1 = 0;
+            float ri2 = 0;
+            for (int j=0 ; j<3 ; j++) {
+                float rhs_ij = rhs.get(i,j);
+                ri0 += lhs.get(j,0) * rhs_ij;
+                ri1 += lhs.get(j,1) * rhs_ij;
+                ri2 += lhs.get(j,2) * rhs_ij;
+            }
+            set(i,0, ri0);
+            set(i,1, ri1);
+            set(i,2, ri2);
+        }
+    }
+
+    public void multiply(Matrix3f rhs) {
+        Matrix3f tmp = new Matrix3f();
+        tmp.loadMultiply(this, rhs);
+        load(tmp);
+    }
+    public void rotate(float rot, float x, float y, float z) {
+        Matrix3f tmp = new Matrix3f();
+        tmp.loadRotate(rot, x, y, z);
+        multiply(tmp);
+    }
+    public void rotate(float rot) {
+        Matrix3f tmp = new Matrix3f();
+        tmp.loadRotate(rot);
+        multiply(tmp);
+    }
+    public void scale(float x, float y) {
+        Matrix3f tmp = new Matrix3f();
+        tmp.loadScale(x, y);
+        multiply(tmp);
+    }
+    public void scale(float x, float y, float z) {
+        Matrix3f tmp = new Matrix3f();
+        tmp.loadScale(x, y, z);
+        multiply(tmp);
+    }
+    public void translate(float x, float y) {
+        Matrix3f tmp = new Matrix3f();
+        tmp.loadTranslate(x, y);
+        multiply(tmp);
+    }
+    public void transpose() {
+        for(int i = 0; i < 2; ++i) {
+            for(int j = i + 1; j < 3; ++j) {
+                float temp = mMat[i*3 + j];
+                mMat[i*3 + j] = mMat[j*3 + i];
+                mMat[j*3 + i] = temp;
+            }
+        }
+    }
+
     final float[] mMat;
 }
 
diff --git a/graphics/java/android/renderscript/Matrix4f.java b/graphics/java/android/renderscript/Matrix4f.java
index ebd5bde..e854cd9 100644
--- a/graphics/java/android/renderscript/Matrix4f.java
+++ b/graphics/java/android/renderscript/Matrix4f.java
@@ -179,6 +179,15 @@
         tmp.loadTranslate(x, y, z);
         multiply(tmp);
     }
+    public void transpose() {
+        for(int i = 0; i < 3; ++i) {
+            for(int j = i + 1; j < 4; ++j) {
+                float temp = mMat[i*4 + j];
+                mMat[i*4 + j] = mMat[j*4 + i];
+                mMat[j*4 + i] = temp;
+            }
+        }
+    }
 
     final float[] mMat;
 }
diff --git a/graphics/java/android/renderscript/Mesh.java b/graphics/java/android/renderscript/Mesh.java
new file mode 100644
index 0000000..bf02319
--- /dev/null
+++ b/graphics/java/android/renderscript/Mesh.java
@@ -0,0 +1,480 @@
+/*
+ * 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.
+ */
+
+package android.renderscript;
+
+import java.util.Vector;
+
+import android.util.Config;
+import android.util.Log;
+
+/**
+ * @hide
+ *
+ **/
+public class Mesh extends BaseObj {
+
+    Allocation[] mVertexBuffers;
+    Allocation[] mIndexBuffers;
+    Primitive[] mPrimitives;
+
+    Mesh(int id, RenderScript rs) {
+        super(rs);
+        mID = id;
+    }
+
+    public int getVertexAllocationCount() {
+        if(mVertexBuffers == null) {
+            return 0;
+        }
+        return mVertexBuffers.length;
+    }
+    public Allocation getVertexAllocation(int slot) {
+        return mVertexBuffers[slot];
+    }
+
+    public int getPrimitiveCount() {
+        if(mIndexBuffers == null) {
+            return 0;
+        }
+        return mIndexBuffers.length;
+    }
+    public Allocation getIndexAllocation(int slot) {
+        return mIndexBuffers[slot];
+    }
+    public Primitive getPrimitive(int slot) {
+        return mPrimitives[slot];
+    }
+
+    @Override
+    void updateFromNative() {
+        mName = mRS.nGetName(mID);
+        int vtxCount = mRS.nMeshGetVertexBufferCount(mID);
+        int idxCount = mRS.nMeshGetIndexCount(mID);
+
+        int[] vtxIDs = new int[vtxCount];
+        int[] idxIDs = new int[idxCount];
+        int[] primitives = new int[idxCount];
+
+        mRS.nMeshGetVertices(mID, vtxIDs, vtxCount);
+        mRS.nMeshGetIndices(mID, idxIDs, primitives, vtxCount);
+
+        mVertexBuffers = new Allocation[vtxCount];
+        mIndexBuffers = new Allocation[idxCount];
+        mPrimitives = new Primitive[idxCount];
+
+        for(int i = 0; i < vtxCount; i ++) {
+            if(vtxIDs[i] != 0) {
+                mVertexBuffers[i] = new Allocation(vtxIDs[i], mRS);
+                mVertexBuffers[i].updateFromNative();
+            }
+        }
+
+        for(int i = 0; i < idxCount; i ++) {
+            if(idxIDs[i] != 0) {
+                mIndexBuffers[i] = new Allocation(idxIDs[i], mRS);
+                mIndexBuffers[i].updateFromNative();
+            }
+            mPrimitives[i] = Primitive.values()[primitives[i]];
+        }
+    }
+
+    public static class Builder {
+        RenderScript mRS;
+
+        class Entry {
+            Type t;
+            Element e;
+            int size;
+            Primitive prim;
+        }
+
+        int mVertexTypeCount;
+        Entry[] mVertexTypes;
+        Vector mIndexTypes;
+
+        public Builder(RenderScript rs) {
+            mRS = rs;
+            mVertexTypeCount = 0;
+            mVertexTypes = new Entry[16];
+            mIndexTypes = new Vector();
+        }
+
+        public int addVertexType(Type t) throws IllegalStateException {
+            if (mVertexTypeCount >= mVertexTypes.length) {
+                throw new IllegalStateException("Max vertex types exceeded.");
+            }
+
+            int addedIndex = mVertexTypeCount;
+            mVertexTypes[mVertexTypeCount] = new Entry();
+            mVertexTypes[mVertexTypeCount].t = t;
+            mVertexTypes[mVertexTypeCount].e = null;
+            mVertexTypeCount++;
+            return addedIndex;
+        }
+
+        public int addVertexType(Element e, int size) throws IllegalStateException {
+            if (mVertexTypeCount >= mVertexTypes.length) {
+                throw new IllegalStateException("Max vertex types exceeded.");
+            }
+
+            int addedIndex = mVertexTypeCount;
+            mVertexTypes[mVertexTypeCount] = new Entry();
+            mVertexTypes[mVertexTypeCount].t = null;
+            mVertexTypes[mVertexTypeCount].e = e;
+            mVertexTypes[mVertexTypeCount].size = size;
+            mVertexTypeCount++;
+            return addedIndex;
+        }
+
+        public int addIndexType(Type t, Primitive p) {
+            int addedIndex  = mIndexTypes.size();
+            Entry indexType = new Entry();
+            indexType.t = t;
+            indexType.e = null;
+            indexType.size = 0;
+            indexType.prim = p;
+            mIndexTypes.addElement(indexType);
+            return addedIndex;
+        }
+
+        public int addIndexType(Primitive p) {
+            int addedIndex  = mIndexTypes.size();
+            Entry indexType = new Entry();
+            indexType.t = null;
+            indexType.e = null;
+            indexType.size = 0;
+            indexType.prim = p;
+            mIndexTypes.addElement(indexType);
+            return addedIndex;
+        }
+
+        public int addIndexType(Element e, int size, Primitive p) {
+            int addedIndex  = mIndexTypes.size();
+            Entry indexType = new Entry();
+            indexType.t = null;
+            indexType.e = e;
+            indexType.size = size;
+            indexType.prim = p;
+            mIndexTypes.addElement(indexType);
+            return addedIndex;
+        }
+
+        Type newType(Element e, int size) {
+            Type.Builder tb = new Type.Builder(mRS, e);
+            tb.add(Dimension.X, size);
+            return tb.create();
+        }
+
+        static synchronized Mesh internalCreate(RenderScript rs, Builder b) {
+
+            int id = rs.nMeshCreate(b.mVertexTypeCount, b.mIndexTypes.size());
+            Mesh newMesh = new Mesh(id, rs);
+            newMesh.mIndexBuffers = new Allocation[b.mIndexTypes.size()];
+            newMesh.mPrimitives = new Primitive[b.mIndexTypes.size()];
+            newMesh.mVertexBuffers = new Allocation[b.mVertexTypeCount];
+
+            for(int ct = 0; ct < b.mIndexTypes.size(); ct ++) {
+                Allocation alloc = null;
+                Entry entry = (Entry)b.mIndexTypes.elementAt(ct);
+                if (entry.t != null) {
+                    alloc = Allocation.createTyped(rs, entry.t);
+                }
+                else if(entry.e != null) {
+                    alloc = Allocation.createSized(rs, entry.e, entry.size);
+                }
+                int allocID = (alloc == null) ? 0 : alloc.getID();
+                rs.nMeshBindIndex(id, allocID, entry.prim.mID, ct);
+                newMesh.mIndexBuffers[ct] = alloc;
+                newMesh.mPrimitives[ct] = entry.prim;
+            }
+
+            for(int ct = 0; ct < b.mVertexTypeCount; ct ++) {
+                Allocation alloc = null;
+                Entry entry = b.mVertexTypes[ct];
+                if (entry.t != null) {
+                    alloc = Allocation.createTyped(rs, entry.t);
+                } else if(entry.e != null) {
+                    alloc = Allocation.createSized(rs, entry.e, entry.size);
+                }
+                rs.nMeshBindVertex(id, alloc.getID(), ct);
+                newMesh.mVertexBuffers[ct] = alloc;
+            }
+
+            return newMesh;
+        }
+
+        public Mesh create() {
+            mRS.validate();
+            Mesh sm = internalCreate(mRS, this);
+            return sm;
+        }
+    }
+
+    public static class AllocationBuilder {
+        RenderScript mRS;
+
+        class Entry {
+            Allocation a;
+            Primitive prim;
+        }
+
+        int mVertexTypeCount;
+        Entry[] mVertexTypes;
+
+        Vector mIndexTypes;
+
+        public AllocationBuilder(RenderScript rs) {
+            mRS = rs;
+            mVertexTypeCount = 0;
+            mVertexTypes = new Entry[16];
+            mIndexTypes = new Vector();
+        }
+
+        public int addVertexAllocation(Allocation a) throws IllegalStateException {
+            if (mVertexTypeCount >= mVertexTypes.length) {
+                throw new IllegalStateException("Max vertex types exceeded.");
+            }
+
+            int addedIndex = mVertexTypeCount;
+            mVertexTypes[mVertexTypeCount] = new Entry();
+            mVertexTypes[mVertexTypeCount].a = a;
+            mVertexTypeCount++;
+            return addedIndex;
+        }
+
+        public int addIndexAllocation(Allocation a, Primitive p) {
+            int addedIndex  = mIndexTypes.size();
+            Entry indexType = new Entry();
+            indexType.a = a;
+            indexType.prim = p;
+            mIndexTypes.addElement(indexType);
+            return addedIndex;
+        }
+
+        public int addIndexType(Primitive p) {
+            int addedIndex  = mIndexTypes.size();
+            Entry indexType = new Entry();
+            indexType.a = null;
+            indexType.prim = p;
+            mIndexTypes.addElement(indexType);
+            return addedIndex;
+        }
+
+        static synchronized Mesh internalCreate(RenderScript rs, AllocationBuilder b) {
+
+            int id = rs.nMeshCreate(b.mVertexTypeCount, b.mIndexTypes.size());
+            Mesh newMesh = new Mesh(id, rs);
+            newMesh.mIndexBuffers = new Allocation[b.mIndexTypes.size()];
+            newMesh.mPrimitives = new Primitive[b.mIndexTypes.size()];
+            newMesh.mVertexBuffers = new Allocation[b.mVertexTypeCount];
+
+            for(int ct = 0; ct < b.mIndexTypes.size(); ct ++) {
+                Entry entry = (Entry)b.mIndexTypes.elementAt(ct);
+                int allocID = (entry.a == null) ? 0 : entry.a.getID();
+                rs.nMeshBindIndex(id, allocID, entry.prim.mID, ct);
+                newMesh.mIndexBuffers[ct] = entry.a;
+                newMesh.mPrimitives[ct] = entry.prim;
+            }
+
+            for(int ct = 0; ct < b.mVertexTypeCount; ct ++) {
+                Entry entry = b.mVertexTypes[ct];
+                rs.nMeshBindVertex(id, entry.a.mID, ct);
+                newMesh.mVertexBuffers[ct] = entry.a;
+            }
+
+            return newMesh;
+        }
+
+        public Mesh create() {
+            mRS.validate();
+            Mesh sm = internalCreate(mRS, this);
+            return sm;
+        }
+    }
+
+
+    public static class TriangleMeshBuilder {
+        float mVtxData[];
+        int mVtxCount;
+        short mIndexData[];
+        int mIndexCount;
+        RenderScript mRS;
+        Element mElement;
+
+        float mNX = 0;
+        float mNY = 0;
+        float mNZ = -1;
+        float mS0 = 0;
+        float mT0 = 0;
+        float mR = 1;
+        float mG = 1;
+        float mB = 1;
+        float mA = 1;
+
+        int mVtxSize;
+        int mFlags;
+
+        public static final int COLOR = 0x0001;
+        public static final int NORMAL = 0x0002;
+        public static final int TEXTURE_0 = 0x0100;
+
+        public TriangleMeshBuilder(RenderScript rs, int vtxSize, int flags) {
+            mRS = rs;
+            mVtxCount = 0;
+            mIndexCount = 0;
+            mVtxData = new float[128];
+            mIndexData = new short[128];
+            mVtxSize = vtxSize;
+            mFlags = flags;
+
+            if (vtxSize < 2 || vtxSize > 3) {
+                throw new IllegalArgumentException("Vertex size out of range.");
+            }
+        }
+
+        private void makeSpace(int count) {
+            if ((mVtxCount + count) >= mVtxData.length) {
+                float t[] = new float[mVtxData.length * 2];
+                System.arraycopy(mVtxData, 0, t, 0, mVtxData.length);
+                mVtxData = t;
+            }
+        }
+
+        private void latch() {
+            if ((mFlags & COLOR) != 0) {
+                makeSpace(4);
+                mVtxData[mVtxCount++] = mR;
+                mVtxData[mVtxCount++] = mG;
+                mVtxData[mVtxCount++] = mB;
+                mVtxData[mVtxCount++] = mA;
+            }
+            if ((mFlags & TEXTURE_0) != 0) {
+                makeSpace(2);
+                mVtxData[mVtxCount++] = mS0;
+                mVtxData[mVtxCount++] = mT0;
+            }
+            if ((mFlags & NORMAL) != 0) {
+                makeSpace(3);
+                mVtxData[mVtxCount++] = mNX;
+                mVtxData[mVtxCount++] = mNY;
+                mVtxData[mVtxCount++] = mNZ;
+            }
+        }
+
+        public void addVertex(float x, float y) {
+            if (mVtxSize != 2) {
+                throw new IllegalStateException("add mistmatch with declared components.");
+            }
+            makeSpace(2);
+            mVtxData[mVtxCount++] = x;
+            mVtxData[mVtxCount++] = y;
+            latch();
+        }
+
+        public void addVertex(float x, float y, float z) {
+            if (mVtxSize != 3) {
+                throw new IllegalStateException("add mistmatch with declared components.");
+            }
+            makeSpace(3);
+            mVtxData[mVtxCount++] = x;
+            mVtxData[mVtxCount++] = y;
+            mVtxData[mVtxCount++] = z;
+            latch();
+        }
+
+        public void setTexture(float s, float t) {
+            if ((mFlags & TEXTURE_0) == 0) {
+                throw new IllegalStateException("add mistmatch with declared components.");
+            }
+            mS0 = s;
+            mT0 = t;
+        }
+
+        public void setNormal(float x, float y, float z) {
+            if ((mFlags & NORMAL) == 0) {
+                throw new IllegalStateException("add mistmatch with declared components.");
+            }
+            mNX = x;
+            mNY = y;
+            mNZ = z;
+        }
+
+        public void setColor(float r, float g, float b, float a) {
+            if ((mFlags & COLOR) == 0) {
+                throw new IllegalStateException("add mistmatch with declared components.");
+            }
+            mR = r;
+            mG = g;
+            mB = b;
+            mA = a;
+        }
+
+        public void addTriangle(int idx1, int idx2, int idx3) {
+            if((idx1 >= mVtxCount) || (idx1 < 0) ||
+               (idx2 >= mVtxCount) || (idx2 < 0) ||
+               (idx3 >= mVtxCount) || (idx3 < 0)) {
+               throw new IllegalStateException("Index provided greater than vertex count.");
+            }
+            if ((mIndexCount + 3) >= mIndexData.length) {
+                short t[] = new short[mIndexData.length * 2];
+                System.arraycopy(mIndexData, 0, t, 0, mIndexData.length);
+                mIndexData = t;
+            }
+            mIndexData[mIndexCount++] = (short)idx1;
+            mIndexData[mIndexCount++] = (short)idx2;
+            mIndexData[mIndexCount++] = (short)idx3;
+        }
+
+        public Mesh create(boolean uploadToBufferObject) {
+            Element.Builder b = new Element.Builder(mRS);
+            int floatCount = mVtxSize;
+            b.add(Element.createVector(mRS,
+                                       Element.DataType.FLOAT_32,
+                                       mVtxSize), "position");
+            if ((mFlags & COLOR) != 0) {
+                floatCount += 4;
+                b.add(Element.F32_4(mRS), "color");
+            }
+            if ((mFlags & TEXTURE_0) != 0) {
+                floatCount += 2;
+                b.add(Element.F32_2(mRS), "texture0");
+            }
+            if ((mFlags & NORMAL) != 0) {
+                floatCount += 3;
+                b.add(Element.F32_3(mRS), "normal");
+            }
+            mElement = b.create();
+
+            Builder smb = new Builder(mRS);
+            smb.addVertexType(mElement, mVtxCount / floatCount);
+            smb.addIndexType(Element.U16(mRS), mIndexCount, Primitive.TRIANGLE);
+
+            Mesh sm = smb.create();
+
+            sm.getVertexAllocation(0).data(mVtxData);
+            if(uploadToBufferObject) {
+                sm.getVertexAllocation(0).uploadToBufferObject();
+            }
+
+            sm.getIndexAllocation(0).data(mIndexData);
+            sm.getIndexAllocation(0).uploadToBufferObject();
+
+            return sm;
+        }
+    }
+}
+
diff --git a/graphics/java/android/renderscript/Program.java b/graphics/java/android/renderscript/Program.java
index 1614ec5..b16dac1 100644
--- a/graphics/java/android/renderscript/Program.java
+++ b/graphics/java/android/renderscript/Program.java
@@ -91,8 +91,9 @@
             mTextureCount = 0;
         }
 
-        public void setShader(String s) {
+        public BaseProgramBuilder setShader(String s) {
             mShader = s;
+            return this;
         }
 
         public void addInput(Element e) throws IllegalStateException {
@@ -120,12 +121,13 @@
             return mConstantCount++;
         }
 
-        public void setTextureCount(int count) throws IllegalArgumentException {
+        public BaseProgramBuilder setTextureCount(int count) throws IllegalArgumentException {
             // Should check for consistant and non-conflicting names...
             if(count >= MAX_CONSTANT) {
                 throw new IllegalArgumentException("Max texture count exceeded.");
             }
             mTextureCount = count;
+            return this;
         }
 
         protected void initProgram(Program p) {
diff --git a/graphics/java/android/renderscript/ProgramFragment.java b/graphics/java/android/renderscript/ProgramFragment.java
index 5e04f0c..04091a3 100644
--- a/graphics/java/android/renderscript/ProgramFragment.java
+++ b/graphics/java/android/renderscript/ProgramFragment.java
@@ -66,6 +66,7 @@
         public static final int MAX_TEXTURE = 2;
         RenderScript mRS;
         boolean mPointSpriteEnable;
+        boolean mVaryingColorEnable;
 
         public enum EnvMode {
             REPLACE (1),
@@ -106,21 +107,28 @@
             mPointSpriteEnable = false;
         }
 
-        public void setTexture(EnvMode env, Format fmt, int slot)
+        public Builder setTexture(EnvMode env, Format fmt, int slot)
             throws IllegalArgumentException {
             if((slot < 0) || (slot >= MAX_TEXTURE)) {
                 throw new IllegalArgumentException("MAX_TEXTURE exceeded.");
             }
             mSlots[slot] = new Slot(env, fmt);
+            return this;
         }
 
-        public void setPointSpriteTexCoordinateReplacement(boolean enable) {
+        public Builder setPointSpriteTexCoordinateReplacement(boolean enable) {
             mPointSpriteEnable = enable;
+            return this;
+        }
+
+        public Builder setVaryingColor(boolean enable) {
+            mVaryingColorEnable = enable;
+            return this;
         }
 
         public ProgramFragment create() {
             mRS.validate();
-            int[] tmp = new int[MAX_TEXTURE * 2 + 1];
+            int[] tmp = new int[MAX_TEXTURE * 2 + 2];
             if (mSlots[0] != null) {
                 tmp[0] = mSlots[0].env.mID;
                 tmp[1] = mSlots[0].format.mID;
@@ -130,6 +138,7 @@
                 tmp[3] = mSlots[1].format.mID;
             }
             tmp[4] = mPointSpriteEnable ? 1 : 0;
+            tmp[5] = mVaryingColorEnable ? 1 : 0;
             int id = mRS.nProgramFragmentCreate(tmp);
             ProgramFragment pf = new ProgramFragment(id, mRS);
             pf.mTextureCount = MAX_TEXTURE;
diff --git a/graphics/java/android/renderscript/ProgramRaster.java b/graphics/java/android/renderscript/ProgramRaster.java
index 56f9bf4..6fc9fff 100644
--- a/graphics/java/android/renderscript/ProgramRaster.java
+++ b/graphics/java/android/renderscript/ProgramRaster.java
@@ -26,23 +26,34 @@
  *
  **/
 public class ProgramRaster extends BaseObj {
+
+    public enum CullMode {
+        BACK (0),
+        FRONT (1),
+        NONE (2);
+
+        int mID;
+        CullMode(int id) {
+            mID = id;
+        }
+    }
+
     boolean mPointSmooth;
     boolean mLineSmooth;
     boolean mPointSprite;
-    float mPointSize;
     float mLineWidth;
-    Element mIn;
-    Element mOut;
+    CullMode mCullMode;
 
     ProgramRaster(int id, RenderScript rs) {
         super(rs);
         mID = id;
 
-        mPointSize = 1.0f;
         mLineWidth = 1.0f;
         mPointSmooth = false;
         mLineSmooth = false;
         mPointSprite = false;
+
+        mCullMode = CullMode.BACK;
     }
 
     public void setLineWidth(float w) {
@@ -51,51 +62,43 @@
         mRS.nProgramRasterSetLineWidth(mID, w);
     }
 
-    public void setPointSize(float s) {
+    public void setCullMode(CullMode m) {
         mRS.validate();
-        mPointSize = s;
-        mRS.nProgramRasterSetPointSize(mID, s);
+        mCullMode = m;
+        mRS.nProgramRasterSetCullMode(mID, m.mID);
     }
 
-    void internalInit() {
-        int inID = 0;
-        int outID = 0;
-        if (mIn != null) {
-            inID = mIn.mID;
-        }
-        if (mOut != null) {
-            outID = mOut.mID;
-        }
-        mID = mRS.nProgramRasterCreate(inID, outID, mPointSmooth, mLineSmooth, mPointSprite);
-    }
-
-
     public static class Builder {
         RenderScript mRS;
-        ProgramRaster mPR;
+        boolean mPointSprite;
+        boolean mPointSmooth;
+        boolean mLineSmooth;
 
-        public Builder(RenderScript rs, Element in, Element out) {
+        public Builder(RenderScript rs) {
             mRS = rs;
-            mPR = new ProgramRaster(0, rs);
+            mPointSmooth = false;
+            mLineSmooth = false;
+            mPointSprite = false;
         }
 
-        public void setPointSpriteEnable(boolean enable) {
-            mPR.mPointSprite = enable;
+        public Builder setPointSpriteEnable(boolean enable) {
+            mPointSprite = enable;
+            return this;
         }
 
-        public void setPointSmoothEnable(boolean enable) {
-            mPR.mPointSmooth = enable;
+        public Builder setPointSmoothEnable(boolean enable) {
+            mPointSmooth = enable;
+            return this;
         }
 
-        public void setLineSmoothEnable(boolean enable) {
-            mPR.mLineSmooth = enable;
+        public Builder setLineSmoothEnable(boolean enable) {
+            mLineSmooth = enable;
+            return this;
         }
 
-
         static synchronized ProgramRaster internalCreate(RenderScript rs, Builder b) {
-            b.mPR.internalInit();
-            ProgramRaster pr = b.mPR;
-            b.mPR = new ProgramRaster(0, b.mRS);
+            int id = rs.nProgramRasterCreate(b.mPointSmooth, b.mLineSmooth, b.mPointSprite);
+            ProgramRaster pr = new ProgramRaster(id, rs);
             return pr;
         }
 
@@ -111,3 +114,4 @@
 
 
 
+
diff --git a/graphics/java/android/renderscript/ProgramStore.java b/graphics/java/android/renderscript/ProgramStore.java
index 69be245..a92cbb6 100644
--- a/graphics/java/android/renderscript/ProgramStore.java
+++ b/graphics/java/android/renderscript/ProgramStore.java
@@ -114,28 +114,33 @@
 
         }
 
-        public void setDepthFunc(DepthFunc func) {
+        public Builder setDepthFunc(DepthFunc func) {
             mDepthFunc = func;
+            return this;
         }
 
-        public void setDepthMask(boolean enable) {
+        public Builder setDepthMask(boolean enable) {
             mDepthMask = enable;
+            return this;
         }
 
-        public void setColorMask(boolean r, boolean g, boolean b, boolean a) {
+        public Builder setColorMask(boolean r, boolean g, boolean b, boolean a) {
             mColorMaskR = r;
             mColorMaskG = g;
             mColorMaskB = b;
             mColorMaskA = a;
+            return this;
         }
 
-        public void setBlendFunc(BlendSrcFunc src, BlendDstFunc dst) {
+        public Builder setBlendFunc(BlendSrcFunc src, BlendDstFunc dst) {
             mBlendSrc = src;
             mBlendDst = dst;
+            return this;
         }
 
-        public void setDitherEnable(boolean enable) {
+        public Builder setDitherEnable(boolean enable) {
             mDither = enable;
+            return this;
         }
 
         static synchronized ProgramStore internalCreate(RenderScript rs, Builder b) {
@@ -147,17 +152,17 @@
             if (b.mOut != null) {
                 outID = b.mOut.mID;
             }
-            rs.nProgramFragmentStoreBegin(inID, outID);
-            rs.nProgramFragmentStoreDepthFunc(b.mDepthFunc.mID);
-            rs.nProgramFragmentStoreDepthMask(b.mDepthMask);
-            rs.nProgramFragmentStoreColorMask(b.mColorMaskR,
+            rs.nProgramStoreBegin(inID, outID);
+            rs.nProgramStoreDepthFunc(b.mDepthFunc.mID);
+            rs.nProgramStoreDepthMask(b.mDepthMask);
+            rs.nProgramStoreColorMask(b.mColorMaskR,
                                               b.mColorMaskG,
                                               b.mColorMaskB,
                                               b.mColorMaskA);
-            rs.nProgramFragmentStoreBlendFunc(b.mBlendSrc.mID, b.mBlendDst.mID);
-            rs.nProgramFragmentStoreDither(b.mDither);
+            rs.nProgramStoreBlendFunc(b.mBlendSrc.mID, b.mBlendDst.mID);
+            rs.nProgramStoreDither(b.mDither);
 
-            int id = rs.nProgramFragmentStoreCreate();
+            int id = rs.nProgramStoreCreate();
             return new ProgramStore(id, rs);
         }
 
diff --git a/graphics/java/android/renderscript/ProgramVertex.java b/graphics/java/android/renderscript/ProgramVertex.java
index 1b155d7..ec377e2 100644
--- a/graphics/java/android/renderscript/ProgramVertex.java
+++ b/graphics/java/android/renderscript/ProgramVertex.java
@@ -47,8 +47,9 @@
             mRS = rs;
         }
 
-        public void setTextureMatrixEnable(boolean enable) {
+        public Builder setTextureMatrixEnable(boolean enable) {
             mTextureMatrixEnable = enable;
+            return this;
         }
 
         public ProgramVertex create() {
diff --git a/graphics/java/android/renderscript/RenderScript.java b/graphics/java/android/renderscript/RenderScript.java
index a935243..4eeb4d3 100644
--- a/graphics/java/android/renderscript/RenderScript.java
+++ b/graphics/java/android/renderscript/RenderScript.java
@@ -68,10 +68,11 @@
     native void nContextSetSurface(int w, int h, Surface sur);
     native void nContextSetPriority(int p);
     native void nContextDump(int bits);
+    native void nContextFinish();
 
     native void nContextBindRootScript(int script);
     native void nContextBindSampler(int sampler, int slot);
-    native void nContextBindProgramFragmentStore(int pfs);
+    native void nContextBindProgramStore(int pfs);
     native void nContextBindProgramFragment(int pf);
     native void nContextBindProgramVertex(int pf);
     native void nContextBindProgramRaster(int pr);
@@ -82,6 +83,7 @@
     native void nContextDeinitToClient();
 
     native void nAssignName(int obj, byte[] name);
+    native String nGetName(int obj);
     native void nObjDestroy(int id);
     native void nObjDestroyOOB(int id);
     native int  nFileOpen(byte[] name);
@@ -89,12 +91,15 @@
 
     native int  nElementCreate(int type, int kind, boolean norm, int vecSize);
     native int  nElementCreate2(int[] elements, String[] names);
+    native void nElementGetNativeData(int id, int[] elementData);
+    native void nElementGetSubElements(int id, int[] IDs, String[] names);
 
     native void nTypeBegin(int elementID);
     native void nTypeAdd(int dim, int val);
     native int  nTypeCreate();
     native void nTypeFinalDestroy(Type t);
     native void nTypeSetupFields(Type t, int[] types, int[] bits, Field[] IDs);
+    native void nTypeGetNativeData(int id, int[] typeData);
 
     native int  nAllocationCreateTyped(int type);
     native int  nAllocationCreateFromBitmap(int dstFmt, boolean genMips, Bitmap bmp);
@@ -116,6 +121,14 @@
     native void nAllocationRead(int id, float[] d);
     native void nAllocationSubDataFromObject(int id, Type t, int offset, Object o);
     native void nAllocationSubReadFromObject(int id, Type t, int offset, Object o);
+    native int  nAllocationGetType(int id);
+
+    native int  nFileA3DCreateFromAssetStream(int assetStream);
+    native int  nFileA3DGetNumIndexEntries(int fileA3D);
+    native void nFileA3DGetIndexEntries(int fileA3D, int numEntries, int[] IDs, String[] names);
+    native int  nFileA3DGetEntryByIndex(int fileA3D, int index);
+
+    native int  nFontCreateFromFile(String fileName, int size, int dpi);
 
     native void nAdapter1DBindAllocation(int ad, int alloc);
     native void nAdapter1DSetConstraint(int ad, int dim, int value);
@@ -134,36 +147,32 @@
     native int  nAdapter2DCreate();
 
     native void nScriptBindAllocation(int script, int alloc, int slot);
-    native void nScriptSetClearColor(int script, float r, float g, float b, float a);
-    native void nScriptSetClearDepth(int script, float depth);
-    native void nScriptSetClearStencil(int script, int stencil);
     native void nScriptSetTimeZone(int script, byte[] timeZone);
-    native void nScriptSetType(int type, boolean writable, String name, int slot);
-    native void nScriptSetRoot(boolean isRoot);
-    native void nScriptSetInvokable(String name, int slot);
     native void nScriptInvoke(int id, int slot);
+    native void nScriptInvokeV(int id, int slot, byte[] params);
+    native void nScriptSetVarI(int id, int slot, int val);
+    native void nScriptSetVarF(int id, int slot, float val);
+    native void nScriptSetVarV(int id, int slot, byte[] val);
 
     native void nScriptCBegin();
     native void nScriptCSetScript(byte[] script, int offset, int length);
     native int  nScriptCCreate();
-    native void nScriptCAddDefineI32(String name, int value);
-    native void nScriptCAddDefineF(String name, float value);
 
     native void nSamplerBegin();
     native void nSamplerSet(int param, int value);
     native int  nSamplerCreate();
 
-    native void nProgramFragmentStoreBegin(int in, int out);
-    native void nProgramFragmentStoreDepthFunc(int func);
-    native void nProgramFragmentStoreDepthMask(boolean enable);
-    native void nProgramFragmentStoreColorMask(boolean r, boolean g, boolean b, boolean a);
-    native void nProgramFragmentStoreBlendFunc(int src, int dst);
-    native void nProgramFragmentStoreDither(boolean enable);
-    native int  nProgramFragmentStoreCreate();
+    native void nProgramStoreBegin(int in, int out);
+    native void nProgramStoreDepthFunc(int func);
+    native void nProgramStoreDepthMask(boolean enable);
+    native void nProgramStoreColorMask(boolean r, boolean g, boolean b, boolean a);
+    native void nProgramStoreBlendFunc(int src, int dst);
+    native void nProgramStoreDither(boolean enable);
+    native int  nProgramStoreCreate();
 
-    native int  nProgramRasterCreate(int in, int out, boolean pointSmooth, boolean lineSmooth, boolean pointSprite);
+    native int  nProgramRasterCreate(boolean pointSmooth, boolean lineSmooth, boolean pointSprite);
     native void nProgramRasterSetLineWidth(int pr, float v);
-    native void nProgramRasterSetPointSize(int pr, float v);
+    native void nProgramRasterSetCullMode(int pr, int mode);
 
     native void nProgramBindConstants(int pv, int slot, int mID);
     native void nProgramBindTexture(int vpf, int slot, int a);
@@ -182,9 +191,13 @@
     native void nLightSetColor(int l, float r, float g, float b);
     native void nLightSetPosition(int l, float x, float y, float z);
 
-    native int  nSimpleMeshCreate(int batchID, int idxID, int[] vtxID, int prim);
-    native void nSimpleMeshBindVertex(int id, int alloc, int slot);
-    native void nSimpleMeshBindIndex(int id, int alloc);
+    native int  nMeshCreate(int vtxCount, int indexCount);
+    native void nMeshBindVertex(int id, int alloc, int slot);
+    native void nMeshBindIndex(int id, int alloc, int prim, int slot);
+    native int  nMeshGetVertexBufferCount(int id);
+    native int  nMeshGetIndexCount(int id);
+    native void nMeshGetVertices(int id, int[] vtxIds, int vtxIdCount);
+    native void nMeshGetIndices(int id, int[] idxIds, int[] primitives, int vtxIdCount);
 
     native void nAnimationBegin(int attribCount, int keyframeCount);
     native void nAnimationAdd(float time, float[] attribs);
@@ -195,13 +208,25 @@
     @SuppressWarnings({"FieldCanBeLocal"})
     protected MessageThread mMessageThread;
 
-    Element mElement_USER_U8;
-    Element mElement_USER_I8;
-    Element mElement_USER_U16;
-    Element mElement_USER_I16;
-    Element mElement_USER_U32;
-    Element mElement_USER_I32;
-    Element mElement_USER_F32;
+    Element mElement_U8;
+    Element mElement_I8;
+    Element mElement_U16;
+    Element mElement_I16;
+    Element mElement_U32;
+    Element mElement_I32;
+    Element mElement_F32;
+    Element mElement_BOOLEAN;
+
+    Element mElement_ELEMENT;
+    Element mElement_TYPE;
+    Element mElement_ALLOCATION;
+    Element mElement_SAMPLER;
+    Element mElement_SCRIPT;
+    Element mElement_MESH;
+    Element mElement_PROGRAM_FRAGMENT;
+    Element mElement_PROGRAM_VERTEX;
+    Element mElement_PROGRAM_RASTER;
+    Element mElement_PROGRAM_STORE;
 
     Element mElement_A_8;
     Element mElement_RGB_565;
@@ -210,13 +235,17 @@
     Element mElement_RGBA_4444;
     Element mElement_RGBA_8888;
 
-    Element mElement_INDEX_16;
-    Element mElement_POSITION_2;
-    Element mElement_POSITION_3;
-    Element mElement_TEXTURE_2;
-    Element mElement_NORMAL_3;
-    Element mElement_COLOR_U8_4;
-    Element mElement_COLOR_F32_4;
+    Element mElement_FLOAT_2;
+    Element mElement_FLOAT_3;
+    Element mElement_FLOAT_4;
+    Element mElement_UCHAR_4;
+
+    Sampler mSampler_CLAMP_NEAREST;
+    Sampler mSampler_CLAMP_LINEAR;
+    Sampler mSampler_CLAMP_LINEAR_MIP_LINEAR;
+    Sampler mSampler_WRAP_NEAREST;
+    Sampler mSampler_WRAP_LINEAR;
+    Sampler mSampler_WRAP_LINEAR_MIP_LINEAR;
 
     ///////////////////////////////////////////////////////////////////////////////////
     //
@@ -282,7 +311,6 @@
                     mRS.mMessageCallback.mID = msg;
                     mRS.mMessageCallback.run();
                 }
-                //Log.d(LOG_TAG, "MessageThread msg " + msg + " v1 " + rbuf[0] + " v2 " + rbuf[1] + " v3 " +rbuf[2]);
             }
             Log.d(LOG_TAG, "MessageThread exiting.");
         }
@@ -307,6 +335,10 @@
         nContextDump(bits);
     }
 
+    public void finish() {
+        nContextFinish();
+    }
+
     public void destroy() {
         validate();
         nContextDeinitToClient();
@@ -335,3 +367,4 @@
 }
 
 
+
diff --git a/graphics/java/android/renderscript/RenderScriptGL.java b/graphics/java/android/renderscript/RenderScriptGL.java
index d1df23d..e90b4fc 100644
--- a/graphics/java/android/renderscript/RenderScriptGL.java
+++ b/graphics/java/android/renderscript/RenderScriptGL.java
@@ -74,9 +74,9 @@
         nContextBindRootScript(safeID(s));
     }
 
-    public void contextBindProgramFragmentStore(ProgramStore p) {
+    public void contextBindProgramStore(ProgramStore p) {
         validate();
-        nContextBindProgramFragmentStore(safeID(p));
+        nContextBindProgramStore(safeID(p));
     }
 
     public void contextBindProgramFragment(ProgramFragment p) {
diff --git a/graphics/java/android/renderscript/Sampler.java b/graphics/java/android/renderscript/Sampler.java
index 40ba722..da83d04 100644
--- a/graphics/java/android/renderscript/Sampler.java
+++ b/graphics/java/android/renderscript/Sampler.java
@@ -51,6 +51,86 @@
         mID = id;
     }
 
+    Sampler mSampler_CLAMP_NEAREST;
+    Sampler mSampler_CLAMP_LINEAR;
+    Sampler mSampler_CLAMP_LINEAR_MIP;
+    Sampler mSampler_WRAP_NEAREST;
+    Sampler mSampler_WRAP_LINEAR;
+    Sampler mSampler_WRAP_LINEAR_MIP;
+
+    public static Sampler CLAMP_NEAREST(RenderScript rs) {
+        if(rs.mSampler_CLAMP_NEAREST == null) {
+            Builder b = new Builder(rs);
+            b.setMin(Value.NEAREST);
+            b.setMag(Value.NEAREST);
+            b.setWrapS(Value.CLAMP);
+            b.setWrapT(Value.CLAMP);
+            rs.mSampler_CLAMP_NEAREST = b.create();
+        }
+        return rs.mSampler_CLAMP_NEAREST;
+    }
+
+    public static Sampler CLAMP_LINEAR(RenderScript rs) {
+        if(rs.mSampler_CLAMP_LINEAR == null) {
+            Builder b = new Builder(rs);
+            b.setMin(Value.LINEAR);
+            b.setMag(Value.LINEAR);
+            b.setWrapS(Value.CLAMP);
+            b.setWrapT(Value.CLAMP);
+            rs.mSampler_CLAMP_LINEAR = b.create();
+        }
+        return rs.mSampler_CLAMP_LINEAR;
+    }
+
+    public static Sampler CLAMP_LINEAR_MIP_LINEAR(RenderScript rs) {
+        if(rs.mSampler_CLAMP_LINEAR_MIP_LINEAR == null) {
+            Builder b = new Builder(rs);
+            b.setMin(Value.LINEAR_MIP_LINEAR);
+            b.setMag(Value.LINEAR_MIP_LINEAR);
+            b.setWrapS(Value.CLAMP);
+            b.setWrapT(Value.CLAMP);
+            rs.mSampler_CLAMP_LINEAR_MIP_LINEAR = b.create();
+        }
+        return rs.mSampler_CLAMP_LINEAR_MIP_LINEAR;
+    }
+
+    public static Sampler WRAP_NEAREST(RenderScript rs) {
+        if(rs.mSampler_WRAP_NEAREST == null) {
+            Builder b = new Builder(rs);
+            b.setMin(Value.NEAREST);
+            b.setMag(Value.NEAREST);
+            b.setWrapS(Value.WRAP);
+            b.setWrapT(Value.WRAP);
+            rs.mSampler_WRAP_NEAREST = b.create();
+        }
+        return rs.mSampler_WRAP_NEAREST;
+    }
+
+    public static Sampler WRAP_LINEAR(RenderScript rs) {
+        if(rs.mSampler_WRAP_LINEAR == null) {
+            Builder b = new Builder(rs);
+            b.setMin(Value.LINEAR);
+            b.setMag(Value.LINEAR);
+            b.setWrapS(Value.WRAP);
+            b.setWrapT(Value.WRAP);
+            rs.mSampler_WRAP_LINEAR = b.create();
+        }
+        return rs.mSampler_WRAP_LINEAR;
+    }
+
+    public static Sampler WRAP_LINEAR_MIP_LINEAR(RenderScript rs) {
+        if(rs.mSampler_WRAP_LINEAR_MIP_LINEAR == null) {
+            Builder b = new Builder(rs);
+            b.setMin(Value.LINEAR_MIP_LINEAR);
+            b.setMag(Value.LINEAR_MIP_LINEAR);
+            b.setWrapS(Value.WRAP);
+            b.setWrapT(Value.WRAP);
+            rs.mSampler_WRAP_LINEAR_MIP_LINEAR = b.create();
+        }
+        return rs.mSampler_WRAP_LINEAR_MIP_LINEAR;
+    }
+
+
     public static class Builder {
         RenderScript mRS;
         Value mMin;
diff --git a/graphics/java/android/renderscript/Script.java b/graphics/java/android/renderscript/Script.java
index 57ccfa3..d9aec59 100644
--- a/graphics/java/android/renderscript/Script.java
+++ b/graphics/java/android/renderscript/Script.java
@@ -42,6 +42,19 @@
         }
     }
 
+    protected void invoke(int slot) {
+        mRS.nScriptInvoke(mID, slot);
+    }
+
+    protected void invoke(int slot, FieldPacker v) {
+        if (v != null) {
+            mRS.nScriptInvokeV(mID, slot, v.getData());
+        } else {
+            mRS.nScriptInvoke(mID, slot);
+        }
+    }
+
+
     Script(int id, RenderScript rs) {
         super(rs);
         mID = id;
@@ -49,22 +62,27 @@
 
     public void bindAllocation(Allocation va, int slot) {
         mRS.validate();
-        mRS.nScriptBindAllocation(mID, va.mID, slot);
+        if (va != null) {
+            mRS.nScriptBindAllocation(mID, va.mID, slot);
+        } else {
+            mRS.nScriptBindAllocation(mID, 0, slot);
+        }
     }
 
-    public void setClearColor(float r, float g, float b, float a) {
-        mRS.validate();
-        mRS.nScriptSetClearColor(mID, r, g, b, a);
+    public void setVar(int index, float v) {
+        mRS.nScriptSetVarF(mID, index, v);
     }
 
-    public void setClearDepth(float d) {
-        mRS.validate();
-        mRS.nScriptSetClearDepth(mID, d);
+    public void setVar(int index, int v) {
+        mRS.nScriptSetVarI(mID, index, v);
     }
 
-    public void setClearStencil(int stencil) {
-        mRS.validate();
-        mRS.nScriptSetClearStencil(mID, stencil);
+    public void setVar(int index, boolean v) {
+        mRS.nScriptSetVarI(mID, index, v ? 1 : 0);
+    }
+
+    public void setVar(int index, FieldPacker v) {
+        mRS.nScriptSetVarV(mID, index, v.getData());
     }
 
     public void setTimeZone(String timeZone) {
@@ -78,72 +96,54 @@
 
     public static class Builder {
         RenderScript mRS;
-        boolean mIsRoot = false;
-        Type[] mTypes;
-        String[] mNames;
-        boolean[] mWritable;
-        int mInvokableCount = 0;
-        Invokable[] mInvokables;
 
         Builder(RenderScript rs) {
             mRS = rs;
-            mTypes = new Type[MAX_SLOT];
-            mNames = new String[MAX_SLOT];
-            mWritable = new boolean[MAX_SLOT];
-            mInvokables = new Invokable[MAX_SLOT];
         }
-
-        public void setType(Type t, int slot) {
-            mTypes[slot] = t;
-            mNames[slot] = null;
-        }
-
-        public void setType(Type t, String name, int slot) {
-            mTypes[slot] = t;
-            mNames[slot] = name;
-        }
-
-        public Invokable addInvokable(String func) {
-            Invokable i = new Invokable();
-            i.mName = func;
-            i.mRS = mRS;
-            i.mSlot = mInvokableCount;
-            mInvokables[mInvokableCount++] = i;
-            return i;
-        }
-
-        public void setType(boolean writable, int slot) {
-            mWritable[slot] = writable;
-        }
-
-        void transferCreate() {
-            mRS.nScriptSetRoot(mIsRoot);
-            for(int ct=0; ct < mTypes.length; ct++) {
-                if(mTypes[ct] != null) {
-                    mRS.nScriptSetType(mTypes[ct].mID, mWritable[ct], mNames[ct], ct);
-                }
-            }
-            for(int ct=0; ct < mInvokableCount; ct++) {
-                mRS.nScriptSetInvokable(mInvokables[ct].mName, ct);
-            }
-        }
-
-        void transferObject(Script s) {
-            s.mIsRoot = mIsRoot;
-            s.mTypes = mTypes;
-            s.mInvokables = new Invokable[mInvokableCount];
-            for(int ct=0; ct < mInvokableCount; ct++) {
-                s.mInvokables[ct] = mInvokables[ct];
-                s.mInvokables[ct].mScript = s;
-            }
-            s.mInvokables = null;
-        }
-
-        public void setRoot(boolean r) {
-            mIsRoot = r;
-        }
-
     }
 
+
+    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..5959be4 100644
--- a/graphics/java/android/renderscript/ScriptC.java
+++ b/graphics/java/android/renderscript/ScriptC.java
@@ -37,11 +37,49 @@
         super(id, rs);
     }
 
+    protected ScriptC(RenderScript rs, Resources resources, int resourceID, boolean isRoot) {
+        super(0, rs);
+        mID = internalCreate(rs, resources, resourceID);
+    }
+
+
+    private static synchronized int internalCreate(RenderScript rs, Resources resources, int resourceID) {
+        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);
+        return rs.nScriptCCreate();
+    }
+
     public static class Builder extends Script.Builder {
         byte[] mProgram;
         int mProgramLength;
-        HashMap<String,Integer> mIntDefines = new HashMap();
-        HashMap<String,Float> mFloatDefines = new HashMap();
 
         public Builder(RenderScript rs) {
             super(rs);
@@ -92,66 +130,20 @@
 
         static synchronized ScriptC internalCreate(Builder b) {
             b.mRS.nScriptCBegin();
-            b.transferCreate();
 
-            for (Entry<String,Integer> e: b.mIntDefines.entrySet()) {
-                b.mRS.nScriptCAddDefineI32(e.getKey(), e.getValue().intValue());
-            }
-            for (Entry<String,Float> e: b.mFloatDefines.entrySet()) {
-                b.mRS.nScriptCAddDefineF(e.getKey(), e.getValue().floatValue());
-            }
-
+            android.util.Log.e("rs", "len = " + b.mProgramLength);
             b.mRS.nScriptCSetScript(b.mProgram, 0, b.mProgramLength);
 
             int id = b.mRS.nScriptCCreate();
             ScriptC obj = new ScriptC(id, b.mRS);
-            b.transferObject(obj);
-
             return obj;
         }
 
-        public void addDefine(String name, int value) {
-            mIntDefines.put(name, value);
-        }
-
-        public void addDefine(String name, float value) {
-            mFloatDefines.put(name, value);
-        }
-
-        /**
-         * Takes the all public static final fields for a class, and adds defines
-         * for them, using the name of the field as the name of the define.
-         */
-        public void addDefines(Class cl) {
-            addDefines(cl.getFields(), (Modifier.STATIC | Modifier.FINAL | Modifier.PUBLIC), null);
-        }
-
-        /**
-         * Takes the all public fields for an object, and adds defines
-         * for them, using the name of the field as the name of the define.
-         */
-        public void addDefines(Object o) {
-            addDefines(o.getClass().getFields(), Modifier.PUBLIC, o);
-        }
-
-        void addDefines(Field[] fields, int mask, Object o) {
-            for (Field f: fields) {
-                try {
-                    if ((f.getModifiers() & mask) == mask) {
-                        Class t = f.getType();
-                        if (t == int.class) {
-                            mIntDefines.put(f.getName(), f.getInt(o));
-                        }
-                        else if (t == float.class) {
-                            mFloatDefines.put(f.getName(), f.getFloat(o));
-                        }
-                    }
-                } catch (IllegalAccessException ex) {
-                    // TODO: Do we want this log?
-                    Log.d(TAG, "addDefines skipping field " + f.getName());
-                }
-            }
-        }
+        public void addDefine(String name, int value) {}
+        public void addDefine(String name, float value) {}
+        public void addDefines(Class cl) {}
+        public void addDefines(Object o) {}
+        void addDefines(Field[] fields, int mask, Object o) {}
 
         public ScriptC create() {
             return internalCreate(this);
diff --git a/graphics/java/android/renderscript/Short2.java b/graphics/java/android/renderscript/Short2.java
new file mode 100644
index 0000000..426801f
--- /dev/null
+++ b/graphics/java/android/renderscript/Short2.java
@@ -0,0 +1,37 @@
+/*
+ * 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.renderscript;
+
+import java.lang.Math;
+import android.util.Log;
+
+
+/**
+ * @hide
+ *
+ **/
+public class Short2 {
+    public Short2() {
+    }
+
+    public short x;
+    public short y;
+}
+
+
+
+
diff --git a/graphics/java/android/renderscript/Short3.java b/graphics/java/android/renderscript/Short3.java
new file mode 100644
index 0000000..7b9c305
--- /dev/null
+++ b/graphics/java/android/renderscript/Short3.java
@@ -0,0 +1,38 @@
+/*
+ * 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.renderscript;
+
+import java.lang.Math;
+import android.util.Log;
+
+
+/**
+ * @hide
+ *
+ **/
+public class Short3 {
+    public Short3() {
+    }
+
+    public short x;
+    public short y;
+    public short z;
+}
+
+
+
+
diff --git a/graphics/java/android/renderscript/Short4.java b/graphics/java/android/renderscript/Short4.java
new file mode 100644
index 0000000..9a474e2
--- /dev/null
+++ b/graphics/java/android/renderscript/Short4.java
@@ -0,0 +1,38 @@
+/*
+ * 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.renderscript;
+
+import java.lang.Math;
+import android.util.Log;
+
+
+/**
+ * @hide
+ *
+ **/
+public class Short4 {
+    public Short4() {
+    }
+
+    public short x;
+    public short y;
+    public short z;
+    public short w;
+}
+
+
+
diff --git a/graphics/java/android/renderscript/SimpleMesh.java b/graphics/java/android/renderscript/SimpleMesh.java
deleted file mode 100644
index 4a217a9..0000000
--- a/graphics/java/android/renderscript/SimpleMesh.java
+++ /dev/null
@@ -1,364 +0,0 @@
-/*
- * 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.
- */
-
-package android.renderscript;
-
-import android.util.Config;
-import android.util.Log;
-
-/**
- * @hide
- *
- **/
-public class SimpleMesh extends BaseObj {
-    Type[] mVertexTypes;
-    Type mIndexType;
-    //Type mBatcheType;
-    Primitive mPrimitive;
-
-    SimpleMesh(int id, RenderScript rs) {
-        super(rs);
-        mID = id;
-    }
-
-    public void bindVertexAllocation(Allocation a, int slot) {
-        mRS.validate();
-        mRS.nSimpleMeshBindVertex(mID, a.mID, slot);
-    }
-
-    public void bindIndexAllocation(Allocation a) {
-        mRS.validate();
-        mRS.nSimpleMeshBindIndex(mID, a.mID);
-    }
-
-    public Allocation createVertexAllocation(int slot) {
-        mRS.validate();
-        return Allocation.createTyped(mRS, mVertexTypes[slot]);
-    }
-
-    public Allocation createIndexAllocation() {
-        mRS.validate();
-        return Allocation.createTyped(mRS, mIndexType);
-    }
-
-    public Type getVertexType(int slot) {
-        return mVertexTypes[slot];
-    }
-
-    public Type getIndexType() {
-        return mIndexType;
-    }
-
-    public static class Builder {
-        RenderScript mRS;
-
-        class Entry {
-            Type t;
-            Element e;
-            int size;
-        }
-
-        int mVertexTypeCount;
-        Entry[] mVertexTypes;
-        Entry mIndexType;
-        //Entry mBatchType;
-        Primitive mPrimitive;
-
-
-        public Builder(RenderScript rs) {
-            mRS = rs;
-            mVertexTypeCount = 0;
-            mVertexTypes = new Entry[16];
-            mIndexType = new Entry();
-        }
-
-        public int addVertexType(Type t) throws IllegalStateException {
-            if (mVertexTypeCount >= mVertexTypes.length) {
-                throw new IllegalStateException("Max vertex types exceeded.");
-            }
-
-            int addedIndex = mVertexTypeCount;
-            mVertexTypes[mVertexTypeCount] = new Entry();
-            mVertexTypes[mVertexTypeCount].t = t;
-            mVertexTypeCount++;
-            return addedIndex;
-        }
-
-        public int addVertexType(Element e, int size) throws IllegalStateException {
-            if (mVertexTypeCount >= mVertexTypes.length) {
-                throw new IllegalStateException("Max vertex types exceeded.");
-            }
-
-            int addedIndex = mVertexTypeCount;
-            mVertexTypes[mVertexTypeCount] = new Entry();
-            mVertexTypes[mVertexTypeCount].e = e;
-            mVertexTypes[mVertexTypeCount].size = size;
-            mVertexTypeCount++;
-            return addedIndex;
-        }
-
-        public void setIndexType(Type t) {
-            mIndexType.t = t;
-            mIndexType.e = null;
-            mIndexType.size = 0;
-        }
-
-        public void setIndexType(Element e, int size) {
-            mIndexType.t = null;
-            mIndexType.e = e;
-            mIndexType.size = size;
-        }
-
-        public void setPrimitive(Primitive p) {
-            mPrimitive = p;
-        }
-
-
-        Type newType(Element e, int size) {
-            Type.Builder tb = new Type.Builder(mRS, e);
-            tb.add(Dimension.X, size);
-            return tb.create();
-        }
-
-        static synchronized SimpleMesh internalCreate(RenderScript rs, Builder b) {
-            Type[] toDestroy = new Type[18];
-            int toDestroyCount = 0;
-
-            int indexID = 0;
-            if (b.mIndexType.t != null) {
-                indexID = b.mIndexType.t.mID;
-            } else if (b.mIndexType.size != 0) {
-                b.mIndexType.t = b.newType(b.mIndexType.e, b.mIndexType.size);
-                indexID = b.mIndexType.t.mID;
-                toDestroy[toDestroyCount++] = b.mIndexType.t;
-            }
-
-            int[] IDs = new int[b.mVertexTypeCount];
-            for(int ct=0; ct < b.mVertexTypeCount; ct++) {
-                if (b.mVertexTypes[ct].t != null) {
-                    IDs[ct] = b.mVertexTypes[ct].t.mID;
-                } else {
-                    b.mVertexTypes[ct].t = b.newType(b.mVertexTypes[ct].e, b.mVertexTypes[ct].size);
-                    IDs[ct] = b.mVertexTypes[ct].t.mID;
-                    toDestroy[toDestroyCount++] = b.mVertexTypes[ct].t;
-                }
-            }
-
-            int id = rs.nSimpleMeshCreate(0, indexID, IDs, b.mPrimitive.mID);
-            for(int ct=0; ct < toDestroyCount; ct++) {
-                toDestroy[ct].destroy();
-            }
-
-            return new SimpleMesh(id, rs);
-        }
-
-        public SimpleMesh create() {
-            mRS.validate();
-            SimpleMesh sm = internalCreate(mRS, this);
-            sm.mVertexTypes = new Type[mVertexTypeCount];
-            for(int ct=0; ct < mVertexTypeCount; ct++) {
-                sm.mVertexTypes[ct] = mVertexTypes[ct].t;
-            }
-            sm.mIndexType = mIndexType.t;
-            sm.mPrimitive = mPrimitive;
-            return sm;
-        }
-    }
-
-    public static class TriangleMeshBuilder {
-        float mVtxData[];
-        int mVtxCount;
-        short mIndexData[];
-        int mIndexCount;
-        RenderScript mRS;
-        Element mElement;
-
-        float mNX = 0;
-        float mNY = 0;
-        float mNZ = -1;
-        float mS0 = 0;
-        float mT0 = 0;
-        float mR = 1;
-        float mG = 1;
-        float mB = 1;
-        float mA = 1;
-
-        int mVtxSize;
-        int mFlags;
-
-        public static final int COLOR = 0x0001;
-        public static final int NORMAL = 0x0002;
-        public static final int TEXTURE_0 = 0x0100;
-
-        public TriangleMeshBuilder(RenderScript rs, int vtxSize, int flags) {
-            mRS = rs;
-            mVtxCount = 0;
-            mIndexCount = 0;
-            mVtxData = new float[128];
-            mIndexData = new short[128];
-            mVtxSize = vtxSize;
-            mFlags = flags;
-
-            if (vtxSize < 2 || vtxSize > 3) {
-                throw new IllegalArgumentException("Vertex size out of range.");
-            }
-        }
-
-        private void makeSpace(int count) {
-            if ((mVtxCount + count) >= mVtxData.length) {
-                float t[] = new float[mVtxData.length * 2];
-                System.arraycopy(mVtxData, 0, t, 0, mVtxData.length);
-                mVtxData = t;
-            }
-        }
-
-        private void latch() {
-            if ((mFlags & COLOR) != 0) {
-                makeSpace(4);
-                mVtxData[mVtxCount++] = mR;
-                mVtxData[mVtxCount++] = mG;
-                mVtxData[mVtxCount++] = mB;
-                mVtxData[mVtxCount++] = mA;
-            }
-            if ((mFlags & TEXTURE_0) != 0) {
-                makeSpace(2);
-                mVtxData[mVtxCount++] = mS0;
-                mVtxData[mVtxCount++] = mT0;
-            }
-            if ((mFlags & NORMAL) != 0) {
-                makeSpace(3);
-                mVtxData[mVtxCount++] = mNX;
-                mVtxData[mVtxCount++] = mNY;
-                mVtxData[mVtxCount++] = mNZ;
-            }
-        }
-
-        public void addVertex(float x, float y) {
-            if (mVtxSize != 2) {
-                throw new IllegalStateException("add mistmatch with declared components.");
-            }
-            makeSpace(2);
-            mVtxData[mVtxCount++] = x;
-            mVtxData[mVtxCount++] = y;
-            latch();
-        }
-
-        public void addVertex(float x, float y, float z) {
-            if (mVtxSize != 3) {
-                throw new IllegalStateException("add mistmatch with declared components.");
-            }
-            makeSpace(3);
-            mVtxData[mVtxCount++] = x;
-            mVtxData[mVtxCount++] = y;
-            mVtxData[mVtxCount++] = z;
-            latch();
-        }
-
-        public void setTexture(float s, float t) {
-            if ((mFlags & TEXTURE_0) == 0) {
-                throw new IllegalStateException("add mistmatch with declared components.");
-            }
-            mS0 = s;
-            mT0 = t;
-        }
-
-        public void setNormal(float x, float y, float z) {
-            if ((mFlags & NORMAL) == 0) {
-                throw new IllegalStateException("add mistmatch with declared components.");
-            }
-            mNX = x;
-            mNY = y;
-            mNZ = z;
-        }
-
-        public void setColor(float r, float g, float b, float a) {
-            if ((mFlags & COLOR) == 0) {
-                throw new IllegalStateException("add mistmatch with declared components.");
-            }
-            mR = r;
-            mG = g;
-            mB = b;
-            mA = a;
-        }
-
-        public void addTriangle(int idx1, int idx2, int idx3) {
-            if((idx1 >= mVtxCount) || (idx1 < 0) ||
-               (idx2 >= mVtxCount) || (idx2 < 0) ||
-               (idx3 >= mVtxCount) || (idx3 < 0)) {
-               throw new IllegalStateException("Index provided greater than vertex count.");
-            }
-            if ((mIndexCount + 3) >= mIndexData.length) {
-                short t[] = new short[mIndexData.length * 2];
-                System.arraycopy(mIndexData, 0, t, 0, mIndexData.length);
-                mIndexData = t;
-            }
-            mIndexData[mIndexCount++] = (short)idx1;
-            mIndexData[mIndexCount++] = (short)idx2;
-            mIndexData[mIndexCount++] = (short)idx3;
-        }
-
-        public SimpleMesh create() {
-            Element.Builder b = new Element.Builder(mRS);
-            int floatCount = mVtxSize;
-            b.add(Element.createAttrib(mRS,
-                                       Element.DataType.FLOAT_32,
-                                       Element.DataKind.POSITION,
-                                       mVtxSize), "position");
-            if ((mFlags & COLOR) != 0) {
-                floatCount += 4;
-                b.add(Element.createAttrib(mRS,
-                                           Element.DataType.FLOAT_32,
-                                           Element.DataKind.COLOR,
-                                           4), "color");
-            }
-            if ((mFlags & TEXTURE_0) != 0) {
-                floatCount += 2;
-                b.add(Element.createAttrib(mRS,
-                                           Element.DataType.FLOAT_32,
-                                           Element.DataKind.TEXTURE,
-                                           2), "texture");
-            }
-            if ((mFlags & NORMAL) != 0) {
-                floatCount += 3;
-                b.add(Element.createAttrib(mRS,
-                                           Element.DataType.FLOAT_32,
-                                           Element.DataKind.NORMAL,
-                                           3), "normal");
-            }
-            mElement = b.create();
-
-            Builder smb = new Builder(mRS);
-            smb.addVertexType(mElement, mVtxCount / floatCount);
-            smb.setIndexType(Element.createIndex(mRS), mIndexCount);
-            smb.setPrimitive(Primitive.TRIANGLE);
-            SimpleMesh sm = smb.create();
-
-            Allocation vertexAlloc = sm.createVertexAllocation(0);
-            Allocation indexAlloc = sm.createIndexAllocation();
-            sm.bindVertexAllocation(vertexAlloc, 0);
-            sm.bindIndexAllocation(indexAlloc);
-
-            vertexAlloc.data(mVtxData);
-            vertexAlloc.uploadToBufferObject();
-
-            indexAlloc.data(mIndexData);
-            indexAlloc.uploadToBufferObject();
-
-            return sm;
-        }
-    }
-}
-
diff --git a/graphics/java/android/renderscript/Type.java b/graphics/java/android/renderscript/Type.java
index 62d3867..8e45f2b 100644
--- a/graphics/java/android/renderscript/Type.java
+++ b/graphics/java/android/renderscript/Type.java
@@ -16,7 +16,9 @@
 
 package android.renderscript;
 
+
 import java.lang.reflect.Field;
+import android.util.Log;
 
 /**
  * @hide
@@ -108,48 +110,30 @@
         super.finalize();
     }
 
-    public static Type createFromClass(RenderScript rs, Class c, int size) {
-        Element e = Element.createFromClass(rs, c);
-        Builder b = new Builder(rs, e);
-        b.add(Dimension.X, size);
-        Type t = b.create();
-        e.destroy();
+    @Override
+    void updateFromNative() {
+        // We have 6 integer to obtain mDimX; mDimY; mDimZ;
+        // mDimLOD; mDimFaces; mElement;
+        int[] dataBuffer = new int[6];
+        mRS.nTypeGetNativeData(mID, dataBuffer);
 
-        // native fields
-        {
-            Field[] fields = c.getFields();
-            int[] arTypes = new int[fields.length];
-            int[] arBits = new int[fields.length];
+        mDimX = dataBuffer[0];
+        mDimY = dataBuffer[1];
+        mDimZ = dataBuffer[2];
+        mDimLOD = dataBuffer[3] == 1 ? true : false;
+        mDimFaces = dataBuffer[4] == 1 ? true : false;
 
-            for(int ct=0; ct < fields.length; ct++) {
-                Field f = fields[ct];
-                Class fc = f.getType();
-                if(fc == int.class) {
-                    arTypes[ct] = Element.DataType.SIGNED_32.mID;
-                    arBits[ct] = 32;
-                } else if(fc == short.class) {
-                    arTypes[ct] = Element.DataType.SIGNED_16.mID;
-                    arBits[ct] = 16;
-                } else if(fc == byte.class) {
-                    arTypes[ct] = Element.DataType.SIGNED_8.mID;
-                    arBits[ct] = 8;
-                } else if(fc == float.class) {
-                    arTypes[ct] = Element.DataType.FLOAT_32.mID;
-                    arBits[ct] = 32;
-                } else {
-                    throw new IllegalArgumentException("Unkown field type");
-                }
-            }
-            rs.nTypeSetupFields(t, arTypes, arBits, fields);
+        int elementID = dataBuffer[5];
+        if(elementID != 0) {
+            mElement = new Element(mRS, elementID);
+            mElement.updateFromNative();
         }
-        t.mJavaClass = c;
-        return t;
+        calcElementCount();
     }
 
     public static Type createFromClass(RenderScript rs, Class c, int size, String scriptName) {
-        Type t = createFromClass(rs, c, size);
-        t.setName(scriptName);
-        return t;
+        android.util.Log.e("RenderScript", "Calling depricated createFromClass");
+        return null;
     }
 
 
diff --git a/graphics/java/android/renderscript/Vector2f.java b/graphics/java/android/renderscript/Vector2f.java
deleted file mode 100644
index 567d57fa..0000000
--- a/graphics/java/android/renderscript/Vector2f.java
+++ /dev/null
@@ -1,37 +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.renderscript;
-
-import java.lang.Math;
-import android.util.Log;
-
-
-/**
- * @hide
- *
- **/
-public class Vector2f {
-    public Vector2f() {
-    }
-
-    public float x;
-    public float y;
-}
-
-
-
-
diff --git a/graphics/java/android/renderscript/Vector3f.java b/graphics/java/android/renderscript/Vector3f.java
deleted file mode 100644
index f2842f3..0000000
--- a/graphics/java/android/renderscript/Vector3f.java
+++ /dev/null
@@ -1,38 +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.renderscript;
-
-import java.lang.Math;
-import android.util.Log;
-
-
-/**
- * @hide
- *
- **/
-public class Vector3f {
-    public Vector3f() {
-    }
-
-    public float x;
-    public float y;
-    public float z;
-}
-
-
-
-
diff --git a/graphics/java/android/renderscript/Vector4f.java b/graphics/java/android/renderscript/Vector4f.java
deleted file mode 100644
index fabd959..0000000
--- a/graphics/java/android/renderscript/Vector4f.java
+++ /dev/null
@@ -1,38 +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.renderscript;
-
-import java.lang.Math;
-import android.util.Log;
-
-
-/**
- * @hide
- *
- **/
-public class Vector4f {
-    public Vector4f() {
-    }
-
-    public float x;
-    public float y;
-    public float z;
-    public float w;
-}
-
-
-
diff --git a/graphics/jni/android_renderscript_RenderScript.cpp b/graphics/jni/android_renderscript_RenderScript.cpp
index 45cc72e..8968dfb 100644
--- a/graphics/jni/android_renderscript_RenderScript.cpp
+++ b/graphics/jni/android_renderscript_RenderScript.cpp
@@ -85,6 +85,14 @@
 // ---------------------------------------------------------------------------
 
 static void
+nContextFinish(JNIEnv *_env, jobject _this)
+{
+    RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
+    LOG_API("nContextFinish, con(%p)", con);
+    rsContextFinish(con);
+}
+
+static void
 nAssignName(JNIEnv *_env, jobject _this, jint obj, jbyteArray str)
 {
     RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
@@ -96,6 +104,17 @@
     _env->ReleasePrimitiveArrayCritical(str, cptr, JNI_ABORT);
 }
 
+static jstring
+nGetName(JNIEnv *_env, jobject _this, jint obj)
+{
+    RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
+    LOG_API("nGetName, con(%p), obj(%p)", con, (void *)obj);
+
+    const char *name = NULL;
+    rsGetName(con, (void *)obj, &name);
+    return _env->NewStringUTF(name);
+}
+
 static void
 nObjDestroy(JNIEnv *_env, jobject _this, jint obj)
 {
@@ -289,6 +308,46 @@
     return (jint)id;
 }
 
+static void
+nElementGetNativeData(JNIEnv *_env, jobject _this, jint id, jintArray _elementData)
+{
+    int dataSize = _env->GetArrayLength(_elementData);
+    RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
+    LOG_API("nElementGetNativeData, con(%p)", con);
+
+    // we will pack mType; mKind; mNormalized; mVectorSize; NumSubElements
+    assert(dataSize == 5);
+
+    uint32_t elementData[5];
+    rsElementGetNativeData(con, (RsElement)id, elementData, dataSize);
+
+    for(jint i = 0; i < dataSize; i ++) {
+        _env->SetIntArrayRegion(_elementData, i, 1, (const jint*)&elementData[i]);
+    }
+}
+
+
+static void
+nElementGetSubElements(JNIEnv *_env, jobject _this, jint id, jintArray _IDs, jobjectArray _names)
+{
+    int dataSize = _env->GetArrayLength(_IDs);
+    RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
+    LOG_API("nElementGetSubElements, con(%p)", con);
+
+    uint32_t *ids = (uint32_t *)malloc((uint32_t)dataSize * sizeof(uint32_t));
+    const char **names = (const char **)malloc((uint32_t)dataSize * sizeof(const char *));
+
+    rsElementGetSubElements(con, (RsElement)id, ids, names, (uint32_t)dataSize);
+
+    for(jint i = 0; i < dataSize; i ++) {
+        _env->SetObjectArrayElement(_names, i, _env->NewStringUTF(names[i]));
+        _env->SetIntArrayRegion(_IDs, i, 1, (const jint*)&ids[i]);
+    }
+
+    free(ids);
+    free(names);
+}
+
 // -----------------------------------
 
 static void
@@ -315,6 +374,26 @@
     return (jint)rsTypeCreate(con);
 }
 
+static void
+nTypeGetNativeData(JNIEnv *_env, jobject _this, jint id, jintArray _typeData)
+{
+    // We are packing 6 items: mDimX; mDimY; mDimZ;
+    // mDimLOD; mDimFaces; mElement; into typeData
+    int elementCount = _env->GetArrayLength(_typeData);
+
+    assert(elementCount == 6);
+
+    RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
+    LOG_API("nTypeCreate, con(%p)", con);
+
+    uint32_t typeData[6];
+    rsTypeGetNativeData(con, (RsType)id, typeData, 6);
+
+    for(jint i = 0; i < elementCount; i ++) {
+        _env->SetIntArrayRegion(_typeData, i, 1, (const jint*)&typeData[i]);
+    }
+}
+
 static void * SF_LoadInt(JNIEnv *_env, jobject _obj, jfieldID _field, void *buffer)
 {
     ((int32_t *)buffer)[0] = _env->GetIntField(_obj, _field);
@@ -700,6 +779,78 @@
     free(bufAlloc);
 }
 
+static jint
+nAllocationGetType(JNIEnv *_env, jobject _this, jint a)
+{
+    RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
+    LOG_API("nAllocationGetType, con(%p), a(%p)", con, (RsAllocation)a);
+    return (jint) rsAllocationGetType(con, (RsAllocation)a);
+}
+
+// -----------------------------------
+
+static int
+nFileA3DCreateFromAssetStream(JNIEnv *_env, jobject _this, jint native_asset)
+{
+    LOGV("______nFileA3D %u", (uint32_t) native_asset);
+    RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
+
+    Asset* asset = reinterpret_cast<Asset*>(native_asset);
+
+    jint id = (jint)rsFileA3DCreateFromAssetStream(con, asset->getBuffer(false), asset->getLength());
+    return id;
+}
+
+static int
+nFileA3DGetNumIndexEntries(JNIEnv *_env, jobject _this, jint fileA3D)
+{
+    RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
+
+    int32_t numEntries = 0;
+    rsFileA3DGetNumIndexEntries(con, &numEntries, (RsFile)fileA3D);
+    return numEntries;
+}
+
+static void
+nFileA3DGetIndexEntries(JNIEnv *_env, jobject _this, jint fileA3D, jint numEntries, jintArray _ids, jobjectArray _entries)
+{
+    LOGV("______nFileA3D %u", (uint32_t) fileA3D);
+    RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
+
+    RsFileIndexEntry *fileEntries = (RsFileIndexEntry*)malloc((uint32_t)numEntries * sizeof(RsFileIndexEntry));
+
+    rsFileA3DGetIndexEntries(con, fileEntries, (uint32_t)numEntries, (RsFile)fileA3D);
+
+    for(jint i = 0; i < numEntries; i ++) {
+        _env->SetObjectArrayElement(_entries, i, _env->NewStringUTF(fileEntries[i].objectName));
+        _env->SetIntArrayRegion(_ids, i, 1, (const jint*)&fileEntries[i].classID);
+    }
+
+    free(fileEntries);
+}
+
+static int
+nFileA3DGetEntryByIndex(JNIEnv *_env, jobject _this, jint fileA3D, jint index)
+{
+    LOGV("______nFileA3D %u", (uint32_t) fileA3D);
+    RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
+
+    jint id = (jint)rsFileA3DGetEntryByIndex(con, (uint32_t)index, (RsFile)fileA3D);
+    return id;
+}
+
+// -----------------------------------
+
+static int
+nFontCreateFromFile(JNIEnv *_env, jobject _this, jstring fileName, jint fontSize, jint dpi)
+{
+    RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
+    const char* fileNameUTF = _env->GetStringUTFChars(fileName, NULL);
+
+    jint id = (jint)rsFontCreateFromFile(con, fileNameUTF, fontSize, dpi);
+    return id;
+}
+
 
 // -----------------------------------
 
@@ -854,29 +1005,33 @@
 }
 
 static void
-nScriptSetClearColor(JNIEnv *_env, jobject _this, jint script, jfloat r, jfloat g, jfloat b, jfloat a)
+nScriptSetVarI(JNIEnv *_env, jobject _this, jint script, jint slot, jint val)
 {
     RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
-    LOG_API("nScriptSetClearColor, con(%p), s(%p), r(%f), g(%f), b(%f), a(%f)", con, (void *)script, r, g, b, a);
-    rsScriptSetClearColor(con, (RsScript)script, r, g, b, a);
+    LOG_API("nScriptSetVarI, con(%p), s(%p), slot(%i), val(%i), b(%f), a(%f)", con, (void *)script, slot, val);
+    rsScriptSetVarI(con, (RsScript)script, slot, val);
 }
 
 static void
-nScriptSetClearDepth(JNIEnv *_env, jobject _this, jint script, jfloat d)
+nScriptSetVarF(JNIEnv *_env, jobject _this, jint script, jint slot, float val)
 {
     RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
-    LOG_API("nScriptCSetClearDepth, con(%p), s(%p), depth(%f)", con, (void *)script, d);
-    rsScriptSetClearDepth(con, (RsScript)script, d);
+    LOG_API("nScriptSetVarI, con(%p), s(%p), slot(%i), val(%i), b(%f), a(%f)", con, (void *)script, slot, val);
+    rsScriptSetVarF(con, (RsScript)script, slot, val);
 }
 
 static void
-nScriptSetClearStencil(JNIEnv *_env, jobject _this, jint script, jint stencil)
+nScriptSetVarV(JNIEnv *_env, jobject _this, jint script, jint slot, jbyteArray data)
 {
     RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
-    LOG_API("nScriptCSetClearStencil, con(%p), s(%p), stencil(%i)", con, (void *)script, stencil);
-    rsScriptSetClearStencil(con, (RsScript)script, stencil);
+    LOG_API("nScriptSetVarV, con(%p), s(%p), slot(%i)", con, (void *)script, slot);
+    jint len = _env->GetArrayLength(data);
+    jbyte *ptr = _env->GetByteArrayElements(data, NULL);
+    rsScriptSetVarV(con, (RsScript)script, slot, ptr, len);
+    _env->ReleaseByteArrayElements(data, ptr, JNI_ABORT);
 }
 
+
 static void
 nScriptSetTimeZone(JNIEnv *_env, jobject _this, jint script, jbyteArray timeZone)
 {
@@ -895,36 +1050,6 @@
 }
 
 static void
-nScriptSetType(JNIEnv *_env, jobject _this, jint type, jboolean writable, jstring _str, jint slot)
-{
-    RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
-    LOG_API("nScriptCAddType, con(%p), type(%p), writable(%i), slot(%i)", con, (RsType)type, writable, slot);
-    const char* n = NULL;
-    if (_str) {
-        n = _env->GetStringUTFChars(_str, NULL);
-    }
-    rsScriptSetType(con, (RsType)type, slot, writable, n);
-    if (n) {
-        _env->ReleaseStringUTFChars(_str, n);
-    }
-}
-
-static void
-nScriptSetInvoke(JNIEnv *_env, jobject _this, jstring _str, jint slot)
-{
-    RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
-    LOG_API("nScriptSetInvoke, con(%p)", con);
-    const char* n = NULL;
-    if (_str) {
-        n = _env->GetStringUTFChars(_str, NULL);
-    }
-    rsScriptSetInvoke(con, n, slot);
-    if (n) {
-        _env->ReleaseStringUTFChars(_str, n);
-    }
-}
-
-static void
 nScriptInvoke(JNIEnv *_env, jobject _this, jint obj, jint slot)
 {
     RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
@@ -933,13 +1058,17 @@
 }
 
 static void
-nScriptSetRoot(JNIEnv *_env, jobject _this, jboolean isRoot)
+nScriptInvokeV(JNIEnv *_env, jobject _this, jint script, jint slot, jbyteArray data)
 {
     RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
-    LOG_API("nScriptCSetRoot, con(%p), isRoot(%i)", con, isRoot);
-    rsScriptSetRoot(con, isRoot);
+    LOG_API("nScriptInvokeV, con(%p), s(%p), slot(%i)", con, (void *)script, slot);
+    jint len = _env->GetArrayLength(data);
+    jbyte *ptr = _env->GetByteArrayElements(data, NULL);
+    rsScriptInvokeV(con, (RsScript)script, slot, ptr, len);
+    _env->ReleaseByteArrayElements(data, ptr, JNI_ABORT);
 }
 
+
 // -----------------------------------
 
 static void
@@ -1002,83 +1131,63 @@
     return (jint)rsScriptCCreate(con);
 }
 
-static void
-nScriptCAddDefineI32(JNIEnv *_env, jobject _this, jstring name, jint value)
-{
-    RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
-    const char* n = _env->GetStringUTFChars(name, NULL);
-    LOG_API("nScriptCAddDefineI32, con(%p) name(%s) value(%d)", con, n, value);
-    rsScriptCSetDefineI32(con, n, value);
-    _env->ReleaseStringUTFChars(name, n);
-}
-
-static void
-nScriptCAddDefineF(JNIEnv *_env, jobject _this, jstring name, jfloat value)
-{
-    RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
-    const char* n = _env->GetStringUTFChars(name, NULL);
-    LOG_API("nScriptCAddDefineF, con(%p) name(%s) value(%f)", con, n, value);
-    rsScriptCSetDefineF(con, n, value);
-    _env->ReleaseStringUTFChars(name, n);
-}
-
 // ---------------------------------------------------------------------------
 
 static void
-nProgramFragmentStoreBegin(JNIEnv *_env, jobject _this, jint in, jint out)
+nProgramStoreBegin(JNIEnv *_env, jobject _this, jint in, jint out)
 {
     RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
-    LOG_API("nProgramFragmentStoreBegin, con(%p), in(%p), out(%p)", con, (RsElement)in, (RsElement)out);
-    rsProgramFragmentStoreBegin(con, (RsElement)in, (RsElement)out);
+    LOG_API("nProgramStoreBegin, con(%p), in(%p), out(%p)", con, (RsElement)in, (RsElement)out);
+    rsProgramStoreBegin(con, (RsElement)in, (RsElement)out);
 }
 
 static void
-nProgramFragmentStoreDepthFunc(JNIEnv *_env, jobject _this, jint func)
+nProgramStoreDepthFunc(JNIEnv *_env, jobject _this, jint func)
 {
     RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
-    LOG_API("nProgramFragmentStoreDepthFunc, con(%p), func(%i)", con, func);
-    rsProgramFragmentStoreDepthFunc(con, (RsDepthFunc)func);
+    LOG_API("nProgramStoreDepthFunc, con(%p), func(%i)", con, func);
+    rsProgramStoreDepthFunc(con, (RsDepthFunc)func);
 }
 
 static void
-nProgramFragmentStoreDepthMask(JNIEnv *_env, jobject _this, jboolean enable)
+nProgramStoreDepthMask(JNIEnv *_env, jobject _this, jboolean enable)
 {
     RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
-    LOG_API("nProgramFragmentStoreDepthMask, con(%p), enable(%i)", con, enable);
-    rsProgramFragmentStoreDepthMask(con, enable);
+    LOG_API("nProgramStoreDepthMask, con(%p), enable(%i)", con, enable);
+    rsProgramStoreDepthMask(con, enable);
 }
 
 static void
-nProgramFragmentStoreColorMask(JNIEnv *_env, jobject _this, jboolean r, jboolean g, jboolean b, jboolean a)
+nProgramStoreColorMask(JNIEnv *_env, jobject _this, jboolean r, jboolean g, jboolean b, jboolean a)
 {
     RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
-    LOG_API("nProgramFragmentStoreColorMask, con(%p), r(%i), g(%i), b(%i), a(%i)", con, r, g, b, a);
-    rsProgramFragmentStoreColorMask(con, r, g, b, a);
+    LOG_API("nProgramStoreColorMask, con(%p), r(%i), g(%i), b(%i), a(%i)", con, r, g, b, a);
+    rsProgramStoreColorMask(con, r, g, b, a);
 }
 
 static void
-nProgramFragmentStoreBlendFunc(JNIEnv *_env, jobject _this, int src, int dst)
+nProgramStoreBlendFunc(JNIEnv *_env, jobject _this, int src, int dst)
 {
     RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
-    LOG_API("nProgramFragmentStoreBlendFunc, con(%p), src(%i), dst(%i)", con, src, dst);
-    rsProgramFragmentStoreBlendFunc(con, (RsBlendSrcFunc)src, (RsBlendDstFunc)dst);
+    LOG_API("nProgramStoreBlendFunc, con(%p), src(%i), dst(%i)", con, src, dst);
+    rsProgramStoreBlendFunc(con, (RsBlendSrcFunc)src, (RsBlendDstFunc)dst);
 }
 
 static void
-nProgramFragmentStoreDither(JNIEnv *_env, jobject _this, jboolean enable)
+nProgramStoreDither(JNIEnv *_env, jobject _this, jboolean enable)
 {
     RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
-    LOG_API("nProgramFragmentStoreDither, con(%p), enable(%i)", con, enable);
-    rsProgramFragmentStoreDither(con, enable);
+    LOG_API("nProgramStoreDither, con(%p), enable(%i)", con, enable);
+    rsProgramStoreDither(con, enable);
 }
 
 static jint
-nProgramFragmentStoreCreate(JNIEnv *_env, jobject _this)
+nProgramStoreCreate(JNIEnv *_env, jobject _this)
 {
     RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
-    LOG_API("nProgramFragmentStoreCreate, con(%p)", con);
+    LOG_API("nProgramStoreCreate, con(%p)", con);
 
-    return (jint)rsProgramFragmentStoreCreate(con);
+    return (jint)rsProgramStoreCreate(con);
 }
 
 // ---------------------------------------------------------------------------
@@ -1171,21 +1280,12 @@
 // ---------------------------------------------------------------------------
 
 static jint
-nProgramRasterCreate(JNIEnv *_env, jobject _this, jint in, jint out,
-                     jboolean pointSmooth, jboolean lineSmooth, jboolean pointSprite)
+nProgramRasterCreate(JNIEnv *_env, jobject _this, jboolean pointSmooth, jboolean lineSmooth, jboolean pointSprite)
 {
     RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
-    LOG_API("nProgramRasterCreate, con(%p), in(%p), out(%p), pointSmooth(%i), lineSmooth(%i), pointSprite(%i)",
-            con, (RsElement)in, (RsElement)out, pointSmooth, lineSmooth, pointSprite);
-    return (jint)rsProgramRasterCreate(con, (RsElement)in, (RsElement)out, pointSmooth, lineSmooth, pointSprite);
-}
-
-static void
-nProgramRasterSetPointSize(JNIEnv *_env, jobject _this, jint vpr, jfloat v)
-{
-    RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
-    LOG_API("nProgramRasterSetPointSize, con(%p), vpf(%p), value(%f)", con, (RsProgramRaster)vpr, v);
-    rsProgramRasterSetPointSize(con, (RsProgramFragment)vpr, v);
+    LOG_API("nProgramRasterCreate, con(%p), pointSmooth(%i), lineSmooth(%i), pointSprite(%i)",
+            con, pointSmooth, lineSmooth, pointSprite);
+    return (jint)rsProgramRasterCreate(con, pointSmooth, lineSmooth, pointSprite);
 }
 
 static void
@@ -1193,7 +1293,15 @@
 {
     RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
     LOG_API("nProgramRasterSetLineWidth, con(%p), vpf(%p), value(%f)", con, (RsProgramRaster)vpr, v);
-    rsProgramRasterSetLineWidth(con, (RsProgramFragment)vpr, v);
+    rsProgramRasterSetLineWidth(con, (RsProgramRaster)vpr, v);
+}
+
+static void
+nProgramRasterSetCullMode(JNIEnv *_env, jobject _this, jint vpr, jint v)
+{
+    RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
+    LOG_API("nProgramRasterSetCullMode, con(%p), vpf(%p), value(%i)", con, (RsProgramRaster)vpr, v);
+    rsProgramRasterSetCullMode(con, (RsProgramRaster)vpr, (RsCullMode)v);
 }
 
 
@@ -1208,11 +1316,11 @@
 }
 
 static void
-nContextBindProgramFragmentStore(JNIEnv *_env, jobject _this, jint pfs)
+nContextBindProgramStore(JNIEnv *_env, jobject _this, jint pfs)
 {
     RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
-    LOG_API("nContextBindProgramFragmentStore, con(%p), pfs(%p)", con, (RsProgramFragmentStore)pfs);
-    rsContextBindProgramFragmentStore(con, (RsProgramFragmentStore)pfs);
+    LOG_API("nContextBindProgramStore, con(%p), pfs(%p)", con, (RsProgramStore)pfs);
+    rsContextBindProgramStore(con, (RsProgramStore)pfs);
 }
 
 static void
@@ -1319,32 +1427,84 @@
 // ---------------------------------------------------------------------------
 
 static jint
-nSimpleMeshCreate(JNIEnv *_env, jobject _this, jint batchID, jint indexID, jintArray vtxIDs, jint primID)
+nMeshCreate(JNIEnv *_env, jobject _this, jint vtxCount, jint idxCount)
 {
     RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
-    jint len = _env->GetArrayLength(vtxIDs);
-    LOG_API("nSimpleMeshCreate, con(%p), batchID(%i), indexID(%i), vtxIDs.len(%i), primID(%i)",
-            con, batchID, indexID, len, primID);
-    jint *ptr = _env->GetIntArrayElements(vtxIDs, NULL);
-    int id = (int)rsSimpleMeshCreate(con, (void *)batchID, (void *)indexID, (void **)ptr, len, primID);
-    _env->ReleaseIntArrayElements(vtxIDs, ptr, 0/*JNI_ABORT*/);
+    LOG_API("nMeshCreate, con(%p), vtxCount(%i), idxCount(%i)", con, vtxCount, idxCount);
+    int id = (int)rsMeshCreate(con, vtxCount, idxCount);
     return id;
 }
 
 static void
-nSimpleMeshBindVertex(JNIEnv *_env, jobject _this, jint s, jint alloc, jint slot)
+nMeshBindVertex(JNIEnv *_env, jobject _this, jint mesh, jint alloc, jint slot)
 {
     RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
-    LOG_API("nSimpleMeshBindVertex, con(%p), SimpleMesh(%p), Alloc(%p), slot(%i)", con, (RsSimpleMesh)s, (RsAllocation)alloc, slot);
-    rsSimpleMeshBindVertex(con, (RsSimpleMesh)s, (RsAllocation)alloc, slot);
+    LOG_API("nMeshBindVertex, con(%p), Mesh(%p), Alloc(%p), slot(%i)", con, (RsMesh)mesh, (RsAllocation)alloc, slot);
+    rsMeshBindVertex(con, (RsMesh)mesh, (RsAllocation)alloc, slot);
 }
 
 static void
-nSimpleMeshBindIndex(JNIEnv *_env, jobject _this, jint s, jint alloc)
+nMeshBindIndex(JNIEnv *_env, jobject _this, jint mesh, jint alloc, jint primID, jint slot)
 {
     RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
-    LOG_API("nSimpleMeshBindIndex, con(%p), SimpleMesh(%p), Alloc(%p)", con, (RsSimpleMesh)s, (RsAllocation)alloc);
-    rsSimpleMeshBindIndex(con, (RsSimpleMesh)s, (RsAllocation)alloc);
+    LOG_API("nMeshBindIndex, con(%p), Mesh(%p), Alloc(%p)", con, (RsMesh)mesh, (RsAllocation)alloc);
+    rsMeshBindIndex(con, (RsMesh)mesh, (RsAllocation)alloc, primID, slot);
+}
+
+static jint
+nMeshGetVertexBufferCount(JNIEnv *_env, jobject _this, jint mesh)
+{
+    RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
+    LOG_API("nMeshGetVertexBufferCount, con(%p), Mesh(%p)", con, (RsMesh)mesh);
+    jint vtxCount = 0;
+    rsMeshGetVertexBufferCount(con, (RsMesh)mesh, &vtxCount);
+    return vtxCount;
+}
+
+static jint
+nMeshGetIndexCount(JNIEnv *_env, jobject _this, jint mesh)
+{
+    RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
+    LOG_API("nMeshGetIndexCount, con(%p), Mesh(%p)", con, (RsMesh)mesh);
+    jint idxCount = 0;
+    rsMeshGetIndexCount(con, (RsMesh)mesh, &idxCount);
+    return idxCount;
+}
+
+static void
+nMeshGetVertices(JNIEnv *_env, jobject _this, jint mesh, jintArray _ids, int numVtxIDs)
+{
+    RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
+    LOG_API("nMeshGetVertices, con(%p), Mesh(%p)", con, (RsMesh)mesh);
+
+    RsAllocation *allocs = (RsAllocation*)malloc((uint32_t)numVtxIDs * sizeof(RsAllocation));
+    rsMeshGetVertices(con, (RsMesh)mesh, allocs, (uint32_t)numVtxIDs);
+
+    for(jint i = 0; i < numVtxIDs; i ++) {
+        _env->SetIntArrayRegion(_ids, i, 1, (const jint*)&allocs[i]);
+    }
+
+    free(allocs);
+}
+
+static void
+nMeshGetIndices(JNIEnv *_env, jobject _this, jint mesh, jintArray _idxIds, jintArray _primitives, int numIndices)
+{
+    RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
+    LOG_API("nMeshGetVertices, con(%p), Mesh(%p)", con, (RsMesh)mesh);
+
+    RsAllocation *allocs = (RsAllocation*)malloc((uint32_t)numIndices * sizeof(RsAllocation));
+    uint32_t *prims= (uint32_t*)malloc((uint32_t)numIndices * sizeof(uint32_t));
+
+    rsMeshGetIndices(con, (RsMesh)mesh, allocs, prims, (uint32_t)numIndices);
+
+    for(jint i = 0; i < numIndices; i ++) {
+        _env->SetIntArrayRegion(_idxIds, i, 1, (const jint*)&allocs[i]);
+        _env->SetIntArrayRegion(_primitives, i, 1, (const jint*)&prims[i]);
+    }
+
+    free(allocs);
+    free(prims);
 }
 
 // ---------------------------------------------------------------------------
@@ -1361,6 +1521,7 @@
 {"nDeviceSetConfig",               "(III)V",                               (void*)nDeviceSetConfig },
 {"nContextCreate",                 "(II)I",                                (void*)nContextCreate },
 {"nContextCreateGL",               "(IIZ)I",                               (void*)nContextCreateGL },
+{"nContextFinish",                 "()V",                                  (void*)nContextFinish },
 {"nContextSetPriority",            "(I)V",                                 (void*)nContextSetPriority },
 {"nContextSetSurface",             "(IILandroid/view/Surface;)V",          (void*)nContextSetSurface },
 {"nContextDestroy",                "(I)V",                                 (void*)nContextDestroy },
@@ -1368,6 +1529,7 @@
 {"nContextPause",                  "()V",                                  (void*)nContextPause },
 {"nContextResume",                 "()V",                                  (void*)nContextResume },
 {"nAssignName",                    "(I[B)V",                               (void*)nAssignName },
+{"nGetName",                       "(I)Ljava/lang/String;",               (void*)nGetName },
 {"nObjDestroy",                    "(I)V",                                 (void*)nObjDestroy },
 {"nObjDestroyOOB",                 "(I)V",                                 (void*)nObjDestroyOOB },
 {"nContextGetMessage",             "([IZ)I",                               (void*)nContextGetMessage },
@@ -1375,15 +1537,24 @@
 {"nContextDeinitToClient",         "()V",                                  (void*)nContextDeinitToClient },
 
 {"nFileOpen",                      "([B)I",                                (void*)nFileOpen },
+{"nFileA3DCreateFromAssetStream", "(I)I",                                 (void*)nFileA3DCreateFromAssetStream },
+{"nFileA3DGetNumIndexEntries",     "(I)I",                                 (void*)nFileA3DGetNumIndexEntries },
+{"nFileA3DGetIndexEntries",        "(II[I[Ljava/lang/String;)V",          (void*)nFileA3DGetIndexEntries },
+{"nFileA3DGetEntryByIndex",        "(II)I",                                (void*)nFileA3DGetEntryByIndex },
+
+{"nFontCreateFromFile",           "(Ljava/lang/String;II)I",             (void*)nFontCreateFromFile },
 
 {"nElementCreate",                 "(IIZI)I",                              (void*)nElementCreate },
 {"nElementCreate2",                "([I[Ljava/lang/String;)I",             (void*)nElementCreate2 },
+{"nElementGetNativeData",         "(I[I)V",                               (void*)nElementGetNativeData },
+{"nElementGetSubElements",        "(I[I[Ljava/lang/String;)V",           (void*)nElementGetSubElements },
 
 {"nTypeBegin",                     "(I)V",                                 (void*)nTypeBegin },
 {"nTypeAdd",                       "(II)V",                                (void*)nTypeAdd },
 {"nTypeCreate",                    "()I",                                  (void*)nTypeCreate },
 {"nTypeFinalDestroy",              "(Landroid/renderscript/Type;)V",       (void*)nTypeFinalDestroy },
 {"nTypeSetupFields",               "(Landroid/renderscript/Type;[I[I[Ljava/lang/reflect/Field;)V", (void*)nTypeSetupFields },
+{"nTypeGetNativeData",             "(I[I)V",                                (void*)nTypeGetNativeData },
 
 {"nAllocationCreateTyped",         "(I)I",                                 (void*)nAllocationCreateTyped },
 {"nAllocationCreateFromBitmap",    "(IZLandroid/graphics/Bitmap;)I",       (void*)nAllocationCreateFromBitmap },
@@ -1402,6 +1573,7 @@
 {"nAllocationRead",                "(I[F)V",                               (void*)nAllocationRead_f },
 {"nAllocationSubDataFromObject",   "(ILandroid/renderscript/Type;ILjava/lang/Object;)V",   (void*)nAllocationSubDataFromObject },
 {"nAllocationSubReadFromObject",   "(ILandroid/renderscript/Type;ILjava/lang/Object;)V",   (void*)nAllocationSubReadFromObject },
+{"nAllocationGetType",              "(I)I",                                 (void*)nAllocationGetType},
 
 {"nAdapter1DBindAllocation",       "(II)V",                                (void*)nAdapter1DBindAllocation },
 {"nAdapter1DSetConstraint",        "(III)V",                               (void*)nAdapter1DSetConstraint },
@@ -1420,28 +1592,24 @@
 {"nAdapter2DCreate",               "()I",                                  (void*)nAdapter2DCreate },
 
 {"nScriptBindAllocation",          "(III)V",                               (void*)nScriptBindAllocation },
-{"nScriptSetClearColor",           "(IFFFF)V",                             (void*)nScriptSetClearColor },
-{"nScriptSetClearDepth",           "(IF)V",                                (void*)nScriptSetClearDepth },
-{"nScriptSetClearStencil",         "(II)V",                                (void*)nScriptSetClearStencil },
 {"nScriptSetTimeZone",             "(I[B)V",                               (void*)nScriptSetTimeZone },
-{"nScriptSetType",                 "(IZLjava/lang/String;I)V",             (void*)nScriptSetType },
-{"nScriptSetRoot",                 "(Z)V",                                 (void*)nScriptSetRoot },
-{"nScriptSetInvokable",            "(Ljava/lang/String;I)V",               (void*)nScriptSetInvoke },
 {"nScriptInvoke",                  "(II)V",                                (void*)nScriptInvoke },
+{"nScriptInvokeV",                 "(II[B)V",                              (void*)nScriptInvokeV },
+{"nScriptSetVarI",                 "(III)V",                               (void*)nScriptSetVarI },
+{"nScriptSetVarF",                 "(IIF)V",                               (void*)nScriptSetVarF },
+{"nScriptSetVarV",                 "(II[B)V",                              (void*)nScriptSetVarV },
 
 {"nScriptCBegin",                  "()V",                                  (void*)nScriptCBegin },
 {"nScriptCSetScript",              "([BII)V",                              (void*)nScriptCSetScript },
 {"nScriptCCreate",                 "()I",                                  (void*)nScriptCCreate },
-{"nScriptCAddDefineI32",           "(Ljava/lang/String;I)V",               (void*)nScriptCAddDefineI32 },
-{"nScriptCAddDefineF",             "(Ljava/lang/String;F)V",               (void*)nScriptCAddDefineF },
 
-{"nProgramFragmentStoreBegin",     "(II)V",                                (void*)nProgramFragmentStoreBegin },
-{"nProgramFragmentStoreDepthFunc", "(I)V",                                 (void*)nProgramFragmentStoreDepthFunc },
-{"nProgramFragmentStoreDepthMask", "(Z)V",                                 (void*)nProgramFragmentStoreDepthMask },
-{"nProgramFragmentStoreColorMask", "(ZZZZ)V",                              (void*)nProgramFragmentStoreColorMask },
-{"nProgramFragmentStoreBlendFunc", "(II)V",                                (void*)nProgramFragmentStoreBlendFunc },
-{"nProgramFragmentStoreDither",    "(Z)V",                                 (void*)nProgramFragmentStoreDither },
-{"nProgramFragmentStoreCreate",    "()I",                                  (void*)nProgramFragmentStoreCreate },
+{"nProgramStoreBegin",             "(II)V",                                (void*)nProgramStoreBegin },
+{"nProgramStoreDepthFunc",         "(I)V",                                 (void*)nProgramStoreDepthFunc },
+{"nProgramStoreDepthMask",         "(Z)V",                                 (void*)nProgramStoreDepthMask },
+{"nProgramStoreColorMask",         "(ZZZZ)V",                              (void*)nProgramStoreColorMask },
+{"nProgramStoreBlendFunc",         "(II)V",                                (void*)nProgramStoreBlendFunc },
+{"nProgramStoreDither",            "(Z)V",                                 (void*)nProgramStoreDither },
+{"nProgramStoreCreate",            "()I",                                  (void*)nProgramStoreCreate },
 
 {"nProgramBindConstants",          "(III)V",                               (void*)nProgramBindConstants },
 {"nProgramBindTexture",            "(III)V",                               (void*)nProgramBindTexture },
@@ -1450,9 +1618,9 @@
 {"nProgramFragmentCreate",         "([I)I",                                (void*)nProgramFragmentCreate },
 {"nProgramFragmentCreate2",        "(Ljava/lang/String;[I)I",              (void*)nProgramFragmentCreate2 },
 
-{"nProgramRasterCreate",           "(IIZZZ)I",                             (void*)nProgramRasterCreate },
-{"nProgramRasterSetPointSize",     "(IF)V",                                (void*)nProgramRasterSetPointSize },
+{"nProgramRasterCreate",           "(ZZZ)I",                             (void*)nProgramRasterCreate },
 {"nProgramRasterSetLineWidth",     "(IF)V",                                (void*)nProgramRasterSetLineWidth },
+{"nProgramRasterSetCullMode",      "(II)V",                                (void*)nProgramRasterSetCullMode },
 
 {"nProgramVertexCreate",           "(Z)I",                                 (void*)nProgramVertexCreate },
 {"nProgramVertexCreate2",          "(Ljava/lang/String;[I)I",              (void*)nProgramVertexCreate2 },
@@ -1465,7 +1633,7 @@
 {"nLightSetPosition",              "(IFFF)V",                              (void*)nLightSetPosition },
 
 {"nContextBindRootScript",         "(I)V",                                 (void*)nContextBindRootScript },
-{"nContextBindProgramFragmentStore","(I)V",                                (void*)nContextBindProgramFragmentStore },
+{"nContextBindProgramStore",       "(I)V",                                (void*)nContextBindProgramStore },
 {"nContextBindProgramFragment",    "(I)V",                                 (void*)nContextBindProgramFragment },
 {"nContextBindProgramVertex",      "(I)V",                                 (void*)nContextBindProgramVertex },
 {"nContextBindProgramRaster",      "(I)V",                                 (void*)nContextBindProgramRaster },
@@ -1474,9 +1642,14 @@
 {"nSamplerSet",                    "(II)V",                                (void*)nSamplerSet },
 {"nSamplerCreate",                 "()I",                                  (void*)nSamplerCreate },
 
-{"nSimpleMeshCreate",              "(II[II)I",                             (void*)nSimpleMeshCreate },
-{"nSimpleMeshBindVertex",          "(III)V",                               (void*)nSimpleMeshBindVertex },
-{"nSimpleMeshBindIndex",           "(II)V",                                (void*)nSimpleMeshBindIndex },
+{"nMeshCreate",                    "(II)I",                                (void*)nMeshCreate },
+{"nMeshBindVertex",                "(III)V",                               (void*)nMeshBindVertex },
+{"nMeshBindIndex",                 "(IIII)V",                              (void*)nMeshBindIndex },
+
+{"nMeshGetVertexBufferCount",     "(I)I",                                 (void*)nMeshGetVertexBufferCount },
+{"nMeshGetIndexCount",             "(I)I",                                 (void*)nMeshGetIndexCount },
+{"nMeshGetVertices",               "(I[II)V",                             (void*)nMeshGetVertices },
+{"nMeshGetIndices",                "(I[I[II)V",                            (void*)nMeshGetIndices },
 
 };
 
@@ -1510,3 +1683,4 @@
 bail:
     return result;
 }
+
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/include/android_runtime/AndroidRuntime.h b/include/android_runtime/AndroidRuntime.h
index 09f0de1..22c9b72 100644
--- a/include/android_runtime/AndroidRuntime.h
+++ b/include/android_runtime/AndroidRuntime.h
@@ -30,7 +30,9 @@
 
 
 namespace android {
-    
+
+class CursorWindow;
+
 class AndroidRuntime
 {
 public:
@@ -122,6 +124,8 @@
 // Returns the Unix file descriptor for a ParcelFileDescriptor object
 extern int getParcelFileDescriptorFD(JNIEnv* env, jobject object);
 
+extern CursorWindow * get_window_from_object(JNIEnv * env, jobject javaWindow);
+
 }
 
 #endif
diff --git a/include/binder/CursorWindow.h b/include/binder/CursorWindow.h
new file mode 100644
index 0000000..4fbff2a
--- /dev/null
+++ b/include/binder/CursorWindow.h
@@ -0,0 +1,200 @@
+/*
+ * 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.
+ */
+
+#ifndef _ANDROID__DATABASE_WINDOW_H
+#define _ANDROID__DATABASE_WINDOW_H
+
+#include <cutils/log.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#include <binder/IMemory.h>
+#include <utils/RefBase.h>
+
+#define DEFAULT_WINDOW_SIZE 4096
+#define MAX_WINDOW_SIZE (1024 * 1024)
+#define WINDOW_ALLOCATION_SIZE 4096
+
+#define ROW_SLOT_CHUNK_NUM_ROWS 16
+
+// Row slots are allocated in chunks of ROW_SLOT_CHUNK_NUM_ROWS,
+// with an offset after the rows that points to the next chunk
+#define ROW_SLOT_CHUNK_SIZE ((ROW_SLOT_CHUNK_NUM_ROWS * sizeof(row_slot_t)) + sizeof(uint32_t))
+
+
+#if LOG_NDEBUG
+
+#define IF_LOG_WINDOW() if (false)
+#define LOG_WINDOW(...)
+
+#else
+
+#define IF_LOG_WINDOW() IF_LOG(LOG_DEBUG, "CursorWindow")
+#define LOG_WINDOW(...) LOG(LOG_DEBUG, "CursorWindow", __VA_ARGS__)
+
+#endif
+
+
+// When defined to true strings are stored as UTF8, otherwise they're UTF16
+#define WINDOW_STORAGE_UTF8 1
+
+// When defined to true numberic values are stored inline in the field_slot_t, otherwise they're allocated in the window
+#define WINDOW_STORAGE_INLINE_NUMERICS 1
+
+namespace android {
+
+typedef struct
+{
+    uint32_t numRows;
+    uint32_t numColumns;
+} window_header_t;
+
+typedef struct
+{
+    uint32_t offset;
+} row_slot_t;
+
+typedef struct
+{
+    uint8_t type;
+    union {
+        double d;
+        int64_t l;
+        struct {
+            uint32_t offset;
+            uint32_t size;
+        } buffer;
+    } data;
+} __attribute__((packed)) field_slot_t;
+
+#define FIELD_TYPE_NULL 0
+#define FIELD_TYPE_INTEGER 1
+#define FIELD_TYPE_FLOAT 2
+#define FIELD_TYPE_STRING 3
+#define FIELD_TYPE_BLOB 4
+
+/**
+ * This class stores a set of rows from a database in a buffer. The begining of the
+ * window has first chunk of row_slot_ts, which are offsets to the row directory, followed by
+ * an offset to the next chunk in a linked-list of additional chunk of row_slot_ts in case
+ * the pre-allocated chunk isn't big enough to refer to all rows. Each row directory has a
+ * field_slot_t per column, which has the size, offset, and type of the data for that field.
+ * Note that the data types come from sqlite3.h.
+ */
+class CursorWindow
+{
+public:
+                        CursorWindow(size_t maxSize);
+                        CursorWindow(){}
+    bool                setMemory(const sp<IMemory>&);
+                        ~CursorWindow();
+
+    bool                initBuffer(bool localOnly);
+    sp<IMemory>         getMemory() {return mMemory;}
+
+    size_t              size() {return mSize;}
+    uint8_t *           data() {return mData;}
+    uint32_t            getNumRows() {return mHeader->numRows;}
+    uint32_t            getNumColumns() {return mHeader->numColumns;}
+    void                freeLastRow() {
+                            if (mHeader->numRows > 0) {
+                                mHeader->numRows--;
+                            }
+                        }
+    bool                setNumColumns(uint32_t numColumns)
+                            {
+                                uint32_t cur = mHeader->numColumns;
+                                if (cur > 0 && cur != numColumns) {
+                                    LOGE("Trying to go from %d columns to %d", cur, numColumns);
+                                    return false;
+                                }
+                                mHeader->numColumns = numColumns;
+                                return true;
+                            }
+
+    int32_t             freeSpace();
+
+    void                clear();
+
+                        /**
+                         * Allocate a row slot and its directory. The returned
+                         * pointer points to the begining of the row's directory
+                         * or NULL if there wasn't room. The directory is
+                         * initialied with NULL entries for each field.
+                         */
+    field_slot_t *      allocRow();
+
+                        /**
+                         * Allocate a portion of the window. Returns the offset
+                         * of the allocation, or 0 if there isn't enough space.
+                         * If aligned is true, the allocation gets 4 byte alignment.
+                         */
+    uint32_t            alloc(size_t size, bool aligned = false);
+
+    uint32_t            read_field_slot(int row, int column, field_slot_t * slot);
+
+                        /**
+                         * Copy data into the window at the given offset.
+                         */
+    void                copyIn(uint32_t offset, uint8_t const * data, size_t size);
+    void                copyIn(uint32_t offset, int64_t data);
+    void                copyIn(uint32_t offset, double data);
+
+    void                copyOut(uint32_t offset, uint8_t * data, size_t size);
+    int64_t             copyOutLong(uint32_t offset);
+    double              copyOutDouble(uint32_t offset);
+
+    bool                putLong(unsigned int row, unsigned int col, int64_t value);
+    bool                putDouble(unsigned int row, unsigned int col, double value);
+    bool                putNull(unsigned int row, unsigned int col);
+
+    bool                getLong(unsigned int row, unsigned int col, int64_t * valueOut);
+    bool                getDouble(unsigned int row, unsigned int col, double * valueOut);
+    bool                getNull(unsigned int row, unsigned int col, bool * valueOut);
+
+    uint8_t *           offsetToPtr(uint32_t offset) {return mData + offset;}
+
+    row_slot_t *        allocRowSlot();
+
+    row_slot_t *        getRowSlot(int row);
+
+                        /**
+                         * return NULL if Failed to find rowSlot or
+                         * Invalid rowSlot
+                         */
+    field_slot_t *      getFieldSlotWithCheck(int row, int column);
+    field_slot_t *      getFieldSlot(int row, int column)
+                            {
+                                int fieldDirOffset = getRowSlot(row)->offset;
+                                return ((field_slot_t *)offsetToPtr(fieldDirOffset)) + column;
+                            }
+
+private:
+    uint8_t * mData;
+    size_t mSize;
+    size_t mMaxSize;
+    window_header_t * mHeader;
+    sp<IMemory> mMemory;
+
+    /**
+     * Offset of the lowest unused data byte in the array.
+     */
+    uint32_t mFreeOffset;
+};
+
+}; // namespace android
+
+#endif
diff --git a/include/camera/CameraParameters.h b/include/camera/CameraParameters.h
index e12a694..b2808f5 100644
--- a/include/camera/CameraParameters.h
+++ b/include/camera/CameraParameters.h
@@ -22,6 +22,21 @@
 
 namespace android {
 
+struct Size {
+    int width;
+    int height;
+
+    Size() {
+        width = 0;
+        height = 0;
+    }
+
+    Size(int w, int h) {
+        width = w;
+        height = h;
+    }
+};
+
 class CameraParameters
 {
 public:
@@ -43,12 +58,14 @@
 
     void setPreviewSize(int width, int height);
     void getPreviewSize(int *width, int *height) const;
+    void getSupportedPreviewSizes(Vector<Size> &sizes) const;
     void setPreviewFrameRate(int fps);
     int getPreviewFrameRate() const;
     void setPreviewFormat(const char *format);
     const char *getPreviewFormat() const;
     void setPictureSize(int width, int height);
     void getPictureSize(int *width, int *height) const;
+    void getSupportedPictureSizes(Vector<Size> &sizes) const;
     void setPictureFormat(const char *format);
     const char *getPictureFormat() const;
 
diff --git a/include/media/stagefright/CameraSource.h b/include/media/stagefright/CameraSource.h
index 3192d03..ed5f09f 100644
--- a/include/media/stagefright/CameraSource.h
+++ b/include/media/stagefright/CameraSource.h
@@ -22,7 +22,6 @@
 #include <media/stagefright/MediaSource.h>
 #include <utils/List.h>
 #include <utils/RefBase.h>
-#include <utils/threads.h>
 
 namespace android {
 
@@ -47,12 +46,34 @@
 
     virtual void signalBufferReturned(MediaBuffer* buffer);
 
-private:
-    friend class CameraSourceListener;
-
+protected:
     sp<Camera> mCamera;
     sp<MetaData> mMeta;
 
+    int64_t mStartTimeUs;
+    int32_t mNumFramesReceived;
+    int64_t mLastFrameTimestampUs;
+    bool mStarted;
+
+    CameraSource(const sp<Camera> &camera);
+
+    virtual void startCameraRecording();
+    virtual void stopCameraRecording();
+    virtual void releaseRecordingFrame(const sp<IMemory>& frame);
+
+    // Returns true if need to skip the current frame.
+    // Called from dataCallbackTimestamp.
+    virtual bool skipCurrentFrame(int64_t timestampUs) {return false;}
+
+    // Callback called when still camera raw data is available.
+    virtual void dataCallback(int32_t msgType, const sp<IMemory> &data) {}
+
+    virtual void dataCallbackTimestamp(int64_t timestampUs, int32_t msgType,
+            const sp<IMemory> &data);
+
+private:
+    friend class CameraSourceListener;
+
     Mutex mLock;
     Condition mFrameAvailableCondition;
     Condition mFrameCompleteCondition;
@@ -60,21 +81,12 @@
     List<sp<IMemory> > mFramesBeingEncoded;
     List<int64_t> mFrameTimes;
 
-    int64_t mStartTimeUs;
     int64_t mFirstFrameTimeUs;
-    int64_t mLastFrameTimestampUs;
-    int32_t mNumFramesReceived;
     int32_t mNumFramesEncoded;
     int32_t mNumFramesDropped;
     int32_t mNumGlitches;
     int64_t mGlitchDurationThresholdUs;
     bool mCollectStats;
-    bool mStarted;
-
-    CameraSource(const sp<Camera> &camera);
-
-    void dataCallbackTimestamp(
-            int64_t timestampUs, int32_t msgType, const sp<IMemory> &data);
 
     void releaseQueuedFrames();
     void releaseOneRecordingFrame(const sp<IMemory>& frame);
diff --git a/include/media/stagefright/CameraSourceTimeLapse.h b/include/media/stagefright/CameraSourceTimeLapse.h
new file mode 100644
index 0000000..7135a33
--- /dev/null
+++ b/include/media/stagefright/CameraSourceTimeLapse.h
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef CAMERA_SOURCE_TIME_LAPSE_H_
+
+#define CAMERA_SOURCE_TIME_LAPSE_H_
+
+#include <pthread.h>
+
+#include <utils/RefBase.h>
+#include <utils/threads.h>
+
+namespace android {
+
+class ICamera;
+class IMemory;
+class Camera;
+
+class CameraSourceTimeLapse : public CameraSource {
+public:
+    static CameraSourceTimeLapse *Create(bool useStillCameraForTimeLapse,
+        int64_t timeBetweenTimeLapseFrameCaptureUs,
+        int32_t width, int32_t height,
+        int32_t videoFrameRate);
+
+    static CameraSourceTimeLapse *CreateFromCamera(const sp<Camera> &camera,
+        bool useStillCameraForTimeLapse,
+        int64_t timeBetweenTimeLapseFrameCaptureUs,
+        int32_t width, int32_t height,
+        int32_t videoFrameRate);
+
+    virtual ~CameraSourceTimeLapse();
+
+private:
+    // If true, will use still camera takePicture() for time lapse frames
+    // If false, will use the videocamera frames instead.
+    bool mUseStillCameraForTimeLapse;
+
+    // Size of picture taken from still camera. This may be larger than the size
+    // of the video, as still camera may not support the exact video resolution
+    // demanded. See setPictureSizeToClosestSupported().
+    int32_t mPictureWidth;
+    int32_t mPictureHeight;
+
+    // size of the encoded video.
+    int32_t mVideoWidth;
+    int32_t mVideoHeight;
+
+    // True if we need to crop the still camera image to get the video frame.
+    bool mNeedCropping;
+
+    // Start location of the cropping rectangle.
+    int32_t mCropRectStartX;
+    int32_t mCropRectStartY;
+
+    // Time between capture of two frames during time lapse recording
+    // Negative value indicates that timelapse is disabled.
+    int64_t mTimeBetweenTimeLapseFrameCaptureUs;
+
+    // Time between two frames in final video (1/frameRate)
+    int64_t mTimeBetweenTimeLapseVideoFramesUs;
+
+    // Real timestamp of the last encoded time lapse frame
+    int64_t mLastTimeLapseFrameRealTimestampUs;
+
+    // Thread id of thread which takes still picture and sleeps in a loop.
+    pthread_t mThreadTimeLapse;
+
+    // Variable set in dataCallbackTimestamp() to help skipCurrentFrame()
+    // to know if current frame needs to be skipped.
+    bool mSkipCurrentFrame;
+
+    // True if camera is in preview mode and ready for takePicture().
+    bool mCameraIdle;
+
+    CameraSourceTimeLapse(const sp<Camera> &camera,
+        bool useStillCameraForTimeLapse,
+        int64_t timeBetweenTimeLapseFrameCaptureUs,
+        int32_t width, int32_t height,
+        int32_t videoFrameRate);
+
+    // For still camera case starts a thread which calls camera's takePicture()
+    // in a loop. For video camera case, just starts the camera's video recording.
+    virtual void startCameraRecording();
+
+    // For still camera case joins the thread created in startCameraRecording().
+    // For video camera case, just stops the camera's video recording.
+    virtual void stopCameraRecording();
+
+    // For still camera case don't need to do anything as memory is locally
+    // allocated with refcounting.
+    // For video camera case just tell the camera to release the frame.
+    virtual void releaseRecordingFrame(const sp<IMemory>& frame);
+
+    // mSkipCurrentFrame is set to true in dataCallbackTimestamp() if the current
+    // frame needs to be skipped and this function just returns the value of mSkipCurrentFrame.
+    virtual bool skipCurrentFrame(int64_t timestampUs);
+
+    // Handles the callback to handle raw frame data from the still camera.
+    // Creates a copy of the frame data as the camera can reuse the frame memory
+    // once this callback returns. The function also sets a new timstamp corresponding
+    // to one frame time ahead of the last encoded frame's time stamp. It then
+    // calls dataCallbackTimestamp() of the base class with the copied data and the
+    // modified timestamp, which will think that it recieved the frame from a video
+    // camera and proceed as usual.
+    virtual void dataCallback(int32_t msgType, const sp<IMemory> &data);
+
+    // In the video camera case calls skipFrameAndModifyTimeStamp() to modify
+    // timestamp and set mSkipCurrentFrame.
+    // Then it calls the base CameraSource::dataCallbackTimestamp()
+    virtual void dataCallbackTimestamp(int64_t timestampUs, int32_t msgType,
+            const sp<IMemory> &data);
+
+    // The still camera may not support the demanded video width and height.
+    // We look for the supported picture sizes from the still camera and
+    // choose the smallest one with either dimensions higher than the corresponding
+    // video dimensions. The still picture will be cropped to get the video frame.
+    // The function returns true if the camera supports picture sizes greater than
+    // or equal to the passed in width and height, and false otherwise.
+    bool setPictureSizeToClosestSupported(int32_t width, int32_t height);
+
+    // Computes the offset of the rectangle from where to start cropping the
+    // still image into the video frame. We choose the center of the image to be
+    // cropped. The offset is stored in (mCropRectStartX, mCropRectStartY).
+    bool computeCropRectangleOffset();
+
+    // Crops the source data into a smaller image starting at
+    // (mCropRectStartX, mCropRectStartY) and of the size of the video frame.
+    // The data is returned into a newly allocated IMemory.
+    sp<IMemory> cropYUVImage(const sp<IMemory> &source_data);
+
+    // When video camera is used for time lapse capture, returns true
+    // until enough time has passed for the next time lapse frame. When
+    // the frame needs to be encoded, it returns false and also modifies
+    // the time stamp to be one frame time ahead of the last encoded
+    // frame's time stamp.
+    bool skipFrameAndModifyTimeStamp(int64_t *timestampUs);
+
+    // Wrapper to enter threadTimeLapseEntry()
+    static void *ThreadTimeLapseWrapper(void *me);
+
+    // Runs a loop which sleeps until a still picture is required
+    // and then calls mCamera->takePicture() to take the still picture.
+    // Used only in the case mUseStillCameraForTimeLapse = true.
+    void threadTimeLapseEntry();
+
+    // Wrapper to enter threadStartPreview()
+    static void *ThreadStartPreviewWrapper(void *me);
+
+    // Starts the camera's preview.
+    void threadStartPreview();
+
+    // Starts thread ThreadStartPreviewWrapper() for restarting preview.
+    // Needs to be done in a thread so that dataCallback() which calls this function
+    // can return, and the camera can know that takePicture() is done.
+    void restartPreview();
+
+    // Creates a copy of source_data into a new memory of final type MemoryBase.
+    sp<IMemory> createIMemoryCopy(const sp<IMemory> &source_data);
+
+    CameraSourceTimeLapse(const CameraSourceTimeLapse &);
+    CameraSourceTimeLapse &operator=(const CameraSourceTimeLapse &);
+};
+
+}  // namespace android
+
+#endif  // CAMERA_SOURCE_TIME_LAPSE_H_
diff --git a/include/media/stagefright/YUVCanvas.h b/include/media/stagefright/YUVCanvas.h
new file mode 100644
index 0000000..5e17046
--- /dev/null
+++ b/include/media/stagefright/YUVCanvas.h
@@ -0,0 +1,67 @@
+/*
+ * 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.
+ */
+
+// YUVCanvas holds a reference to a YUVImage on which it can do various
+// drawing operations. It provides various utility functions for filling,
+// cropping, etc.
+
+
+#ifndef YUV_CANVAS_H_
+
+#define YUV_CANVAS_H_
+
+#include <stdint.h>
+
+namespace android {
+
+class YUVImage;
+class Rect;
+
+class YUVCanvas {
+public:
+
+    // Constructor takes in reference to a yuvImage on which it can do
+    // various drawing opreations.
+    YUVCanvas(YUVImage &yuvImage);
+    ~YUVCanvas();
+
+    // Fills the entire image with the given YUV values.
+    void FillYUV(uint8_t yValue, uint8_t uValue, uint8_t vValue);
+
+    // Fills the rectangular region [startX,endX]x[startY,endY] with the given YUV values.
+    void FillYUVRectangle(const Rect& rect,
+            uint8_t yValue, uint8_t uValue, uint8_t vValue);
+
+    // Copies the region [startX,endX]x[startY,endY] from srcImage into the
+    // canvas' target image (mYUVImage) starting at
+    // (destinationStartX,destinationStartY).
+    // Note that undefined behavior may occur if srcImage is same as the canvas'
+    // target image.
+    void CopyImageRect(
+            const Rect& srcRect,
+            int32_t destStartX, int32_t destStartY,
+            const YUVImage &srcImage);
+
+private:
+    YUVImage& mYUVImage;
+
+    YUVCanvas(const YUVCanvas &);
+    YUVCanvas &operator=(const YUVCanvas &);
+};
+
+}  // namespace android
+
+#endif  // YUV_CANVAS_H_
diff --git a/include/media/stagefright/YUVImage.h b/include/media/stagefright/YUVImage.h
new file mode 100644
index 0000000..afeac3f
--- /dev/null
+++ b/include/media/stagefright/YUVImage.h
@@ -0,0 +1,174 @@
+/*
+ * 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.
+ */
+
+// A container class to hold YUV data and provide various utilities,
+// e.g. to set/get pixel values.
+// Supported formats:
+//  - YUV420 Planar
+//  - YUV420 Semi Planar
+//
+//  Currently does not support variable strides.
+//
+//  Implementation: Two simple abstractions are done to simplify access
+//  to YUV channels for different formats:
+//  - initializeYUVPointers() sets up pointers (mYdata, mUdata, mVdata) to
+//  point to the right start locations of the different channel data depending
+//  on the format.
+//  - getOffsets() returns the correct offset for the different channels
+//  depending on the format.
+//  Location of any pixel's YUV channels can then be easily computed using these.
+//
+
+#ifndef YUV_IMAGE_H_
+
+#define YUV_IMAGE_H_
+
+#include <stdint.h>
+#include <cstring>
+
+namespace android {
+
+class Rect;
+
+class YUVImage {
+public:
+    // Supported YUV formats
+    enum YUVFormat {
+        YUV420Planar,
+        YUV420SemiPlanar
+    };
+
+    // Constructs an image with the given size, format. Also allocates and owns
+    // the required memory.
+    YUVImage(YUVFormat yuvFormat, int32_t width, int32_t height);
+
+    // Constructs an image with the given size, format. The memory is provided
+    // by the caller and we don't own it.
+    YUVImage(YUVFormat yuvFormat, int32_t width, int32_t height, uint8_t *buffer);
+
+    // Destructor to delete the memory if it owns it.
+    ~YUVImage();
+
+    // Returns the size of the buffer required to store the YUV data for the given
+    // format and geometry. Useful when the caller wants to allocate the requisite
+    // memory.
+    static size_t bufferSize(YUVFormat yuvFormat, int32_t width, int32_t height);
+
+    int32_t width() {return mWidth;}
+    int32_t height() {return mHeight;}
+
+    // Get the pixel YUV value at pixel (x,y).
+    // Note that the range of x is [0, width-1] and the range of y is [0, height-1].
+    // Returns true if get was succesful and false otherwise.
+    bool getPixelValue(int32_t x, int32_t y,
+            uint8_t *yPtr, uint8_t *uPtr, uint8_t *vPtr) const;
+
+    // Set the pixel YUV value at pixel (x,y).
+    // Note that the range of x is [0, width-1] and the range of y is [0, height-1].
+    // Returns true if set was succesful and false otherwise.
+    bool setPixelValue(int32_t x, int32_t y,
+            uint8_t yValue, uint8_t uValue, uint8_t vValue);
+
+    // Uses memcpy to copy an entire row of data
+    static void fastCopyRectangle420Planar(
+            const Rect& srcRect,
+            int32_t destStartX, int32_t destStartY,
+            const YUVImage &srcImage, YUVImage &destImage);
+
+    // Uses memcpy to copy an entire row of data
+    static void fastCopyRectangle420SemiPlanar(
+            const Rect& srcRect,
+            int32_t destStartX, int32_t destStartY,
+            const YUVImage &srcImage, YUVImage &destImage);
+
+    // Tries to use memcopy to copy entire rows of data.
+    // Returns false if fast copy is not possible for the passed image formats.
+    static bool fastCopyRectangle(
+            const Rect& srcRect,
+            int32_t destStartX, int32_t destStartY,
+            const YUVImage &srcImage, YUVImage &destImage);
+
+    // Convert the given YUV value to RGB.
+    void yuv2rgb(uint8_t yValue, uint8_t uValue, uint8_t vValue,
+        uint8_t *r, uint8_t *g, uint8_t *b) const;
+
+    // Write the image to a human readable PPM file.
+    // Returns true if write was succesful and false otherwise.
+    bool writeToPPM(const char *filename) const;
+
+private:
+    // YUV Format of the image.
+    YUVFormat mYUVFormat;
+
+    int32_t mWidth;
+    int32_t mHeight;
+
+    // Pointer to the memory buffer.
+    uint8_t *mBuffer;
+
+    // Boolean telling whether we own the memory buffer.
+    bool mOwnBuffer;
+
+    // Pointer to start of the Y data plane.
+    uint8_t *mYdata;
+
+    // Pointer to start of the U data plane. Note that in case of interleaved formats like
+    // YUV420 semiplanar, mUdata points to the start of the U data in the UV plane.
+    uint8_t *mUdata;
+
+    // Pointer to start of the V data plane. Note that in case of interleaved formats like
+    // YUV420 semiplanar, mVdata points to the start of the V data in the UV plane.
+    uint8_t *mVdata;
+
+    // Initialize the pointers mYdata, mUdata, mVdata to point to the right locations for
+    // the given format and geometry.
+    // Returns true if initialize was succesful and false otherwise.
+    bool initializeYUVPointers();
+
+    // For the given pixel location, this returns the offset of the location of y, u and v
+    // data from the corresponding base pointers -- mYdata, mUdata, mVdata.
+    // Note that the range of x is [0, width-1] and the range of y is [0, height-1].
+    // Returns true if getting offsets was succesful and false otherwise.
+    bool getOffsets(int32_t x, int32_t y,
+        int32_t *yOffset, int32_t *uOffset, int32_t *vOffset) const;
+
+    // Returns the offset increments incurred in going from one data row to the next data row
+    // for the YUV channels. Note that this corresponds to data rows and not pixel rows.
+    // E.g. depending on formats, U/V channels may have only one data row corresponding
+    // to two pixel rows.
+    bool getOffsetIncrementsPerDataRow(
+        int32_t *yDataOffsetIncrement,
+        int32_t *uDataOffsetIncrement,
+        int32_t *vDataOffsetIncrement) const;
+
+    // Given the offset return the address of the corresponding channel's data.
+    uint8_t* getYAddress(int32_t offset) const;
+    uint8_t* getUAddress(int32_t offset) const;
+    uint8_t* getVAddress(int32_t offset) const;
+
+    // Given the pixel location, returns the address of the corresponding channel's data.
+    // Note that the range of x is [0, width-1] and the range of y is [0, height-1].
+    bool getYUVAddresses(int32_t x, int32_t y,
+        uint8_t **yAddr, uint8_t **uAddr, uint8_t **vAddr) const;
+
+    // Disallow implicit casting and copying.
+    YUVImage(const YUVImage &);
+    YUVImage &operator=(const YUVImage &);
+};
+
+}  // namespace android
+
+#endif  // YUV_IMAGE_H_
diff --git a/libs/binder/Android.mk b/libs/binder/Android.mk
index 13dc500..f9d9f25 100644
--- a/libs/binder/Android.mk
+++ b/libs/binder/Android.mk
@@ -16,6 +16,7 @@
 sources := \
     Binder.cpp \
     BpBinder.cpp \
+    CursorWindow.cpp \
     IInterface.cpp \
     IMemory.cpp \
     IPCThreadState.cpp \
diff --git a/libs/binder/CursorWindow.cpp b/libs/binder/CursorWindow.cpp
new file mode 100644
index 0000000..20b27c9
--- /dev/null
+++ b/libs/binder/CursorWindow.cpp
@@ -0,0 +1,408 @@
+/*
+ * Copyright (C) 2006-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.
+ */
+
+#undef LOG_TAG
+#define LOG_TAG "CursorWindow"
+
+#include <utils/Log.h>
+#include <binder/CursorWindow.h>
+#include <binder/MemoryHeapBase.h>
+#include <binder/MemoryBase.h>
+
+#include <assert.h>
+#include <string.h>
+#include <stdlib.h>
+
+namespace android {
+
+CursorWindow::CursorWindow(size_t maxSize) :
+    mMaxSize(maxSize)
+{
+}
+
+bool CursorWindow::setMemory(const sp<IMemory>& memory)
+{
+    mMemory = memory;
+    mData = (uint8_t *) memory->pointer();
+    if (mData == NULL) {
+        return false;
+    }
+    mHeader = (window_header_t *) mData;
+
+    // Make the window read-only
+    ssize_t size = memory->size();
+    mSize = size;
+    mMaxSize = size;
+    mFreeOffset = size;
+LOG_WINDOW("Created CursorWindow from existing IMemory: mFreeOffset = %d, numRows = %d, numColumns = %d, mSize = %d, mMaxSize = %d, mData = %p", mFreeOffset, mHeader->numRows, mHeader->numColumns, mSize, mMaxSize, mData);
+    return true;
+}
+
+bool CursorWindow::initBuffer(bool localOnly)
+{
+    //TODO Use a non-memory dealer mmap region for localOnly
+
+    sp<MemoryHeapBase> heap;
+    heap = new MemoryHeapBase(mMaxSize, 0, "CursorWindow");
+    if (heap != NULL) {
+        mMemory = new MemoryBase(heap, 0, mMaxSize);
+        if (mMemory != NULL) {
+            mData = (uint8_t *) mMemory->pointer();
+            if (mData) {
+                mHeader = (window_header_t *) mData;
+                mSize = mMaxSize;
+
+                // Put the window into a clean state
+                clear();
+            LOG_WINDOW("Created CursorWindow with new MemoryDealer: mFreeOffset = %d, mSize = %d, mMaxSize = %d, mData = %p", mFreeOffset, mSize, mMaxSize, mData);
+                return true;                
+            }
+        } 
+        LOGE("CursorWindow heap allocation failed");
+        return false;
+    } else {
+        LOGE("failed to create the CursorWindow heap");
+        return false;
+    }
+}
+
+CursorWindow::~CursorWindow()
+{
+    // Everything that matters is a smart pointer
+}
+
+void CursorWindow::clear()
+{
+    mHeader->numRows = 0;
+    mHeader->numColumns = 0;
+    mFreeOffset = sizeof(window_header_t) + ROW_SLOT_CHUNK_SIZE;
+    // Mark the first chunk's next 'pointer' as null
+    *((uint32_t *)(mData + mFreeOffset - sizeof(uint32_t))) = 0;
+}
+
+int32_t CursorWindow::freeSpace()
+{
+    int32_t freeSpace = mSize - mFreeOffset;
+    if (freeSpace < 0) {
+        freeSpace = 0;
+    }
+    return freeSpace;
+}
+
+field_slot_t * CursorWindow::allocRow()
+{
+    // Fill in the row slot
+    row_slot_t * rowSlot = allocRowSlot();
+    if (rowSlot == NULL) {
+        return NULL;
+    }
+
+    // Allocate the slots for the field directory
+    size_t fieldDirSize = mHeader->numColumns * sizeof(field_slot_t);
+    uint32_t fieldDirOffset = alloc(fieldDirSize);
+    if (!fieldDirOffset) {
+        mHeader->numRows--;
+        LOGE("The row failed, so back out the new row accounting from allocRowSlot %d", mHeader->numRows);
+        return NULL;
+    }
+    field_slot_t * fieldDir = (field_slot_t *)offsetToPtr(fieldDirOffset);
+    memset(fieldDir, 0x0, fieldDirSize);
+
+LOG_WINDOW("Allocated row %u, rowSlot is at offset %u, fieldDir is %d bytes at offset %u\n", (mHeader->numRows - 1), ((uint8_t *)rowSlot) - mData, fieldDirSize, fieldDirOffset);
+    rowSlot->offset = fieldDirOffset;
+
+    return fieldDir;
+}
+
+uint32_t CursorWindow::alloc(size_t requestedSize, bool aligned)
+{
+    int32_t size;
+    uint32_t padding;
+    if (aligned) {
+        // 4 byte alignment
+        padding = 4 - (mFreeOffset & 0x3);
+    } else {
+        padding = 0;
+    }
+
+    size = requestedSize + padding;
+
+    if (size > freeSpace()) {
+        LOGE("need to grow: mSize = %d, size = %d, freeSpace() = %d, numRows = %d", mSize, size, freeSpace(), mHeader->numRows);
+        // Only grow the window if the first row doesn't fit
+        if (mHeader->numRows > 1) {
+LOGE("not growing since there are already %d row(s), max size %d", mHeader->numRows, mMaxSize);
+            return 0;
+        }
+
+        // Find a new size that will fit the allocation
+        int allocated = mSize - freeSpace();
+        int newSize = mSize + WINDOW_ALLOCATION_SIZE;
+        while (size > (newSize - allocated)) {
+            newSize += WINDOW_ALLOCATION_SIZE;
+            if (newSize > mMaxSize) {
+                LOGE("Attempting to grow window beyond max size (%d)", mMaxSize);
+                return 0;
+            }
+        }
+LOG_WINDOW("found size %d", newSize);
+        mSize = newSize;
+    }
+
+    uint32_t offset = mFreeOffset + padding;
+    mFreeOffset += size;
+    return offset;
+}
+
+row_slot_t * CursorWindow::getRowSlot(int row)
+{
+    LOG_WINDOW("enter getRowSlot current row num %d, this row %d", mHeader->numRows, row);
+    int chunkNum = row / ROW_SLOT_CHUNK_NUM_ROWS;
+    int chunkPos = row % ROW_SLOT_CHUNK_NUM_ROWS;
+    int chunkPtrOffset = sizeof(window_header_t) + ROW_SLOT_CHUNK_SIZE - sizeof(uint32_t);
+    uint8_t * rowChunk = mData + sizeof(window_header_t);
+    for (int i = 0; i < chunkNum; i++) {
+        rowChunk = offsetToPtr(*((uint32_t *)(mData + chunkPtrOffset)));
+        chunkPtrOffset = rowChunk - mData + (ROW_SLOT_CHUNK_NUM_ROWS * sizeof(row_slot_t));
+    }
+    return (row_slot_t *)(rowChunk + (chunkPos * sizeof(row_slot_t)));
+    LOG_WINDOW("exit getRowSlot current row num %d, this row %d", mHeader->numRows, row);    
+}
+
+row_slot_t * CursorWindow::allocRowSlot()
+{
+    int chunkNum = mHeader->numRows / ROW_SLOT_CHUNK_NUM_ROWS;
+    int chunkPos = mHeader->numRows % ROW_SLOT_CHUNK_NUM_ROWS;
+    int chunkPtrOffset = sizeof(window_header_t) + ROW_SLOT_CHUNK_SIZE - sizeof(uint32_t);
+    uint8_t * rowChunk = mData + sizeof(window_header_t);
+LOG_WINDOW("Allocating row slot, mHeader->numRows is %d, chunkNum is %d, chunkPos is %d", mHeader->numRows, chunkNum, chunkPos);
+    for (int i = 0; i < chunkNum; i++) {
+        uint32_t nextChunkOffset = *((uint32_t *)(mData + chunkPtrOffset));
+LOG_WINDOW("nextChunkOffset is %d", nextChunkOffset);
+        if (nextChunkOffset == 0) {
+            // Allocate a new row chunk
+            nextChunkOffset = alloc(ROW_SLOT_CHUNK_SIZE, true);
+            if (nextChunkOffset == 0) {
+                return NULL;
+            }
+            rowChunk = offsetToPtr(nextChunkOffset);
+LOG_WINDOW("allocated new chunk at %d, rowChunk = %p", nextChunkOffset, rowChunk);
+            *((uint32_t *)(mData + chunkPtrOffset)) = rowChunk - mData;
+            // Mark the new chunk's next 'pointer' as null
+            *((uint32_t *)(rowChunk + ROW_SLOT_CHUNK_SIZE - sizeof(uint32_t))) = 0;
+        } else {
+LOG_WINDOW("follwing 'pointer' to next chunk, offset of next pointer is %d", chunkPtrOffset);
+            rowChunk = offsetToPtr(nextChunkOffset);
+            chunkPtrOffset = rowChunk - mData + (ROW_SLOT_CHUNK_NUM_ROWS * sizeof(row_slot_t));
+        }
+    }
+    mHeader->numRows++;
+
+    return (row_slot_t *)(rowChunk + (chunkPos * sizeof(row_slot_t)));
+}
+
+field_slot_t * CursorWindow::getFieldSlotWithCheck(int row, int column)
+{
+  if (row < 0 || row >= mHeader->numRows || column < 0 || column >= mHeader->numColumns) {
+      LOGE("Bad request for field slot %d,%d. numRows = %d, numColumns = %d", row, column, mHeader->numRows, mHeader->numColumns);
+      return NULL;
+  }        
+  row_slot_t * rowSlot = getRowSlot(row);
+  if (!rowSlot) {
+      LOGE("Failed to find rowSlot for row %d", row);
+      return NULL;
+  }
+  if (rowSlot->offset == 0 || rowSlot->offset >= mSize) {
+      LOGE("Invalid rowSlot, offset = %d", rowSlot->offset);
+      return NULL;
+  }  
+  int fieldDirOffset = rowSlot->offset;
+  return ((field_slot_t *)offsetToPtr(fieldDirOffset)) + column;  
+}
+
+uint32_t CursorWindow::read_field_slot(int row, int column, field_slot_t * slotOut)
+{
+    if (row < 0 || row >= mHeader->numRows || column < 0 || column >= mHeader->numColumns) {
+        LOGE("Bad request for field slot %d,%d. numRows = %d, numColumns = %d", row, column, mHeader->numRows, mHeader->numColumns);
+        return -1;
+    }        
+    row_slot_t * rowSlot = getRowSlot(row);
+    if (!rowSlot) {
+        LOGE("Failed to find rowSlot for row %d", row);
+        return -1;
+    }
+    if (rowSlot->offset == 0 || rowSlot->offset >= mSize) {
+        LOGE("Invalid rowSlot, offset = %d", rowSlot->offset);
+        return -1;
+    }
+LOG_WINDOW("Found field directory for %d,%d at rowSlot %d, offset %d", row, column, (uint8_t *)rowSlot - mData, rowSlot->offset);
+    field_slot_t * fieldDir = (field_slot_t *)offsetToPtr(rowSlot->offset);
+LOG_WINDOW("Read field_slot_t %d,%d: offset = %d, size = %d, type = %d", row, column, fieldDir[column].data.buffer.offset, fieldDir[column].data.buffer.size, fieldDir[column].type);
+
+    // Copy the data to the out param
+    slotOut->data.buffer.offset = fieldDir[column].data.buffer.offset;
+    slotOut->data.buffer.size = fieldDir[column].data.buffer.size;
+    slotOut->type = fieldDir[column].type;
+    return 0;
+}
+
+void CursorWindow::copyIn(uint32_t offset, uint8_t const * data, size_t size)
+{
+    assert(offset + size <= mSize);    
+    memcpy(mData + offset, data, size);
+}
+
+void CursorWindow::copyIn(uint32_t offset, int64_t data)
+{
+    assert(offset + sizeof(int64_t) <= mSize);
+    memcpy(mData + offset, (uint8_t *)&data, sizeof(int64_t));
+}
+
+void CursorWindow::copyIn(uint32_t offset, double data)
+{
+    assert(offset + sizeof(double) <= mSize);
+    memcpy(mData + offset, (uint8_t *)&data, sizeof(double));
+}
+
+void CursorWindow::copyOut(uint32_t offset, uint8_t * data, size_t size)
+{
+    assert(offset + size <= mSize);
+    memcpy(data, mData + offset, size);
+}
+
+int64_t CursorWindow::copyOutLong(uint32_t offset)
+{
+    int64_t value;
+    assert(offset + sizeof(int64_t) <= mSize);
+    memcpy(&value, mData + offset, sizeof(int64_t));
+    return value;
+}
+
+double CursorWindow::copyOutDouble(uint32_t offset)
+{
+    double value;
+    assert(offset + sizeof(double) <= mSize);
+    memcpy(&value, mData + offset, sizeof(double));
+    return value;
+}
+
+bool CursorWindow::putLong(unsigned int row, unsigned int col, int64_t value)
+{
+    field_slot_t * fieldSlot = getFieldSlotWithCheck(row, col);
+    if (!fieldSlot) {
+        return false;
+    }
+
+#if WINDOW_STORAGE_INLINE_NUMERICS
+    fieldSlot->data.l = value;
+#else
+    int offset = alloc(sizeof(int64_t));
+    if (!offset) {
+        return false;
+    }
+
+    copyIn(offset, value);
+
+    fieldSlot->data.buffer.offset = offset;
+    fieldSlot->data.buffer.size = sizeof(int64_t);
+#endif
+    fieldSlot->type = FIELD_TYPE_INTEGER;
+    return true;
+}
+
+bool CursorWindow::putDouble(unsigned int row, unsigned int col, double value)
+{
+    field_slot_t * fieldSlot = getFieldSlotWithCheck(row, col);
+    if (!fieldSlot) {
+        return false;
+    }
+
+#if WINDOW_STORAGE_INLINE_NUMERICS
+    fieldSlot->data.d = value;
+#else
+    int offset = alloc(sizeof(int64_t));
+    if (!offset) {
+        return false;
+    }
+
+    copyIn(offset, value);
+
+    fieldSlot->data.buffer.offset = offset;
+    fieldSlot->data.buffer.size = sizeof(double);
+#endif
+    fieldSlot->type = FIELD_TYPE_FLOAT;
+    return true;
+}
+
+bool CursorWindow::putNull(unsigned int row, unsigned int col)
+{
+    field_slot_t * fieldSlot = getFieldSlotWithCheck(row, col);
+    if (!fieldSlot) {
+        return false;
+    }
+
+    fieldSlot->type = FIELD_TYPE_NULL;
+    fieldSlot->data.buffer.offset = 0;
+    fieldSlot->data.buffer.size = 0;
+    return true;
+}
+
+bool CursorWindow::getLong(unsigned int row, unsigned int col, int64_t * valueOut)
+{
+    field_slot_t * fieldSlot = getFieldSlotWithCheck(row, col);
+    if (!fieldSlot || fieldSlot->type != FIELD_TYPE_INTEGER) {
+        return false;
+    }
+    
+#if WINDOW_STORAGE_INLINE_NUMERICS
+    *valueOut = fieldSlot->data.l;
+#else
+    *valueOut = copyOutLong(fieldSlot->data.buffer.offset);
+#endif
+    return true;
+}
+
+bool CursorWindow::getDouble(unsigned int row, unsigned int col, double * valueOut)
+{
+    field_slot_t * fieldSlot = getFieldSlotWithCheck(row, col);
+    if (!fieldSlot || fieldSlot->type != FIELD_TYPE_FLOAT) {
+        return false;
+    }
+
+#if WINDOW_STORAGE_INLINE_NUMERICS
+    *valueOut = fieldSlot->data.d;
+#else
+    *valueOut = copyOutDouble(fieldSlot->data.buffer.offset);
+#endif
+    return true;
+}
+
+bool CursorWindow::getNull(unsigned int row, unsigned int col, bool * valueOut)
+{
+    field_slot_t * fieldSlot = getFieldSlotWithCheck(row, col);
+    if (!fieldSlot) {
+        return false;
+    }
+    
+    if (fieldSlot->type != FIELD_TYPE_NULL) {
+        *valueOut = false;
+    } else {
+        *valueOut = true;
+    }
+    return true;
+}
+
+}; // namespace android
diff --git a/libs/camera/CameraParameters.cpp b/libs/camera/CameraParameters.cpp
index 1415493..abd418a 100644
--- a/libs/camera/CameraParameters.cpp
+++ b/libs/camera/CameraParameters.cpp
@@ -269,7 +269,7 @@
     mMap.removeItem(String8(key));
 }
 
-static int parse_size(const char *str, int &width, int &height)
+static int parse_size(const char *str, int &width, int &height, char **endptr = NULL)
 {
     // Find the width.
     char *end;
@@ -279,14 +279,42 @@
         return -1;
 
     // Find the height, immediately after the 'x'.
-    int h = (int)strtol(end+1, 0, 10);
+    int h = (int)strtol(end+1, &end, 10);
 
     width = w;
     height = h;
 
+    if (endptr) {
+        *endptr = end;
+    }
+
     return 0;
 }
 
+static void parseSizesList(const char *sizesStr, Vector<Size> &sizes)
+{
+    if (sizesStr == 0) {
+        return;
+    }
+
+    char *sizeStartPtr = (char *)sizesStr;
+
+    while (true) {
+        int width, height;
+        int success = parse_size(sizeStartPtr, width, height, &sizeStartPtr);
+        if (success == -1 || (*sizeStartPtr != ',' && *sizeStartPtr != '\0')) {
+            LOGE("Picture sizes string \"%s\" contains invalid character.", sizesStr);
+            return;
+        }
+        sizes.push(Size(width, height));
+
+        if (*sizeStartPtr == '\0') {
+            return;
+        }
+        sizeStartPtr++;
+    }
+}
+
 void CameraParameters::setPreviewSize(int width, int height)
 {
     char str[32];
@@ -311,6 +339,12 @@
     }
 }
 
+void CameraParameters::getSupportedPreviewSizes(Vector<Size> &sizes) const
+{
+    const char *previewSizesStr = get(KEY_SUPPORTED_PREVIEW_SIZES);
+    parseSizesList(previewSizesStr, sizes);
+}
+
 void CameraParameters::setPreviewFrameRate(int fps)
 {
     set(KEY_PREVIEW_FRAME_RATE, fps);
@@ -355,6 +389,12 @@
     }
 }
 
+void CameraParameters::getSupportedPictureSizes(Vector<Size> &sizes) const
+{
+    const char *pictureSizesStr = get(KEY_SUPPORTED_PICTURE_SIZES);
+    parseSizesList(pictureSizesStr, sizes);
+}
+
 void CameraParameters::setPictureFormat(const char *format)
 {
     set(KEY_PICTURE_FORMAT, format);
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
new file mode 100644
index 0000000..8f28612
--- /dev/null
+++ b/libs/hwui/Android.mk
@@ -0,0 +1,32 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES:= \
+	FontRenderer.cpp \
+	GradientCache.cpp \
+	LayerCache.cpp \
+	Matrix.cpp \
+	OpenGLRenderer.cpp \
+	Patch.cpp \
+	PatchCache.cpp \
+	Program.cpp \
+	ProgramCache.cpp \
+	SkiaColorFilter.cpp \
+	SkiaShader.cpp \
+	TextureCache.cpp
+
+LOCAL_C_INCLUDES += \
+	$(JNI_H_INCLUDE) \
+	$(LOCAL_PATH)/../../include/utils \
+	external/skia/include/core \
+	external/skia/include/effects \
+	external/skia/include/images \
+	external/skia/src/ports \
+	external/skia/include/utils
+
+LOCAL_MODULE_CLASS := SHARED_LIBRARIES
+LOCAL_SHARED_LIBRARIES := libcutils libutils libGLESv2 libskia
+LOCAL_MODULE := libhwui
+LOCAL_PRELINK_MODULE := false
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/libs/hwui/Extensions.h b/libs/hwui/Extensions.h
new file mode 100644
index 0000000..7778290
--- /dev/null
+++ b/libs/hwui/Extensions.h
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_UI_EXTENSIONS_H
+#define ANDROID_UI_EXTENSIONS_H
+
+#include <utils/SortedVector.h>
+#include <utils/String8.h>
+
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
+
+namespace android {
+namespace uirenderer {
+
+class Extensions {
+public:
+    Extensions() {
+        const char* buffer = (const char*) glGetString(GL_EXTENSIONS);
+        const char* current = buffer;
+        const char* head = current;
+        do {
+            head = strchr(current, ' ');
+            String8 s(current, head ? head - current : strlen(current));
+            if (s.length()) {
+                mExtensionList.add(s);
+            }
+            current = head + 1;
+        } while (head);
+
+        mHasNPot = hasExtension("GL_OES_texture_npot");
+        mHasDrawPath = hasExtension("GL_NV_draw_path");
+        mHasCoverageSample = hasExtension("GL_NV_coverage_sample");
+
+        mExtensions = buffer;
+    }
+
+    inline bool hasNPot() const { return mHasNPot; }
+    inline bool hasDrawPath() const { return mHasDrawPath; }
+    inline bool hasCoverageSample() const { return mHasCoverageSample; }
+
+    bool hasExtension(const char* extension) const {
+        const String8 s(extension);
+        return mExtensionList.indexOf(s) >= 0;
+    }
+
+    void dump() {
+        LOGD("Supported extensions:\n%s", mExtensions);
+    }
+
+private:
+    SortedVector<String8> mExtensionList;
+
+    const char* mExtensions;
+
+    bool mHasNPot;
+    bool mHasDrawPath;
+    bool mHasCoverageSample;
+}; // class Extensions
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_UI_EXTENSIONS_H
diff --git a/libs/hwui/FontRenderer.cpp b/libs/hwui/FontRenderer.cpp
new file mode 100644
index 0000000..feb45ec
--- /dev/null
+++ b/libs/hwui/FontRenderer.cpp
@@ -0,0 +1,731 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "OpenGLRenderer"
+
+#include <SkUtils.h>
+
+#include <cutils/properties.h>
+#include <utils/Log.h>
+
+#include "FontRenderer.h"
+
+namespace android {
+namespace uirenderer {
+
+///////////////////////////////////////////////////////////////////////////////
+// Defines
+///////////////////////////////////////////////////////////////////////////////
+
+#define DEFAULT_TEXT_CACHE_WIDTH 1024
+#define DEFAULT_TEXT_CACHE_HEIGHT 256
+
+///////////////////////////////////////////////////////////////////////////////
+// Font
+///////////////////////////////////////////////////////////////////////////////
+
+Font::Font(FontRenderer* state, uint32_t fontId, float fontSize) :
+    mState(state), mFontId(fontId), mFontSize(fontSize) {
+}
+
+
+Font::~Font() {
+    for (uint32_t ct = 0; ct < mState->mActiveFonts.size(); ct++) {
+        if (mState->mActiveFonts[ct] == this) {
+            mState->mActiveFonts.removeAt(ct);
+            break;
+        }
+    }
+
+    for (uint32_t i = 0; i < mCachedGlyphs.size(); i++) {
+        CachedGlyphInfo* glyph = mCachedGlyphs.valueAt(i);
+        delete glyph;
+    }
+}
+
+void Font::invalidateTextureCache() {
+    for (uint32_t i = 0; i < mCachedGlyphs.size(); i++) {
+        mCachedGlyphs.valueAt(i)->mIsValid = false;
+    }
+}
+
+void Font::drawCachedGlyph(CachedGlyphInfo* glyph, int x, int y) {
+    int nPenX = x + glyph->mBitmapLeft;
+    int nPenY = y + glyph->mBitmapTop + glyph->mBitmapHeight;
+
+    float u1 = glyph->mBitmapMinU;
+    float u2 = glyph->mBitmapMaxU;
+    float v1 = glyph->mBitmapMinV;
+    float v2 = glyph->mBitmapMaxV;
+
+    int width = (int) glyph->mBitmapWidth;
+    int height = (int) glyph->mBitmapHeight;
+
+    mState->appendMeshQuad(nPenX, nPenY, 0, u1, v2,
+            nPenX + width, nPenY, 0, u2, v2,
+            nPenX + width, nPenY - height, 0, u2, v1,
+            nPenX, nPenY - height, 0, u1, v1);
+}
+
+void Font::drawCachedGlyph(CachedGlyphInfo *glyph, int x, int y,
+                             uint8_t *bitmap, uint32_t bitmapW, uint32_t bitmapH) {
+    int nPenX = x + glyph->mBitmapLeft;
+    int nPenY = y + glyph->mBitmapTop;
+
+    uint32_t endX = glyph->mStartX + glyph->mBitmapWidth;
+    uint32_t endY = glyph->mStartY + glyph->mBitmapHeight;
+
+    if(nPenX < 0 || nPenY < 0) {
+        LOGE("Cannot render into a bitmap, some of the glyph is below zero");
+        return;
+    }
+
+    if(nPenX + glyph->mBitmapWidth >= bitmapW || nPenY + glyph->mBitmapHeight >= bitmapH) {
+        LOGE("Cannot render into a bitmap, dimentions too small");
+        return;
+    }
+
+    uint32_t cacheWidth = mState->getCacheWidth();
+    const uint8_t* cacheBuffer = mState->getTextTextureData();
+
+    uint32_t cacheX = 0, bX = 0, cacheY = 0, bY = 0;
+    for (cacheX = glyph->mStartX, bX = nPenX; cacheX < endX; cacheX++, bX++) {
+        for (cacheY = glyph->mStartY, bY = nPenY; cacheY < endY; cacheY++, bY++) {
+            uint8_t tempCol = cacheBuffer[cacheY * cacheWidth + cacheX];
+            bitmap[bY * bitmapW + bX] = tempCol;
+        }
+    }
+
+}
+
+Font::CachedGlyphInfo* Font::getCachedUTFChar(SkPaint* paint, int32_t utfChar) {
+    CachedGlyphInfo* cachedGlyph = mCachedGlyphs.valueFor(utfChar);
+    if (cachedGlyph == NULL) {
+        cachedGlyph = cacheGlyph(paint, utfChar);
+    }
+
+    // Is the glyph still in texture cache?
+    if (!cachedGlyph->mIsValid) {
+        const SkGlyph& skiaGlyph = paint->getUnicharMetrics(utfChar);
+        updateGlyphCache(paint, skiaGlyph, cachedGlyph);
+    }
+
+    return cachedGlyph;
+}
+
+void Font::renderUTF(SkPaint* paint, const char* text, uint32_t start, uint32_t len,
+        int numGlyphs, int x, int y, uint8_t *bitmap, uint32_t bitmapW, uint32_t bitmapH) {
+    if (numGlyphs == 0 || text == NULL || len == 0) {
+        return;
+    }
+
+    int penX = x, penY = y;
+    int glyphsLeft = 1;
+    if (numGlyphs > 0) {
+        glyphsLeft = numGlyphs;
+    }
+
+    text += start;
+
+    while (glyphsLeft > 0) {
+        int32_t utfChar = SkUTF16_NextUnichar((const uint16_t**) &text);
+
+        // Reached the end of the string or encountered
+        if (utfChar < 0) {
+            break;
+        }
+
+        CachedGlyphInfo* cachedGlyph = getCachedUTFChar(paint, utfChar);
+
+        // If it's still not valid, we couldn't cache it, so we shouldn't draw garbage
+        if (cachedGlyph->mIsValid) {
+            if(bitmap != NULL) {
+                drawCachedGlyph(cachedGlyph, penX, penY, bitmap, bitmapW, bitmapH);
+            }
+            else {
+                drawCachedGlyph(cachedGlyph, penX, penY);
+            }
+        }
+
+        penX += SkFixedFloor(cachedGlyph->mAdvanceX);
+
+        // If we were given a specific number of glyphs, decrement
+        if (numGlyphs > 0) {
+            glyphsLeft--;
+        }
+    }
+}
+
+void Font::updateGlyphCache(SkPaint* paint, const SkGlyph& skiaGlyph, CachedGlyphInfo* glyph) {
+    glyph->mAdvanceX = skiaGlyph.fAdvanceX;
+    glyph->mAdvanceY = skiaGlyph.fAdvanceY;
+    glyph->mBitmapLeft = skiaGlyph.fLeft;
+    glyph->mBitmapTop = skiaGlyph.fTop;
+
+    uint32_t startX = 0;
+    uint32_t startY = 0;
+
+    // Get the bitmap for the glyph
+    paint->findImage(skiaGlyph);
+    glyph->mIsValid = mState->cacheBitmap(skiaGlyph, &startX, &startY);
+
+    if (!glyph->mIsValid) {
+        return;
+    }
+
+    uint32_t endX = startX + skiaGlyph.fWidth;
+    uint32_t endY = startY + skiaGlyph.fHeight;
+
+    glyph->mStartX = startX;
+    glyph->mStartY = startY;
+    glyph->mBitmapWidth = skiaGlyph.fWidth;
+    glyph->mBitmapHeight = skiaGlyph.fHeight;
+
+    uint32_t cacheWidth = mState->getCacheWidth();
+    uint32_t cacheHeight = mState->getCacheHeight();
+
+    glyph->mBitmapMinU = (float) startX / (float) cacheWidth;
+    glyph->mBitmapMinV = (float) startY / (float) cacheHeight;
+    glyph->mBitmapMaxU = (float) endX / (float) cacheWidth;
+    glyph->mBitmapMaxV = (float) endY / (float) cacheHeight;
+
+    mState->mUploadTexture = true;
+}
+
+Font::CachedGlyphInfo* Font::cacheGlyph(SkPaint* paint, int32_t glyph) {
+    CachedGlyphInfo* newGlyph = new CachedGlyphInfo();
+    mCachedGlyphs.add(glyph, newGlyph);
+
+    const SkGlyph& skiaGlyph = paint->getUnicharMetrics(glyph);
+    newGlyph->mGlyphIndex = skiaGlyph.fID;
+    newGlyph->mIsValid = false;
+
+    updateGlyphCache(paint, skiaGlyph, newGlyph);
+
+    return newGlyph;
+}
+
+Font* Font::create(FontRenderer* state, uint32_t fontId, float fontSize) {
+    Vector<Font*> &activeFonts = state->mActiveFonts;
+
+    for (uint32_t i = 0; i < activeFonts.size(); i++) {
+        Font* font = activeFonts[i];
+        if (font->mFontId == fontId && font->mFontSize == fontSize) {
+            return font;
+        }
+    }
+
+    Font* newFont = new Font(state, fontId, fontSize);
+    activeFonts.push(newFont);
+    return newFont;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// FontRenderer
+///////////////////////////////////////////////////////////////////////////////
+
+FontRenderer::FontRenderer() {
+    LOGD("Creating FontRenderer");
+
+    mInitialized = false;
+    mMaxNumberOfQuads = 1024;
+    mCurrentQuadIndex = 0;
+    mTextureId = 0;
+
+    mIndexBufferID = 0;
+
+    mCacheWidth = DEFAULT_TEXT_CACHE_WIDTH;
+    mCacheHeight = DEFAULT_TEXT_CACHE_HEIGHT;
+
+    char property[PROPERTY_VALUE_MAX];
+    if (property_get(PROPERTY_TEXT_CACHE_WIDTH, property, NULL) > 0) {
+        LOGD("  Setting text cache width to %s pixels", property);
+        mCacheWidth = atoi(property);
+    } else {
+        LOGD("  Using default text cache width of %i pixels", mCacheWidth);
+    }
+
+    if (property_get(PROPERTY_TEXT_CACHE_HEIGHT, property, NULL) > 0) {
+        LOGD("  Setting text cache width to %s pixels", property);
+        mCacheHeight = atoi(property);
+    } else {
+        LOGD("  Using default text cache height of %i pixels", mCacheHeight);
+    }
+}
+
+FontRenderer::~FontRenderer() {
+    for (uint32_t i = 0; i < mCacheLines.size(); i++) {
+        delete mCacheLines[i];
+    }
+    mCacheLines.clear();
+
+    delete[] mTextMeshPtr;
+    delete[] mTextTexture;
+
+    if(mTextureId) {
+        glDeleteTextures(1, &mTextureId);
+    }
+
+    Vector<Font*> fontsToDereference = mActiveFonts;
+    for (uint32_t i = 0; i < fontsToDereference.size(); i++) {
+        delete fontsToDereference[i];
+    }
+}
+
+void FontRenderer::flushAllAndInvalidate() {
+    if (mCurrentQuadIndex != 0) {
+        issueDrawCommand();
+        mCurrentQuadIndex = 0;
+    }
+    for (uint32_t i = 0; i < mActiveFonts.size(); i++) {
+        mActiveFonts[i]->invalidateTextureCache();
+    }
+    for (uint32_t i = 0; i < mCacheLines.size(); i++) {
+        mCacheLines[i]->mCurrentCol = 0;
+    }
+}
+
+bool FontRenderer::cacheBitmap(const SkGlyph& glyph, uint32_t* retOriginX, uint32_t* retOriginY) {
+    // If the glyph is too tall, don't cache it
+    if (glyph.fWidth > mCacheLines[mCacheLines.size() - 1]->mMaxHeight) {
+        LOGE("Font size to large to fit in cache. width, height = %i, %i",
+                (int) glyph.fWidth, (int) glyph.fHeight);
+        return false;
+    }
+
+    // Now copy the bitmap into the cache texture
+    uint32_t startX = 0;
+    uint32_t startY = 0;
+
+    bool bitmapFit = false;
+    for (uint32_t i = 0; i < mCacheLines.size(); i++) {
+        bitmapFit = mCacheLines[i]->fitBitmap(glyph, &startX, &startY);
+        if (bitmapFit) {
+            break;
+        }
+    }
+
+    // If the new glyph didn't fit, flush the state so far and invalidate everything
+    if (!bitmapFit) {
+        flushAllAndInvalidate();
+
+        // Try to fit it again
+        for (uint32_t i = 0; i < mCacheLines.size(); i++) {
+            bitmapFit = mCacheLines[i]->fitBitmap(glyph, &startX, &startY);
+            if (bitmapFit) {
+                break;
+            }
+        }
+
+        // if we still don't fit, something is wrong and we shouldn't draw
+        if (!bitmapFit) {
+            LOGE("Bitmap doesn't fit in cache. width, height = %i, %i",
+                    (int) glyph.fWidth, (int) glyph.fHeight);
+            return false;
+        }
+    }
+
+    *retOriginX = startX;
+    *retOriginY = startY;
+
+    uint32_t endX = startX + glyph.fWidth;
+    uint32_t endY = startY + glyph.fHeight;
+
+    uint32_t cacheWidth = mCacheWidth;
+
+    uint8_t* cacheBuffer = mTextTexture;
+    uint8_t* bitmapBuffer = (uint8_t*) glyph.fImage;
+    unsigned int stride = glyph.rowBytes();
+
+    uint32_t cacheX = 0, bX = 0, cacheY = 0, bY = 0;
+    for (cacheX = startX, bX = 0; cacheX < endX; cacheX++, bX++) {
+        for (cacheY = startY, bY = 0; cacheY < endY; cacheY++, bY++) {
+            uint8_t tempCol = bitmapBuffer[bY * stride + bX];
+            cacheBuffer[cacheY * cacheWidth + cacheX] = tempCol;
+        }
+    }
+
+    return true;
+}
+
+void FontRenderer::initTextTexture() {
+    mTextTexture = new uint8_t[mCacheWidth * mCacheHeight];
+    mUploadTexture = false;
+
+    glGenTextures(1, &mTextureId);
+    glBindTexture(GL_TEXTURE_2D, mTextureId);
+    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+    // Initialize texture dimentions
+    glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, mCacheWidth, mCacheHeight, 0,
+                  GL_ALPHA, GL_UNSIGNED_BYTE, 0);
+
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+
+    // Split up our cache texture into lines of certain widths
+    int nextLine = 0;
+    mCacheLines.push(new CacheTextureLine(mCacheWidth, 16, nextLine, 0));
+    nextLine += mCacheLines.top()->mMaxHeight;
+    mCacheLines.push(new CacheTextureLine(mCacheWidth, 24, nextLine, 0));
+    nextLine += mCacheLines.top()->mMaxHeight;
+    mCacheLines.push(new CacheTextureLine(mCacheWidth, 24, nextLine, 0));
+    nextLine += mCacheLines.top()->mMaxHeight;
+    mCacheLines.push(new CacheTextureLine(mCacheWidth, 32, nextLine, 0));
+    nextLine += mCacheLines.top()->mMaxHeight;
+    mCacheLines.push(new CacheTextureLine(mCacheWidth, 32, nextLine, 0));
+    nextLine += mCacheLines.top()->mMaxHeight;
+    mCacheLines.push(new CacheTextureLine(mCacheWidth, 40, nextLine, 0));
+    nextLine += mCacheLines.top()->mMaxHeight;
+    mCacheLines.push(new CacheTextureLine(mCacheWidth, mCacheHeight - nextLine, nextLine, 0));
+}
+
+// Avoid having to reallocate memory and render quad by quad
+void FontRenderer::initVertexArrayBuffers() {
+    uint32_t numIndicies = mMaxNumberOfQuads * 6;
+    uint32_t indexBufferSizeBytes = numIndicies * sizeof(uint16_t);
+    uint16_t* indexBufferData = (uint16_t*) malloc(indexBufferSizeBytes);
+
+    // Four verts, two triangles , six indices per quad
+    for (uint32_t i = 0; i < mMaxNumberOfQuads; i++) {
+        int i6 = i * 6;
+        int i4 = i * 4;
+
+        indexBufferData[i6 + 0] = i4 + 0;
+        indexBufferData[i6 + 1] = i4 + 1;
+        indexBufferData[i6 + 2] = i4 + 2;
+
+        indexBufferData[i6 + 3] = i4 + 0;
+        indexBufferData[i6 + 4] = i4 + 2;
+        indexBufferData[i6 + 5] = i4 + 3;
+    }
+
+    glGenBuffers(1, &mIndexBufferID);
+    glBindBuffer(GL_ARRAY_BUFFER, mIndexBufferID);
+    glBufferData(GL_ARRAY_BUFFER, indexBufferSizeBytes, indexBufferData, GL_DYNAMIC_DRAW);
+    glBindBuffer(GL_ARRAY_BUFFER, 0);
+
+    free(indexBufferData);
+
+    uint32_t coordSize = 3;
+    uint32_t uvSize = 2;
+    uint32_t vertsPerQuad = 4;
+    uint32_t vertexBufferSize = mMaxNumberOfQuads * vertsPerQuad * coordSize * uvSize;
+    mTextMeshPtr = new float[vertexBufferSize];
+}
+
+// We don't want to allocate anything unless we actually draw text
+void FontRenderer::checkInit() {
+    if (mInitialized) {
+        return;
+    }
+
+    initTextTexture();
+    initVertexArrayBuffers();
+
+    // We store a string with letters in a rough frequency of occurrence
+    mLatinPrecache = String16("eisarntolcdugpmhbyfvkwzxjq ");
+    mLatinPrecache += String16("EISARNTOLCDUGPMHBYFVKWZXJQ");
+    mLatinPrecache += String16(",.?!()-+@;:`'");
+    mLatinPrecache += String16("0123456789");
+
+    mInitialized = true;
+}
+
+void FontRenderer::checkTextureUpdate() {
+    if (!mUploadTexture) {
+        return;
+    }
+
+    glBindTexture(GL_TEXTURE_2D, mTextureId);
+
+    // Iterate over all the cache lines and see which ones need to be updated
+    for (uint32_t i = 0; i < mCacheLines.size(); i++) {
+        CacheTextureLine* cl = mCacheLines[i];
+        if(cl->mDirty) {
+            uint32_t xOffset = 0;
+            uint32_t yOffset = cl->mCurrentRow;
+            uint32_t width   = mCacheWidth;
+            uint32_t height  = cl->mMaxHeight;
+            void*    textureData = mTextTexture + yOffset*width;
+
+            glTexSubImage2D(GL_TEXTURE_2D, 0, xOffset, yOffset, width, height,
+                             GL_ALPHA, GL_UNSIGNED_BYTE, textureData);
+
+            cl->mDirty = false;
+        }
+    }
+
+    mUploadTexture = false;
+}
+
+void FontRenderer::issueDrawCommand() {
+
+    checkTextureUpdate();
+
+    float* vtx = mTextMeshPtr;
+    float* tex = vtx + 3;
+
+    // position is slot 0
+    uint32_t slot = 0;
+    glVertexAttribPointer(slot, 3, GL_FLOAT, false, 20, vtx);
+
+    // texture0 is slot 1
+    slot = 1;
+    glVertexAttribPointer(slot, 2, GL_FLOAT, false, 20, tex);
+
+    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mIndexBufferID);
+    glDrawElements(GL_TRIANGLES, mCurrentQuadIndex * 6, GL_UNSIGNED_SHORT, NULL);
+}
+
+void FontRenderer::appendMeshQuad(float x1, float y1, float z1, float u1, float v1, float x2,
+        float y2, float z2, float u2, float v2, float x3, float y3, float z3, float u3, float v3,
+        float x4, float y4, float z4, float u4, float v4) {
+    if (x1 > mClip->right || y1 < mClip->top || x2 < mClip->left || y4 > mClip->bottom) {
+        return;
+    }
+
+    const uint32_t vertsPerQuad = 4;
+    const uint32_t floatsPerVert = 5;
+    float* currentPos = mTextMeshPtr + mCurrentQuadIndex * vertsPerQuad * floatsPerVert;
+
+    (*currentPos++) = x1;
+    (*currentPos++) = y1;
+    (*currentPos++) = z1;
+    (*currentPos++) = u1;
+    (*currentPos++) = v1;
+
+    (*currentPos++) = x2;
+    (*currentPos++) = y2;
+    (*currentPos++) = z2;
+    (*currentPos++) = u2;
+    (*currentPos++) = v2;
+
+    (*currentPos++) = x3;
+    (*currentPos++) = y3;
+    (*currentPos++) = z3;
+    (*currentPos++) = u3;
+    (*currentPos++) = v3;
+
+    (*currentPos++) = x4;
+    (*currentPos++) = y4;
+    (*currentPos++) = z4;
+    (*currentPos++) = u4;
+    (*currentPos++) = v4;
+
+    mCurrentQuadIndex++;
+
+    if (mCurrentQuadIndex == mMaxNumberOfQuads) {
+        issueDrawCommand();
+        mCurrentQuadIndex = 0;
+    }
+}
+
+uint32_t FontRenderer::getRemainingCacheCapacity() {
+    uint32_t remainingCapacity = 0;
+    float totalPixels = 0;
+    for(uint32_t i = 0; i < mCacheLines.size(); i ++) {
+         remainingCapacity += (mCacheLines[i]->mMaxWidth - mCacheLines[i]->mCurrentCol);
+         totalPixels += mCacheLines[i]->mMaxWidth;
+    }
+    remainingCapacity = (remainingCapacity * 100) / totalPixels;
+    return remainingCapacity;
+}
+
+void FontRenderer::precacheLatin(SkPaint* paint) {
+    // Remaining capacity is measured in %
+    uint32_t remainingCapacity = getRemainingCacheCapacity();
+    uint32_t precacheIdx = 0;
+    while(remainingCapacity > 25 && precacheIdx < mLatinPrecache.size()) {
+        mCurrentFont->getCachedUTFChar(paint, (int32_t)mLatinPrecache[precacheIdx]);
+        remainingCapacity = getRemainingCacheCapacity();
+        precacheIdx ++;
+    }
+}
+
+void FontRenderer::setFont(SkPaint* paint, uint32_t fontId, float fontSize) {
+    uint32_t currentNumFonts = mActiveFonts.size();
+    mCurrentFont = Font::create(this, fontId, fontSize);
+
+    const float maxPrecacheFontSize = 40.0f;
+    bool isNewFont = currentNumFonts != mActiveFonts.size();
+
+    if(isNewFont && fontSize <= maxPrecacheFontSize ){
+        precacheLatin(paint);
+    }
+}
+
+void FontRenderer::renderText(SkPaint* paint, const Rect* clip, const char *text,
+        uint32_t startIndex, uint32_t len, int numGlyphs, int x, int y) {
+    checkInit();
+
+    if (!mCurrentFont) {
+        LOGE("No font set");
+        return;
+    }
+
+    mClip = clip;
+    mCurrentFont->renderUTF(paint, text, startIndex, len, numGlyphs, x, y);
+
+    if (mCurrentQuadIndex != 0) {
+        issueDrawCommand();
+        mCurrentQuadIndex = 0;
+    }
+}
+
+void FontRenderer::computeGaussianWeights(float* weights, int32_t radius) {
+    // Compute gaussian weights for the blur
+    // e is the euler's number
+    float e = 2.718281828459045f;
+    float pi = 3.1415926535897932f;
+    // g(x) = ( 1 / sqrt( 2 * pi ) * sigma) * e ^ ( -x^2 / 2 * sigma^2 )
+    // x is of the form [-radius .. 0 .. radius]
+    // and sigma varies with radius.
+    // Based on some experimental radius values and sigma's
+    // we approximately fit sigma = f(radius) as
+    // sigma = radius * 0.4  + 0.6
+    // The larger the radius gets, the more our gaussian blur
+    // will resemble a box blur since with large sigma
+    // the gaussian curve begins to lose its shape
+    float sigma = 0.4f * (float)radius + 0.6f;
+
+    // Now compute the coefficints
+    // We will store some redundant values to save some math during
+    // the blur calculations
+    // precompute some values
+    float coeff1 = 1.0f / (sqrt( 2.0f * pi ) * sigma);
+    float coeff2 = - 1.0f / (2.0f * sigma * sigma);
+
+    float normalizeFactor = 0.0f;
+    for(int32_t r = -radius; r <= radius; r ++) {
+        float floatR = (float)r;
+        weights[r + radius] = coeff1 * pow(e, floatR * floatR * coeff2);
+        normalizeFactor += weights[r + radius];
+    }
+
+    //Now we need to normalize the weights because all our coefficients need to add up to one
+    normalizeFactor = 1.0f / normalizeFactor;
+    for(int32_t r = -radius; r <= radius; r ++) {
+        weights[r + radius] *= normalizeFactor;
+    }
+}
+
+void FontRenderer::horizontalBlur(float* weights, int32_t radius,
+                                    const uint8_t* source, uint8_t* dest,
+                                    int32_t width, int32_t height) {
+    float blurredPixel = 0.0f;
+    float currentPixel = 0.0f;
+
+    for(int32_t y = 0; y < height; y ++) {
+
+        const uint8_t* input = source + y * width;
+        uint8_t* output = dest + y * width;
+
+        for(int32_t x = 0; x < width; x ++) {
+            blurredPixel = 0.0f;
+            const float* gPtr = weights;
+            // Optimization for non-border pixels
+            if ((x > radius) && (x < (width - radius))) {
+                const uint8_t *i = input + (x - radius);
+                for(int r = -radius; r <= radius; r ++) {
+                    currentPixel = (float)(*i);
+                    blurredPixel += currentPixel * gPtr[0];
+                    gPtr++;
+                    i++;
+                }
+            } else {
+                for(int32_t r = -radius; r <= radius; r ++) {
+                    // Stepping left and right away from the pixel
+                    int validW = x + r;
+                    if(validW < 0) {
+                        validW = 0;
+                    }
+                    if(validW > width - 1) {
+                        validW = width - 1;
+                    }
+
+                    currentPixel = (float)(input[validW]);
+                    blurredPixel += currentPixel * gPtr[0];
+                    gPtr++;
+                }
+            }
+            *output = (uint8_t)blurredPixel;
+            output ++;
+        }
+    }
+}
+
+void FontRenderer::verticalBlur(float* weights, int32_t radius,
+                                  const uint8_t* source, uint8_t* dest,
+                                  int32_t width, int32_t height) {
+    float blurredPixel = 0.0f;
+    float currentPixel = 0.0f;
+
+    for(int32_t y = 0; y < height; y ++) {
+
+        uint8_t* output = dest + y * width;
+
+        for(int32_t x = 0; x < width; x ++) {
+            blurredPixel = 0.0f;
+            const float* gPtr = weights;
+            const uint8_t* input = source + x;
+            // Optimization for non-border pixels
+            if ((y > radius) && (y < (height - radius))) {
+                const uint8_t *i = input + ((y - radius) * width);
+                for(int32_t r = -radius; r <= radius; r ++) {
+                    currentPixel = (float)(*i);
+                    blurredPixel += currentPixel * gPtr[0];
+                    gPtr++;
+                    i += width;
+                }
+            } else {
+                for(int32_t r = -radius; r <= radius; r ++) {
+                    int validH = y + r;
+                    // Clamp to zero and width
+                    if(validH < 0) {
+                        validH = 0;
+                    }
+                    if(validH > height - 1) {
+                        validH = height - 1;
+                    }
+
+                    const uint8_t *i = input + validH * width;
+                    currentPixel = (float)(*i);
+                    blurredPixel += currentPixel * gPtr[0];
+                    gPtr++;
+                }
+            }
+            *output = (uint8_t)blurredPixel;
+            output ++;
+        }
+    }
+}
+
+
+void FontRenderer::blurImage(uint8_t *image, int32_t width, int32_t height, int32_t radius) {
+    float *gaussian = new float[2 * radius + 1];
+    computeGaussianWeights(gaussian, radius);
+    uint8_t* scratch = new uint8_t[width * height];
+    horizontalBlur(gaussian, radius, image, scratch, width, height);
+    verticalBlur(gaussian, radius, scratch, image, width, height);
+    delete[] gaussian;
+    delete[] scratch;
+}
+
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/FontRenderer.h b/libs/hwui/FontRenderer.h
new file mode 100644
index 0000000..4cd902e
--- /dev/null
+++ b/libs/hwui/FontRenderer.h
@@ -0,0 +1,220 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_UI_FONT_RENDERER_H
+#define ANDROID_UI_FONT_RENDERER_H
+
+#include <utils/String8.h>
+#include <utils/String16.h>
+#include <utils/Vector.h>
+#include <utils/KeyedVector.h>
+
+#include <SkScalerContext.h>
+#include <SkPaint.h>
+
+#include <GLES2/gl2.h>
+
+#include "Rect.h"
+#include "Properties.h"
+
+namespace android {
+namespace uirenderer {
+
+class FontRenderer;
+
+/**
+ * Represents a font, defined by a Skia font id and a font size. A font is used
+ * to generate glyphs and cache them in the FontState.
+ */
+class Font {
+public:
+    ~Font();
+
+    /**
+     * Renders the specified string of text.
+     */
+    void renderUTF(SkPaint* paint, const char *text, uint32_t start, uint32_t len,
+                     int numGlyphs, int x, int y,
+                     uint8_t *bitmap = NULL, uint32_t bitmapW = 0, uint32_t bitmapH = 0);
+    /**
+     * Creates a new font associated with the specified font state.
+     */
+    static Font* create(FontRenderer* state, uint32_t fontId, float fontSize);
+
+protected:
+    friend class FontRenderer;
+
+    struct CachedGlyphInfo {
+        // Has the cache been invalidated?
+        bool mIsValid;
+        // Location of the cached glyph in the bitmap
+        // in case we need to resize the texture or
+        // render to bitmap
+        uint32_t mStartX;
+        uint32_t mStartY;
+        uint32_t mBitmapWidth;
+        uint32_t mBitmapHeight;
+        // Also cache texture coords for the quad
+        float mBitmapMinU;
+        float mBitmapMinV;
+        float mBitmapMaxU;
+        float mBitmapMaxV;
+        // Minimize how much we call freetype
+        uint32_t mGlyphIndex;
+        uint32_t mAdvanceX;
+        uint32_t mAdvanceY;
+        // Values below contain a glyph's origin in the bitmap
+        int32_t mBitmapLeft;
+        int32_t mBitmapTop;
+    };
+
+    Font(FontRenderer* state, uint32_t fontId, float fontSize);
+
+    DefaultKeyedVector<int32_t, CachedGlyphInfo*> mCachedGlyphs;
+
+    void invalidateTextureCache();
+
+    CachedGlyphInfo* cacheGlyph(SkPaint* paint, int32_t glyph);
+    void updateGlyphCache(SkPaint* paint, const SkGlyph& skiaGlyph, CachedGlyphInfo *glyph);
+    void drawCachedGlyph(CachedGlyphInfo *glyph, int x, int y);
+    void drawCachedGlyph(CachedGlyphInfo *glyph, int x, int y,
+                          uint8_t *bitmap, uint32_t bitmapW, uint32_t bitmapH);
+
+    CachedGlyphInfo* getCachedUTFChar(SkPaint* paint, int32_t utfChar);
+
+    FontRenderer* mState;
+    uint32_t mFontId;
+    float mFontSize;
+};
+
+class FontRenderer {
+public:
+    FontRenderer();
+    ~FontRenderer();
+
+    void init();
+    void deinit();
+
+    void setFont(SkPaint* paint, uint32_t fontId, float fontSize);
+    void renderText(SkPaint* paint, const Rect* clip, const char *text, uint32_t startIndex,
+            uint32_t len, int numGlyphs, int x, int y);
+
+    GLuint getTexture() {
+        checkInit();
+        return mTextureId;
+    }
+
+protected:
+    friend class Font;
+
+    struct CacheTextureLine {
+        uint16_t mMaxHeight;
+        uint16_t mMaxWidth;
+        uint32_t mCurrentRow;
+        uint32_t mCurrentCol;
+        bool     mDirty;
+
+        CacheTextureLine(uint16_t maxWidth, uint16_t maxHeight, uint32_t currentRow,
+                uint32_t currentCol):
+                    mMaxHeight(maxHeight),
+                    mMaxWidth(maxWidth),
+                    mCurrentRow(currentRow),
+                    mCurrentCol(currentCol),
+                    mDirty(false) {
+        }
+
+        bool fitBitmap(const SkGlyph& glyph, uint32_t *retOriginX, uint32_t *retOriginY) {
+            if (glyph.fHeight > mMaxHeight) {
+                return false;
+            }
+
+            if (mCurrentCol + glyph.fWidth < mMaxWidth) {
+                *retOriginX = mCurrentCol;
+                *retOriginY = mCurrentRow;
+                mCurrentCol += glyph.fWidth;
+                mDirty = true;
+                return true;
+            }
+
+            return false;
+        }
+    };
+
+    uint32_t getCacheWidth() const {
+        return mCacheWidth;
+    }
+
+    uint32_t getCacheHeight() const {
+        return mCacheHeight;
+    }
+
+    void initTextTexture();
+    bool cacheBitmap(const SkGlyph& glyph, uint32_t *retOriginX, uint32_t *retOriginY);
+
+    void flushAllAndInvalidate();
+    void initVertexArrayBuffers();
+
+    void checkInit();
+
+    String16 mLatinPrecache;
+    void precacheLatin(SkPaint* paint);
+
+    void issueDrawCommand();
+    void appendMeshQuad(float x1, float y1, float z1, float u1, float v1, float x2, float y2,
+            float z2, float u2, float v2, float x3, float y3, float z3, float u3, float v3,
+            float x4, float y4, float z4, float u4, float v4);
+
+    uint32_t mCacheWidth;
+    uint32_t mCacheHeight;
+
+    Vector<CacheTextureLine*> mCacheLines;
+    uint32_t getRemainingCacheCapacity();
+
+    Font* mCurrentFont;
+    Vector<Font*> mActiveFonts;
+
+    // Texture to cache glyph bitmaps
+    uint8_t* mTextTexture;
+    const uint8_t* getTextTextureData() const {
+        return mTextTexture;
+    }
+    GLuint mTextureId;
+    void checkTextureUpdate();
+    bool mUploadTexture;
+
+    // Pointer to vertex data to speed up frame to frame work
+    float *mTextMeshPtr;
+    uint32_t mCurrentQuadIndex;
+    uint32_t mMaxNumberOfQuads;
+
+    uint32_t mIndexBufferID;
+
+    const Rect* mClip;
+
+    bool mInitialized;
+
+    void computeGaussianWeights(float* weights, int32_t radius);
+    void horizontalBlur(float* weights, int32_t radius, const uint8_t *source, uint8_t *dest,
+                         int32_t width, int32_t height);
+    void verticalBlur(float* weights, int32_t radius, const uint8_t *source, uint8_t *dest,
+                         int32_t width, int32_t height);
+    void blurImage(uint8_t* image, int32_t width, int32_t height, int32_t radius);
+};
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_UI_FONT_RENDERER_H
diff --git a/libs/hwui/GenerationCache.h b/libs/hwui/GenerationCache.h
new file mode 100644
index 0000000..45b3ffa
--- /dev/null
+++ b/libs/hwui/GenerationCache.h
@@ -0,0 +1,232 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_UI_GENERATION_CACHE_H
+#define ANDROID_UI_GENERATION_CACHE_H
+
+#include <utils/KeyedVector.h>
+#include <utils/RefBase.h>
+
+namespace android {
+namespace uirenderer {
+
+template<typename EntryKey, typename EntryValue>
+class OnEntryRemoved {
+public:
+    virtual ~OnEntryRemoved() { };
+    virtual void operator()(EntryKey& key, EntryValue& value) = 0;
+}; // class OnEntryRemoved
+
+template<typename EntryKey, typename EntryValue>
+struct Entry: public LightRefBase<Entry<EntryKey, EntryValue> > {
+    Entry() { }
+    Entry(const Entry<EntryKey, EntryValue>& e):
+            key(e.key), value(e.value), parent(e.parent), child(e.child) { }
+    Entry(sp<Entry<EntryKey, EntryValue> > e):
+            key(e->key), value(e->value), parent(e->parent), child(e->child) { }
+
+    EntryKey key;
+    EntryValue value;
+
+    sp<Entry<EntryKey, EntryValue> > parent;
+    sp<Entry<EntryKey, EntryValue> > child;
+}; // struct Entry
+
+template<typename K, typename V>
+class GenerationCache {
+public:
+    GenerationCache(uint32_t maxCapacity);
+    virtual ~GenerationCache();
+
+    enum Capacity {
+        kUnlimitedCapacity,
+    };
+
+    void setOnEntryRemovedListener(OnEntryRemoved<K, V>* listener);
+
+    void clear();
+
+    bool contains(K key) const;
+    V get(K key);
+    void put(K key, V value);
+    V remove(K key);
+    V removeOldest();
+
+    uint32_t size() const;
+
+    void addToCache(sp<Entry<K, V> > entry, K key, V value);
+    void attachToCache(sp<Entry<K, V> > entry);
+    void detachFromCache(sp<Entry<K, V> > entry);
+
+    V removeAt(ssize_t index);
+
+    KeyedVector<K, sp<Entry<K, V> > > mCache;
+    uint32_t mMaxCapacity;
+
+    OnEntryRemoved<K, V>* mListener;
+
+    sp<Entry<K, V> > mOldest;
+    sp<Entry<K, V> > mYoungest;
+}; // class GenerationCache
+
+template<typename K, typename V>
+GenerationCache<K, V>::GenerationCache(uint32_t maxCapacity): mMaxCapacity(maxCapacity), mListener(NULL) {
+};
+
+template<typename K, typename V>
+GenerationCache<K, V>::~GenerationCache() {
+    clear();
+};
+
+template<typename K, typename V>
+uint32_t GenerationCache<K, V>::size() const {
+    return mCache.size();
+}
+
+template<typename K, typename V>
+void GenerationCache<K, V>::setOnEntryRemovedListener(OnEntryRemoved<K, V>* listener) {
+    mListener = listener;
+}
+
+template<typename K, typename V>
+void GenerationCache<K, V>::clear() {
+    if (mListener) {
+        while (mCache.size() > 0) {
+            removeOldest();
+        }
+    } else {
+        mCache.clear();
+    }
+    mYoungest.clear();
+    mOldest.clear();
+}
+
+template<typename K, typename V>
+bool GenerationCache<K, V>::contains(K key) const {
+    return mCache.indexOfKey(key) >= 0;
+}
+
+template<typename K, typename V>
+V GenerationCache<K, V>::get(K key) {
+    ssize_t index = mCache.indexOfKey(key);
+    if (index >= 0) {
+        sp<Entry<K, V> > entry = mCache.valueAt(index);
+        if (entry.get()) {
+            detachFromCache(entry);
+            attachToCache(entry);
+            return entry->value;
+        }
+    }
+
+    return NULL;
+}
+
+template<typename K, typename V>
+void GenerationCache<K, V>::put(K key, V value) {
+    if (mMaxCapacity != kUnlimitedCapacity && mCache.size() >= mMaxCapacity) {
+        removeOldest();
+    }
+
+    ssize_t index = mCache.indexOfKey(key);
+    if (index >= 0) {
+        sp<Entry<K, V> > entry = mCache.valueAt(index);
+        detachFromCache(entry);
+        addToCache(entry, key, value);
+    } else {
+        sp<Entry<K, V> > entry = new Entry<K, V>;
+        addToCache(entry, key, value);
+    }
+}
+
+template<typename K, typename V>
+void GenerationCache<K, V>::addToCache(sp<Entry<K, V> > entry, K key, V value) {
+    entry->key = key;
+    entry->value = value;
+    mCache.add(key, entry);
+    attachToCache(entry);
+}
+
+template<typename K, typename V>
+V GenerationCache<K, V>::remove(K key) {
+    ssize_t index = mCache.indexOfKey(key);
+    if (index >= 0) {
+        return removeAt(index);
+    }
+
+    return NULL;
+}
+
+template<typename K, typename V>
+V GenerationCache<K, V>::removeAt(ssize_t index) {
+    sp<Entry<K, V> > entry = mCache.valueAt(index);
+    if (mListener) {
+        (*mListener)(entry->key, entry->value);
+    }
+    mCache.removeItemsAt(index, 1);
+    detachFromCache(entry);
+
+    return entry->value;
+}
+
+template<typename K, typename V>
+V GenerationCache<K, V>::removeOldest() {
+    if (mOldest.get()) {
+        ssize_t index = mCache.indexOfKey(mOldest->key);
+        if (index >= 0) {
+            return removeAt(index);
+        }
+    }
+
+    return NULL;
+}
+
+template<typename K, typename V>
+void GenerationCache<K, V>::attachToCache(sp<Entry<K, V> > entry) {
+    if (!mYoungest.get()) {
+        mYoungest = mOldest = entry;
+    } else {
+        entry->parent = mYoungest;
+        mYoungest->child = entry;
+        mYoungest = entry;
+    }
+}
+
+template<typename K, typename V>
+void GenerationCache<K, V>::detachFromCache(sp<Entry<K, V> > entry) {
+    if (entry->parent.get()) {
+        entry->parent->child = entry->child;
+    }
+
+    if (entry->child.get()) {
+        entry->child->parent = entry->parent;
+    }
+
+    if (mOldest == entry) {
+        mOldest = entry->child;
+    }
+
+    if (mYoungest == entry) {
+        mYoungest = entry->parent;
+    }
+
+    entry->parent.clear();
+    entry->child.clear();
+}
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_UI_GENERATION_CACHE_H
diff --git a/libs/hwui/GradientCache.cpp b/libs/hwui/GradientCache.cpp
new file mode 100644
index 0000000..aeda416
--- /dev/null
+++ b/libs/hwui/GradientCache.cpp
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "OpenGLRenderer"
+
+#include <GLES2/gl2.h>
+
+#include <SkCanvas.h>
+#include <SkGradientShader.h>
+
+#include "GradientCache.h"
+
+namespace android {
+namespace uirenderer {
+
+///////////////////////////////////////////////////////////////////////////////
+// Constructors/destructor
+///////////////////////////////////////////////////////////////////////////////
+
+GradientCache::GradientCache(uint32_t maxByteSize):
+        mCache(GenerationCache<SkShader*, Texture*>::kUnlimitedCapacity),
+        mSize(0), mMaxSize(maxByteSize) {
+    mCache.setOnEntryRemovedListener(this);
+}
+
+GradientCache::~GradientCache() {
+    mCache.clear();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Size management
+///////////////////////////////////////////////////////////////////////////////
+
+uint32_t GradientCache::getSize() {
+    return mSize;
+}
+
+uint32_t GradientCache::getMaxSize() {
+    return mMaxSize;
+}
+
+void GradientCache::setMaxSize(uint32_t maxSize) {
+    mMaxSize = maxSize;
+    while (mSize > mMaxSize) {
+        mCache.removeOldest();
+    }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Callbacks
+///////////////////////////////////////////////////////////////////////////////
+
+void GradientCache::operator()(SkShader*& shader, Texture*& texture) {
+    if (shader) {
+        const uint32_t size = texture->width * texture->height * 4;
+        mSize -= size;
+    }
+
+    if (texture) {
+        glDeleteTextures(1, &texture->id);
+        delete texture;
+    }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Caching
+///////////////////////////////////////////////////////////////////////////////
+
+Texture* GradientCache::get(SkShader* shader) {
+    Texture* texture = mCache.get(shader);
+    return texture;
+}
+
+void GradientCache::remove(SkShader* shader) {
+    mCache.remove(shader);
+}
+
+void GradientCache::clear() {
+    mCache.clear();
+}
+
+Texture* GradientCache::addLinearGradient(SkShader* shader, float* bounds, uint32_t* colors,
+        float* positions, int count, SkShader::TileMode tileMode) {
+    SkBitmap bitmap;
+    bitmap.setConfig(SkBitmap::kARGB_8888_Config, 1024, 1);
+    bitmap.allocPixels();
+    bitmap.eraseColor(0);
+
+    SkCanvas canvas(bitmap);
+
+    SkPoint points[2];
+    points[0].set(0.0f, 0.0f);
+    points[1].set(bitmap.width(), 0.0f);
+
+    SkShader* localShader = SkGradientShader::CreateLinear(points,
+            reinterpret_cast<const SkColor*>(colors), positions, count, tileMode);
+
+    SkPaint p;
+    p.setStyle(SkPaint::kStrokeAndFill_Style);
+    p.setShader(localShader)->unref();
+
+    canvas.drawRectCoords(0.0f, 0.0f, bitmap.width(), 1.0f, p);
+
+    // Asume the cache is always big enough
+    const uint32_t size = bitmap.rowBytes() * bitmap.height();
+    while (mSize + size > mMaxSize) {
+        mCache.removeOldest();
+    }
+
+    Texture* texture = new Texture;
+    generateTexture(&bitmap, texture);
+
+    mSize += size;
+    mCache.put(shader, texture);
+
+    return texture;
+}
+
+void GradientCache::generateTexture(SkBitmap* bitmap, Texture* texture) {
+    SkAutoLockPixels autoLock(*bitmap);
+    if (!bitmap->readyToDraw()) {
+        LOGE("Cannot generate texture from shader");
+        return;
+    }
+
+    texture->generation = bitmap->getGenerationID();
+    texture->width = bitmap->width();
+    texture->height = bitmap->height();
+
+    glGenTextures(1, &texture->id);
+
+    glBindTexture(GL_TEXTURE_2D, texture->id);
+    glPixelStorei(GL_UNPACK_ALIGNMENT, bitmap->bytesPerPixel());
+
+    texture->blend = !bitmap->isOpaque();
+    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bitmap->rowBytesAsPixels(), texture->height, 0,
+            GL_RGBA, GL_UNSIGNED_BYTE, bitmap->getPixels());
+
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+}
+
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/GradientCache.h b/libs/hwui/GradientCache.h
new file mode 100644
index 0000000..12c8a23
--- /dev/null
+++ b/libs/hwui/GradientCache.h
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_UI_GRADIENT_CACHE_H
+#define ANDROID_UI_GRADIENT_CACHE_H
+
+#include <SkShader.h>
+
+#include "Texture.h"
+#include "GenerationCache.h"
+
+namespace android {
+namespace uirenderer {
+
+/**
+ * A simple LRU gradient cache. The cache has a maximum size expressed in bytes.
+ * Any texture added to the cache causing the cache to grow beyond the maximum
+ * allowed size will also cause the oldest texture to be kicked out.
+ */
+class GradientCache: public OnEntryRemoved<SkShader*, Texture*> {
+public:
+    GradientCache(uint32_t maxByteSize);
+    ~GradientCache();
+
+    /**
+     * Used as a callback when an entry is removed from the cache.
+     * Do not invoke directly.
+     */
+    void operator()(SkShader*& shader, Texture*& texture);
+
+    /**
+     * Adds a new linear gradient to the cache. The generated texture is
+     * returned.
+     */
+    Texture* addLinearGradient(SkShader* shader, float* bounds, uint32_t* colors,
+            float* positions, int count, SkShader::TileMode tileMode);
+    /**
+     * Returns the texture associated with the specified shader.
+     */
+    Texture* get(SkShader* shader);
+    /**
+     * Removes the texture associated with the specified shader. Returns NULL
+     * if the texture cannot be found. Upon remove the texture is freed.
+     */
+    void remove(SkShader* shader);
+    /**
+     * Clears the cache. This causes all textures to be deleted.
+     */
+    void clear();
+
+    /**
+     * Sets the maximum size of the cache in bytes.
+     */
+    void setMaxSize(uint32_t maxSize);
+    /**
+     * Returns the maximum size of the cache in bytes.
+     */
+    uint32_t getMaxSize();
+    /**
+     * Returns the current size of the cache in bytes.
+     */
+    uint32_t getSize();
+
+private:
+    void generateTexture(SkBitmap* bitmap, Texture* texture);
+
+    GenerationCache<SkShader*, Texture*> mCache;
+
+    uint32_t mSize;
+    uint32_t mMaxSize;
+}; // class GradientCache
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_UI_GRADIENT_CACHE_H
diff --git a/libs/hwui/Layer.h b/libs/hwui/Layer.h
new file mode 100644
index 0000000..d4db782
--- /dev/null
+++ b/libs/hwui/Layer.h
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_UI_LAYER_H
+#define ANDROID_UI_LAYER_H
+
+#include <sys/types.h>
+
+#include <GLES2/gl2.h>
+
+#include <SkXfermode.h>
+
+#include "Rect.h"
+
+namespace android {
+namespace uirenderer {
+
+/**
+ * Dimensions of a layer.
+ */
+struct LayerSize {
+    LayerSize(): width(0), height(0), id(0) { }
+    LayerSize(const uint32_t width, const uint32_t height): width(width), height(height), id(0) { }
+    LayerSize(const LayerSize& size): width(size.width), height(size.height), id(size.id) { }
+
+    uint32_t width;
+    uint32_t height;
+
+    // Incremental id used by the layer cache to store multiple
+    // LayerSize with the same dimensions
+    uint32_t id;
+
+    bool operator<(const LayerSize& rhs) const {
+        if (id != 0 && rhs.id != 0) {
+            return id < rhs.id;
+        }
+        if (width == rhs.width) {
+            return height < rhs.height;
+        }
+        return width < rhs.width;
+    }
+
+    bool operator==(const LayerSize& rhs) const {
+        return width == rhs.width && height == rhs.height;
+    }
+}; // struct LayerSize
+
+/**
+ * A layer has dimensions and is backed by an OpenGL texture.
+ */
+struct Layer {
+    /**
+     * Coordinates of the layer corresponding to this snapshot.
+     * Only set when the flag kFlagIsLayer is set.
+     */
+    Rect layer;
+    /**
+     * Name of the texture used to render the layer.
+     * Only set when the flag kFlagIsLayer is set.
+     */
+    GLuint texture;
+    /**
+     * Name of the FBO used to render the layer.
+     * Only set when the flag kFlagIsLayer is set.
+     */
+    GLuint fbo;
+    /**
+     * Opacity of the layer.
+     * Only set when the flag kFlagIsLayer is set.
+     */
+    float alpha;
+    /**
+     * Blending mode of the layer.
+     * Only set when the flag kFlagIsLayer is set.
+     */
+    SkXfermode::Mode mode;
+    /**
+     * Indicates whether this layer should be blended.
+     */
+    bool blend;
+}; // struct Layer
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_UI_LAYER_H
diff --git a/libs/hwui/LayerCache.cpp b/libs/hwui/LayerCache.cpp
new file mode 100644
index 0000000..3d263a3
--- /dev/null
+++ b/libs/hwui/LayerCache.cpp
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "OpenGLRenderer"
+
+#include <GLES2/gl2.h>
+
+#include <utils/Log.h>
+
+#include "LayerCache.h"
+
+namespace android {
+namespace uirenderer {
+
+///////////////////////////////////////////////////////////////////////////////
+// Constructors/destructor
+///////////////////////////////////////////////////////////////////////////////
+
+LayerCache::LayerCache(uint32_t maxByteSize):
+        mCache(GenerationCache<LayerSize, Layer*>::kUnlimitedCapacity),
+        mIdGenerator(1), mSize(0), mMaxSize(maxByteSize) {
+}
+
+LayerCache::~LayerCache() {
+    clear();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Size management
+///////////////////////////////////////////////////////////////////////////////
+
+uint32_t LayerCache::getSize() {
+    return mSize;
+}
+
+uint32_t LayerCache::getMaxSize() {
+    return mMaxSize;
+}
+
+void LayerCache::setMaxSize(uint32_t maxSize) {
+    mMaxSize = maxSize;
+    while (mSize > mMaxSize) {
+        Layer* oldest = mCache.removeOldest();
+        deleteLayer(oldest);
+    }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Callbacks
+///////////////////////////////////////////////////////////////////////////////
+
+void LayerCache::operator()(LayerSize& size, Layer*& layer) {
+    deleteLayer(layer);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Caching
+///////////////////////////////////////////////////////////////////////////////
+
+void LayerCache::deleteLayer(Layer* layer) {
+    if (layer) {
+        mSize -= layer->layer.getWidth() * layer->layer.getHeight() * 4;
+
+        glDeleteFramebuffers(1, &layer->fbo);
+        glDeleteTextures(1, &layer->texture);
+        delete layer;
+    }
+}
+
+void LayerCache::clear() {
+    mCache.setOnEntryRemovedListener(this);
+    mCache.clear();
+    mCache.setOnEntryRemovedListener(NULL);
+}
+
+Layer* LayerCache::get(LayerSize& size, GLuint previousFbo) {
+    Layer* layer = mCache.remove(size);
+    if (layer) {
+        LAYER_LOGD("Reusing layer");
+
+        mSize -= layer->layer.getWidth() * layer->layer.getHeight() * 4;
+    } else {
+        LAYER_LOGD("Creating new layer");
+
+        layer = new Layer;
+        layer->blend = true;
+
+        // Generate the FBO and attach the texture
+        glGenFramebuffers(1, &layer->fbo);
+        glBindFramebuffer(GL_FRAMEBUFFER, layer->fbo);
+
+        // Generate the texture in which the FBO will draw
+        glGenTextures(1, &layer->texture);
+        glBindTexture(GL_TEXTURE_2D, layer->texture);
+
+        // The FBO will not be scaled, so we can use lower quality filtering
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+
+        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, size.width, size.height, 0,
+                GL_RGBA, GL_UNSIGNED_BYTE, NULL);
+
+        // Bind texture to FBO
+        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
+                layer->texture, 0);
+
+        GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
+        if (status != GL_FRAMEBUFFER_COMPLETE) {
+            LOGE("Framebuffer incomplete (GL error code 0x%x)", status);
+
+            glBindFramebuffer(GL_FRAMEBUFFER, previousFbo);
+
+            glDeleteFramebuffers(1, &layer->fbo);
+            glDeleteTextures(1, &layer->texture);
+            delete layer;
+
+            return NULL;
+        }
+    }
+
+    return layer;
+}
+
+bool LayerCache::put(LayerSize& layerSize, Layer* layer) {
+    const uint32_t size = layerSize.width * layerSize.height * 4;
+    // Don't even try to cache a layer that's bigger than the cache
+    if (size < mMaxSize) {
+        while (mSize + size > mMaxSize) {
+            Layer* oldest = mCache.removeOldest();
+            deleteLayer(oldest);
+        }
+
+        layerSize.id = mIdGenerator++;
+        mCache.put(layerSize, layer);
+        mSize += size;
+
+        return true;
+    }
+    return false;
+}
+
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/LayerCache.h b/libs/hwui/LayerCache.h
new file mode 100644
index 0000000..2580551
--- /dev/null
+++ b/libs/hwui/LayerCache.h
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_UI_LAYER_CACHE_H
+#define ANDROID_UI_LAYER_CACHE_H
+
+#include "Layer.h"
+#include "GenerationCache.h"
+
+namespace android {
+namespace uirenderer {
+
+///////////////////////////////////////////////////////////////////////////////
+// Defines
+///////////////////////////////////////////////////////////////////////////////
+
+// Debug
+#define DEBUG_LAYERS 0
+
+// Debug
+#if DEBUG_LAYERS
+    #define LAYER_LOGD(...) LOGD(__VA_ARGS__)
+#else
+    #define LAYER_LOGD(...)
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+// Cache
+///////////////////////////////////////////////////////////////////////////////
+
+class LayerCache: public OnEntryRemoved<LayerSize, Layer*> {
+public:
+    LayerCache(uint32_t maxByteSize);
+    ~LayerCache();
+
+    /**
+     * Used as a callback when an entry is removed from the cache.
+     * Do not invoke directly.
+     */
+    void operator()(LayerSize& size, Layer*& layer);
+
+    /**
+     * Returns the layer of specified dimensions. If not suitable layer
+     * can be found, a new one is created and returned. If creating a new
+     * layer fails, NULL is returned.
+     *
+     * When a layer is obtained from the cache, it is removed and the total
+     * size of the cache goes down.
+     *
+     * @param size The dimensions of the desired layer
+     * @param previousFbo The name of the FBO to bind to if creating a new
+     *        layer fails
+     */
+    Layer* get(LayerSize& size, GLuint previousFbo);
+    /**
+     * Adds the layer to the cache. The layer will not be added if there is
+     * not enough space available.
+     *
+     * @param size The dimensions of the layer
+     * @param layer The layer to add to the cache
+     *
+     * @return True if the layer was added, false otherwise.
+     */
+    bool put(LayerSize& size, Layer* layer);
+    /**
+     * Clears the cache. This causes all layers to be deleted.
+     */
+    void clear();
+
+    /**
+     * Sets the maximum size of the cache in bytes.
+     */
+    void setMaxSize(uint32_t maxSize);
+    /**
+     * Returns the maximum size of the cache in bytes.
+     */
+    uint32_t getMaxSize();
+    /**
+     * Returns the current size of the cache in bytes.
+     */
+    uint32_t getSize();
+
+private:
+    void deleteLayer(Layer* layer);
+
+    GenerationCache<LayerSize, Layer*> mCache;
+    uint32_t mIdGenerator;
+
+    uint32_t mSize;
+    uint32_t mMaxSize;
+}; // class LayerCache
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_UI_LAYER_CACHE_H
diff --git a/libs/hwui/MODULE_LICENSE_APACHE2 b/libs/hwui/MODULE_LICENSE_APACHE2
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/libs/hwui/MODULE_LICENSE_APACHE2
diff --git a/libs/hwui/Matrix.cpp b/libs/hwui/Matrix.cpp
new file mode 100644
index 0000000..0c31ba9
--- /dev/null
+++ b/libs/hwui/Matrix.cpp
@@ -0,0 +1,236 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "OpenGLRenderer"
+
+#include <math.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <utils/Log.h>
+
+#include <SkMatrix.h>
+
+#include "Matrix.h"
+
+namespace android {
+namespace uirenderer {
+
+void Matrix4::loadIdentity() {
+    data[0]  = 1.0f;
+    data[1]  = 0.0f;
+    data[2]  = 0.0f;
+    data[3]  = 0.0f;
+
+    data[4]  = 0.0f;
+    data[5]  = 1.0f;
+    data[6]  = 0.0f;
+    data[7]  = 0.0f;
+
+    data[8]  = 0.0f;
+    data[9]  = 0.0f;
+    data[10] = 1.0f;
+    data[11] = 0.0f;
+
+    data[12] = 0.0f;
+    data[13] = 0.0f;
+    data[14] = 0.0f;
+    data[15] = 1.0f;
+}
+
+void Matrix4::load(const float* v) {
+    memcpy(data, v, sizeof(data));
+}
+
+void Matrix4::load(const Matrix4& v) {
+    memcpy(data, v.data, sizeof(data));
+}
+
+void Matrix4::load(const SkMatrix& v) {
+    memset(data, 0, sizeof(data));
+
+    data[0]  = v[SkMatrix::kMScaleX];
+    data[4]  = v[SkMatrix::kMSkewX];
+    data[12] = v[SkMatrix::kMTransX];
+
+    data[1]  = v[SkMatrix::kMSkewY];
+    data[5]  = v[SkMatrix::kMScaleY];
+    data[13] = v[SkMatrix::kMTransY];
+
+    data[3]  = v[SkMatrix::kMPersp0];
+    data[7]  = v[SkMatrix::kMPersp1];
+    data[15] = v[SkMatrix::kMPersp2];
+
+    data[10] = 1.0f;
+}
+
+void Matrix4::copyTo(SkMatrix& v) const {
+    v.reset();
+
+    v.set(SkMatrix::kMScaleX, data[0]);
+    v.set(SkMatrix::kMSkewX,  data[4]);
+    v.set(SkMatrix::kMTransX, data[12]);
+
+    v.set(SkMatrix::kMSkewY,  data[1]);
+    v.set(SkMatrix::kMScaleY, data[5]);
+    v.set(SkMatrix::kMTransY, data[13]);
+
+    v.set(SkMatrix::kMPersp0, data[3]);
+    v.set(SkMatrix::kMPersp1, data[7]);
+    v.set(SkMatrix::kMPersp2, data[15]);
+}
+
+void Matrix4::loadInverse(const Matrix4& v) {
+    double scale = 1.0 /
+            (v.data[0]  * ((double) v.data[5]  * v.data[15] - (double) v.data[13] * v.data[7]) +
+             v.data[4]  * ((double) v.data[13] * v.data[3]  - (double) v.data[1]  * v.data[15]) +
+             v.data[12] * ((double) v.data[1]  * v.data[7]  - (double) v.data[5]  * v.data[3]));
+
+    data[0]  = (v.data[5] * v.data[15] - v.data[13] * v.data[7])  * scale;
+    data[4]  = (v.data[12] * v.data[7] - v.data[4]  * v.data[15]) * scale;
+    data[12] = (v.data[4] * v.data[13] - v.data[12] * v.data[5])  * scale;
+
+    data[1]  = (v.data[13] * v.data[3] - v.data[1]  * v.data[15]) * scale;
+    data[5]  = (v.data[0] * v.data[15] - v.data[12] * v.data[3])  * scale;
+    data[13] = (v.data[12] * v.data[1] - v.data[0]  * v.data[13]) * scale;
+
+    data[3]  = (v.data[1] * v.data[7] - v.data[5] * v.data[3]) * scale;
+    data[7]  = (v.data[4] * v.data[3] - v.data[0] * v.data[7]) * scale;
+    data[15] = (v.data[0] * v.data[5] - v.data[4] * v.data[1]) * scale;
+}
+
+void Matrix4::copyTo(float* v) const {
+    memcpy(v, data, sizeof(data));
+}
+
+float Matrix4::getTranslateX() {
+    return data[12];
+}
+
+float Matrix4::getTranslateY() {
+    return data[13];
+}
+
+void Matrix4::loadTranslate(float x, float y, float z) {
+    loadIdentity();
+    data[12] = x;
+    data[13] = y;
+    data[14] = z;
+}
+
+void Matrix4::loadScale(float sx, float sy, float sz) {
+    loadIdentity();
+    data[0]  = sx;
+    data[5]  = sy;
+    data[10] = sz;
+}
+
+void Matrix4::loadRotate(float angle, float x, float y, float z) {
+    data[3]  = 0.0f;
+    data[7]  = 0.0f;
+    data[11] = 0.0f;
+    data[12] = 0.0f;
+    data[13] = 0.0f;
+    data[14] = 0.0f;
+    data[15] = 1.0f;
+
+    angle *= float(M_PI / 180.0f);
+    float c = cosf(angle);
+    float s = sinf(angle);
+
+    const float length = sqrtf(x * x + y * y + z * z);
+    float recipLen = 1.0f / length;
+    x *= recipLen;
+    y *= recipLen;
+    z *= recipLen;
+
+    const float nc = 1.0f - c;
+    const float xy = x * y;
+    const float yz = y * z;
+    const float zx = z * x;
+    const float xs = x * s;
+    const float ys = y * s;
+    const float zs = z * s;
+
+    data[0]  = x * x * nc +  c;
+    data[4]  =    xy * nc - zs;
+    data[8]  =    zx * nc + ys;
+    data[1]  =    xy * nc + zs;
+    data[5]  = y * y * nc +  c;
+    data[9]  =    yz * nc - xs;
+    data[2]  =    zx * nc - ys;
+    data[6]  =    yz * nc + xs;
+    data[10] = z * z * nc +  c;
+}
+
+void Matrix4::loadMultiply(const Matrix4& u, const Matrix4& v) {
+    for (int i = 0 ; i < 4 ; i++) {
+        float x = 0;
+        float y = 0;
+        float z = 0;
+        float w = 0;
+
+        for (int j = 0 ; j < 4 ; j++) {
+            const float e = v.get(i, j);
+            x += u.get(j, 0) * e;
+            y += u.get(j, 1) * e;
+            z += u.get(j, 2) * e;
+            w += u.get(j, 3) * e;
+        }
+
+        set(i, 0, x);
+        set(i, 1, y);
+        set(i, 2, z);
+        set(i, 3, w);
+    }
+}
+
+void Matrix4::loadOrtho(float left, float right, float bottom, float top, float near, float far) {
+    loadIdentity();
+    data[0]  = 2.0f / (right - left);
+    data[5]  = 2.0f / (top - bottom);
+    data[10] = -2.0f / (far - near);
+    data[12] = -(right + left) / (right - left);
+    data[13] = -(top + bottom) / (top - bottom);
+    data[14] = -(far + near) / (far - near);
+}
+
+#define MUL_ADD_STORE(a, b, c) a = (a) * (b) + (c)
+
+void Matrix4::mapRect(Rect& r) const {
+    const float sx = data[0];
+    const float sy = data[5];
+
+    const float tx = data[12];
+    const float ty = data[13];
+
+    MUL_ADD_STORE(r.left, sx, tx);
+    MUL_ADD_STORE(r.right, sx, tx);
+    MUL_ADD_STORE(r.top, sy, ty);
+    MUL_ADD_STORE(r.bottom, sy, ty);
+}
+
+void Matrix4::dump() const {
+    LOGD("Matrix4[");
+    LOGD("  %f %f %f %f", data[0], data[4], data[ 8], data[12]);
+    LOGD("  %f %f %f %f", data[1], data[5], data[ 9], data[13]);
+    LOGD("  %f %f %f %f", data[2], data[6], data[10], data[14]);
+    LOGD("  %f %f %f %f", data[3], data[7], data[11], data[15]);
+    LOGD("]");
+}
+
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/Matrix.h b/libs/hwui/Matrix.h
new file mode 100644
index 0000000..b8a4da7
--- /dev/null
+++ b/libs/hwui/Matrix.h
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_UI_MATRIX_H
+#define ANDROID_UI_MATRIX_H
+
+#include <SkMatrix.h>
+
+#include "Rect.h"
+
+namespace android {
+namespace uirenderer {
+
+///////////////////////////////////////////////////////////////////////////////
+// Classes
+///////////////////////////////////////////////////////////////////////////////
+
+class Matrix4 {
+public:
+    float data[16];
+
+    Matrix4() {
+        loadIdentity();
+    }
+
+    Matrix4(const float* v) {
+        load(v);
+    }
+
+    Matrix4(const Matrix4& v) {
+        load(v);
+    }
+
+    Matrix4(const SkMatrix& v) {
+        load(v);
+    }
+
+    void loadIdentity();
+
+    void load(const float* v);
+    void load(const Matrix4& v);
+    void load(const SkMatrix& v);
+
+    void loadInverse(const Matrix4& v);
+
+    void loadTranslate(float x, float y, float z);
+    void loadScale(float sx, float sy, float sz);
+    void loadRotate(float angle, float x, float y, float z);
+    void loadMultiply(const Matrix4& u, const Matrix4& v);
+
+    void loadOrtho(float left, float right, float bottom, float top, float near, float far);
+
+    void multiply(const Matrix4& v) {
+        Matrix4 u;
+        u.loadMultiply(*this, v);
+        load(u);
+    }
+
+    void translate(float x, float y, float z) {
+        Matrix4 u;
+        u.loadTranslate(x, y, z);
+        multiply(u);
+    }
+
+    void scale(float sx, float sy, float sz) {
+        Matrix4 u;
+        u.loadScale(sx, sy, sz);
+        multiply(u);
+    }
+
+    void rotate(float angle, float x, float y, float z) {
+        Matrix4 u;
+        u.loadRotate(angle, x, y, z);
+        multiply(u);
+    }
+
+    void copyTo(float* v) const;
+    void copyTo(SkMatrix& v) const;
+
+    /**
+     * Does not apply rotations!
+     */
+    void mapRect(Rect& r) const;
+
+    float getTranslateX();
+    float getTranslateY();
+
+    void dump() const;
+
+private:
+    inline float get(int i, int j) const {
+        return data[i * 4 + j];
+    }
+
+    inline void set(int i, int j, float v) {
+        data[i * 4 + j] = v;
+    }
+}; // class Matrix4
+
+///////////////////////////////////////////////////////////////////////////////
+// Types
+///////////////////////////////////////////////////////////////////////////////
+
+typedef Matrix4 mat4;
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_UI_MATRIX_H
diff --git a/libs/hwui/NOTICE b/libs/hwui/NOTICE
new file mode 100644
index 0000000..c5b1efa
--- /dev/null
+++ b/libs/hwui/NOTICE
@@ -0,0 +1,190 @@
+
+   Copyright (c) 2005-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.
+
+   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.
+
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
new file mode 100644
index 0000000..d694039
--- /dev/null
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -0,0 +1,820 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "OpenGLRenderer"
+
+#include <stdlib.h>
+#include <stdint.h>
+#include <sys/types.h>
+
+#include <SkCanvas.h>
+#include <SkTypeface.h>
+
+#include <cutils/properties.h>
+#include <utils/Log.h>
+
+#include "OpenGLRenderer.h"
+#include "Properties.h"
+
+namespace android {
+namespace uirenderer {
+
+///////////////////////////////////////////////////////////////////////////////
+// Defines
+///////////////////////////////////////////////////////////////////////////////
+
+#define DEFAULT_TEXTURE_CACHE_SIZE 20.0f
+#define DEFAULT_LAYER_CACHE_SIZE 10.0f
+#define DEFAULT_PATCH_CACHE_SIZE 100
+#define DEFAULT_GRADIENT_CACHE_SIZE 0.5f
+
+#define REQUIRED_TEXTURE_UNITS_COUNT 3
+
+// Converts a number of mega-bytes into bytes
+#define MB(s) s * 1024 * 1024
+
+// Generates simple and textured vertices
+#define FV(x, y, u, v) { { x, y }, { u, v } }
+
+///////////////////////////////////////////////////////////////////////////////
+// Globals
+///////////////////////////////////////////////////////////////////////////////
+
+// This array is never used directly but used as a memcpy source in the
+// OpenGLRenderer constructor
+static const TextureVertex gMeshVertices[] = {
+        FV(0.0f, 0.0f, 0.0f, 0.0f),
+        FV(1.0f, 0.0f, 1.0f, 0.0f),
+        FV(0.0f, 1.0f, 0.0f, 1.0f),
+        FV(1.0f, 1.0f, 1.0f, 1.0f)
+};
+static const GLsizei gMeshStride = sizeof(TextureVertex);
+static const GLsizei gMeshCount = 4;
+
+/**
+ * Structure mapping Skia xfermodes to OpenGL blending factors.
+ */
+struct Blender {
+    SkXfermode::Mode mode;
+    GLenum src;
+    GLenum dst;
+}; // struct Blender
+
+// In this array, the index of each Blender equals the value of the first
+// entry. For instance, gBlends[1] == gBlends[SkXfermode::kSrc_Mode]
+static const Blender gBlends[] = {
+        { SkXfermode::kClear_Mode,   GL_ZERO,                 GL_ZERO },
+        { SkXfermode::kSrc_Mode,     GL_ONE,                  GL_ZERO },
+        { SkXfermode::kDst_Mode,     GL_ZERO,                 GL_ONE },
+        { SkXfermode::kSrcOver_Mode, GL_ONE,                  GL_ONE_MINUS_SRC_ALPHA },
+        { SkXfermode::kDstOver_Mode, GL_ONE_MINUS_DST_ALPHA,  GL_ONE },
+        { SkXfermode::kSrcIn_Mode,   GL_DST_ALPHA,            GL_ZERO },
+        { SkXfermode::kDstIn_Mode,   GL_ZERO,                 GL_SRC_ALPHA },
+        { SkXfermode::kSrcOut_Mode,  GL_ONE_MINUS_DST_ALPHA,  GL_ZERO },
+        { SkXfermode::kDstOut_Mode,  GL_ZERO,                 GL_ONE_MINUS_SRC_ALPHA },
+        { SkXfermode::kSrcATop_Mode, GL_DST_ALPHA,            GL_ONE_MINUS_SRC_ALPHA },
+        { SkXfermode::kDstATop_Mode, GL_ONE_MINUS_DST_ALPHA,  GL_SRC_ALPHA },
+        { SkXfermode::kXor_Mode,     GL_ONE_MINUS_DST_ALPHA,  GL_ONE_MINUS_SRC_ALPHA }
+};
+
+static const GLenum gTextureUnits[] = {
+        GL_TEXTURE0,
+        GL_TEXTURE1,
+        GL_TEXTURE2
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// Constructors/destructor
+///////////////////////////////////////////////////////////////////////////////
+
+OpenGLRenderer::OpenGLRenderer():
+        mBlend(false), mLastSrcMode(GL_ZERO), mLastDstMode(GL_ZERO),
+        mTextureCache(MB(DEFAULT_TEXTURE_CACHE_SIZE)),
+        mLayerCache(MB(DEFAULT_LAYER_CACHE_SIZE)),
+        mGradientCache(MB(DEFAULT_GRADIENT_CACHE_SIZE)),
+        mPatchCache(DEFAULT_PATCH_CACHE_SIZE) {
+    LOGD("Create OpenGLRenderer");
+
+    char property[PROPERTY_VALUE_MAX];
+    if (property_get(PROPERTY_TEXTURE_CACHE_SIZE, property, NULL) > 0) {
+        LOGD("  Setting texture cache size to %sMB", property);
+        mTextureCache.setMaxSize(MB(atof(property)));
+    } else {
+        LOGD("  Using default texture cache size of %.2fMB", DEFAULT_TEXTURE_CACHE_SIZE);
+    }
+
+    if (property_get(PROPERTY_LAYER_CACHE_SIZE, property, NULL) > 0) {
+        LOGD("  Setting layer cache size to %sMB", property);
+        mLayerCache.setMaxSize(MB(atof(property)));
+    } else {
+        LOGD("  Using default layer cache size of %.2fMB", DEFAULT_LAYER_CACHE_SIZE);
+    }
+
+    if (property_get(PROPERTY_GRADIENT_CACHE_SIZE, property, NULL) > 0) {
+        LOGD("  Setting gradient cache size to %sMB", property);
+        mLayerCache.setMaxSize(MB(atof(property)));
+    } else {
+        LOGD("  Using default gradient cache size of %.2fMB", DEFAULT_GRADIENT_CACHE_SIZE);
+    }
+
+    mCurrentProgram = NULL;
+    mShader = NULL;
+    mColorFilter = NULL;
+
+    memcpy(mMeshVertices, gMeshVertices, sizeof(gMeshVertices));
+
+    mFirstSnapshot = new Snapshot;
+
+    GLint maxTextureUnits;
+    glGetIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, &maxTextureUnits);
+    if (maxTextureUnits < REQUIRED_TEXTURE_UNITS_COUNT) {
+        LOGW("At least %d texture units are required!", REQUIRED_TEXTURE_UNITS_COUNT);
+    }
+}
+
+OpenGLRenderer::~OpenGLRenderer() {
+    LOGD("Destroy OpenGLRenderer");
+
+    mTextureCache.clear();
+    mLayerCache.clear();
+    mGradientCache.clear();
+    mPatchCache.clear();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Setup
+///////////////////////////////////////////////////////////////////////////////
+
+void OpenGLRenderer::setViewport(int width, int height) {
+    glViewport(0, 0, width, height);
+
+    mOrthoMatrix.loadOrtho(0, width, height, 0, -1, 1);
+
+    mWidth = width;
+    mHeight = height;
+    mFirstSnapshot->height = height;
+}
+
+void OpenGLRenderer::prepare() {
+    mSnapshot = new Snapshot(mFirstSnapshot);
+    mSaveCount = 0;
+
+    glDisable(GL_SCISSOR_TEST);
+
+    glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
+    glClear(GL_COLOR_BUFFER_BIT);
+
+    glEnable(GL_SCISSOR_TEST);
+    glScissor(0, 0, mWidth, mHeight);
+
+    mSnapshot->setClip(0.0f, 0.0f, mWidth, mHeight);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// State management
+///////////////////////////////////////////////////////////////////////////////
+
+int OpenGLRenderer::getSaveCount() const {
+    return mSaveCount;
+}
+
+int OpenGLRenderer::save(int flags) {
+    return saveSnapshot();
+}
+
+void OpenGLRenderer::restore() {
+    if (mSaveCount == 0) return;
+
+    if (restoreSnapshot()) {
+        setScissorFromClip();
+    }
+}
+
+void OpenGLRenderer::restoreToCount(int saveCount) {
+    if (saveCount <= 0 || saveCount > mSaveCount) return;
+
+    bool restoreClip = false;
+
+    while (mSaveCount != saveCount - 1) {
+        restoreClip |= restoreSnapshot();
+    }
+
+    if (restoreClip) {
+        setScissorFromClip();
+    }
+}
+
+int OpenGLRenderer::saveSnapshot() {
+    mSnapshot = new Snapshot(mSnapshot);
+    return ++mSaveCount;
+}
+
+bool OpenGLRenderer::restoreSnapshot() {
+    bool restoreClip = mSnapshot->flags & Snapshot::kFlagClipSet;
+    bool restoreLayer = mSnapshot->flags & Snapshot::kFlagIsLayer;
+    bool restoreOrtho = mSnapshot->flags & Snapshot::kFlagDirtyOrtho;
+
+    sp<Snapshot> current = mSnapshot;
+    sp<Snapshot> previous = mSnapshot->previous;
+
+    if (restoreOrtho) {
+        mOrthoMatrix.load(current->orthoMatrix);
+    }
+
+    if (restoreLayer) {
+        composeLayer(current, previous);
+    }
+
+    mSnapshot = previous;
+    mSaveCount--;
+
+    return restoreClip;
+}
+
+void OpenGLRenderer::composeLayer(sp<Snapshot> current, sp<Snapshot> previous) {
+    if (!current->layer) {
+        LOGE("Attempting to compose a layer that does not exist");
+        return;
+    }
+
+    // Unbind current FBO and restore previous one
+    // Most of the time, previous->fbo will be 0 to bind the default buffer
+    glBindFramebuffer(GL_FRAMEBUFFER, previous->fbo);
+
+    // Restore the clip from the previous snapshot
+    const Rect& clip = previous->clipRect;
+    glScissor(clip.left, mHeight - clip.bottom, clip.getWidth(), clip.getHeight());
+
+    Layer* layer = current->layer;
+    const Rect& rect = layer->layer;
+
+    drawTextureRect(rect.left, rect.top, rect.right, rect.bottom,
+            layer->texture, layer->alpha, layer->mode, layer->blend);
+
+    LayerSize size(rect.getWidth(), rect.getHeight());
+    // Failing to add the layer to the cache should happen only if the
+    // layer is too large
+    if (!mLayerCache.put(size, layer)) {
+        LAYER_LOGD("Deleting layer");
+
+        glDeleteFramebuffers(1, &layer->fbo);
+        glDeleteTextures(1, &layer->texture);
+
+        delete layer;
+    }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Layers
+///////////////////////////////////////////////////////////////////////////////
+
+int OpenGLRenderer::saveLayer(float left, float top, float right, float bottom,
+        const SkPaint* p, int flags) {
+    int count = saveSnapshot();
+
+    int alpha = 255;
+    SkXfermode::Mode mode;
+
+    if (p) {
+        alpha = p->getAlpha();
+        const bool isMode = SkXfermode::IsMode(p->getXfermode(), &mode);
+        if (!isMode) {
+            // Assume SRC_OVER
+            mode = SkXfermode::kSrcOver_Mode;
+        }
+    } else {
+        mode = SkXfermode::kSrcOver_Mode;
+    }
+
+    createLayer(mSnapshot, left, top, right, bottom, alpha, mode, flags);
+
+    return count;
+}
+
+int OpenGLRenderer::saveLayerAlpha(float left, float top, float right, float bottom,
+        int alpha, int flags) {
+    int count = saveSnapshot();
+    createLayer(mSnapshot, left, top, right, bottom, alpha, SkXfermode::kSrcOver_Mode, flags);
+    return count;
+}
+
+bool OpenGLRenderer::createLayer(sp<Snapshot> snapshot, float left, float top,
+        float right, float bottom, int alpha, SkXfermode::Mode mode,int flags) {
+    LAYER_LOGD("Requesting layer %fx%f", right - left, bottom - top);
+    LAYER_LOGD("Layer cache size = %d", mLayerCache.getSize());
+
+    GLuint previousFbo = snapshot->previous.get() ? snapshot->previous->fbo : 0;
+    LayerSize size(right - left, bottom - top);
+
+    Layer* layer = mLayerCache.get(size, previousFbo);
+    if (!layer) {
+        return false;
+    }
+
+    glBindFramebuffer(GL_FRAMEBUFFER, layer->fbo);
+
+    // Clear the FBO
+    glDisable(GL_SCISSOR_TEST);
+    glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
+    glClear(GL_COLOR_BUFFER_BIT);
+    glEnable(GL_SCISSOR_TEST);
+
+    // Save the layer in the snapshot
+    snapshot->flags |= Snapshot::kFlagIsLayer;
+    layer->mode = mode;
+    layer->alpha = alpha / 255.0f;
+    layer->layer.set(left, top, right, bottom);
+
+    snapshot->layer = layer;
+    snapshot->fbo = layer->fbo;
+
+    // Creates a new snapshot to draw into the FBO
+    saveSnapshot();
+    // TODO: This doesn't preserve other transformations (check Skia first)
+    mSnapshot->transform.loadTranslate(-left, -top, 0.0f);
+    mSnapshot->setClip(0.0f, 0.0f, right - left, bottom - top);
+    mSnapshot->height = bottom - top;
+    setScissorFromClip();
+
+    mSnapshot->flags = Snapshot::kFlagDirtyOrtho | Snapshot::kFlagClipSet;
+    mSnapshot->orthoMatrix.load(mOrthoMatrix);
+
+    // Change the ortho projection
+    mOrthoMatrix.loadOrtho(0.0f, right - left, bottom - top, 0.0f, 0.0f, 1.0f);
+
+    return true;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Transforms
+///////////////////////////////////////////////////////////////////////////////
+
+void OpenGLRenderer::translate(float dx, float dy) {
+    mSnapshot->transform.translate(dx, dy, 0.0f);
+}
+
+void OpenGLRenderer::rotate(float degrees) {
+    mSnapshot->transform.rotate(degrees, 0.0f, 0.0f, 1.0f);
+}
+
+void OpenGLRenderer::scale(float sx, float sy) {
+    mSnapshot->transform.scale(sx, sy, 1.0f);
+}
+
+void OpenGLRenderer::setMatrix(SkMatrix* matrix) {
+    mSnapshot->transform.load(*matrix);
+}
+
+void OpenGLRenderer::getMatrix(SkMatrix* matrix) {
+    mSnapshot->transform.copyTo(*matrix);
+}
+
+void OpenGLRenderer::concatMatrix(SkMatrix* matrix) {
+    mat4 m(*matrix);
+    mSnapshot->transform.multiply(m);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Clipping
+///////////////////////////////////////////////////////////////////////////////
+
+void OpenGLRenderer::setScissorFromClip() {
+    const Rect& clip = mSnapshot->clipRect;
+    glScissor(clip.left, mSnapshot->height - clip.bottom, clip.getWidth(), clip.getHeight());
+}
+
+const Rect& OpenGLRenderer::getClipBounds() {
+    return mSnapshot->getLocalClip();
+}
+
+bool OpenGLRenderer::quickReject(float left, float top, float right, float bottom) {
+    Rect r(left, top, right, bottom);
+    mSnapshot->transform.mapRect(r);
+    return !mSnapshot->clipRect.intersects(r);
+}
+
+bool OpenGLRenderer::clipRect(float left, float top, float right, float bottom, SkRegion::Op op) {
+    bool clipped = mSnapshot->clip(left, top, right, bottom, op);
+    if (clipped) {
+        setScissorFromClip();
+    }
+    return !mSnapshot->clipRect.isEmpty();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Drawing
+///////////////////////////////////////////////////////////////////////////////
+
+void OpenGLRenderer::drawBitmap(SkBitmap* bitmap, float left, float top, const SkPaint* paint) {
+    const float right = left + bitmap->width();
+    const float bottom = top + bitmap->height();
+
+    if (quickReject(left, top, right, bottom)) {
+        return;
+    }
+
+    const Texture* texture = mTextureCache.get(bitmap);
+    drawTextureRect(left, top, right, bottom, texture, paint);
+}
+
+void OpenGLRenderer::drawBitmap(SkBitmap* bitmap, const SkMatrix* matrix, const SkPaint* paint) {
+    Rect r(0.0f, 0.0f, bitmap->width(), bitmap->height());
+    const mat4 transform(*matrix);
+    transform.mapRect(r);
+
+    if (quickReject(r.left, r.top, r.right, r.bottom)) {
+        return;
+    }
+
+    const Texture* texture = mTextureCache.get(bitmap);
+    drawTextureRect(r.left, r.top, r.right, r.bottom, texture, paint);
+}
+
+void OpenGLRenderer::drawBitmap(SkBitmap* bitmap,
+         float srcLeft, float srcTop, float srcRight, float srcBottom,
+         float dstLeft, float dstTop, float dstRight, float dstBottom,
+         const SkPaint* paint) {
+    if (quickReject(dstLeft, dstTop, dstRight, dstBottom)) {
+        return;
+    }
+
+    const Texture* texture = mTextureCache.get(bitmap);
+
+    const float width = texture->width;
+    const float height = texture->height;
+
+    const float u1 = srcLeft / width;
+    const float v1 = srcTop / height;
+    const float u2 = srcRight / width;
+    const float v2 = srcBottom / height;
+
+    resetDrawTextureTexCoords(u1, v1, u2, v2);
+
+    drawTextureRect(dstLeft, dstTop, dstRight, dstBottom, texture, paint);
+
+    resetDrawTextureTexCoords(0.0f, 0.0f, 1.0f, 1.0f);
+}
+
+void OpenGLRenderer::drawPatch(SkBitmap* bitmap, Res_png_9patch* patch,
+        float left, float top, float right, float bottom, const SkPaint* paint) {
+    if (quickReject(left, top, right, bottom)) {
+        return;
+    }
+
+    const Texture* texture = mTextureCache.get(bitmap);
+
+    int alpha;
+    SkXfermode::Mode mode;
+    getAlphaAndMode(paint, &alpha, &mode);
+
+    Patch* mesh = mPatchCache.get(patch);
+    mesh->updateVertices(bitmap, left, top, right, bottom,
+            &patch->xDivs[0], &patch->yDivs[0], patch->numXDivs, patch->numYDivs);
+
+    // Specify right and bottom as +1.0f from left/top to prevent scaling since the
+    // patch mesh already defines the final size
+    drawTextureMesh(left, top, left + 1.0f, top + 1.0f, texture->id, alpha / 255.0f,
+            mode, texture->blend, &mesh->vertices[0].position[0],
+            &mesh->vertices[0].texture[0], mesh->indices, mesh->indicesCount);
+}
+
+void OpenGLRenderer::drawColor(int color, SkXfermode::Mode mode) {
+    const Rect& clip = mSnapshot->clipRect;
+    drawColorRect(clip.left, clip.top, clip.right, clip.bottom, color, mode, true);
+}
+
+void OpenGLRenderer::drawRect(float left, float top, float right, float bottom, const SkPaint* p) {
+    if (quickReject(left, top, right, bottom)) {
+        return;
+    }
+
+    SkXfermode::Mode mode;
+
+    const bool isMode = SkXfermode::IsMode(p->getXfermode(), &mode);
+    if (!isMode) {
+        // Assume SRC_OVER
+        mode = SkXfermode::kSrcOver_Mode;
+    }
+
+    // Skia draws using the color's alpha channel if < 255
+    // Otherwise, it uses the paint's alpha
+    int color = p->getColor();
+    if (((color >> 24) & 0xff) == 255) {
+        color |= p->getAlpha() << 24;
+    }
+
+    drawColorRect(left, top, right, bottom, color, mode);
+}
+
+void OpenGLRenderer::drawText(const char* text, int bytesCount, int count,
+        float x, float y, SkPaint* paint) {
+    if (text == NULL || count == 0 || (paint->getAlpha() == 0 && paint->getXfermode() == NULL)) {
+        return;
+    }
+
+    float length;
+    switch (paint->getTextAlign()) {
+        case SkPaint::kCenter_Align:
+            length = paint->measureText(text, bytesCount);
+            x -= length / 2.0f;
+            break;
+        case SkPaint::kRight_Align:
+            length = paint->measureText(text, bytesCount);
+            x -= length;
+            break;
+        default:
+            break;
+    }
+
+    int alpha;
+    SkXfermode::Mode mode;
+    getAlphaAndMode(paint, &alpha, &mode);
+
+    uint32_t color = paint->getColor();
+    const GLfloat a = alpha / 255.0f;
+    const GLfloat r = a * ((color >> 16) & 0xFF) / 255.0f;
+    const GLfloat g = a * ((color >>  8) & 0xFF) / 255.0f;
+    const GLfloat b = a * ((color      ) & 0xFF) / 255.0f;
+
+    mModelView.loadIdentity();
+
+    GLuint textureUnit = 0;
+    // Needs to be set prior to calling FontRenderer::getTexture()
+    glActiveTexture(gTextureUnits[textureUnit]);
+
+    ProgramDescription description;
+    description.hasTexture = true;
+    description.hasAlpha8Texture = true;
+    if (mShader) {
+        mShader->describe(description, mExtensions);
+    }
+    if (mColorFilter) {
+        mColorFilter->describe(description, mExtensions);
+    }
+
+    useProgram(mProgramCache.get(description));
+    mCurrentProgram->set(mOrthoMatrix, mModelView, mSnapshot->transform);
+
+    // Text is always blended, no need to check the shader
+    chooseBlending(true, mode);
+    bindTexture(mFontRenderer.getTexture(), GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE, textureUnit);
+    glUniform1i(mCurrentProgram->getUniform("sampler"), textureUnit);
+
+    int texCoordsSlot = mCurrentProgram->getAttrib("texCoords");
+    glEnableVertexAttribArray(texCoordsSlot);
+
+    // Always premultiplied
+    glUniform4f(mCurrentProgram->color, r, g, b, a);
+
+    textureUnit++;
+    // Setup attributes and uniforms required by the shaders
+    if (mShader) {
+        mShader->setupProgram(mCurrentProgram, mModelView, *mSnapshot, &textureUnit);
+    }
+    if (mColorFilter) {
+        mColorFilter->setupProgram(mCurrentProgram);
+    }
+
+    // TODO: Implement scale properly
+    const Rect& clip = mSnapshot->getLocalClip();
+    mFontRenderer.setFont(paint, SkTypeface::UniqueID(paint->getTypeface()), paint->getTextSize());
+    mFontRenderer.renderText(paint, &clip, text, 0, bytesCount, count, x, y);
+
+    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
+    glDisableVertexAttribArray(texCoordsSlot);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Shaders
+///////////////////////////////////////////////////////////////////////////////
+
+void OpenGLRenderer::resetShader() {
+    mShader = NULL;
+}
+
+void OpenGLRenderer::setupShader(SkiaShader* shader) {
+    mShader = shader;
+    if (mShader) {
+        mShader->set(&mTextureCache, &mGradientCache);
+    }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Color filters
+///////////////////////////////////////////////////////////////////////////////
+
+void OpenGLRenderer::resetColorFilter() {
+    mColorFilter = NULL;
+}
+
+void OpenGLRenderer::setupColorFilter(SkiaColorFilter* filter) {
+    mColorFilter = filter;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Drawing implementation
+///////////////////////////////////////////////////////////////////////////////
+
+void OpenGLRenderer::drawColorRect(float left, float top, float right, float bottom,
+        int color, SkXfermode::Mode mode, bool ignoreTransform) {
+    // If a shader is set, preserve only the alpha
+    if (mShader) {
+        color |= 0x00ffffff;
+    }
+
+    // Render using pre-multiplied alpha
+    const int alpha = (color >> 24) & 0xFF;
+    const GLfloat a = alpha / 255.0f;
+    const GLfloat r = a * ((color >> 16) & 0xFF) / 255.0f;
+    const GLfloat g = a * ((color >>  8) & 0xFF) / 255.0f;
+    const GLfloat b = a * ((color      ) & 0xFF) / 255.0f;
+
+    GLuint textureUnit = 0;
+
+    // Setup the blending mode
+    chooseBlending(alpha < 255 || (mShader && mShader->blend()), mode);
+
+    // Describe the required shaders
+    ProgramDescription description;
+    if (mShader) {
+        mShader->describe(description, mExtensions);
+    }
+    if (mColorFilter) {
+        mColorFilter->describe(description, mExtensions);
+    }
+
+    // Build and use the appropriate shader
+    useProgram(mProgramCache.get(description));
+
+    // Setup attributes
+    glVertexAttribPointer(mCurrentProgram->position, 2, GL_FLOAT, GL_FALSE,
+            gMeshStride, &mMeshVertices[0].position[0]);
+
+    // Setup uniforms
+    mModelView.loadTranslate(left, top, 0.0f);
+    mModelView.scale(right - left, bottom - top, 1.0f);
+    if (!ignoreTransform) {
+        mCurrentProgram->set(mOrthoMatrix, mModelView, mSnapshot->transform);
+    } else {
+        mat4 identity;
+        mCurrentProgram->set(mOrthoMatrix, mModelView, identity);
+    }
+    glUniform4f(mCurrentProgram->color, r, g, b, a);
+
+    // Setup attributes and uniforms required by the shaders
+    if (mShader) {
+        mShader->setupProgram(mCurrentProgram, mModelView, *mSnapshot, &textureUnit);
+    }
+    if (mColorFilter) {
+        mColorFilter->setupProgram(mCurrentProgram);
+    }
+
+    // Draw the mesh
+    glDrawArrays(GL_TRIANGLE_STRIP, 0, gMeshCount);
+}
+
+void OpenGLRenderer::drawTextureRect(float left, float top, float right, float bottom,
+        const Texture* texture, const SkPaint* paint) {
+    int alpha;
+    SkXfermode::Mode mode;
+    getAlphaAndMode(paint, &alpha, &mode);
+
+    drawTextureMesh(left, top, right, bottom, texture->id, alpha / 255.0f, mode, texture->blend,
+            &mMeshVertices[0].position[0], &mMeshVertices[0].texture[0], NULL);
+}
+
+void OpenGLRenderer::drawTextureRect(float left, float top, float right, float bottom,
+        GLuint texture, float alpha, SkXfermode::Mode mode, bool blend) {
+    drawTextureMesh(left, top, right, bottom, texture, alpha, mode, blend,
+            &mMeshVertices[0].position[0], &mMeshVertices[0].texture[0], NULL);
+}
+
+void OpenGLRenderer::drawTextureMesh(float left, float top, float right, float bottom,
+        GLuint texture, float alpha, SkXfermode::Mode mode, bool blend,
+        GLvoid* vertices, GLvoid* texCoords, GLvoid* indices, GLsizei elementsCount) {
+    ProgramDescription description;
+    description.hasTexture = true;
+    if (mColorFilter) {
+        mColorFilter->describe(description, mExtensions);
+    }
+
+    mModelView.loadTranslate(left, top, 0.0f);
+    mModelView.scale(right - left, bottom - top, 1.0f);
+
+    useProgram(mProgramCache.get(description));
+    mCurrentProgram->set(mOrthoMatrix, mModelView, mSnapshot->transform);
+
+    chooseBlending(blend || alpha < 1.0f, mode);
+
+    // Texture
+    bindTexture(texture, GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE, 0);
+    glUniform1i(mCurrentProgram->getUniform("sampler"), 0);
+
+    // Always premultiplied
+    glUniform4f(mCurrentProgram->color, alpha, alpha, alpha, alpha);
+
+    // Mesh
+    int texCoordsSlot = mCurrentProgram->getAttrib("texCoords");
+    glEnableVertexAttribArray(texCoordsSlot);
+    glVertexAttribPointer(mCurrentProgram->position, 2, GL_FLOAT, GL_FALSE,
+            gMeshStride, vertices);
+    glVertexAttribPointer(texCoordsSlot, 2, GL_FLOAT, GL_FALSE, gMeshStride, texCoords);
+
+    // Color filter
+    if (mColorFilter) {
+        mColorFilter->setupProgram(mCurrentProgram);
+    }
+
+    if (!indices) {
+        glDrawArrays(GL_TRIANGLE_STRIP, 0, gMeshCount);
+    } else {
+        glDrawElements(GL_TRIANGLES, elementsCount, GL_UNSIGNED_SHORT, indices);
+    }
+    glDisableVertexAttribArray(texCoordsSlot);
+}
+
+void OpenGLRenderer::chooseBlending(bool blend, SkXfermode::Mode mode, bool isPremultiplied) {
+    blend = blend || mode != SkXfermode::kSrcOver_Mode;
+    if (blend) {
+        if (!mBlend) {
+            glEnable(GL_BLEND);
+        }
+
+        GLenum sourceMode = gBlends[mode].src;
+        GLenum destMode = gBlends[mode].dst;
+        if (!isPremultiplied && sourceMode == GL_ONE) {
+            sourceMode = GL_SRC_ALPHA;
+        }
+
+        if (sourceMode != mLastSrcMode || destMode != mLastDstMode) {
+            glBlendFunc(sourceMode, destMode);
+            mLastSrcMode = sourceMode;
+            mLastDstMode = destMode;
+        }
+    } else if (mBlend) {
+        glDisable(GL_BLEND);
+    }
+    mBlend = blend;
+}
+
+bool OpenGLRenderer::useProgram(Program* program) {
+    if (!program->isInUse()) {
+        if (mCurrentProgram != NULL) mCurrentProgram->remove();
+        program->use();
+        mCurrentProgram = program;
+        return false;
+    }
+    return true;
+}
+
+void OpenGLRenderer::resetDrawTextureTexCoords(float u1, float v1, float u2, float v2) {
+    TextureVertex* v = &mMeshVertices[0];
+    TextureVertex::setUV(v++, u1, v1);
+    TextureVertex::setUV(v++, u2, v1);
+    TextureVertex::setUV(v++, u1, v2);
+    TextureVertex::setUV(v++, u2, v2);
+}
+
+void OpenGLRenderer::getAlphaAndMode(const SkPaint* paint, int* alpha, SkXfermode::Mode* mode) {
+    if (paint) {
+        const bool isMode = SkXfermode::IsMode(paint->getXfermode(), mode);
+        if (!isMode) {
+            // Assume SRC_OVER
+            *mode = SkXfermode::kSrcOver_Mode;
+        }
+
+        // Skia draws using the color's alpha channel if < 255
+        // Otherwise, it uses the paint's alpha
+        int color = paint->getColor();
+        *alpha = (color >> 24) & 0xFF;
+        if (*alpha == 255) {
+            *alpha = paint->getAlpha();
+        }
+    } else {
+        *mode = SkXfermode::kSrcOver_Mode;
+        *alpha = 255;
+    }
+}
+
+void OpenGLRenderer::bindTexture(GLuint texture, GLenum wrapS, GLenum wrapT, GLuint textureUnit) {
+    glActiveTexture(gTextureUnits[textureUnit]);
+    glBindTexture(GL_TEXTURE_2D, texture);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrapS);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrapT);
+}
+
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h
new file mode 100644
index 0000000..d2a291f
--- /dev/null
+++ b/libs/hwui/OpenGLRenderer.h
@@ -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.
+ */
+
+#ifndef ANDROID_UI_OPENGL_RENDERER_H
+#define ANDROID_UI_OPENGL_RENDERER_H
+
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
+
+#include <SkBitmap.h>
+#include <SkMatrix.h>
+#include <SkPaint.h>
+#include <SkRegion.h>
+#include <SkShader.h>
+#include <SkXfermode.h>
+
+#include <utils/RefBase.h>
+#include <utils/ResourceTypes.h>
+
+#include "Extensions.h"
+#include "Matrix.h"
+#include "Program.h"
+#include "Rect.h"
+#include "Snapshot.h"
+#include "TextureCache.h"
+#include "LayerCache.h"
+#include "GradientCache.h"
+#include "PatchCache.h"
+#include "Vertex.h"
+#include "FontRenderer.h"
+#include "ProgramCache.h"
+#include "SkiaShader.h"
+#include "SkiaColorFilter.h"
+
+namespace android {
+namespace uirenderer {
+
+///////////////////////////////////////////////////////////////////////////////
+// Renderer
+///////////////////////////////////////////////////////////////////////////////
+
+/**
+ * OpenGL renderer used to draw accelerated 2D graphics. The API is a
+ * simplified version of Skia's Canvas API.
+ */
+class OpenGLRenderer {
+public:
+    OpenGLRenderer();
+    ~OpenGLRenderer();
+
+    void setViewport(int width, int height);
+    void prepare();
+
+    int getSaveCount() const;
+    int save(int flags);
+    void restore();
+    void restoreToCount(int saveCount);
+
+    int saveLayer(float left, float top, float right, float bottom, const SkPaint* p, int flags);
+    int saveLayerAlpha(float left, float top, float right, float bottom, int alpha, int flags);
+
+    void translate(float dx, float dy);
+    void rotate(float degrees);
+    void scale(float sx, float sy);
+
+    void setMatrix(SkMatrix* matrix);
+    void getMatrix(SkMatrix* matrix);
+    void concatMatrix(SkMatrix* matrix);
+
+    const Rect& getClipBounds();
+    bool quickReject(float left, float top, float right, float bottom);
+    bool clipRect(float left, float top, float right, float bottom, SkRegion::Op op);
+
+    void drawBitmap(SkBitmap* bitmap, float left, float top, const SkPaint* paint);
+    void drawBitmap(SkBitmap* bitmap, const SkMatrix* matrix, const SkPaint* paint);
+    void drawBitmap(SkBitmap* bitmap, float srcLeft, float srcTop, float srcRight, float srcBottom,
+            float dstLeft, float dstTop, float dstRight, float dstBottom, const SkPaint* paint);
+    void drawPatch(SkBitmap* bitmap, Res_png_9patch* patch, float left, float top,
+            float right, float bottom, const SkPaint* paint);
+    void drawColor(int color, SkXfermode::Mode mode);
+    void drawRect(float left, float top, float right, float bottom, const SkPaint* paint);
+
+    void resetShader();
+    void setupShader(SkiaShader* shader);
+
+    void resetColorFilter();
+    void setupColorFilter(SkiaColorFilter* filter);
+
+    void drawText(const char* text, int bytesCount, int count, float x, float y, SkPaint* paint);
+
+private:
+    /**
+     * Saves the current state of the renderer as a new snapshot.
+     * The new snapshot is saved in mSnapshot and the previous snapshot
+     * is linked from mSnapshot->previous.
+     *
+     * @return The new save count. This value can be passed to #restoreToCount()
+     */
+    int saveSnapshot();
+
+    /**
+     * Restores the current snapshot; mSnapshot becomes mSnapshot->previous.
+     *
+     * @return True if the clip should be also reapplied by calling
+     *         #setScissorFromClip().
+     */
+    bool restoreSnapshot();
+
+    /**
+     * Sets the clipping rectangle using glScissor. The clip is defined by
+     * the current snapshot's clipRect member.
+     */
+    void setScissorFromClip();
+
+    /**
+     * Compose the layer defined in the current snapshot with the layer
+     * defined by the previous snapshot.
+     *
+     * The current snapshot *must* be a layer (flag kFlagIsLayer set.)
+     *
+     * @param curent The current snapshot containing the layer to compose
+     * @param previous The previous snapshot to compose the current layer with
+     */
+    void composeLayer(sp<Snapshot> current, sp<Snapshot> previous);
+
+    /**
+     * Creates a new layer stored in the specified snapshot.
+     *
+     * @param snapshot The snapshot associated with the new layer
+     * @param left The left coordinate of the layer
+     * @param top The top coordinate of the layer
+     * @param right The right coordinate of the layer
+     * @param bottom The bottom coordinate of the layer
+     * @param alpha The translucency of the layer
+     * @param mode The blending mode of the layer
+     * @param flags The layer save flags
+     *
+     * @return True if the layer was successfully created, false otherwise
+     */
+    bool createLayer(sp<Snapshot> snapshot, float left, float top, float right, float bottom,
+            int alpha, SkXfermode::Mode mode, int flags);
+
+    /**
+     * Draws a colored rectangle with the specified color. The specified coordinates
+     * are transformed by the current snapshot's transform matrix.
+     *
+     * @param left The left coordinate of the rectangle
+     * @param top The top coordinate of the rectangle
+     * @param right The right coordinate of the rectangle
+     * @param bottom The bottom coordinate of the rectangle
+     * @param color The rectangle's ARGB color, defined as a packed 32 bits word
+     * @param mode The Skia xfermode to use
+     * @param ignoreTransform True if the current transform should be ignored
+     */
+    void drawColorRect(float left, float top, float right, float bottom,
+    		int color, SkXfermode::Mode mode, bool ignoreTransform = false);
+
+    /**
+     * Draws a textured rectangle with the specified texture. The specified coordinates
+     * are transformed by the current snapshot's transform matrix.
+     *
+     * @param left The left coordinate of the rectangle
+     * @param top The top coordinate of the rectangle
+     * @param right The right coordinate of the rectangle
+     * @param bottom The bottom coordinate of the rectangle
+     * @param texture The texture name to map onto the rectangle
+     * @param alpha An additional translucency parameter, between 0.0f and 1.0f
+     * @param mode The blending mode
+     * @param blend True if the texture contains an alpha channel
+     */
+    void drawTextureRect(float left, float top, float right, float bottom, GLuint texture,
+            float alpha, SkXfermode::Mode mode, bool blend);
+
+    /**
+     * Draws a textured rectangle with the specified texture. The specified coordinates
+     * are transformed by the current snapshot's transform matrix.
+     *
+     * @param left The left coordinate of the rectangle
+     * @param top The top coordinate of the rectangle
+     * @param right The right coordinate of the rectangle
+     * @param bottom The bottom coordinate of the rectangle
+     * @param texture The texture to use
+     * @param paint The paint containing the alpha, blending mode, etc.
+     */
+    void drawTextureRect(float left, float top, float right, float bottom,
+            const Texture* texture, const SkPaint* paint);
+
+    /**
+     * Draws a textured mesh with the specified texture. If the indices are omitted, the
+     * mesh is drawn as a simple quad.
+     *
+     * @param left The left coordinate of the rectangle
+     * @param top The top coordinate of the rectangle
+     * @param right The right coordinate of the rectangle
+     * @param bottom The bottom coordinate of the rectangle
+     * @param texture The texture name to map onto the rectangle
+     * @param alpha An additional translucency parameter, between 0.0f and 1.0f
+     * @param mode The blending mode
+     * @param blend True if the texture contains an alpha channel
+     * @param vertices The vertices that define the mesh
+     * @param texCoords The texture coordinates of each vertex
+     * @param indices The indices of the vertices, can be NULL
+     * @param elementsCount The number of elements in the mesh, required by indices
+     */
+    void drawTextureMesh(float left, float top, float right, float bottom, GLuint texture,
+            float alpha, SkXfermode::Mode mode, bool blend,
+            GLvoid* vertices, GLvoid* texCoords, GLvoid* indices, GLsizei elementsCount = 0);
+
+    /**
+     * Resets the texture coordinates stored in mMeshVertices. Setting the values
+     * back to default is achieved by calling:
+     *
+     * resetDrawTextureTexCoords(0.0f, 0.0f, 1.0f, 1.0f);
+     *
+     * @param u1 The left coordinate of the texture
+     * @param v1 The bottom coordinate of the texture
+     * @param u2 The right coordinate of the texture
+     * @param v2 The top coordinate of the texture
+     */
+    void resetDrawTextureTexCoords(float u1, float v1, float u2, float v2);
+
+    /**
+     * Gets the alpha and xfermode out of a paint object. If the paint is null
+     * alpha will be 255 and the xfermode will be SRC_OVER.
+     *
+     * @param paint The paint to extract values from
+     * @param alpha Where to store the resulting alpha
+     * @param mode Where to store the resulting xfermode
+     */
+    inline void getAlphaAndMode(const SkPaint* paint, int* alpha, SkXfermode::Mode* mode);
+
+    /**
+     * Binds the specified texture with the specified wrap modes.
+     */
+    inline void bindTexture(GLuint texture, GLenum wrapS, GLenum wrapT, GLuint textureUnit = 0);
+
+    /**
+     * Enable or disable blending as necessary. This function sets the appropriate
+     * blend function based on the specified xfermode.
+     */
+    inline void chooseBlending(bool blend, SkXfermode::Mode mode, bool isPremultiplied = true);
+
+    /**
+     * Use the specified program with the current GL context. If the program is already
+     * in use, it will not be bound again. If it is not in use, the current program is
+     * marked unused and the specified program becomes used and becomes the new
+     * current program.
+     *
+     * @param program The program to use
+     *
+     * @return true If the specified program was already in use, false otherwise.
+     */
+    inline bool useProgram(Program* program);
+
+    // Dimensions of the drawing surface
+    int mWidth, mHeight;
+
+    // Matrix used for ortho projection in shaders
+    mat4 mOrthoMatrix;
+
+    // Model-view matrix used to position/size objects
+    mat4 mModelView;
+
+    // Number of saved states
+    int mSaveCount;
+    // Base state
+    sp<Snapshot> mFirstSnapshot;
+    // Current state
+    sp<Snapshot> mSnapshot;
+
+    // Shaders
+    Program* mCurrentProgram;
+    SkiaShader* mShader;
+
+    // Color filters
+    SkiaColorFilter* mColorFilter;
+
+    // Used to draw textured quads
+    TextureVertex mMeshVertices[4];
+
+    // Last known blend state
+    bool mBlend;
+    GLenum mLastSrcMode;
+    GLenum mLastDstMode;
+
+    // GL extensions
+    Extensions mExtensions;
+
+    // Font renderer
+    FontRenderer mFontRenderer;
+
+    // Various caches
+    TextureCache mTextureCache;
+    LayerCache mLayerCache;
+    GradientCache mGradientCache;
+    ProgramCache mProgramCache;
+    PatchCache mPatchCache;
+}; // class OpenGLRenderer
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_UI_OPENGL_RENDERER_H
diff --git a/libs/hwui/Patch.cpp b/libs/hwui/Patch.cpp
new file mode 100644
index 0000000..4b6bb37
--- /dev/null
+++ b/libs/hwui/Patch.cpp
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "OpenGLRenderer"
+
+#include <cstring>
+
+#include <utils/Log.h>
+
+#include "Patch.h"
+
+namespace android {
+namespace uirenderer {
+
+///////////////////////////////////////////////////////////////////////////////
+// Constructors/destructor
+///////////////////////////////////////////////////////////////////////////////
+
+Patch::Patch(const uint32_t xCount, const uint32_t yCount):
+        xCount(xCount + 2), yCount(yCount + 2) {
+    verticesCount = (xCount + 2) * (yCount + 2);
+    vertices = new TextureVertex[verticesCount];
+
+    // 2 triangles per patch, 3 vertices per triangle
+    indicesCount = (xCount + 1) * (yCount + 1) * 2 * 3;
+    indices = new uint16_t[indicesCount];
+
+    const uint32_t xNum = xCount + 1;
+    const uint32_t yNum = yCount + 1;
+
+    uint16_t* startIndices = indices;
+    uint32_t n = 0;
+    for (uint32_t y = 0; y < yNum; y++) {
+        for (uint32_t x = 0; x < xNum; x++) {
+            *startIndices++ = n;
+            *startIndices++ = n + 1;
+            *startIndices++ = n + xNum + 2;
+
+            *startIndices++ = n;
+            *startIndices++ = n + xNum + 2;
+            *startIndices++ = n + xNum + 1;
+
+            n += 1;
+        }
+        n += 1;
+    }
+}
+
+Patch::~Patch() {
+    delete indices;
+    delete vertices;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Vertices management
+///////////////////////////////////////////////////////////////////////////////
+
+void Patch::updateVertices(const SkBitmap* bitmap, float left, float top, float right,
+        float bottom, const int32_t* xDivs,  const int32_t* yDivs, const uint32_t width,
+        const uint32_t height) {
+    const uint32_t xStretchCount = (width + 1) >> 1;
+    const uint32_t yStretchCount = (height + 1) >> 1;
+
+    float xStretch = 0;
+    float yStretch = 0;
+    float xStretchTex = 0;
+    float yStretchTex = 0;
+
+    const float meshWidth = right - left;
+
+    const float bitmapWidth = float(bitmap->width());
+    const float bitmapHeight = float(bitmap->height());
+
+    if (xStretchCount > 0) {
+        uint32_t stretchSize = 0;
+        for (uint32_t i = 1; i < width; i += 2) {
+            stretchSize += xDivs[i] - xDivs[i - 1];
+        }
+        xStretchTex = (stretchSize / bitmapWidth) / xStretchCount;
+        const float fixed = bitmapWidth - stretchSize;
+        xStretch = (right - left - fixed) / xStretchCount;
+    }
+
+    if (yStretchCount > 0) {
+        uint32_t stretchSize = 0;
+        for (uint32_t i = 1; i < height; i += 2) {
+            stretchSize += yDivs[i] - yDivs[i - 1];
+        }
+        yStretchTex = (stretchSize / bitmapHeight) / yStretchCount;
+        const float fixed = bitmapHeight - stretchSize;
+        yStretch = (bottom - top - fixed) / yStretchCount;
+    }
+
+    float vy = 0.0f;
+    float ty = 0.0f;
+    TextureVertex* vertex = vertices;
+
+    generateVertices(vertex, 0.0f, 0.0f, xDivs, width, xStretch, xStretchTex,
+            meshWidth, bitmapWidth);
+    vertex += width + 2;
+
+    for (uint32_t y = 0; y < height; y++) {
+        if (y & 1) {
+            vy += yStretch;
+            ty += yStretchTex;
+        } else {
+            const float step = float(yDivs[y]);
+            vy += step;
+            ty += step / bitmapHeight;
+        }
+        generateVertices(vertex, vy, ty, xDivs, width, xStretch, xStretchTex,
+                meshWidth, bitmapWidth);
+        vertex += width + 2;
+    }
+
+    generateVertices(vertex, bottom - top, 1.0f, xDivs, width, xStretch, xStretchTex,
+            meshWidth, bitmapWidth);
+}
+
+inline void Patch::generateVertices(TextureVertex* vertex, float y, float v,
+        const int32_t xDivs[], uint32_t xCount, float xStretch, float xStretchTex,
+        float width, float widthTex) {
+    float vx = 0.0f;
+    float tx = 0.0f;
+
+    TextureVertex::set(vertex, vx, y, tx, v);
+    vertex++;
+
+    for (uint32_t x = 0; x < xCount; x++) {
+        if (x & 1) {
+            vx += xStretch;
+            tx += xStretchTex;
+        } else {
+            const float step = float(xDivs[x]);
+            vx += step;
+            tx += step / widthTex;
+        }
+
+        TextureVertex::set(vertex, vx, y, tx, v);
+        vertex++;
+    }
+
+    TextureVertex::set(vertex, width, y, 1.0f, v);
+    vertex++;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Debug tools
+///////////////////////////////////////////////////////////////////////////////
+
+void Patch::dump() {
+    LOGD("Vertices [");
+    for (uint32_t y = 0; y < yCount; y++) {
+        char buffer[512];
+        buffer[0] = '\0';
+        uint32_t offset = 0;
+        for (uint32_t x = 0; x < xCount; x++) {
+            TextureVertex* v = &vertices[y * xCount + x];
+            offset += sprintf(&buffer[offset], " (%.4f,%.4f)-(%.4f,%.4f)",
+                    v->position[0], v->position[1], v->texture[0], v->texture[1]);
+        }
+        LOGD("  [%s ]", buffer);
+    }
+    LOGD("]\nIndices [ ");
+    char buffer[4096];
+    buffer[0] = '\0';
+    uint32_t offset = 0;
+    for (uint32_t i = 0; i < indicesCount; i++) {
+        offset += sprintf(&buffer[offset], "%d ", indices[i]);
+    }
+    LOGD("  %s\n]", buffer);
+}
+
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/Patch.h b/libs/hwui/Patch.h
new file mode 100644
index 0000000..5d3ad03
--- /dev/null
+++ b/libs/hwui/Patch.h
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_UI_PATCH_H
+#define ANDROID_UI_PATCH_H
+
+#include <sys/types.h>
+
+#include <SkBitmap.h>
+
+#include "Vertex.h"
+
+namespace android {
+namespace uirenderer {
+
+/**
+ * Description of a patch.
+ */
+struct PatchDescription {
+    PatchDescription(): xCount(0), yCount(0) { }
+    PatchDescription(const uint32_t xCount, const uint32_t yCount):
+            xCount(xCount), yCount(yCount) { }
+    PatchDescription(const PatchDescription& description):
+            xCount(description.xCount), yCount(description.yCount) { }
+
+    uint32_t xCount;
+    uint32_t yCount;
+
+    bool operator<(const PatchDescription& rhs) const {
+        if (xCount == rhs.xCount) {
+            return yCount < rhs.yCount;
+        }
+        return xCount < rhs.xCount;
+    }
+
+    bool operator==(const PatchDescription& rhs) const {
+        return xCount == rhs.xCount && yCount == rhs.yCount;
+    }
+}; // struct PatchDescription
+
+/**
+ * An OpenGL patch. This contains an array of vertices and an array of
+ * indices to render the vertices.
+ */
+struct Patch {
+    Patch(const uint32_t xCount, const uint32_t yCount);
+    ~Patch();
+
+    void updateVertices(const SkBitmap* bitmap, float left, float top, float right, float bottom,
+            const int32_t* xDivs,  const int32_t* yDivs,
+            const uint32_t width, const uint32_t height);
+    void dump();
+
+    uint32_t xCount;
+    uint32_t yCount;
+
+    uint16_t* indices;
+    uint32_t indicesCount;
+
+    TextureVertex* vertices;
+    uint32_t verticesCount;
+
+private:
+    static inline void generateVertices(TextureVertex* vertex, float y, float v,
+            const int32_t xDivs[], uint32_t xCount, float xStretch, float xStretchTex,
+            float width, float widthTex);
+}; // struct Patch
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_UI_PATCH_H
diff --git a/libs/hwui/PatchCache.cpp b/libs/hwui/PatchCache.cpp
new file mode 100644
index 0000000..694e3fd
--- /dev/null
+++ b/libs/hwui/PatchCache.cpp
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "OpenGLRenderer"
+
+#include <utils/Log.h>
+#include <utils/ResourceTypes.h>
+
+#include "PatchCache.h"
+
+namespace android {
+namespace uirenderer {
+
+///////////////////////////////////////////////////////////////////////////////
+// Constructors/destructor
+///////////////////////////////////////////////////////////////////////////////
+
+PatchCache::PatchCache(uint32_t maxEntries): mCache(maxEntries) {
+}
+
+PatchCache::~PatchCache() {
+    clear();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Callbacks
+///////////////////////////////////////////////////////////////////////////////
+
+void PatchCache::operator()(PatchDescription& description, Patch*& mesh) {
+    if (mesh) delete mesh;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Caching
+///////////////////////////////////////////////////////////////////////////////
+
+void PatchCache::clear() {
+    mCache.setOnEntryRemovedListener(this);
+    mCache.clear();
+    mCache.setOnEntryRemovedListener(NULL);
+}
+
+Patch* PatchCache::get(const Res_png_9patch* patch) {
+    const uint32_t width = patch->numXDivs;
+    const uint32_t height = patch->numYDivs;
+    const PatchDescription description(width, height);
+
+    Patch* mesh = mCache.get(description);
+    if (!mesh) {
+        PATCH_LOGD("Creating new patch mesh, w=%d h=%d", width, height);
+        mesh = new Patch(width, height);
+        mCache.put(description, mesh);
+    }
+
+    return mesh;
+}
+
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/PatchCache.h b/libs/hwui/PatchCache.h
new file mode 100644
index 0000000..de95087
--- /dev/null
+++ b/libs/hwui/PatchCache.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_UI_PATCH_CACHE_H
+#define ANDROID_UI_PATCH_CACHE_H
+
+#include "Patch.h"
+#include "GenerationCache.h"
+
+namespace android {
+namespace uirenderer {
+
+///////////////////////////////////////////////////////////////////////////////
+// Defines
+///////////////////////////////////////////////////////////////////////////////
+
+// Debug
+#define DEBUG_PATCHES 0
+
+// Debug
+#if DEBUG_PATCHES
+    #define PATCH_LOGD(...) LOGD(__VA_ARGS__)
+#else
+    #define PATCH_LOGD(...)
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+// Cache
+///////////////////////////////////////////////////////////////////////////////
+
+class PatchCache: public OnEntryRemoved<PatchDescription, Patch*> {
+public:
+    PatchCache(uint32_t maxCapacity);
+    ~PatchCache();
+
+    /**
+     * Used as a callback when an entry is removed from the cache.
+     * Do not invoke directly.
+     */
+    void operator()(PatchDescription& description, Patch*& mesh);
+
+    Patch* get(const Res_png_9patch* patch);
+    void clear();
+
+private:
+    GenerationCache<PatchDescription, Patch*> mCache;
+}; // class PatchCache
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_UI_PATCH_CACHE_H
diff --git a/libs/hwui/Program.cpp b/libs/hwui/Program.cpp
new file mode 100644
index 0000000..dbae38e
--- /dev/null
+++ b/libs/hwui/Program.cpp
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "OpenGLRenderer"
+
+#include "Program.h"
+
+namespace android {
+namespace uirenderer {
+
+///////////////////////////////////////////////////////////////////////////////
+// Shaders
+///////////////////////////////////////////////////////////////////////////////
+
+#define SHADER_SOURCE(name, source) const char* name = #source
+
+///////////////////////////////////////////////////////////////////////////////
+// Base program
+///////////////////////////////////////////////////////////////////////////////
+
+Program::Program(const char* vertex, const char* fragment) {
+    vertexShader = buildShader(vertex, GL_VERTEX_SHADER);
+    fragmentShader = buildShader(fragment, GL_FRAGMENT_SHADER);
+
+    id = glCreateProgram();
+    glAttachShader(id, vertexShader);
+    glAttachShader(id, fragmentShader);
+    glLinkProgram(id);
+
+    GLint status;
+    glGetProgramiv(id, GL_LINK_STATUS, &status);
+    if (status != GL_TRUE) {
+        GLint infoLen = 0;
+        glGetProgramiv(id, GL_INFO_LOG_LENGTH, &infoLen);
+        if (infoLen > 1) {
+            char* log = (char*) malloc(sizeof(char) * infoLen);
+            glGetProgramInfoLog(id, infoLen, 0, log);
+            LOGE("Error while linking shaders: %s", log);
+            delete log;
+        }
+        glDeleteProgram(id);
+    }
+
+    mUse = false;
+
+    position = addAttrib("position");
+    color = addUniform("color");
+    transform = addUniform("transform");
+}
+
+Program::~Program() {
+    glDeleteShader(vertexShader);
+    glDeleteShader(fragmentShader);
+    glDeleteProgram(id);
+}
+
+int Program::addAttrib(const char* name) {
+    int slot = glGetAttribLocation(id, name);
+    attributes.add(name, slot);
+    return slot;
+}
+
+int Program::getAttrib(const char* name) {
+    ssize_t index = attributes.indexOfKey(name);
+    if (index >= 0) {
+        return attributes.valueAt(index);
+    }
+    return addAttrib(name);
+}
+
+int Program::addUniform(const char* name) {
+    int slot = glGetUniformLocation(id, name);
+    uniforms.add(name, slot);
+    return slot;
+}
+
+int Program::getUniform(const char* name) {
+    ssize_t index = uniforms.indexOfKey(name);
+    if (index >= 0) {
+        return uniforms.valueAt(index);
+    }
+    return addUniform(name);
+}
+
+GLuint Program::buildShader(const char* source, GLenum type) {
+    GLuint shader = glCreateShader(type);
+    glShaderSource(shader, 1, &source, 0);
+    glCompileShader(shader);
+
+    GLint status;
+    glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
+    if (status != GL_TRUE) {
+        // Some drivers return wrong values for GL_INFO_LOG_LENGTH
+        // use a fixed size instead
+        GLchar log[512];
+        glGetShaderInfoLog(shader, sizeof(log), 0, &log[0]);
+        LOGE("Error while compiling shader: %s", log);
+        glDeleteShader(shader);
+    }
+
+    return shader;
+}
+
+void Program::set(const mat4& projectionMatrix, const mat4& modelViewMatrix,
+        const mat4& transformMatrix) {
+    mat4 t(projectionMatrix);
+    t.multiply(transformMatrix);
+    t.multiply(modelViewMatrix);
+
+    glUniformMatrix4fv(transform, 1, GL_FALSE, &t.data[0]);
+}
+
+void Program::use() {
+    glUseProgram(id);
+    mUse = true;
+
+    glEnableVertexAttribArray(position);
+}
+
+void Program::remove() {
+    mUse = false;
+
+    glDisableVertexAttribArray(position);
+}
+
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/Program.h b/libs/hwui/Program.h
new file mode 100644
index 0000000..6531c74
--- /dev/null
+++ b/libs/hwui/Program.h
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_UI_PROGRAM_H
+#define ANDROID_UI_PROGRAM_H
+
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
+
+#include <utils/KeyedVector.h>
+
+#include "Matrix.h"
+
+namespace android {
+namespace uirenderer {
+
+/**
+ * A program holds a vertex and a fragment shader. It offers several utility
+ * methods to query attributes and uniforms.
+ */
+class Program {
+public:
+    /**
+     * Creates a new program with the specified vertex and fragment
+     * shaders sources.
+     */
+    Program(const char* vertex, const char* fragment);
+    virtual ~Program();
+
+    /**
+     * Binds this program to the GL context.
+     */
+    virtual void use();
+
+    /**
+     * Marks this program as unused. This will not unbind
+     * the program from the GL context.
+     */
+    virtual void remove();
+
+    /**
+     * Returns the OpenGL name of the specified attribute.
+     */
+    int getAttrib(const char* name);
+
+    /**
+     * Returns the OpenGL name of the specified uniform.
+     */
+    int getUniform(const char* name);
+
+    /**
+     * Indicates whether this program is currently in use with
+     * the GL context.
+     */
+    inline bool isInUse() const {
+        return mUse;
+    }
+
+    /**
+     * Binds the program with the specified projection, modelView and
+     * transform matrices.
+     */
+    void set(const mat4& projectionMatrix, const mat4& modelViewMatrix,
+             const mat4& transformMatrix);
+
+    /**
+     * Name of the position attribute.
+     */
+    int position;
+
+    /**
+     * Name of the color uniform.
+     */
+    int color;
+
+    /**
+     * Name of the transform uniform.
+     */
+    int transform;
+
+protected:
+    /**
+     * Adds an attribute with the specified name.
+     *
+     * @return The OpenGL name of the attribute.
+     */
+    int addAttrib(const char* name);
+
+    /**
+     * Adds a uniform with the specified name.
+     *
+     * @return The OpenGL name of the uniform.
+     */
+    int addUniform(const char* name);
+
+private:
+    /**
+     * Compiles the specified shader of the specified type.
+     *
+     * @return The name of the compiled shader.
+     */
+    GLuint buildShader(const char* source, GLenum type);
+
+    // Name of the OpenGL program
+    GLuint id;
+
+    // Name of the shaders
+    GLuint vertexShader;
+    GLuint fragmentShader;
+
+    // Keeps track of attributes and uniforms slots
+    KeyedVector<const char*, int> attributes;
+    KeyedVector<const char*, int> uniforms;
+
+    bool mUse;
+}; // class Program
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_UI_PROGRAM_H
diff --git a/libs/hwui/ProgramCache.cpp b/libs/hwui/ProgramCache.cpp
new file mode 100644
index 0000000..3205258
--- /dev/null
+++ b/libs/hwui/ProgramCache.cpp
@@ -0,0 +1,414 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "OpenGLRenderer"
+
+#include <utils/String8.h>
+
+#include "ProgramCache.h"
+
+namespace android {
+namespace uirenderer {
+
+///////////////////////////////////////////////////////////////////////////////
+// Vertex shaders snippets
+///////////////////////////////////////////////////////////////////////////////
+
+const char* gVS_Header_Attributes =
+        "attribute vec4 position;\n";
+const char* gVS_Header_Attributes_TexCoords =
+        "attribute vec2 texCoords;\n";
+const char* gVS_Header_Uniforms =
+        "uniform mat4 transform;\n";
+const char* gVS_Header_Uniforms_HasGradient =
+        "uniform float gradientLength;\n"
+        "uniform vec2 gradient;\n"
+        "uniform vec2 gradientStart;\n"
+        "uniform mat4 screenSpace;\n";
+const char* gVS_Header_Uniforms_HasBitmap =
+        "uniform mat4 textureTransform;\n"
+        "uniform vec2 textureDimension;\n";
+const char* gVS_Header_Varyings_HasTexture =
+        "varying vec2 outTexCoords;\n";
+const char* gVS_Header_Varyings_HasBitmap =
+        "varying vec2 outBitmapTexCoords;\n";
+const char* gVS_Header_Varyings_HasGradient =
+        "varying float index;\n";
+const char* gVS_Main =
+        "\nvoid main(void) {\n";
+const char* gVS_Main_OutTexCoords =
+        "    outTexCoords = texCoords;\n";
+const char* gVS_Main_OutGradientIndex =
+        "    vec4 location = screenSpace * position;\n"
+        "    index = dot(location.xy - gradientStart, gradient) * gradientLength;\n";
+const char* gVS_Main_OutBitmapTexCoords =
+        "    vec4 bitmapCoords = textureTransform * position;\n"
+        "    outBitmapTexCoords = bitmapCoords.xy * textureDimension;\n";
+const char* gVS_Main_Position =
+        "    gl_Position = transform * position;\n";
+const char* gVS_Footer =
+        "}\n\n";
+
+///////////////////////////////////////////////////////////////////////////////
+// Fragment shaders snippets
+///////////////////////////////////////////////////////////////////////////////
+
+const char* gFS_Header =
+        "precision mediump float;\n\n";
+const char* gFS_Uniforms_Color =
+        "uniform vec4 color;\n";
+const char* gFS_Uniforms_TextureSampler =
+        "uniform sampler2D sampler;\n";
+const char* gFS_Uniforms_GradientSampler =
+        "uniform sampler2D gradientSampler;\n";
+const char* gFS_Uniforms_BitmapSampler =
+        "uniform sampler2D bitmapSampler;\n";
+const char* gFS_Uniforms_ColorOp[4] = {
+        // None
+        "",
+        // Matrix
+        "uniform mat4 colorMatrix;\n"
+        "uniform vec4 colorMatrixVector;\n",
+        // Lighting
+        "uniform vec4 lightingMul;\n"
+        "uniform vec4 lightingAdd;\n",
+        // PorterDuff
+        "uniform vec4 colorBlend;\n"
+};
+const char* gFS_Main =
+        "\nvoid main(void) {\n"
+        "    vec4 fragColor;\n";
+const char* gFS_Main_FetchColor =
+        "    fragColor = color;\n";
+const char* gFS_Main_FetchTexture =
+        "    fragColor = color * texture2D(sampler, outTexCoords);\n";
+const char* gFS_Main_FetchA8Texture =
+        "    fragColor = color * texture2D(sampler, outTexCoords).a;\n";
+const char* gFS_Main_FetchGradient =
+        "    vec4 gradientColor = texture2D(gradientSampler, vec2(index, 0.5));\n";
+const char* gFS_Main_FetchBitmap =
+        "    vec4 bitmapColor = texture2D(bitmapSampler, outBitmapTexCoords);\n";
+const char* gFS_Main_FetchBitmapNpot =
+        "    vec4 bitmapColor = texture2D(bitmapSampler, wrap(outBitmapTexCoords));\n";
+const char* gFS_Main_BlendShadersBG =
+        "    fragColor = blendShaders(gradientColor, bitmapColor)";
+const char* gFS_Main_BlendShadersGB =
+        "    fragColor = blendShaders(bitmapColor, gradientColor)";
+const char* gFS_Main_BlendShaders_Modulate =
+        " * fragColor.a;\n";
+const char* gFS_Main_GradientShader_Modulate =
+        "    fragColor = gradientColor * fragColor.a;\n";
+const char* gFS_Main_BitmapShader_Modulate =
+        "    fragColor = bitmapColor * fragColor.a;\n";
+const char* gFS_Main_FragColor =
+        "    gl_FragColor = fragColor;\n";
+const char* gFS_Main_ApplyColorOp[4] = {
+        // None
+        "",
+        // Matrix
+        // TODO: Fix premultiplied alpha computations for color matrix
+        "    fragColor *= colorMatrix;\n"
+        "    fragColor += colorMatrixVector;\n"
+        "    fragColor.rgb *= fragColor.a;\n",
+        // Lighting
+        "    float lightingAlpha = fragColor.a;\n"
+        "    fragColor = min(fragColor * lightingMul + (lightingAdd * lightingAlpha), lightingAlpha);\n"
+        "    fragColor.a = lightingAlpha;\n",
+        // PorterDuff
+        "    fragColor = blendColors(colorBlend, fragColor);\n"
+};
+const char* gFS_Footer =
+        "}\n\n";
+
+///////////////////////////////////////////////////////////////////////////////
+// PorterDuff snippets
+///////////////////////////////////////////////////////////////////////////////
+
+const char* gPorterDuff[12] = {
+        // Clear
+        "return vec4(0.0, 0.0, 0.0, 0.0);\n",
+        // Src
+        "return src;\n",
+        // Dst
+        "return dst;\n",
+        // SrcOver
+        "return src + dst * (1.0 - src.a);\n",
+        // DstOver
+        "return dst + src * (1.0 - dst.a);\n",
+        // SrcIn
+        "return src * dst.a;\n",
+        // DstIn
+        "return dst * src.a;\n",
+        // SrcOut
+        "return src * (1.0 - dst.a);\n",
+        // DstOut
+        "return dst * (1.0 - src.a);\n",
+        // SrcAtop
+        "return vec4(src.rgb * dst.a + (1.0 - src.a) * dst.rgb, dst.a);\n",
+        // DstAtop
+        "return vec4(dst.rgb * src.a + (1.0 - dst.a) * src.rgb, src.a);\n",
+        // Xor
+        "return vec4(src.rgb * (1.0 - dst.a) + (1.0 - src.a) * dst.rgb, 1.0, "
+                "src.a + dst.a - 2.0 * src.a * dst.a);\n",
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// Constructors/destructors
+///////////////////////////////////////////////////////////////////////////////
+
+ProgramCache::ProgramCache() {
+}
+
+ProgramCache::~ProgramCache() {
+    clear();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Cache management
+///////////////////////////////////////////////////////////////////////////////
+
+void ProgramCache::clear() {
+    size_t count = mCache.size();
+    for (size_t i = 0; i < count; i++) {
+        delete mCache.valueAt(i);
+    }
+    mCache.clear();
+}
+
+Program* ProgramCache::get(const ProgramDescription& description) {
+    programid key = description.key();
+    ssize_t index = mCache.indexOfKey(key);
+    Program* program = NULL;
+    if (index < 0) {
+        PROGRAM_LOGD("Could not find program with key 0x%x", key);
+        program = generateProgram(description, key);
+        mCache.add(key, program);
+    } else {
+        program = mCache.valueAt(index);
+    }
+    return program;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Program generation
+///////////////////////////////////////////////////////////////////////////////
+
+Program* ProgramCache::generateProgram(const ProgramDescription& description, programid key) {
+    String8 vertexShader = generateVertexShader(description);
+    String8 fragmentShader = generateFragmentShader(description);
+
+    Program* program = new Program(vertexShader.string(), fragmentShader.string());
+    return program;
+}
+
+String8 ProgramCache::generateVertexShader(const ProgramDescription& description) {
+    // Add attributes
+    String8 shader(gVS_Header_Attributes);
+    if (description.hasTexture) {
+        shader.append(gVS_Header_Attributes_TexCoords);
+    }
+    // Uniforms
+    shader.append(gVS_Header_Uniforms);
+    if (description.hasGradient) {
+        shader.append(gVS_Header_Uniforms_HasGradient);
+    }
+    if (description.hasBitmap) {
+        shader.append(gVS_Header_Uniforms_HasBitmap);
+    }
+    // Varyings
+    if (description.hasTexture) {
+        shader.append(gVS_Header_Varyings_HasTexture);
+    }
+    if (description.hasGradient) {
+        shader.append(gVS_Header_Varyings_HasGradient);
+    }
+    if (description.hasBitmap) {
+        shader.append(gVS_Header_Varyings_HasBitmap);
+    }
+
+    // Begin the shader
+    shader.append(gVS_Main); {
+        if (description.hasTexture) {
+            shader.append(gVS_Main_OutTexCoords);
+        }
+        if (description.hasGradient) {
+            shader.append(gVS_Main_OutGradientIndex);
+        }
+        if (description.hasBitmap) {
+            shader.append(gVS_Main_OutBitmapTexCoords);
+        }
+        // Output transformed position
+        shader.append(gVS_Main_Position);
+    }
+    // End the shader
+    shader.append(gVS_Footer);
+
+    PROGRAM_LOGD("*** Generated vertex shader:\n\n%s", shader.string());
+
+    return shader;
+}
+
+String8 ProgramCache::generateFragmentShader(const ProgramDescription& description) {
+    // Set the default precision
+    String8 shader(gFS_Header);
+
+    // Varyings
+    if (description.hasTexture) {
+        shader.append(gVS_Header_Varyings_HasTexture);
+    }
+    if (description.hasGradient) {
+        shader.append(gVS_Header_Varyings_HasGradient);
+    }
+    if (description.hasBitmap) {
+        shader.append(gVS_Header_Varyings_HasBitmap);
+    }
+
+
+    // Uniforms
+    shader.append(gFS_Uniforms_Color);
+    if (description.hasTexture) {
+        shader.append(gFS_Uniforms_TextureSampler);
+    }
+    if (description.hasGradient) {
+        shader.append(gFS_Uniforms_GradientSampler);
+    }
+    if (description.hasBitmap) {
+        shader.append(gFS_Uniforms_BitmapSampler);
+    }
+    shader.append(gFS_Uniforms_ColorOp[description.colorOp]);
+
+    // Generate required functions
+    if (description.hasGradient && description.hasBitmap) {
+        generatePorterDuffBlend(shader, "blendShaders", description.shadersMode);
+    }
+    if (description.colorOp == ProgramDescription::kColorBlend) {
+        generatePorterDuffBlend(shader, "blendColors", description.colorMode);
+    }
+    if (description.isBitmapNpot) {
+        generateTextureWrap(shader, description.bitmapWrapS, description.bitmapWrapT);
+    }
+
+    // Begin the shader
+    shader.append(gFS_Main); {
+        // Stores the result in fragColor directly
+        if (description.hasTexture) {
+            if (description.hasAlpha8Texture) {
+                shader.append(gFS_Main_FetchA8Texture);
+            } else {
+                shader.append(gFS_Main_FetchTexture);
+            }
+        } else {
+            shader.append(gFS_Main_FetchColor);
+        }
+        if (description.hasGradient) {
+            shader.append(gFS_Main_FetchGradient);
+        }
+        if (description.hasBitmap) {
+            if (!description.isBitmapNpot) {
+                shader.append(gFS_Main_FetchBitmap);
+            } else {
+                shader.append(gFS_Main_FetchBitmapNpot);
+            }
+        }
+        // Case when we have two shaders set
+        if (description.hasGradient && description.hasBitmap) {
+            if (description.isBitmapFirst) {
+                shader.append(gFS_Main_BlendShadersBG);
+            } else {
+                shader.append(gFS_Main_BlendShadersGB);
+            }
+            shader.append(gFS_Main_BlendShaders_Modulate);
+        } else {
+            if (description.hasGradient) {
+                shader.append(gFS_Main_GradientShader_Modulate);
+            } else if (description.hasBitmap) {
+                shader.append(gFS_Main_BitmapShader_Modulate);
+            }
+        }
+        // Apply the color op if needed
+        shader.append(gFS_Main_ApplyColorOp[description.colorOp]);
+        // Output the fragment
+        shader.append(gFS_Main_FragColor);
+    }
+    // End the shader
+    shader.append(gFS_Footer);
+
+    if (DEBUG_PROGRAM_CACHE) {
+        PROGRAM_LOGD("*** Generated fragment shader:\n\n");
+        printLongString(shader);
+    }
+
+    return shader;
+}
+
+void ProgramCache::generatePorterDuffBlend(String8& shader, const char* name,
+        SkXfermode::Mode mode) {
+    shader.append("\nvec4 ");
+    shader.append(name);
+    shader.append("(vec4 src, vec4 dst) {\n");
+    shader.append("    ");
+    shader.append(gPorterDuff[mode]);
+    shader.append("}\n");
+}
+
+void ProgramCache::generateTextureWrap(String8& shader, GLenum wrapS, GLenum wrapT) {
+    shader.append("\nvec2 wrap(vec2 texCoords) {\n");
+    if (wrapS == GL_MIRRORED_REPEAT) {
+        shader.append("    float xMod2 = mod(texCoords.x, 2.0);\n");
+        shader.append("    if (xMod2 > 1.0) xMod2 = 2.0 - xMod2;\n");
+    }
+    if (wrapT == GL_MIRRORED_REPEAT) {
+        shader.append("    float yMod2 = mod(texCoords.y, 2.0);\n");
+        shader.append("    if (yMod2 > 1.0) yMod2 = 2.0 - yMod2;\n");
+    }
+    shader.append("    return vec2(");
+    switch (wrapS) {
+        case GL_REPEAT:
+            shader.append("mod(texCoords.x, 1.0)");
+            break;
+        case GL_MIRRORED_REPEAT:
+            shader.append("xMod2");
+            break;
+    }
+    shader.append(", ");
+    switch (wrapT) {
+        case GL_REPEAT:
+            shader.append("mod(texCoords.y, 1.0)");
+            break;
+        case GL_MIRRORED_REPEAT:
+            shader.append("yMod2");
+            break;
+    }
+    shader.append(");\n");
+    shader.append("}\n");
+}
+
+void ProgramCache::printLongString(const String8& shader) const {
+    ssize_t index = 0;
+    ssize_t lastIndex = 0;
+    const char* str = shader.string();
+    while ((index = shader.find("\n", index)) > -1) {
+        String8 line(str, index - lastIndex);
+        if (line.length() == 0) line.append("\n");
+        PROGRAM_LOGD("%s", line.string());
+        index++;
+        str += (index - lastIndex);
+        lastIndex = index;
+    }
+}
+
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/ProgramCache.h b/libs/hwui/ProgramCache.h
new file mode 100644
index 0000000..a1a4a0e
--- /dev/null
+++ b/libs/hwui/ProgramCache.h
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_UI_PROGRAM_CACHE_H
+#define ANDROID_UI_PROGRAM_CACHE_H
+
+#include <utils/KeyedVector.h>
+#include <utils/Log.h>
+#include <utils/String8.h>
+
+#include <GLES2/gl2.h>
+
+#include <SkXfermode.h>
+
+#include "Program.h"
+
+namespace android {
+namespace uirenderer {
+
+///////////////////////////////////////////////////////////////////////////////
+// Defines
+///////////////////////////////////////////////////////////////////////////////
+
+// Debug
+#define DEBUG_PROGRAM_CACHE 0
+
+// Debug
+#if DEBUG_PROGRAM_CACHE
+    #define PROGRAM_LOGD(...) LOGD(__VA_ARGS__)
+#else
+    #define PROGRAM_LOGD(...)
+#endif
+
+#define PROGRAM_KEY_TEXTURE 0x1
+#define PROGRAM_KEY_A8_TEXTURE 0x2
+#define PROGRAM_KEY_BITMAP 0x4
+#define PROGRAM_KEY_GRADIENT 0x8
+#define PROGRAM_KEY_BITMAP_FIRST 0x10
+#define PROGRAM_KEY_COLOR_MATRIX 0x20
+#define PROGRAM_KEY_COLOR_LIGHTING 0x40
+#define PROGRAM_KEY_COLOR_BLEND 0x80
+#define PROGRAM_KEY_BITMAP_NPOT 0x100
+
+#define PROGRAM_KEY_BITMAP_WRAPS_MASK 0x600
+#define PROGRAM_KEY_BITMAP_WRAPT_MASK 0x1800
+
+// Support only the 12 Porter-Duff modes for now
+#define PROGRAM_MAX_XFERMODE 0xC
+#define PROGRAM_XFERMODE_SHADER_SHIFT 24
+#define PROGRAM_XFERMODE_COLOR_OP_SHIFT 20
+
+#define PROGRAM_BITMAP_WRAPS_SHIFT 9
+#define PROGRAM_BITMAP_WRAPT_SHIFT 11
+
+///////////////////////////////////////////////////////////////////////////////
+// Types
+///////////////////////////////////////////////////////////////////////////////
+
+typedef uint32_t programid;
+
+///////////////////////////////////////////////////////////////////////////////
+// Cache
+///////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Describe the features required for a given program. The features
+ * determine the generation of both the vertex and fragment shaders.
+ * A ProgramDescription must be used in conjunction with a ProgramCache.
+ */
+struct ProgramDescription {
+    enum ColorModifier {
+        kColorNone,
+        kColorMatrix,
+        kColorLighting,
+        kColorBlend
+    };
+
+    ProgramDescription():
+        hasTexture(false), hasAlpha8Texture(false),
+        hasBitmap(false), isBitmapNpot(false), hasGradient(false),
+        shadersMode(SkXfermode::kClear_Mode), isBitmapFirst(false),
+        bitmapWrapS(GL_CLAMP_TO_EDGE), bitmapWrapT(GL_CLAMP_TO_EDGE),
+        colorOp(kColorNone), colorMode(SkXfermode::kClear_Mode) {
+    }
+
+    // Texturing
+    bool hasTexture;
+    bool hasAlpha8Texture;
+
+    // Shaders
+    bool hasBitmap;
+    bool isBitmapNpot;
+    bool hasGradient;
+    SkXfermode::Mode shadersMode;
+    bool isBitmapFirst;
+    GLenum bitmapWrapS;
+    GLenum bitmapWrapT;
+
+    // Color operations
+    int colorOp;
+    SkXfermode::Mode colorMode;
+
+    inline uint32_t getEnumForWrap(GLenum wrap) const {
+        switch (wrap) {
+            case GL_CLAMP_TO_EDGE:
+                return 0;
+            case GL_REPEAT:
+                return 1;
+            case GL_MIRRORED_REPEAT:
+                return 2;
+        }
+        return 0;
+    }
+
+    programid key() const {
+        programid key = 0;
+        if (hasTexture) key |= PROGRAM_KEY_TEXTURE;
+        if (hasAlpha8Texture) key |= PROGRAM_KEY_A8_TEXTURE;
+        if (hasBitmap) {
+            key |= PROGRAM_KEY_BITMAP;
+            if (isBitmapNpot) {
+                key |= PROGRAM_KEY_BITMAP_NPOT;
+                key |= getEnumForWrap(bitmapWrapS) << PROGRAM_BITMAP_WRAPS_SHIFT;
+                key |= getEnumForWrap(bitmapWrapT) << PROGRAM_BITMAP_WRAPT_SHIFT;
+            }
+        }
+        if (hasGradient) key |= PROGRAM_KEY_GRADIENT;
+        if (isBitmapFirst) key  |= PROGRAM_KEY_BITMAP_FIRST;
+        if (hasBitmap && hasGradient) {
+            key |= (shadersMode & PROGRAM_MAX_XFERMODE) << PROGRAM_XFERMODE_SHADER_SHIFT;
+        }
+        switch (colorOp) {
+            case kColorMatrix:
+                key |= PROGRAM_KEY_COLOR_MATRIX;
+                break;
+            case kColorLighting:
+                key |= PROGRAM_KEY_COLOR_LIGHTING;
+                break;
+            case kColorBlend:
+                key |= PROGRAM_KEY_COLOR_BLEND;
+                key |= (colorMode & PROGRAM_MAX_XFERMODE) << PROGRAM_XFERMODE_COLOR_OP_SHIFT;
+                break;
+            case kColorNone:
+                break;
+        }
+        return key;
+    }
+}; // struct ProgramDescription
+
+/**
+ * Generates and caches program. Programs are generated based on
+ * ProgramDescriptions.
+ */
+class ProgramCache {
+public:
+    ProgramCache();
+    ~ProgramCache();
+
+    Program* get(const ProgramDescription& description);
+
+    void clear();
+
+private:
+    Program* generateProgram(const ProgramDescription& description, programid key);
+    String8 generateVertexShader(const ProgramDescription& description);
+    String8 generateFragmentShader(const ProgramDescription& description);
+    void generatePorterDuffBlend(String8& shader, const char* name, SkXfermode::Mode mode);
+    void generateTextureWrap(String8& shader, GLenum wrapS, GLenum wrapT);
+
+    void printLongString(const String8& shader) const;
+
+    KeyedVector<programid, Program*> mCache;
+
+}; // class ProgramCache
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_UI_PROGRAM_CACHE_H
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
new file mode 100644
index 0000000..4f20a95
--- /dev/null
+++ b/libs/hwui/Properties.h
@@ -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.
+ */
+
+#ifndef ANDROID_UI_PROPERTIES_H
+#define ANDROID_UI_PROPERTIES_H
+
+/**
+ * This file contains the list of system properties used to configure
+ * the OpenGLRenderer.
+ */
+
+// These properties are defined in mega-bytes
+#define PROPERTY_TEXTURE_CACHE_SIZE "ro.hwui.texture_cache_size"
+#define PROPERTY_LAYER_CACHE_SIZE "ro.hwui.layer_cache_size"
+#define PROPERTY_GRADIENT_CACHE_SIZE "ro.hwui.gradient_cache_size"
+
+// These properties are defined in pixels
+#define PROPERTY_TEXT_CACHE_WIDTH "ro.hwui.text_cache_width"
+#define PROPERTY_TEXT_CACHE_HEIGHT "ro.hwui.text_cache_height"
+
+#endif // ANDROID_UI_PROPERTIES_H
+
diff --git a/libs/hwui/Rect.h b/libs/hwui/Rect.h
new file mode 100644
index 0000000..7be0c340
--- /dev/null
+++ b/libs/hwui/Rect.h
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_UI_RECT_H
+#define ANDROID_UI_RECT_H
+
+#include <utils/Log.h>
+
+namespace android {
+namespace uirenderer {
+
+///////////////////////////////////////////////////////////////////////////////
+// Structs
+///////////////////////////////////////////////////////////////////////////////
+
+struct Rect {
+    float left;
+    float top;
+    float right;
+    float bottom;
+
+    Rect():
+            left(0),
+            top(0),
+            right(0),
+            bottom(0) {
+    }
+
+    Rect(float left, float top, float right, float bottom):
+            left(left),
+            top(top),
+            right(right),
+            bottom(bottom) {
+    }
+
+    Rect(const Rect& r) {
+        set(r);
+    }
+
+    Rect(Rect& r) {
+        set(r);
+    }
+
+    Rect& operator=(const Rect& r) {
+        set(r);
+        return *this;
+    }
+
+    Rect& operator=(Rect& r) {
+        set(r);
+        return *this;
+    }
+
+    friend int operator==(const Rect& a, const Rect& b) {
+        return !memcmp(&a, &b, sizeof(a));
+    }
+
+    friend int operator!=(const Rect& a, const Rect& b) {
+        return memcmp(&a, &b, sizeof(a));
+    }
+
+    bool isEmpty() const {
+        return left >= right || top >= bottom;
+    }
+
+    void setEmpty() {
+        memset(this, 0, sizeof(*this));
+    }
+
+    void set(float left, float top, float right, float bottom) {
+        this->left = left;
+        this->right = right;
+        this->top = top;
+        this->bottom = bottom;
+    }
+
+    void set(const Rect& r) {
+        set(r.left, r.top, r.right, r.bottom);
+    }
+
+    float getWidth() const {
+        return right - left;
+    }
+
+    float getHeight() const {
+        return bottom - top;
+    }
+
+    bool intersects(float left, float top, float right, float bottom) const {
+        return left < right && top < bottom &&
+                this->left < this->right && this->top < this->bottom &&
+                this->left < right && left < this->right &&
+                this->top < bottom && top < this->bottom;
+    }
+
+    bool intersects(const Rect& r) const {
+        return intersects(r.left, r.top, r.right, r.bottom);
+    }
+
+    bool intersect(float left, float top, float right, float bottom) {
+        if (left < right && top < bottom && !this->isEmpty() &&
+                this->left < right && left < this->right &&
+                this->top < bottom && top < this->bottom) {
+
+            if (this->left < left) this->left = left;
+            if (this->top < top) this->top = top;
+            if (this->right > right) this->right = right;
+            if (this->bottom > bottom) this->bottom = bottom;
+
+            return true;
+        }
+        return false;
+    }
+
+    bool intersect(const Rect& r) {
+        return intersect(r.left, r.top, r.right, r.bottom);
+    }
+
+    bool unionWith(const Rect& r) {
+        if (r.left < r.right && r.top < r.bottom) {
+            if (left < right && top < bottom) {
+                if (left > r.left) left = r.left;
+                if (top > r.top) top = r.top;
+                if (right < r.right) right = r.right;
+                if (bottom < r.bottom) bottom = r.bottom;
+                return true;
+            } else {
+                left = r.left;
+                top = r.top;
+                right = r.right;
+                bottom = r.bottom;
+                return true;
+            }
+        }
+        return false;
+    }
+
+    void dump() const {
+        LOGD("Rect[l=%f t=%f r=%f b=%f]", left, top, right, bottom);
+    }
+
+}; // struct Rect
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_RECT_H
diff --git a/libs/hwui/SkiaColorFilter.cpp b/libs/hwui/SkiaColorFilter.cpp
new file mode 100644
index 0000000..fe57ae7
--- /dev/null
+++ b/libs/hwui/SkiaColorFilter.cpp
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "SkiaColorFilter.h"
+
+namespace android {
+namespace uirenderer {
+
+///////////////////////////////////////////////////////////////////////////////
+// Base color filter
+///////////////////////////////////////////////////////////////////////////////
+
+SkiaColorFilter::SkiaColorFilter(Type type, bool blend): mType(type), mBlend(blend) {
+}
+
+SkiaColorFilter::~SkiaColorFilter() {
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Color matrix filter
+///////////////////////////////////////////////////////////////////////////////
+
+SkiaColorMatrixFilter::SkiaColorMatrixFilter(float* matrix, float* vector):
+        SkiaColorFilter(kColorMatrix, true), mMatrix(matrix), mVector(vector) {
+}
+
+SkiaColorMatrixFilter::~SkiaColorMatrixFilter() {
+    delete[] mMatrix;
+    delete[] mVector;
+}
+
+void SkiaColorMatrixFilter::describe(ProgramDescription& description,
+        const Extensions& extensions) {
+    description.colorOp = ProgramDescription::kColorMatrix;
+}
+
+void SkiaColorMatrixFilter::setupProgram(Program* program) {
+    glUniformMatrix4fv(program->getUniform("colorMatrix"), 1, GL_FALSE, &mMatrix[0]);
+    glUniform4fv(program->getUniform("colorMatrixVector"), 1, mVector);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Lighting color filter
+///////////////////////////////////////////////////////////////////////////////
+
+SkiaLightingFilter::SkiaLightingFilter(int multiply, int add):
+        SkiaColorFilter(kLighting, true) {
+    mMulR = ((multiply >> 16) & 0xFF) / 255.0f;
+    mMulG = ((multiply >>  8) & 0xFF) / 255.0f;
+    mMulB = ((multiply      ) & 0xFF) / 255.0f;
+
+    mAddR = ((add >> 16) & 0xFF) / 255.0f;
+    mAddG = ((add >>  8) & 0xFF) / 255.0f;
+    mAddB = ((add      ) & 0xFF) / 255.0f;
+}
+
+void SkiaLightingFilter::describe(ProgramDescription& description, const Extensions& extensions) {
+    description.colorOp = ProgramDescription::kColorLighting;
+}
+
+void SkiaLightingFilter::setupProgram(Program* program) {
+    glUniform4f(program->getUniform("lightingMul"), mMulR, mMulG, mMulB, 1.0f);
+    glUniform4f(program->getUniform("lightingAdd"), mAddR, mAddG, mAddB, 0.0f);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Blend color filter
+///////////////////////////////////////////////////////////////////////////////
+
+SkiaBlendFilter::SkiaBlendFilter(int color, SkXfermode::Mode mode):
+        SkiaColorFilter(kBlend, true), mMode(mode) {
+    const int alpha = (color >> 24) & 0xFF;
+    mA = alpha / 255.0f;
+    mR = mA * ((color >> 16) & 0xFF) / 255.0f;
+    mG = mA * ((color >>  8) & 0xFF) / 255.0f;
+    mB = mA * ((color      ) & 0xFF) / 255.0f;
+}
+
+void SkiaBlendFilter::describe(ProgramDescription& description, const Extensions& extensions) {
+    description.colorOp = ProgramDescription::kColorBlend;
+    description.colorMode = mMode;
+}
+
+void SkiaBlendFilter::setupProgram(Program* program) {
+    glUniform4f(program->getUniform("colorBlend"), mR, mG, mB, mA);
+}
+
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/SkiaColorFilter.h b/libs/hwui/SkiaColorFilter.h
new file mode 100644
index 0000000..865b6f0
--- /dev/null
+++ b/libs/hwui/SkiaColorFilter.h
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_UI_SKIA_COLOR_FILTER_H
+#define ANDROID_UI_SKIA_COLOR_FILTER_H
+
+#include <GLES2/gl2.h>
+
+#include "ProgramCache.h"
+#include "Extensions.h"
+
+namespace android {
+namespace uirenderer {
+
+///////////////////////////////////////////////////////////////////////////////
+// Base color filter
+///////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Represents a Skia color filter. A color filter modifies a ProgramDescription
+ * and sets uniforms on the resulting shaders.
+ */
+struct SkiaColorFilter {
+    /**
+     * Type of Skia color filter in use.
+     */
+    enum Type {
+        kNone,
+        kColorMatrix,
+        kLighting,
+        kBlend,
+    };
+
+    SkiaColorFilter(Type type, bool blend);
+    virtual ~SkiaColorFilter();
+
+    virtual void describe(ProgramDescription& description, const Extensions& extensions) = 0;
+    virtual void setupProgram(Program* program) = 0;
+
+    inline bool blend() const {
+        return mBlend;
+    }
+
+    Type type() const {
+        return mType;
+    }
+
+protected:
+    Type mType;
+    bool mBlend;
+}; // struct SkiaColorFilter
+
+///////////////////////////////////////////////////////////////////////////////
+// Implementations
+///////////////////////////////////////////////////////////////////////////////
+
+/**
+ * A color filter that multiplies the source color with a matrix and adds a vector.
+ */
+struct SkiaColorMatrixFilter: public SkiaColorFilter {
+    SkiaColorMatrixFilter(float* matrix, float* vector);
+    ~SkiaColorMatrixFilter();
+
+    void describe(ProgramDescription& description, const Extensions& extensions);
+    void setupProgram(Program* program);
+
+private:
+    float* mMatrix;
+    float* mVector;
+}; // struct SkiaColorMatrixFilter
+
+/**
+ * A color filters that multiplies the source color with a fixed value and adds
+ * another fixed value. Ignores the alpha channel of both arguments.
+ */
+struct SkiaLightingFilter: public SkiaColorFilter {
+    SkiaLightingFilter(int multiply, int add);
+
+    void describe(ProgramDescription& description, const Extensions& extensions);
+    void setupProgram(Program* program);
+
+private:
+    GLfloat mMulR, mMulG, mMulB;
+    GLfloat mAddR, mAddG, mAddB;
+}; // struct SkiaLightingFilter
+
+/**
+ * A color filters that blends the source color with a specified destination color
+ * and PorterDuff blending mode.
+ */
+struct SkiaBlendFilter: public SkiaColorFilter {
+    SkiaBlendFilter(int color, SkXfermode::Mode mode);
+
+    void describe(ProgramDescription& description, const Extensions& extensions);
+    void setupProgram(Program* program);
+
+private:
+    SkXfermode::Mode mMode;
+    GLfloat mR, mG, mB, mA;
+}; // struct SkiaBlendFilter
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_UI_SKIA_COLOR_FILTER_H
diff --git a/libs/hwui/SkiaShader.cpp b/libs/hwui/SkiaShader.cpp
new file mode 100644
index 0000000..fe4b54d
--- /dev/null
+++ b/libs/hwui/SkiaShader.cpp
@@ -0,0 +1,212 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "OpenGLRenderer"
+
+#include <utils/Log.h>
+
+#include <SkMatrix.h>
+
+#include "SkiaShader.h"
+#include "Texture.h"
+#include "Matrix.h"
+
+namespace android {
+namespace uirenderer {
+
+///////////////////////////////////////////////////////////////////////////////
+// Support
+///////////////////////////////////////////////////////////////////////////////
+
+static const GLenum gTextureUnitsMap[] = {
+        GL_TEXTURE0,
+        GL_TEXTURE1,
+        GL_TEXTURE2
+};
+
+static const GLint gTileModes[] = {
+        GL_CLAMP_TO_EDGE,   // == SkShader::kClamp_TileMode
+        GL_REPEAT,          // == SkShader::kRepeat_Mode
+        GL_MIRRORED_REPEAT  // == SkShader::kMirror_TileMode
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// Base shader
+///////////////////////////////////////////////////////////////////////////////
+
+SkiaShader::SkiaShader(Type type, SkShader* key, SkShader::TileMode tileX,
+        SkShader::TileMode tileY, SkMatrix* matrix, bool blend):
+        mType(type), mKey(key), mTileX(tileX), mTileY(tileY), mMatrix(matrix), mBlend(blend) {
+}
+
+SkiaShader::~SkiaShader() {
+}
+
+void SkiaShader::describe(ProgramDescription& description, const Extensions& extensions) {
+}
+
+void SkiaShader::setupProgram(Program* program, const mat4& modelView, const Snapshot& snapshot,
+        GLuint* textureUnit) {
+}
+
+void SkiaShader::bindTexture(GLuint texture, GLenum wrapS, GLenum wrapT, GLuint textureUnit) {
+    glActiveTexture(gTextureUnitsMap[textureUnit]);
+    glBindTexture(GL_TEXTURE_2D, texture);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrapS);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrapT);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Bitmap shader
+///////////////////////////////////////////////////////////////////////////////
+
+SkiaBitmapShader::SkiaBitmapShader(SkBitmap* bitmap, SkShader* key, SkShader::TileMode tileX,
+        SkShader::TileMode tileY, SkMatrix* matrix, bool blend):
+        SkiaShader(kBitmap, key, tileX, tileY, matrix, blend), mBitmap(bitmap) {
+}
+
+void SkiaBitmapShader::describe(ProgramDescription& description, const Extensions& extensions) {
+    const Texture* texture = mTextureCache->get(mBitmap);
+
+    const float width = texture->width;
+    const float height = texture->height;
+
+    description.hasBitmap = true;
+    // The driver does not support non-power of two mirrored/repeated
+    // textures, so do it ourselves
+    if (!extensions.hasNPot() && !isPowerOfTwo(width) && !isPowerOfTwo(height)) {
+        description.isBitmapNpot = true;
+        description.bitmapWrapS = gTileModes[mTileX];
+        description.bitmapWrapT = gTileModes[mTileY];
+    }
+}
+
+void SkiaBitmapShader::setupProgram(Program* program, const mat4& modelView,
+        const Snapshot& snapshot, GLuint* textureUnit) {
+    GLuint textureSlot = (*textureUnit)++;
+    glActiveTexture(gTextureUnitsMap[textureSlot]);
+    const Texture* texture = mTextureCache->get(mBitmap);
+
+    const float width = texture->width;
+    const float height = texture->height;
+
+    mat4 textureTransform;
+    if (mMatrix) {
+        SkMatrix inverse;
+        mMatrix->invert(&inverse);
+        textureTransform.load(inverse);
+        textureTransform.multiply(modelView);
+    } else {
+        textureTransform.load(modelView);
+    }
+
+    // Uniforms
+    bindTexture(texture->id, gTileModes[mTileX], gTileModes[mTileY], textureSlot);
+    glUniform1i(program->getUniform("bitmapSampler"), textureSlot);
+    glUniformMatrix4fv(program->getUniform("textureTransform"), 1,
+            GL_FALSE, &textureTransform.data[0]);
+    glUniform2f(program->getUniform("textureDimension"), 1.0f / width, 1.0f / height);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Linear gradient shader
+///////////////////////////////////////////////////////////////////////////////
+
+SkiaLinearGradientShader::SkiaLinearGradientShader(float* bounds, uint32_t* colors,
+        float* positions, int count, SkShader* key, SkShader::TileMode tileMode,
+        SkMatrix* matrix, bool blend):
+        SkiaShader(kLinearGradient, key, tileMode, tileMode, matrix, blend),
+        mBounds(bounds), mColors(colors), mPositions(positions), mCount(count) {
+}
+
+SkiaLinearGradientShader::~SkiaLinearGradientShader() {
+    delete mBounds;
+    delete mColors;
+    delete mPositions;
+}
+
+void SkiaLinearGradientShader::describe(ProgramDescription& description,
+        const Extensions& extensions) {
+    description.hasGradient = true;
+}
+
+void SkiaLinearGradientShader::setupProgram(Program* program, const mat4& modelView,
+        const Snapshot& snapshot, GLuint* textureUnit) {
+    GLuint textureSlot = (*textureUnit)++;
+    glActiveTexture(gTextureUnitsMap[textureSlot]);
+
+    Texture* texture = mGradientCache->get(mKey);
+    if (!texture) {
+        texture = mGradientCache->addLinearGradient(mKey, mBounds, mColors, mPositions,
+                mCount, mTileX);
+    }
+
+    Rect start(mBounds[0], mBounds[1], mBounds[2], mBounds[3]);
+    if (mMatrix) {
+        mat4 shaderMatrix(*mMatrix);
+        shaderMatrix.mapRect(start);
+    }
+    snapshot.transform.mapRect(start);
+
+    const float gradientX = start.right - start.left;
+    const float gradientY = start.bottom - start.top;
+
+    mat4 screenSpace(snapshot.transform);
+    screenSpace.multiply(modelView);
+
+    // Uniforms
+    bindTexture(texture->id, gTileModes[mTileX], gTileModes[mTileY], textureSlot);
+    glUniform1i(program->getUniform("gradientSampler"), textureSlot);
+    glUniform2f(program->getUniform("gradientStart"), start.left, start.top);
+    glUniform2f(program->getUniform("gradient"), gradientX, gradientY);
+    glUniform1f(program->getUniform("gradientLength"),
+            1.0f / (gradientX * gradientX + gradientY * gradientY));
+    glUniformMatrix4fv(program->getUniform("screenSpace"), 1, GL_FALSE, &screenSpace.data[0]);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Compose shader
+///////////////////////////////////////////////////////////////////////////////
+
+SkiaComposeShader::SkiaComposeShader(SkiaShader* first, SkiaShader* second,
+        SkXfermode::Mode mode, SkShader* key):
+        SkiaShader(kCompose, key, SkShader::kClamp_TileMode, SkShader::kClamp_TileMode,
+        NULL, first->blend() || second->blend()), mFirst(first), mSecond(second), mMode(mode) {
+}
+
+void SkiaComposeShader::set(TextureCache* textureCache, GradientCache* gradientCache) {
+    SkiaShader::set(textureCache, gradientCache);
+    mFirst->set(textureCache, gradientCache);
+    mSecond->set(textureCache, gradientCache);
+}
+
+void SkiaComposeShader::describe(ProgramDescription& description, const Extensions& extensions) {
+    mFirst->describe(description, extensions);
+    mSecond->describe(description, extensions);
+    if (mFirst->type() == kBitmap) {
+        description.isBitmapFirst = true;
+    }
+    description.shadersMode = mMode;
+}
+
+void SkiaComposeShader::setupProgram(Program* program, const mat4& modelView,
+        const Snapshot& snapshot, GLuint* textureUnit) {
+    mFirst->setupProgram(program, modelView, snapshot, textureUnit);
+    mSecond->setupProgram(program, modelView, snapshot, textureUnit);
+}
+
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/SkiaShader.h b/libs/hwui/SkiaShader.h
new file mode 100644
index 0000000..58f2870
--- /dev/null
+++ b/libs/hwui/SkiaShader.h
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_UI_SKIA_SHADER_H
+#define ANDROID_UI_SKIA_SHADER_H
+
+#include <SkShader.h>
+#include <SkXfermode.h>
+
+#include <GLES2/gl2.h>
+
+#include "Extensions.h"
+#include "ProgramCache.h"
+#include "TextureCache.h"
+#include "GradientCache.h"
+#include "Snapshot.h"
+
+namespace android {
+namespace uirenderer {
+
+///////////////////////////////////////////////////////////////////////////////
+// Base shader
+///////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Represents a Skia shader. A shader will modify the GL context and active
+ * program to recreate the original effect.
+ */
+struct SkiaShader {
+    /**
+     * Type of Skia shader in use.
+     */
+    enum Type {
+        kNone,
+        kBitmap,
+        kLinearGradient,
+        kCircularGradient,
+        kSweepGradient,
+        kCompose
+    };
+
+    SkiaShader(Type type, SkShader* key, SkShader::TileMode tileX, SkShader::TileMode tileY,
+            SkMatrix* matrix, bool blend);
+    virtual ~SkiaShader();
+
+    virtual void describe(ProgramDescription& description, const Extensions& extensions);
+    virtual void setupProgram(Program* program, const mat4& modelView, const Snapshot& snapshot,
+            GLuint* textureUnit);
+
+    inline bool blend() const {
+        return mBlend;
+    }
+
+    Type type() const {
+        return mType;
+    }
+
+    virtual void set(TextureCache* textureCache, GradientCache* gradientCache) {
+        mTextureCache = textureCache;
+        mGradientCache = gradientCache;
+    }
+
+    void setMatrix(SkMatrix* matrix) {
+        mMatrix = matrix;
+    }
+
+protected:
+    inline void bindTexture(GLuint texture, GLenum wrapS, GLenum wrapT, GLuint textureUnit);
+
+    Type mType;
+    SkShader* mKey;
+    SkShader::TileMode mTileX;
+    SkShader::TileMode mTileY;
+    SkMatrix* mMatrix;
+    bool mBlend;
+
+    TextureCache* mTextureCache;
+    GradientCache* mGradientCache;
+}; // struct SkiaShader
+
+
+///////////////////////////////////////////////////////////////////////////////
+// Implementations
+///////////////////////////////////////////////////////////////////////////////
+
+/**
+ * A shader that draws a bitmap.
+ */
+struct SkiaBitmapShader: public SkiaShader {
+    SkiaBitmapShader(SkBitmap* bitmap, SkShader* key, SkShader::TileMode tileX,
+            SkShader::TileMode tileY, SkMatrix* matrix, bool blend);
+
+    void describe(ProgramDescription& description, const Extensions& extensions);
+    void setupProgram(Program* program, const mat4& modelView, const Snapshot& snapshot,
+            GLuint* textureUnit);
+
+private:
+    /**
+     * This method does not work for n == 0.
+     */
+    inline bool isPowerOfTwo(unsigned int n) {
+        return !(n & (n - 1));
+    }
+
+    SkBitmap* mBitmap;
+}; // struct SkiaBitmapShader
+
+/**
+ * A shader that draws a linear gradient.
+ */
+struct SkiaLinearGradientShader: public SkiaShader {
+    SkiaLinearGradientShader(float* bounds, uint32_t* colors, float* positions, int count,
+            SkShader* key, SkShader::TileMode tileMode, SkMatrix* matrix, bool blend);
+    ~SkiaLinearGradientShader();
+
+    void describe(ProgramDescription& description, const Extensions& extensions);
+    void setupProgram(Program* program, const mat4& modelView, const Snapshot& snapshot,
+            GLuint* textureUnit);
+
+private:
+    float* mBounds;
+    uint32_t* mColors;
+    float* mPositions;
+    int mCount;
+}; // struct SkiaLinearGradientShader
+
+/**
+ * A shader that draws two shaders, composited with an xfermode.
+ */
+struct SkiaComposeShader: public SkiaShader {
+    SkiaComposeShader(SkiaShader* first, SkiaShader* second, SkXfermode::Mode mode, SkShader* key);
+
+    void set(TextureCache* textureCache, GradientCache* gradientCache);
+
+    void describe(ProgramDescription& description, const Extensions& extensions);
+    void setupProgram(Program* program, const mat4& modelView, const Snapshot& snapshot,
+            GLuint* textureUnit);
+
+private:
+    SkiaShader* mFirst;
+    SkiaShader* mSecond;
+    SkXfermode::Mode mMode;
+}; // struct SkiaComposeShader
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_UI_SKIA_SHADER_H
diff --git a/libs/hwui/Snapshot.h b/libs/hwui/Snapshot.h
new file mode 100644
index 0000000..399ae68
--- /dev/null
+++ b/libs/hwui/Snapshot.h
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_UI_SNAPSHOT_H
+#define ANDROID_UI_SNAPSHOT_H
+
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
+
+#include <utils/RefBase.h>
+
+#include <SkRegion.h>
+
+#include "Layer.h"
+#include "Matrix.h"
+#include "Rect.h"
+
+namespace android {
+namespace uirenderer {
+
+/**
+ * A snapshot holds information about the current state of the rendering
+ * surface. A snapshot is usually created whenever the user calls save()
+ * and discarded when the user calls restore(). Once a snapshot is created,
+ * it can hold information for deferred rendering.
+ *
+ * Each snapshot has a link to a previous snapshot, indicating the previous
+ * state of the renderer.
+ */
+class Snapshot: public LightRefBase<Snapshot> {
+public:
+    Snapshot(): flags(0), previous(NULL), layer(NULL), fbo(0) { }
+
+    /**
+     * Copies the specified snapshot. Only the transform and clip rectangle
+     * are copied. The layer information is set to 0 and the transform is
+     * assumed to be dirty. The specified snapshot is stored as the previous
+     * snapshot.
+     */
+    Snapshot(const sp<Snapshot>& s):
+            height(s->height),
+            transform(s->transform),
+            clipRect(s->clipRect),
+            flags(0),
+            previous(s),
+            layer(NULL),
+            fbo(s->fbo) {
+        if ((s->flags & Snapshot::kFlagClipSet) &&
+                !(s->flags & Snapshot::kFlagDirtyLocalClip)) {
+            localClip.set(s->localClip);
+        } else {
+            flags |= Snapshot::kFlagDirtyLocalClip;
+        }
+    }
+
+    /**
+     * Various flags set on #flags.
+     */
+    enum Flags {
+        /**
+         * Indicates that the clip region was modified. When this
+         * snapshot is restored so must the clip.
+         */
+        kFlagClipSet = 0x1,
+        /**
+         * Indicates that this snapshot was created when saving
+         * a new layer.
+         */
+        kFlagIsLayer = 0x2,
+        /**
+         * Indicates that this snapshot has changed the ortho matrix.
+         */
+        kFlagDirtyOrtho = 0x4,
+        /**
+         * Indicates that the local clip should be recomputed.
+         */
+        kFlagDirtyLocalClip = 0x8,
+    };
+
+    /**
+     * Intersects the current clip with the new clip rectangle.
+     */
+    bool clip(float left, float top, float right, float bottom, SkRegion::Op op) {
+        bool clipped = false;
+
+        Rect r(left, top, right, bottom);
+        transform.mapRect(r);
+
+        switch (op) {
+            case SkRegion::kDifference_Op:
+                break;
+            case SkRegion::kIntersect_Op:
+                clipped = clipRect.intersect(r);
+                break;
+            case SkRegion::kUnion_Op:
+                clipped = clipRect.unionWith(r);
+                break;
+            case SkRegion::kXOR_Op:
+                break;
+            case SkRegion::kReverseDifference_Op:
+                break;
+            case SkRegion::kReplace_Op:
+                clipRect.set(r);
+                clipped = true;
+                break;
+        }
+
+        if (clipped) {
+            flags |= Snapshot::kFlagClipSet | Snapshot::kFlagDirtyLocalClip;
+        }
+
+        return clipped;
+    }
+
+    /**
+     * Sets the current clip.
+     */
+    void setClip(float left, float top, float right, float bottom) {
+        clipRect.set(left, top, right, bottom);
+        flags |= Snapshot::kFlagClipSet | Snapshot::kFlagDirtyLocalClip;
+    }
+
+    const Rect& getLocalClip() {
+        if (flags & Snapshot::kFlagDirtyLocalClip) {
+            mat4 inverse;
+            inverse.loadInverse(transform);
+            localClip.set(clipRect);
+            inverse.mapRect(localClip);
+            flags &= ~Snapshot::kFlagDirtyLocalClip;
+        }
+        return localClip;
+    }
+
+    /**
+     * Height of the framebuffer the snapshot is rendering into.
+     */
+    int height;
+
+    /**
+     * Local transformation. Holds the current translation, scale and
+     * rotation values.
+     */
+    mat4 transform;
+
+    /**
+     * Current clip region. The clip is stored in canvas-space coordinates,
+     * (screen-space coordinates in the regular case.)
+     */
+    Rect clipRect;
+
+    /**
+     * Dirty flags.
+     */
+    int flags;
+
+    /**
+     * Previous snapshot.
+     */
+    sp<Snapshot> previous;
+
+    /**
+     * Only set when the flag kFlagIsLayer is set.
+     */
+    Layer* layer;
+    GLuint fbo;
+
+    /**
+     * Contains the previous ortho matrix.
+     */
+    mat4 orthoMatrix;
+
+private:
+    Rect localClip;
+
+}; // class Snapshot
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_UI_SNAPSHOT_H
diff --git a/libs/hwui/Texture.h b/libs/hwui/Texture.h
new file mode 100644
index 0000000..d37013d
--- /dev/null
+++ b/libs/hwui/Texture.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_UI_TEXTURE_H
+#define ANDROID_UI_TEXTURE_H
+
+#include <GLES2/gl2.h>
+
+namespace android {
+namespace uirenderer {
+
+/**
+ * Represents an OpenGL texture.
+ */
+struct Texture {
+    /**
+     * Name of the texture.
+     */
+    GLuint id;
+    /**
+     * Generation of the backing bitmap,
+     */
+    uint32_t generation;
+    /**
+     * Indicates whether the texture requires blending.
+     */
+    bool blend;
+    /**
+     * Width of the backing bitmap.
+     */
+    uint32_t width;
+    /**
+     * Height of the backing bitmap.
+     */
+    uint32_t height;
+}; // struct Texture
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_UI_TEXTURE_H
diff --git a/libs/hwui/TextureCache.cpp b/libs/hwui/TextureCache.cpp
new file mode 100644
index 0000000..4975edb
--- /dev/null
+++ b/libs/hwui/TextureCache.cpp
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "OpenGLRenderer"
+
+#include <GLES2/gl2.h>
+
+#include "TextureCache.h"
+
+namespace android {
+namespace uirenderer {
+
+///////////////////////////////////////////////////////////////////////////////
+// Constructors/destructor
+///////////////////////////////////////////////////////////////////////////////
+
+TextureCache::TextureCache(uint32_t maxByteSize):
+        mCache(GenerationCache<SkBitmap*, Texture*>::kUnlimitedCapacity),
+        mSize(0), mMaxSize(maxByteSize) {
+    mCache.setOnEntryRemovedListener(this);
+}
+
+TextureCache::~TextureCache() {
+    mCache.clear();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Size management
+///////////////////////////////////////////////////////////////////////////////
+
+uint32_t TextureCache::getSize() {
+    return mSize;
+}
+
+uint32_t TextureCache::getMaxSize() {
+    return mMaxSize;
+}
+
+void TextureCache::setMaxSize(uint32_t maxSize) {
+    mMaxSize = maxSize;
+    while (mSize > mMaxSize) {
+        mCache.removeOldest();
+    }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Callbacks
+///////////////////////////////////////////////////////////////////////////////
+
+void TextureCache::operator()(SkBitmap*& bitmap, Texture*& texture) {
+    if (bitmap) {
+        const uint32_t size = bitmap->rowBytes() * bitmap->height();
+        mSize -= size;
+    }
+
+    if (texture) {
+        glDeleteTextures(1, &texture->id);
+        delete texture;
+    }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Caching
+///////////////////////////////////////////////////////////////////////////////
+
+Texture* TextureCache::get(SkBitmap* bitmap) {
+    Texture* texture = mCache.get(bitmap);
+    if (!texture) {
+        const uint32_t size = bitmap->rowBytes() * bitmap->height();
+        // Don't even try to cache a bitmap that's bigger than the cache
+        if (size < mMaxSize) {
+            while (mSize + size > mMaxSize) {
+                mCache.removeOldest();
+            }
+        }
+
+        texture = new Texture;
+        generateTexture(bitmap, texture, false);
+
+        if (size < mMaxSize) {
+            mSize += size;
+            mCache.put(bitmap, texture);
+        }
+    } else if (bitmap->getGenerationID() != texture->generation) {
+        generateTexture(bitmap, texture, true);
+    }
+    // TODO: Do something to destroy the texture object if it's too big for the cache
+    return texture;
+}
+
+void TextureCache::remove(SkBitmap* bitmap) {
+    mCache.remove(bitmap);
+}
+
+void TextureCache::clear() {
+    mCache.clear();
+}
+
+void TextureCache::generateTexture(SkBitmap* bitmap, Texture* texture, bool regenerate) {
+    SkAutoLockPixels alp(*bitmap);
+    if (!bitmap->readyToDraw()) {
+        LOGE("Cannot generate texture from bitmap");
+        return;
+    }
+
+    if (!regenerate) {
+        texture->generation = bitmap->getGenerationID();
+        texture->width = bitmap->width();
+        texture->height = bitmap->height();
+
+        glGenTextures(1, &texture->id);
+    }
+
+    glBindTexture(GL_TEXTURE_2D, texture->id);
+    glPixelStorei(GL_UNPACK_ALIGNMENT, bitmap->bytesPerPixel());
+
+    switch (bitmap->getConfig()) {
+    case SkBitmap::kA8_Config:
+        texture->blend = true;
+        glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, bitmap->rowBytesAsPixels(), texture->height, 0,
+                GL_ALPHA, GL_UNSIGNED_BYTE, bitmap->getPixels());
+        break;
+    case SkBitmap::kRGB_565_Config:
+        texture->blend = false;
+        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, bitmap->rowBytesAsPixels(), texture->height, 0,
+                GL_RGB, GL_UNSIGNED_SHORT_5_6_5, bitmap->getPixels());
+        break;
+    case SkBitmap::kARGB_8888_Config:
+        texture->blend = !bitmap->isOpaque();
+        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bitmap->rowBytesAsPixels(), texture->height, 0,
+                GL_RGBA, GL_UNSIGNED_BYTE, bitmap->getPixels());
+        break;
+    default:
+        break;
+    }
+
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+}
+
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/TextureCache.h b/libs/hwui/TextureCache.h
new file mode 100644
index 0000000..bed1191
--- /dev/null
+++ b/libs/hwui/TextureCache.h
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_UI_TEXTURE_CACHE_H
+#define ANDROID_UI_TEXTURE_CACHE_H
+
+#include <SkBitmap.h>
+
+#include "Texture.h"
+#include "GenerationCache.h"
+
+namespace android {
+namespace uirenderer {
+
+/**
+ * A simple LRU texture cache. The cache has a maximum size expressed in bytes.
+ * Any texture added to the cache causing the cache to grow beyond the maximum
+ * allowed size will also cause the oldest texture to be kicked out.
+ */
+class TextureCache: public OnEntryRemoved<SkBitmap*, Texture*> {
+public:
+    TextureCache(uint32_t maxByteSize);
+    ~TextureCache();
+
+    /**
+     * Used as a callback when an entry is removed from the cache.
+     * Do not invoke directly.
+     */
+    void operator()(SkBitmap*& bitmap, Texture*& texture);
+
+    /**
+     * Returns the texture associated with the specified bitmap. If the texture
+     * cannot be found in the cache, a new texture is generated.
+     */
+    Texture* get(SkBitmap* bitmap);
+    /**
+     * Removes the texture associated with the specified bitmap. Returns NULL
+     * if the texture cannot be found. Upon remove the texture is freed.
+     */
+    void remove(SkBitmap* bitmap);
+    /**
+     * Clears the cache. This causes all textures to be deleted.
+     */
+    void clear();
+
+    /**
+     * Sets the maximum size of the cache in bytes.
+     */
+    void setMaxSize(uint32_t maxSize);
+    /**
+     * Returns the maximum size of the cache in bytes.
+     */
+    uint32_t getMaxSize();
+    /**
+     * Returns the current size of the cache in bytes.
+     */
+    uint32_t getSize();
+
+private:
+    /**
+     * Generates the texture from a bitmap into the specified texture structure.
+     *
+     * @param regenerate If true, the bitmap data is reuploaded into the texture, but
+     *        no new texture is generated.
+     */
+    void generateTexture(SkBitmap* bitmap, Texture* texture, bool regenerate = false);
+
+    GenerationCache<SkBitmap*, Texture*> mCache;
+
+    uint32_t mSize;
+    uint32_t mMaxSize;
+}; // class TextureCache
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_UI_TEXTURE_CACHE_H
diff --git a/libs/hwui/Vertex.h b/libs/hwui/Vertex.h
new file mode 100644
index 0000000..1f54086
--- /dev/null
+++ b/libs/hwui/Vertex.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_UI_VERTEX_H
+#define ANDROID_UI_VERTEX_H
+
+namespace android {
+namespace uirenderer {
+
+/**
+ * Simple structure to describe a vertex with a position and a texture.
+ */
+struct TextureVertex {
+    float position[2];
+    float texture[2];
+
+    static inline void set(TextureVertex* vertex, float x, float y, float u, float v) {
+        vertex[0].position[0] = x;
+        vertex[0].position[1] = y;
+        vertex[0].texture[0] = u;
+        vertex[0].texture[1] = v;
+    }
+
+    static inline void setUV(TextureVertex* vertex, float u, float v) {
+        vertex[0].texture[0] = u;
+        vertex[0].texture[1] = v;
+    }
+}; // struct TextureVertex
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_UI_VERTEX_H
diff --git a/libs/rs/Android.mk b/libs/rs/Android.mk
index 98464a0..37c418b 100644
--- a/libs/rs/Android.mk
+++ b/libs/rs/Android.mk
@@ -76,34 +76,44 @@
 LOCAL_SRC_FILES:= \
 	rsAdapter.cpp \
 	rsAllocation.cpp \
+	rsAnimation.cpp \
 	rsComponent.cpp \
 	rsContext.cpp \
 	rsDevice.cpp \
 	rsElement.cpp \
-        rsFileA3D.cpp \
+	rsFileA3D.cpp \
+	rsFont.cpp \
 	rsLight.cpp \
 	rsLocklessFifo.cpp \
 	rsObjectBase.cpp \
 	rsMatrix.cpp \
-        rsMesh.cpp \
-	rsNoise.cpp \
+	rsMesh.cpp \
+	rsMutex.cpp \
 	rsProgram.cpp \
 	rsProgramFragment.cpp \
-	rsProgramFragmentStore.cpp \
+	rsProgramStore.cpp \
 	rsProgramRaster.cpp \
 	rsProgramVertex.cpp \
 	rsSampler.cpp \
 	rsScript.cpp \
 	rsScriptC.cpp \
 	rsScriptC_Lib.cpp \
-        rsShaderCache.cpp \
-	rsSimpleMesh.cpp \
+	rsScriptC_LibCL.cpp \
+	rsScriptC_LibGL.cpp \
+	rsShaderCache.cpp \
+	rsSignal.cpp \
+	rsStream.cpp \
 	rsThreadIO.cpp \
 	rsType.cpp \
 	rsVertexArray.cpp
 
 
-LOCAL_SHARED_LIBRARIES += libcutils libutils libEGL libGLESv1_CM libGLESv2 libui libacc
+LOCAL_SHARED_LIBRARIES += libcutils libutils libEGL libGLESv1_CM libGLESv2 libui libbcc
+
+LOCAL_STATIC_LIBRARIES := libft2
+
+LOCAL_C_INCLUDES += external/freetype/include
+
 LOCAL_LDLIBS := -lpthread -ldl
 LOCAL_MODULE:= libRS
 LOCAL_MODULE_TAGS := optional
diff --git a/libs/rs/RenderScript.h b/libs/rs/RenderScript.h
index d280f50..6636fef 100644
--- a/libs/rs/RenderScript.h
+++ b/libs/rs/RenderScript.h
@@ -30,20 +30,23 @@
 typedef void * RsAdapter1D;
 typedef void * RsAdapter2D;
 typedef void * RsAllocation;
+typedef void * RsAnimation;
 typedef void * RsContext;
 typedef void * RsDevice;
 typedef void * RsElement;
 typedef void * RsFile;
+typedef void * RsFont;
 typedef void * RsSampler;
 typedef void * RsScript;
-typedef void * RsSimpleMesh;
+typedef void * RsMesh;
 typedef void * RsType;
 typedef void * RsLight;
+typedef void * RsObjectBase;
 
 typedef void * RsProgram;
 typedef void * RsProgramVertex;
 typedef void * RsProgramFragment;
-typedef void * RsProgramFragmentStore;
+typedef void * RsProgramStore;
 typedef void * RsProgramRaster;
 
 typedef void (* RsBitmapCallback_t)(void *);
@@ -83,6 +86,8 @@
     RS_TYPE_UNSIGNED_32,
     RS_TYPE_UNSIGNED_64,
 
+    RS_TYPE_BOOLEAN,
+
     RS_TYPE_UNSIGNED_5_6_5,
     RS_TYPE_UNSIGNED_5_5_5_1,
     RS_TYPE_UNSIGNED_4_4_4_4,
@@ -101,19 +106,12 @@
 
 enum RsDataKind {
     RS_KIND_USER,
-    RS_KIND_COLOR,
-    RS_KIND_POSITION,
-    RS_KIND_TEXTURE,
-    RS_KIND_NORMAL,
-    RS_KIND_INDEX,
-    RS_KIND_POINT_SIZE,
 
-    RS_KIND_PIXEL_L,
+    RS_KIND_PIXEL_L = 7,
     RS_KIND_PIXEL_A,
     RS_KIND_PIXEL_LA,
     RS_KIND_PIXEL_RGB,
     RS_KIND_PIXEL_RGBA,
-
 };
 
 enum RsSamplerParam {
@@ -205,9 +203,71 @@
 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
+};
+
+enum RsA3DClassID {
+    RS_A3D_CLASS_ID_UNKNOWN,
+    RS_A3D_CLASS_ID_MESH,
+    RS_A3D_CLASS_ID_TYPE,
+    RS_A3D_CLASS_ID_ELEMENT,
+    RS_A3D_CLASS_ID_ALLOCATION,
+    RS_A3D_CLASS_ID_PROGRAM_VERTEX,
+    RS_A3D_CLASS_ID_PROGRAM_RASTER,
+    RS_A3D_CLASS_ID_PROGRAM_FRAGMENT,
+    RS_A3D_CLASS_ID_PROGRAM_STORE,
+    RS_A3D_CLASS_ID_SAMPLER,
+    RS_A3D_CLASS_ID_ANIMATION,
+    RS_A3D_CLASS_ID_LIGHT,
+    RS_A3D_CLASS_ID_ADAPTER_1D,
+    RS_A3D_CLASS_ID_ADAPTER_2D,
+    RS_A3D_CLASS_ID_SCRIPT_C
+};
+
+enum RsCullMode {
+    RS_CULL_BACK,
+    RS_CULL_FRONT,
+    RS_CULL_NONE
+};
+
+typedef struct {
+    RsA3DClassID classID;
+    const char* objectName;
+} RsFileIndexEntry;
+
+// Script to Script
+typedef struct {
+    uint32_t xStart;
+    uint32_t xEnd;
+    uint32_t yStart;
+    uint32_t yEnd;
+    uint32_t zStart;
+    uint32_t zEnd;
+    uint32_t arrayStart;
+    uint32_t arrayEnd;
+
+} RsScriptCall;
+
 #ifndef NO_RS_FUNCS
 #include "rsgApiFuncDecl.h"
 #endif
diff --git a/libs/rs/RenderScriptEnv.h b/libs/rs/RenderScriptEnv.h
index 99b8c04..9225904 100644
--- a/libs/rs/RenderScriptEnv.h
+++ b/libs/rs/RenderScriptEnv.h
@@ -9,10 +9,10 @@
 typedef void * RsElement;
 typedef void * RsSampler;
 typedef void * RsScript;
-typedef void * RsSimpleMesh;
+typedef void * RsMesh;
 typedef void * RsType;
 typedef void * RsProgramFragment;
-typedef void * RsProgramFragmentStore;
+typedef void * RsProgramStore;
 typedef void * RsLight;
 
 
diff --git a/libs/rs/java/Film/Android.mk b/libs/rs/java/Film/Android.mk
deleted file mode 100644
index 9e6ed7e..0000000
--- a/libs/rs/java/Film/Android.mk
+++ /dev/null
@@ -1,27 +0,0 @@
-#
-# 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.
-#
-
-LOCAL_PATH := $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := optional
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-#LOCAL_STATIC_JAVA_LIBRARIES := android.renderscript
-
-LOCAL_PACKAGE_NAME := Film
-
-include $(BUILD_PACKAGE)
diff --git a/libs/rs/java/Film/AndroidManifest.xml b/libs/rs/java/Film/AndroidManifest.xml
deleted file mode 100644
index a5ce8a1..0000000
--- a/libs/rs/java/Film/AndroidManifest.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.film">
-    <application android:label="Film">
-        <activity android:name="Film"
-                  android:screenOrientation="portrait"
-                  android:theme="@android:style/Theme.Black.NoTitleBar">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
-            </intent-filter>
-        </activity>
-    </application>
-</manifest>
diff --git a/libs/rs/java/Film/res/drawable/p01.png b/libs/rs/java/Film/res/drawable/p01.png
deleted file mode 100644
index a9b9bdb..0000000
--- a/libs/rs/java/Film/res/drawable/p01.png
+++ /dev/null
Binary files differ
diff --git a/libs/rs/java/Film/res/drawable/p02.png b/libs/rs/java/Film/res/drawable/p02.png
deleted file mode 100644
index 8162c82..0000000
--- a/libs/rs/java/Film/res/drawable/p02.png
+++ /dev/null
Binary files differ
diff --git a/libs/rs/java/Film/res/drawable/p03.png b/libs/rs/java/Film/res/drawable/p03.png
deleted file mode 100644
index e3e26c0..0000000
--- a/libs/rs/java/Film/res/drawable/p03.png
+++ /dev/null
Binary files differ
diff --git a/libs/rs/java/Film/res/drawable/p04.png b/libs/rs/java/Film/res/drawable/p04.png
deleted file mode 100644
index daee603..0000000
--- a/libs/rs/java/Film/res/drawable/p04.png
+++ /dev/null
Binary files differ
diff --git a/libs/rs/java/Film/res/drawable/p05.png b/libs/rs/java/Film/res/drawable/p05.png
deleted file mode 100644
index fac5248..0000000
--- a/libs/rs/java/Film/res/drawable/p05.png
+++ /dev/null
Binary files differ
diff --git a/libs/rs/java/Film/res/drawable/p06.png b/libs/rs/java/Film/res/drawable/p06.png
deleted file mode 100644
index 3b51261..0000000
--- a/libs/rs/java/Film/res/drawable/p06.png
+++ /dev/null
Binary files differ
diff --git a/libs/rs/java/Film/res/drawable/p07.png b/libs/rs/java/Film/res/drawable/p07.png
deleted file mode 100644
index d8bd938..0000000
--- a/libs/rs/java/Film/res/drawable/p07.png
+++ /dev/null
Binary files differ
diff --git a/libs/rs/java/Film/res/drawable/p08.png b/libs/rs/java/Film/res/drawable/p08.png
deleted file mode 100644
index ef175e8..0000000
--- a/libs/rs/java/Film/res/drawable/p08.png
+++ /dev/null
Binary files differ
diff --git a/libs/rs/java/Film/res/drawable/p09.png b/libs/rs/java/Film/res/drawable/p09.png
deleted file mode 100644
index 7bf3874..0000000
--- a/libs/rs/java/Film/res/drawable/p09.png
+++ /dev/null
Binary files differ
diff --git a/libs/rs/java/Film/res/drawable/p10.png b/libs/rs/java/Film/res/drawable/p10.png
deleted file mode 100644
index 908827d..0000000
--- a/libs/rs/java/Film/res/drawable/p10.png
+++ /dev/null
Binary files differ
diff --git a/libs/rs/java/Film/res/drawable/p11.png b/libs/rs/java/Film/res/drawable/p11.png
deleted file mode 100644
index 1289f71..0000000
--- a/libs/rs/java/Film/res/drawable/p11.png
+++ /dev/null
Binary files differ
diff --git a/libs/rs/java/Film/res/drawable/p12.png b/libs/rs/java/Film/res/drawable/p12.png
deleted file mode 100644
index e1af16a..0000000
--- a/libs/rs/java/Film/res/drawable/p12.png
+++ /dev/null
Binary files differ
diff --git a/libs/rs/java/Film/res/drawable/p13.png b/libs/rs/java/Film/res/drawable/p13.png
deleted file mode 100644
index d08bcbe..0000000
--- a/libs/rs/java/Film/res/drawable/p13.png
+++ /dev/null
Binary files differ
diff --git a/libs/rs/java/Film/res/raw/filmimage.c b/libs/rs/java/Film/res/raw/filmimage.c
deleted file mode 100644
index d154c68..0000000
--- a/libs/rs/java/Film/res/raw/filmimage.c
+++ /dev/null
@@ -1,110 +0,0 @@
-// Fountain test script
-
-#pragma version(1)
-#pragma stateVertex(orthoWindow)
-#pragma stateRaster(flat)
-#pragma stateFragment(PgmFragBackground)
-#pragma stateStore(MyBlend)
-
-
-int main(void* con, int ft, int launchID) {
-    int count, touch, x, y, rate, maxLife, lifeShift;
-    int life;
-    int ct, ct2;
-    int newPart;
-    int drawCount;
-    int dx, dy, idx;
-    int posx,posy;
-    int c;
-    int srcIdx;
-    int dstIdx;
-
-    count = loadI32(con, 0, 1);
-    touch = loadI32(con, 0, 2);
-    x = loadI32(con, 0, 3);
-    y = loadI32(con, 0, 4);
-
-    rate = 4;
-    maxLife = (count / rate) - 1;
-    lifeShift = 0;
-    {
-        life = maxLife;
-        while (life > 255) {
-            life = life >> 1;
-            lifeShift ++;
-        }
-    }
-
-    drawRect(con, 0, 256, 0, 512);
-    contextBindProgramFragment(con, NAMED_PgmFragParts);
-
-    if (touch) {
-        newPart = loadI32(con, 2, 0);
-        for (ct2=0; ct2<rate; ct2++) {
-            dx = scriptRand(con, 0x10000) - 0x8000;
-            dy = scriptRand(con, 0x10000) - 0x8000;
-
-            idx = newPart * 5 + 1;
-            storeI32(con, 2, idx, dx);
-            storeI32(con, 2, idx + 1, dy);
-            storeI32(con, 2, idx + 2, maxLife);
-            storeI32(con, 2, idx + 3, x << 16);
-            storeI32(con, 2, idx + 4, y << 16);
-
-            newPart++;
-            if (newPart >= count) {
-                newPart = 0;
-            }
-        }
-        storeI32(con, 2, 0, newPart);
-    }
-
-    drawCount = 0;
-    for (ct=0; ct < count; ct++) {
-        srcIdx = ct * 5 + 1;
-
-        dx = loadI32(con, 2, srcIdx);
-        dy = loadI32(con, 2, srcIdx + 1);
-        life = loadI32(con, 2, srcIdx + 2);
-        posx = loadI32(con, 2, srcIdx + 3);
-        posy = loadI32(con, 2, srcIdx + 4);
-
-        if (life) {
-            if (posy < (480 << 16)) {
-                dstIdx = drawCount * 9;
-                c = 0xffafcf | ((life >> lifeShift) << 24);
-
-                storeU32(con, 1, dstIdx, c);
-                storeI32(con, 1, dstIdx + 1, posx);
-                storeI32(con, 1, dstIdx + 2, posy);
-
-                storeU32(con, 1, dstIdx + 3, c);
-                storeI32(con, 1, dstIdx + 4, posx + 0x10000);
-                storeI32(con, 1, dstIdx + 5, posy + dy * 4);
-
-                storeU32(con, 1, dstIdx + 6, c);
-                storeI32(con, 1, dstIdx + 7, posx - 0x10000);
-                storeI32(con, 1, dstIdx + 8, posy + dy * 4);
-                drawCount ++;
-            } else {
-                if (dy > 0) {
-                    dy = (-dy) >> 1;
-                }
-            }
-
-            posx = posx + dx;
-            posy = posy + dy;
-            dy = dy + 0x400;
-            life --;
-
-            //storeI32(con, 2, srcIdx, dx);
-            storeI32(con, 2, srcIdx + 1, dy);
-            storeI32(con, 2, srcIdx + 2, life);
-            storeI32(con, 2, srcIdx + 3, posx);
-            storeI32(con, 2, srcIdx + 4, posy);
-        }
-    }
-
-    drawTriangleArray(con, NAMED_PartBuffer, drawCount);
-    return 1;
-}
diff --git a/libs/rs/java/Film/res/raw/filmstrip.c b/libs/rs/java/Film/res/raw/filmstrip.c
deleted file mode 100644
index bf75675..0000000
--- a/libs/rs/java/Film/res/raw/filmstrip.c
+++ /dev/null
@@ -1,94 +0,0 @@
-// Fountain test script
-
-#pragma version(1)
-#pragma stateVertex(PVBackground)
-#pragma stateFragment(PFBackground)
-#pragma stateStore(PSBackground)
-
-#define STATE_TRIANGLE_OFFSET_COUNT 0
-#define STATE_LAST_FOCUS 1
-
-
-// The script enviroment has 3 env allocations.
-// bank0: (r) The enviroment structure
-// bank1: (r) The position information
-// bank2: (rw) The temporary texture state
-
-int lastFocus;
-
-int main(int index)
-{
-    float mat1[16];
-
-    float trans = Pos->translate;
-    float rot = Pos->rotate;
-
-    matrixLoadScale(mat1, 2.f, 2.f, 2.f);
-    matrixTranslate(mat1, 0.f, 0.f, trans);
-    matrixRotate(mat1, 90.f, 0.f, 0.f, 1.f);
-    matrixRotate(mat1, rot, 1.f, 0.f, 0.f);
-    vpLoadModelMatrix(mat1);
-
-    // Draw the lighting effect in the strip and fill the Z buffer.
-    drawSimpleMesh(NAMED_mesh);
-
-    // Start of images.
-    bindProgramStore(NAMED_PSImages);
-    bindProgramFragment(NAMED_PFImages);
-    bindProgramVertex(NAMED_PVImages);
-
-    float focusPos = Pos->focus;
-    int focusID = 0;
-    int lastFocusID = loadI32(2, STATE_LAST_FOCUS);
-    int imgCount = 13;
-
-    if (trans > (-.3f)) {
-        focusID = -1.0f - focusPos;
-        if (focusID >= imgCount) {
-            focusID = -1;
-        }
-    } else {
-        focusID = -1;
-    }
-
-    /*
-    if (focusID != lastFocusID) {
-        if (lastFocusID >= 0) {
-            uploadToTexture(con, env->tex[lastFocusID], 1);
-        }
-        if (focusID >= 0) {
-            uploadToTexture(con, env->tex[focusID], 0);
-        }
-    }
-    */
-    lastFocus = focusID;
-
-    int triangleOffsetsCount = Pos->triangleOffsetCount;
-
-    int imgId = 0;
-    for (imgId=1; imgId <= imgCount; imgId++) {
-        float pos = focusPos + imgId + 0.4f;
-        int offset = (int)floorf(pos * 2.f);
-        pos = pos - 0.75f;
-
-        offset = offset + triangleOffsetsCount / 2;
-        if (!((offset < 0) || (offset >= triangleOffsetsCount))) {
-            int start = offset -2;
-            int end = offset + 2;
-
-            if (start < 0) {
-                start = 0;
-            }
-            if (end >= triangleOffsetsCount) {
-                end = triangleOffsetsCount-1;
-            }
-
-            bindTexture(NAMED_PFImages, 0, loadI32(0, imgId - 1));
-            matrixLoadTranslate(mat1, -pos - loadF(5, triangleOffsetsCount / 2), 0, 0);
-            vpLoadTextureMatrix(mat1);
-            drawSimpleMeshRange(NAMED_mesh, loadI32(4, start), (loadI32(4, end) - loadI32(4, start)));
-        }
-    }
-    return 0;
-}
-
diff --git a/libs/rs/java/Film/src/com/android/film/Film.java b/libs/rs/java/Film/src/com/android/film/Film.java
deleted file mode 100644
index 6e99816..0000000
--- a/libs/rs/java/Film/src/com/android/film/Film.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * 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.
- */
-
-package com.android.film;
-
-import android.renderscript.RSSurfaceView;
-import android.renderscript.RenderScript;
-
-import android.app.Activity;
-import android.content.res.Configuration;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.provider.Settings.System;
-import android.util.Config;
-import android.util.Log;
-import android.view.Menu;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.Window;
-import android.widget.Button;
-import android.widget.ListView;
-
-import java.lang.Runtime;
-
-public class Film extends Activity {
-    //EventListener mListener = new EventListener();
-
-    private static final String LOG_TAG = "libRS_jni";
-    private static final boolean DEBUG  = false;
-    private static final boolean LOG_ENABLED = DEBUG ? Config.LOGD : Config.LOGV;
-
-    private FilmView mView;
-
-    // get the current looper (from your Activity UI thread for instance
-
-
-
-    @Override
-    public void onCreate(Bundle icicle) {
-        super.onCreate(icicle);
-
-        // Create our Preview view and set it as the content of our
-        // Activity
-        mView = new FilmView(this);
-        setContentView(mView);
-    }
-
-    @Override
-    protected void onResume() {
-        // Ideally a game should implement onResume() and onPause()
-        // to take appropriate action when the activity looses focus
-        super.onResume();
-        mView.onResume();
-    }
-
-    @Override
-    protected void onPause() {
-        // Ideally a game should implement onResume() and onPause()
-        // to take appropriate action when the activity looses focus
-        super.onPause();
-        mView.onPause();
-
-        Runtime.getRuntime().exit(0);
-    }
-
-
-    static void log(String message) {
-        if (LOG_ENABLED) {
-            Log.v(LOG_TAG, message);
-        }
-    }
-
-
-}
-
diff --git a/libs/rs/java/Film/src/com/android/film/FilmRS.java b/libs/rs/java/Film/src/com/android/film/FilmRS.java
deleted file mode 100644
index 7d04502..0000000
--- a/libs/rs/java/Film/src/com/android/film/FilmRS.java
+++ /dev/null
@@ -1,258 +0,0 @@
-/*
- * 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.
- */
-
-package com.android.film;
-
-import java.io.Writer;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.util.Log;
-
-import android.renderscript.*;
-
-public class FilmRS {
-    class StripPosition {
-        public float translate;
-        public float rotate;
-        public float focus;
-        public int triangleOffsetCount;
-    }
-    StripPosition mPos = new StripPosition();
-
-
-    private final int STATE_LAST_FOCUS = 1;
-
-    public FilmRS() {
-    }
-
-    public void init(RenderScriptGL rs, Resources res, int width, int height) {
-        mRS = rs;
-        mRes = res;
-        initRS();
-    }
-
-    public void setFilmStripPosition(int x, int y)
-    {
-        if (x < 50) {
-            x = 50;
-        }
-        if (x > 270) {
-            x = 270;
-        }
-
-        float anim = ((float)x-50) / 270.f;
-        mPos.translate = 2f * anim + 0.5f;   // translation
-        mPos.rotate = (anim * 40);  // rotation
-        mPos.focus = ((float)y) / 16.f - 10.f;  // focusPos
-        mPos.triangleOffsetCount = mFSM.mTriangleOffsetsCount;
-        mAllocPos.data(mPos);
-    }
-
-
-    private Resources mRes;
-    private RenderScriptGL mRS;
-    private Script mScriptStrip;
-    private Script mScriptImage;
-    private Sampler mSampler;
-    private ProgramStore mPSBackground;
-    private ProgramStore mPSImages;
-    private ProgramFragment mPFBackground;
-    private ProgramFragment mPFImages;
-    private ProgramVertex mPVBackground;
-    private ProgramVertex mPVImages;
-    private ProgramVertex.MatrixAllocation mPVA;
-    private Type mStripPositionType;
-
-    private Allocation mImages[];
-    private Allocation mAllocIDs;
-    private Allocation mAllocPos;
-    private Allocation mAllocState;
-    private Allocation mAllocPV;
-    private Allocation mAllocOffsetsTex;
-    private Allocation mAllocOffsets;
-
-    private SimpleMesh mMesh;
-    private Light mLight;
-
-    private FilmStripMesh mFSM;
-
-    private int[] mBufferIDs;
-    private float[] mBufferPos = new float[3];
-    private int[] mBufferState;
-
-    private void initPFS() {
-        ProgramStore.Builder b = new ProgramStore.Builder(mRS, null, null);
-
-        b.setDepthFunc(ProgramStore.DepthFunc.LESS);
-        b.setDitherEnable(true);
-        b.setDepthMask(true);
-        mPSBackground = b.create();
-        mPSBackground.setName("PSBackground");
-
-        b.setDepthFunc(ProgramStore.DepthFunc.EQUAL);
-        b.setDitherEnable(false);
-        b.setDepthMask(false);
-        b.setBlendFunc(ProgramStore.BlendSrcFunc.ONE,
-                       ProgramStore.BlendDstFunc.ONE);
-        mPSImages = b.create();
-        mPSImages.setName("PSImages");
-    }
-
-    private void initPF() {
-        Sampler.Builder bs = new Sampler.Builder(mRS);
-        bs.setMin(Sampler.Value.LINEAR);//_MIP_LINEAR);
-        bs.setMag(Sampler.Value.LINEAR);
-        bs.setWrapS(Sampler.Value.CLAMP);
-        bs.setWrapT(Sampler.Value.WRAP);
-        mSampler = bs.create();
-
-        ProgramFragment.Builder b = new ProgramFragment.Builder(mRS);
-        mPFBackground = b.create();
-        mPFBackground.setName("PFBackground");
-
-        b = new ProgramFragment.Builder(mRS);
-        b.setTexture(ProgramFragment.Builder.EnvMode.REPLACE,
-                     ProgramFragment.Builder.Format.RGBA, 0);
-        mPFImages = b.create();
-        mPFImages.bindSampler(mSampler, 0);
-        mPFImages.setName("PFImages");
-    }
-
-    private void initPV() {
-        mLight = (new Light.Builder(mRS)).create();
-        mLight.setPosition(0, -0.5f, -1.0f);
-
-        ProgramVertex.Builder pvb = new ProgramVertex.Builder(mRS, null, null);
-        //pvb.addLight(mLight);
-        mPVBackground = pvb.create();
-        mPVBackground.setName("PVBackground");
-
-        pvb = new ProgramVertex.Builder(mRS, null, null);
-        pvb.setTextureMatrixEnable(true);
-        mPVImages = pvb.create();
-        mPVImages.setName("PVImages");
-    }
-
-    private void loadImages() {
-        mBufferIDs = new int[13];
-        mImages = new Allocation[13];
-        mAllocIDs = Allocation.createSized(mRS,
-            Element.createUser(mRS, Element.DataType.FLOAT_32),
-            mBufferIDs.length);
-
-        Element ie = Element.createPixel(mRS, Element.DataType.UNSIGNED_5_6_5, Element.DataKind.PIXEL_RGB);
-        mImages[0] = Allocation.createFromBitmapResourceBoxed(mRS, mRes, R.drawable.p01, ie, true);
-        mImages[1] = Allocation.createFromBitmapResourceBoxed(mRS, mRes, R.drawable.p02, ie, true);
-        mImages[2] = Allocation.createFromBitmapResourceBoxed(mRS, mRes, R.drawable.p03, ie, true);
-        mImages[3] = Allocation.createFromBitmapResourceBoxed(mRS, mRes, R.drawable.p04, ie, true);
-        mImages[4] = Allocation.createFromBitmapResourceBoxed(mRS, mRes, R.drawable.p05, ie, true);
-        mImages[5] = Allocation.createFromBitmapResourceBoxed(mRS, mRes, R.drawable.p06, ie, true);
-        mImages[6] = Allocation.createFromBitmapResourceBoxed(mRS, mRes, R.drawable.p07, ie, true);
-        mImages[7] = Allocation.createFromBitmapResourceBoxed(mRS, mRes, R.drawable.p08, ie, true);
-        mImages[8] = Allocation.createFromBitmapResourceBoxed(mRS, mRes, R.drawable.p09, ie, true);
-        mImages[9] = Allocation.createFromBitmapResourceBoxed(mRS, mRes, R.drawable.p10, ie, true);
-        mImages[10] = Allocation.createFromBitmapResourceBoxed(mRS, mRes, R.drawable.p11, ie, true);
-        mImages[11] = Allocation.createFromBitmapResourceBoxed(mRS, mRes, R.drawable.p12, ie, true);
-        mImages[12] = Allocation.createFromBitmapResourceBoxed(mRS, mRes, R.drawable.p13, ie, true);
-
-        int black[] = new int[1024];
-        for(int ct=0; ct < mImages.length; ct++) {
-            Allocation.Adapter2D a = mImages[ct].createAdapter2D();
-
-            int size = 512;
-            int mip = 0;
-            while(size >= 2) {
-                a.subData(0, 0, 2, size, black);
-                a.subData(size-2, 0, 2, size, black);
-                a.subData(0, 0, size, 2, black);
-                a.subData(0, size-2, size, 2, black);
-                size >>= 1;
-                mip++;
-                a.setConstraint(Dimension.LOD, mip);
-            }
-
-            mImages[ct].uploadToTexture(1);
-            mBufferIDs[ct] = mImages[ct].getID();
-        }
-        mAllocIDs.data(mBufferIDs);
-    }
-
-    private void initState()
-    {
-        mBufferState = new int[10];
-        mAllocState = Allocation.createSized(mRS,
-            Element.createUser(mRS, Element.DataType.FLOAT_32),
-            mBufferState.length);
-        mBufferState[STATE_LAST_FOCUS] = -1;
-        mAllocState.data(mBufferState);
-    }
-
-    private void initRS() {
-        mFSM = new FilmStripMesh();
-        mMesh = mFSM.init(mRS);
-        mMesh.setName("mesh");
-
-        initPFS();
-        initPF();
-        initPV();
-
-        Log.e("rs", "Done loading named");
-
-        mStripPositionType = Type.createFromClass(mRS, StripPosition.class, 1);
-
-        ScriptC.Builder sb = new ScriptC.Builder(mRS);
-        sb.setScript(mRes, R.raw.filmstrip);
-        sb.setRoot(true);
-        sb.setType(mStripPositionType, "Pos", 1);
-        mScriptStrip = sb.create();
-        mScriptStrip.setClearColor(0.0f, 0.0f, 0.0f, 1.0f);
-
-        mAllocPos = Allocation.createTyped(mRS, mStripPositionType);
-
-        loadImages();
-        initState();
-
-        mPVA = new ProgramVertex.MatrixAllocation(mRS);
-        mPVBackground.bindAllocation(mPVA);
-        mPVImages.bindAllocation(mPVA);
-        mPVA.setupProjectionNormalized(320, 480);
-
-
-        mScriptStrip.bindAllocation(mAllocIDs, 0);
-        mScriptStrip.bindAllocation(mAllocPos, 1);
-        mScriptStrip.bindAllocation(mAllocState, 2);
-        mScriptStrip.bindAllocation(mPVA.mAlloc, 3);
-
-
-        mAllocOffsets = Allocation.createSized(mRS,
-            Element.createUser(mRS, Element.DataType.SIGNED_32), mFSM.mTriangleOffsets.length);
-        mAllocOffsets.data(mFSM.mTriangleOffsets);
-        mScriptStrip.bindAllocation(mAllocOffsets, 4);
-
-        mAllocOffsetsTex = Allocation.createSized(mRS,
-            Element.createUser(mRS, Element.DataType.FLOAT_32), mFSM.mTriangleOffsetsTex.length);
-        mAllocOffsetsTex.data(mFSM.mTriangleOffsetsTex);
-        mScriptStrip.bindAllocation(mAllocOffsetsTex, 5);
-
-        setFilmStripPosition(0, 0);
-        mRS.contextBindRootScript(mScriptStrip);
-    }
-}
-
-
-
diff --git a/libs/rs/java/Film/src/com/android/film/FilmStripMesh.java b/libs/rs/java/Film/src/com/android/film/FilmStripMesh.java
deleted file mode 100644
index 448cce0..0000000
--- a/libs/rs/java/Film/src/com/android/film/FilmStripMesh.java
+++ /dev/null
@@ -1,259 +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 com.android.film;
-
-import java.io.Writer;
-import java.lang.Math;
-import android.util.Log;
-
-import android.renderscript.RenderScript;
-import android.renderscript.SimpleMesh;
-
-
-class FilmStripMesh {
-
-    class Vertex {
-        float nx;
-        float ny;
-        float nz;
-        float s;
-        float t;
-        float x;
-        float y;
-        float z;
-
-        Vertex() {
-            nx = 0;
-            ny = 0;
-            nz = 0;
-            s = 0;
-            t = 0;
-            x = 0;
-            y = 0;
-            z = 0;
-        }
-
-        void xyz(float _x, float _y, float _z) {
-            x = _x;
-            y = _y;
-            z = _z;
-        }
-
-        void nxyz(float _x, float _y, float _z) {
-            nx = _x;
-            ny = _y;
-            nz = _z;
-        }
-
-        void st(float _s, float _t) {
-            s = _s;
-            t = _t;
-        }
-
-        void computeNorm(Vertex v1, Vertex v2) {
-            float dx = v1.x - v2.x;
-            float dy = v1.y - v2.y;
-            float dz = v1.z - v2.z;
-            float len = (float)java.lang.Math.sqrt(dx*dx + dy*dy + dz*dz);
-            dx /= len;
-            dy /= len;
-            dz /= len;
-
-            nx = dx * dz;
-            ny = dy * dz;
-            nz = (float)java.lang.Math.sqrt(dx*dx + dy*dy);
-
-            len = (float)java.lang.Math.sqrt(nx*nx + ny*ny + nz*nz);
-            nx /= len;
-            ny /= len;
-            nz /= len;
-        }
-    }
-
-    int[] mTriangleOffsets;
-    float[] mTriangleOffsetsTex;
-    int mTriangleOffsetsCount;
-
-    SimpleMesh init(RenderScript rs)
-    {
-        float vtx[] = new float[] {
-            60.431003f, 124.482050f,
-            60.862074f, 120.872604f,
-            61.705303f, 117.336662f,
-            62.949505f, 113.921127f,
-            64.578177f, 110.671304f,
-            66.569716f, 107.630302f,
-            68.897703f, 104.838457f,
-            71.531259f, 102.332803f,
-            74.435452f, 100.146577f,
-            77.571757f, 98.308777f,
-            80.898574f, 96.843781f,
-            84.371773f, 95.771023f,
-            87.945283f, 95.104731f,
-            98.958994f, 95.267098f,
-            109.489523f, 98.497596f,
-            118.699582f, 104.539366f,
-            125.856872f, 112.912022f,
-            130.392311f, 122.949849f,
-            131.945283f, 133.854731f,
-            130.392311f, 144.759613f,
-            125.856872f, 154.797439f,
-            118.699582f, 163.170096f,
-            109.489523f, 169.211866f,
-            98.958994f, 172.442364f,
-            87.945283f, 172.604731f,
-            72.507313f, 172.672927f,
-            57.678920f, 168.377071f,
-            44.668135f, 160.067134f,
-            34.534908f, 148.420104f,
-            28.104767f, 134.384831f,
-            25.901557f, 119.104731f,
-            28.104767f, 103.824631f,
-            34.534908f, 89.789358f,
-            44.668135f, 78.142327f,
-            57.678920f, 69.832390f,
-            72.507313f, 65.536534f,
-            87.945283f, 65.604731f,
-            106.918117f, 65.688542f,
-            125.141795f, 60.409056f,
-            141.131686f, 50.196376f,
-            153.585137f, 35.882502f,
-            161.487600f, 18.633545f,
-            164.195283f, -0.145269f,
-            161.487600f, -18.924084f,
-            153.585137f, -36.173040f,
-            141.131686f, -50.486914f,
-            125.141795f, -60.699594f,
-            106.918117f, -65.979081f,
-            87.945283f, -65.895269f,
-            80f, -65.895269f,
-            60f, -65.895269f,
-            40f, -65.895269f,
-            20f, -65.895269f,
-            0f, -65.895269f,
-            -20f, -65.895269f,
-            -40f, -65.895269f,
-            -60f, -65.895269f,
-            -80f, -65.895269f,
-            -87.945283f, -65.895269f,
-            -106.918117f, -65.979081f,
-            -125.141795f, -60.699594f,
-            -141.131686f, -50.486914f,
-            -153.585137f, -36.173040f,
-            -161.487600f, -18.924084f,
-            -164.195283f, -0.145269f,
-            -161.487600f, 18.633545f,
-             -153.585137f, 35.882502f,
-             -141.131686f, 50.196376f,
-             -125.141795f, 60.409056f,
-             -106.918117f, 65.688542f,
-             -87.945283f, 65.604731f,
-             -72.507313f, 65.536534f,
-             -57.678920f, 69.832390f,
-             -44.668135f, 78.142327f,
-             -34.534908f, 89.789358f,
-             -28.104767f, 103.824631f,
-             -25.901557f, 119.104731f,
-             -28.104767f, 134.384831f,
-             -34.534908f, 148.420104f,
-             -44.668135f, 160.067134f,
-             -57.678920f, 168.377071f,
-             -72.507313f, 172.672927f,
-             -87.945283f, 172.604731f,
-             -98.958994f, 172.442364f,
-             -109.489523f, 169.211866f,
-             -118.699582f, 163.170096f,
-             -125.856872f, 154.797439f,
-             -130.392311f, 144.759613f,
-             -131.945283f, 133.854731f,
-             -130.392311f, 122.949849f,
-             -125.856872f, 112.912022f,
-             -118.699582f, 104.539366f,
-             -109.489523f, 98.497596f,
-             -98.958994f, 95.267098f,
-             -87.945283f, 95.104731f,
-             -84.371773f, 95.771023f,
-             -80.898574f, 96.843781f,
-             -77.571757f, 98.308777f,
-             -74.435452f, 100.146577f,
-             -71.531259f, 102.332803f,
-             -68.897703f, 104.838457f,
-             -66.569716f, 107.630302f,
-             -64.578177f, 110.671304f,
-             -62.949505f, 113.921127f,
-             -61.705303f, 117.336662f,
-             -60.862074f, 120.872604f,
-             -60.431003f, 124.482050f
-        };
-
-
-        mTriangleOffsets = new int[64];
-        mTriangleOffsetsTex = new float[64];
-
-        mTriangleOffsets[0] = 0;
-        mTriangleOffsetsCount = 1;
-
-        Vertex t = new Vertex();
-        t.nxyz(1, 0, 0);
-        int count = vtx.length / 2;
-
-        SimpleMesh.TriangleMeshBuilder tm = new SimpleMesh.TriangleMeshBuilder(
-            rs, 3,
-            SimpleMesh.TriangleMeshBuilder.NORMAL | SimpleMesh.TriangleMeshBuilder.TEXTURE_0);
-
-        float runningS = 0;
-        for (int ct=0; ct < (count-1); ct++) {
-            t.x = -vtx[ct*2] / 100.f;
-            t.z = vtx[ct*2+1] / 100.f;
-            t.s = runningS;
-            t.nx =  (vtx[ct*2+3] - vtx[ct*2 +1]);
-            t.ny =  (vtx[ct*2+2] - vtx[ct*2   ]);
-            float len = (float)java.lang.Math.sqrt(t.nx * t.nx + t.ny * t.ny);
-            runningS += len / 100;
-            t.nx /= len;
-            t.ny /= len;
-            t.y = -0.5f;
-            t.t = 0;
-            tm.setNormal(t.nx, t.ny, t.nz);
-            tm.setTexture(t.s, t.t);
-            tm.addVertex(t.x, t.y, t.z);
-            //android.util.Log.e("rs", "vtx x="+t.x+" y="+t.y+" z="+t.z+" s="+t.s+" t="+t.t);
-            t.y = .5f;
-            t.t = 1;
-            tm.setTexture(t.s, t.t);
-            tm.addVertex(t.x, t.y, t.z);
-            //android.util.Log.e("rs", "vtx x="+t.x+" y="+t.y+" z="+t.z+" s="+t.s+" t="+t.t);
-
-            if((runningS*2) > mTriangleOffsetsCount) {
-                mTriangleOffsets[mTriangleOffsetsCount] = ct*2 * 3;
-                mTriangleOffsetsTex[mTriangleOffsetsCount] = t.s;
-                mTriangleOffsetsCount ++;
-            }
-        }
-
-        count = (count * 2 - 2);
-        for (int ct=0; ct < (count-2); ct+= 2) {
-            tm.addTriangle(ct, ct+1, ct+2);
-            tm.addTriangle(ct+1, ct+3, ct+2);
-        }
-        return tm.create();
-    }
-
-
-}
-
diff --git a/libs/rs/java/Film/src/com/android/film/FilmView.java b/libs/rs/java/Film/src/com/android/film/FilmView.java
deleted file mode 100644
index 5bc2811..0000000
--- a/libs/rs/java/Film/src/com/android/film/FilmView.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * 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.
- */
-
-package com.android.film;
-
-import java.io.Writer;
-import java.util.ArrayList;
-import java.util.concurrent.Semaphore;
-
-import android.renderscript.RSSurfaceView;
-import android.renderscript.RenderScript;
-import android.renderscript.RenderScriptGL;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.os.Handler;
-import android.os.Message;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.Surface;
-import android.view.SurfaceHolder;
-import android.view.SurfaceView;
-import android.view.KeyEvent;
-import android.view.MotionEvent;
-
-public class FilmView extends RSSurfaceView {
-
-    public FilmView(Context context) {
-        super(context);
-        //setFocusable(true);
-    }
-
-    private RenderScriptGL mRS;
-    private FilmRS mRender;
-
-
-    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
-        super.surfaceChanged(holder, format, w, h);
-        if (mRS == null) {
-            mRS = createRenderScript(true);
-            mRS.contextSetSurface(w, h, holder.getSurface());
-            mRender = new FilmRS();
-            mRender.init(mRS, getResources(), w, h);
-        }
-    }
-
-    @Override
-    protected void onDetachedFromWindow() {
-        if(mRS != null) {
-            mRS = null;
-            destroyRenderScript();
-        }
-    }
-
-    @Override
-    public boolean onKeyDown(int keyCode, KeyEvent event)
-    {
-        // break point at here
-        // this method doesn't work when 'extends View' include 'extends ScrollView'.
-        return super.onKeyDown(keyCode, event);
-    }
-
-
-    @Override
-    public boolean onTouchEvent(MotionEvent ev)
-    {
-        boolean ret = true;
-        int act = ev.getAction();
-        if (act == ev.ACTION_UP) {
-            ret = false;
-        }
-        mRender.setFilmStripPosition((int)ev.getX(), (int)ev.getY() / 5);
-        return ret;
-    }
-}
-
-
diff --git a/libs/rs/java/Fountain/Android.mk b/libs/rs/java/Fountain/Android.mk
index f7e53a8..71944b2 100644
--- a/libs/rs/java/Fountain/Android.mk
+++ b/libs/rs/java/Fountain/Android.mk
@@ -14,14 +14,18 @@
 # limitations under the License.
 #
 
+ifneq ($(TARGET_SIMULATOR),true)
+
 LOCAL_PATH := $(call my-dir)
 include $(CLEAR_VARS)
 
 LOCAL_MODULE_TAGS := optional
 
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_SRC_FILES := $(call all-java-files-under, src) $(call all-renderscript-files-under, src)
 #LOCAL_STATIC_JAVA_LIBRARIES := android.renderscript
 
 LOCAL_PACKAGE_NAME := Fountain
 
 include $(BUILD_PACKAGE)
+
+endif
diff --git a/libs/rs/java/Fountain/res/raw/fountain.c b/libs/rs/java/Fountain/res/raw/fountain.c
deleted file mode 100644
index 73b819b..0000000
--- a/libs/rs/java/Fountain/res/raw/fountain.c
+++ /dev/null
@@ -1,52 +0,0 @@
-// Fountain test script
-#pragma version(1)
-
-int newPart = 0;
-
-int main(int launchID) {
-    int ct;
-    int count = Control->count;
-    int rate = Control->rate;
-    float height = getHeight();
-    struct point_s * p = (struct point_s *)point;
-
-    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 |
-                    (0xf0 << 24);
-        struct point_s * np = &p[newPart];
-
-        while (rate--) {
-            vec2Rand((float *)&np->delta.x, rMax);
-            np->position.x = x;
-            np->position.y = y;
-            np->color = color;
-            newPart++;
-            np++;
-            if (newPart >= count) {
-                newPart = 0;
-                np = &p[newPart];
-            }
-        }
-    }
-
-    for (ct=0; ct < count; ct++) {
-        float dy = p->delta.y + 0.15f;
-        float posy = p->position.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++;
-    }
-
-    uploadToBufferObject(NAMED_PartBuffer);
-    drawSimpleMesh(NAMED_PartMesh);
-    return 1;
-}
diff --git a/libs/rs/java/Fountain/res/raw/fountain2.rs b/libs/rs/java/Fountain/res/raw/fountain2.rs
deleted file mode 100644
index 3301140..0000000
--- a/libs/rs/java/Fountain/res/raw/fountain2.rs
+++ /dev/null
@@ -1,73 +0,0 @@
-// Fountain test script
-#pragma version(1)
-
-#include "rs_types.rsh"
-#include "rs_math.rsh"
-#include "rs_graphics.rsh"
-
-static int newPart = 0;
-
-typedef struct Control_s {
-    int x, y;
-    int rate;
-    int count;
-    float r, g, b;
-    rs_allocation partBuffer;
-    rs_mesh partMesh;
-} Control_t;
-Control_t *Control;
-
-typedef struct Point_s{
-    float2 delta;
-    float2 position;
-    unsigned int color;
-} Point_t;
-Point_t *point;
-
-int main(int launchID) {
-    int ct;
-    int count = Control->count;
-    int rate = Control->rate;
-    float height = getHeight();
-    Point_t * p = point;
-
-    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 |
-                    (0xf0 << 24);
-        Point_t * np = &p[newPart];
-
-        while (rate--) {
-            np->delta = vec2Rand(rMax);
-            np->position.x = x;
-            np->position.y = y;
-            np->color = color;
-            newPart++;
-            np++;
-            if (newPart >= count) {
-                newPart = 0;
-                np = &p[newPart];
-            }
-        }
-    }
-
-    for (ct=0; ct < count; ct++) {
-        float dy = p->delta.y + 0.15f;
-        float posy = p->position.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++;
-    }
-
-    uploadToBufferObject(Control->partBuffer);
-    drawSimpleMesh(Control->partMesh);
-    return 1;
-}
diff --git a/libs/rs/java/Fountain/src/com/android/fountain/FountainRS.java b/libs/rs/java/Fountain/src/com/android/fountain/FountainRS.java
index 9356579..297ea07 100644
--- a/libs/rs/java/Fountain/src/com/android/fountain/FountainRS.java
+++ b/libs/rs/java/Fountain/src/com/android/fountain/FountainRS.java
@@ -22,94 +22,50 @@
 
 
 public class FountainRS {
-    public static final int PART_COUNT = 20000;
-
-    static class SomeData {
-        public int x;
-        public int y;
-        public int rate;
-        public int count;
-        public float r;
-        public float g;
-        public float b;
-    }
+    public static final int PART_COUNT = 50000;
 
     public FountainRS() {
     }
 
+    private Resources mRes;
+    private RenderScriptGL mRS;
+    private ScriptC_Fountain mScript;
     public void init(RenderScriptGL rs, Resources res, int width, int height) {
         mRS = rs;
         mRes = res;
-        initRS();
+
+        ProgramFragment.Builder pfb = new ProgramFragment.Builder(rs);
+        pfb.setVaryingColor(true);
+        rs.contextBindProgramFragment(pfb.create());
+
+        ScriptField_Point points = new ScriptField_Point(mRS, PART_COUNT);
+
+        Mesh.AllocationBuilder smb = new Mesh.AllocationBuilder(mRS);
+        smb.addVertexAllocation(points.getAllocation());
+        smb.addIndexType(Primitive.POINT);
+        Mesh sm = smb.create();
+
+        mScript = new ScriptC_Fountain(mRS, mRes, R.raw.fountain, true);
+        mScript.set_partMesh(sm);
+        mScript.bind_point(points);
+        mRS.contextBindRootScript(mScript);
     }
 
-    public void newTouchPosition(int x, int y, int rate) {
-        if (mSD.rate == 0) {
-            mSD.r = ((x & 0x1) != 0) ? 0.f : 1.f;
-            mSD.g = ((x & 0x2) != 0) ? 0.f : 1.f;
-            mSD.b = ((x & 0x4) != 0) ? 0.f : 1.f;
-            if ((mSD.r + mSD.g + mSD.b) < 0.9f) {
-                mSD.r = 0.8f;
-                mSD.g = 0.5f;
-                mSD.b = 1.f;
-            }
+    boolean holdingColor[] = new boolean[10];
+    public void newTouchPosition(float x, float y, float pressure, int id) {
+        if (id > holdingColor.length) {
+            return;
         }
-        mSD.rate = rate;
-        mSD.x = x;
-        mSD.y = y;
-        mIntAlloc.data(mSD);
+        int rate = (int)(pressure * pressure * 500.f);
+        if(rate > 500) {
+            rate = 500;
+        }
+        if (rate > 0) {
+            mScript.invoke_addParticles(rate, x, y, id, !holdingColor[id]);
+            holdingColor[id] = true;
+        } else {
+            holdingColor[id] = false;
+        }
+
     }
-
-
-    /////////////////////////////////////////
-
-    private Resources mRes;
-
-    private RenderScriptGL mRS;
-    private Allocation mIntAlloc;
-    private SimpleMesh mSM;
-    private SomeData mSD;
-    private Type mSDType;
-
-    private void initRS() {
-        mSD = new SomeData();
-        mSDType = Type.createFromClass(mRS, SomeData.class, 1, "SomeData");
-        mIntAlloc = Allocation.createTyped(mRS, mSDType);
-        mSD.count = PART_COUNT;
-        mIntAlloc.data(mSD);
-
-        Element.Builder eb = new Element.Builder(mRS);
-        eb.add(Element.createVector(mRS, Element.DataType.FLOAT_32, 2), "delta");
-        eb.add(Element.createAttrib(mRS, Element.DataType.FLOAT_32, Element.DataKind.POSITION, 2), "position");
-        eb.add(Element.createAttrib(mRS, Element.DataType.UNSIGNED_8, Element.DataKind.COLOR, 4), "color");
-        Element primElement = eb.create();
-
-
-        SimpleMesh.Builder smb = new SimpleMesh.Builder(mRS);
-        int vtxSlot = smb.addVertexType(primElement, PART_COUNT);
-        smb.setPrimitive(Primitive.POINT);
-        mSM = smb.create();
-        mSM.setName("PartMesh");
-
-        Allocation partAlloc = mSM.createVertexAllocation(vtxSlot);
-        partAlloc.setName("PartBuffer");
-        mSM.bindVertexAllocation(partAlloc, 0);
-
-        // All setup of named objects should be done by this point
-        // because we are about to compile the script.
-        ScriptC.Builder sb = new ScriptC.Builder(mRS);
-        sb.setScript(mRes, R.raw.fountain);
-        sb.setRoot(true);
-        sb.setType(mSDType, "Control", 0);
-        sb.setType(mSM.getVertexType(0), "point", 1);
-        Script script = sb.create();
-        script.setClearColor(0.0f, 0.0f, 0.0f, 1.0f);
-
-        script.bindAllocation(mIntAlloc, 0);
-        script.bindAllocation(partAlloc, 1);
-        mRS.contextBindRootScript(script);
-    }
-
 }
-
-
diff --git a/libs/rs/java/Fountain/src/com/android/fountain/FountainView.java b/libs/rs/java/Fountain/src/com/android/fountain/FountainView.java
index dfd6a49..c1411656b 100644
--- a/libs/rs/java/Fountain/src/com/android/fountain/FountainView.java
+++ b/libs/rs/java/Fountain/src/com/android/fountain/FountainView.java
@@ -71,17 +71,33 @@
     @Override
     public boolean onTouchEvent(MotionEvent ev)
     {
-        int act = ev.getAction();
+        int act = ev.getActionMasked();
         if (act == ev.ACTION_UP) {
-            mRender.newTouchPosition(0, 0, 0);
+            mRender.newTouchPosition(0, 0, 0, ev.getPointerId(0));
             return false;
+        } else if (act == MotionEvent.ACTION_POINTER_UP) {
+            // only one pointer going up, we can get the index like this
+            int pointerIndex = ev.getActionIndex();
+            int pointerId = ev.getPointerId(pointerIndex);
+            mRender.newTouchPosition(0, 0, 0, pointerId);
         }
-        float rate = (ev.getPressure() * 50.f);
-        rate *= rate;
-        if(rate > 2000.f) {
-            rate = 2000.f;
+        int count = ev.getHistorySize();
+        int pcount = ev.getPointerCount();
+
+        for (int p=0; p < pcount; p++) {
+            int id = ev.getPointerId(p);
+            mRender.newTouchPosition(ev.getX(p),
+                                     ev.getY(p),
+                                     ev.getPressure(p),
+                                     id);
+
+            for (int i=0; i < count; i++) {
+                mRender.newTouchPosition(ev.getHistoricalX(p, i),
+                                         ev.getHistoricalY(p, i),
+                                         ev.getHistoricalPressure(p, i),
+                                         id);
+            }
         }
-        mRender.newTouchPosition((int)ev.getX(), (int)ev.getY(), (int)rate);
         return true;
     }
 }
diff --git a/libs/rs/java/Fountain/src/com/android/fountain/fountain.rs b/libs/rs/java/Fountain/src/com/android/fountain/fountain.rs
new file mode 100644
index 0000000..812cb7a
--- /dev/null
+++ b/libs/rs/java/Fountain/src/com/android/fountain/fountain.rs
@@ -0,0 +1,72 @@
+// Fountain test script
+#pragma version(1)
+
+#pragma rs java_package_name(com.android.fountain)
+
+#pragma stateFragment(parent)
+
+#include "rs_graphics.rsh"
+
+static int newPart = 0;
+rs_mesh partMesh;
+
+typedef struct __attribute__((packed, aligned(4))) Point {
+    float2 delta;
+    float2 position;
+    uchar4 color;
+} Point_t;
+Point_t *point;
+
+#pragma rs export_var(point, partMesh)
+#pragma rs export_func(addParticles)
+
+int root() {
+    float dt = min(rsGetDt(), 0.1f);
+    rsgClearColor(0.f, 0.f, 0.f, 1.f);
+    const float height = rsgGetHeight();
+    const int size = rsAllocationGetDimX(rsGetAllocation(point));
+    float dy2 = dt * (10.f);
+    Point_t * p = point;
+    for (int ct=0; ct < size; ct++) {
+        p->delta.y += dy2;
+        p->position += p->delta;
+        if ((p->position.y > height) && (p->delta.y > 0)) {
+            p->delta.y *= -0.3f;
+        }
+        p++;
+    }
+
+    rsgDrawMesh(partMesh);
+    return 1;
+}
+
+static float4 partColor[10];
+void addParticles(int rate, float x, float y, int index, bool newColor)
+{
+    if (newColor) {
+        partColor[index].x = rsRand(0.5f, 1.0f);
+        partColor[index].y = rsRand(1.0f);
+        partColor[index].z = rsRand(1.0f);
+    }
+    float rMax = ((float)rate) * 0.02f;
+    int size = rsAllocationGetDimX(rsGetAllocation(point));
+    uchar4 c = rsPackColorTo8888(partColor[index]);
+
+    Point_t * np = &point[newPart];
+    float2 p = {x, y};
+    while (rate--) {
+        float angle = rsRand(3.14f * 2.f);
+        float len = rsRand(rMax);
+        np->delta.x = len * sin(angle);
+        np->delta.y = len * cos(angle);
+        np->position = p;
+        np->color = c;
+        newPart++;
+        np++;
+        if (newPart >= size) {
+            newPart = 0;
+            np = &point[newPart];
+        }
+    }
+}
+
diff --git a/libs/rs/java/ImageProcessing/Android.mk b/libs/rs/java/ImageProcessing/Android.mk
index 833427b..7fa30d0 100644
--- a/libs/rs/java/ImageProcessing/Android.mk
+++ b/libs/rs/java/ImageProcessing/Android.mk
@@ -14,14 +14,19 @@
 # limitations under the License.
 #
 
+ifneq ($(TARGET_SIMULATOR),true)
+
 LOCAL_PATH := $(call my-dir)
 include $(CLEAR_VARS)
 
 LOCAL_MODULE_TAGS := optional
 
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_SRC_FILES := $(call all-java-files-under, src) \
+                   $(call all-renderscript-files-under, src)
 #LOCAL_STATIC_JAVA_LIBRARIES := android.renderscript
 
 LOCAL_PACKAGE_NAME := ImageProcessing
 
 include $(BUILD_PACKAGE)
+
+endif
diff --git a/libs/rs/java/ImageProcessing/AndroidManifest.xml b/libs/rs/java/ImageProcessing/AndroidManifest.xml
index b48d208..d6a2db4 100644
--- a/libs/rs/java/ImageProcessing/AndroidManifest.xml
+++ b/libs/rs/java/ImageProcessing/AndroidManifest.xml
@@ -6,7 +6,8 @@
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
 
     <application android:label="Image Processing">
-        <activity android:name="ImageProcessingActivity">
+        <activity android:name="ImageProcessingActivity"
+                  android:screenOrientation="portrait">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="android.intent.category.LAUNCHER" />
diff --git a/libs/rs/java/ImageProcessing/res/drawable/data.jpg b/libs/rs/java/ImageProcessing/res/drawable/data.jpg
new file mode 100644
index 0000000..81a87b1
--- /dev/null
+++ b/libs/rs/java/ImageProcessing/res/drawable/data.jpg
Binary files differ
diff --git a/libs/rs/java/ImageProcessing/res/layout/main.xml b/libs/rs/java/ImageProcessing/res/layout/main.xml
index 6770c18..c6ec729 100644
--- a/libs/rs/java/ImageProcessing/res/layout/main.xml
+++ b/libs/rs/java/ImageProcessing/res/layout/main.xml
@@ -25,9 +25,147 @@
         android:id="@+id/display"
         android:layout_width="320dip"
         android:layout_height="266dip" />
-    
+
+    <Button
+        android:layout_marginBottom="170dip"
+        android:layout_width="wrap_content"
+        android:layout_height="40dip"
+        android:text="@string/benchmark"
+        android:onClick="benchmark"
+        android:layout_gravity="bottom"/>
+
+    <TextView
+        android:id="@+id/benchmarkText"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:textSize="18sp"
+        android:layout_marginLeft="100dip"
+        android:layout_marginBottom="175dip"
+        android:layout_gravity="bottom"
+        android:text="@string/saturation"/>
+
+     <SeekBar
+        android:id="@+id/inSaturation"
+        android:layout_marginBottom="140dip"
+        android:layout_marginLeft="10dip"
+        android:layout_marginRight="10dip"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_gravity="bottom" />
+
+    <TextView
+        android:id="@+id/inSaturationText"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:textSize="18sp"
+        android:layout_marginLeft="50dip"
+        android:layout_marginBottom="142dip"
+        android:textColor="#000"
+        android:layout_gravity="bottom"
+        android:text="@string/saturation"/>
+
     <SeekBar
-        android:id="@+id/threshold"
+        android:id="@+id/inGamma"
+        android:layout_marginBottom="110dip"
+        android:layout_marginLeft="10dip"
+        android:layout_marginRight="10dip"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_gravity="bottom" />
+
+    <TextView
+        android:id="@+id/inGammaText"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:textSize="18sp"
+        android:layout_marginLeft="50dip"
+        android:layout_marginBottom="112dip"
+        android:textColor="#000"
+        android:layout_gravity="bottom"
+        android:text="@string/gamma"/>
+
+    <SeekBar
+        android:id="@+id/outWhite"
+        android:layout_marginBottom="80dip"
+        android:layout_marginLeft="170dip"
+        android:layout_marginRight="10dip"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_gravity="bottom" />
+
+    <TextView
+        android:id="@+id/outWhiteText"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:textSize="18sp"
+        android:layout_marginLeft="220dip"
+        android:layout_marginBottom="82dip"
+        android:textColor="#000"
+        android:layout_gravity="bottom"
+        android:text="@string/out_white"/>
+
+    <SeekBar
+        android:id="@+id/inWhite"
+        android:layout_marginBottom="80dip"
+        android:layout_marginLeft="10dip"
+        android:layout_marginRight="170dip"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_gravity="bottom" />
+
+    <TextView
+        android:id="@+id/inWhiteText"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:textSize="18sp"
+        android:layout_marginLeft="50dip"
+        android:layout_marginBottom="82dip"
+        android:textColor="#000"
+        android:layout_gravity="bottom"
+        android:text="@string/in_white"/>
+
+    <SeekBar
+        android:id="@+id/outBlack"
+        android:layout_marginBottom="50dip"
+        android:layout_marginLeft="170dip"
+        android:layout_marginRight="10dip"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_gravity="bottom" />
+
+    <TextView
+        android:id="@+id/outBlackText"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:textSize="18sp"
+        android:layout_marginLeft="220dip"
+        android:layout_marginBottom="52dip"
+        android:textColor="#000"
+        android:layout_gravity="bottom"
+        android:text="@string/out_black"/>
+
+    <SeekBar
+        android:id="@+id/inBlack"
+        android:layout_marginBottom="50dip"
+        android:layout_marginLeft="10dip"
+        android:layout_marginRight="170dip"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_gravity="bottom" />
+
+    <TextView
+        android:id="@+id/inBlackText"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:textSize="18sp"
+        android:layout_marginLeft="50dip"
+        android:layout_marginBottom="52dip"
+        android:textColor="#000"
+        android:layout_gravity="bottom"
+        android:text="@string/in_black"/>
+
+    <SeekBar
+        android:id="@+id/radius"
         android:layout_marginBottom="10dip"
         android:layout_marginLeft="10dip"
         android:layout_marginRight="10dip"
@@ -35,4 +173,15 @@
         android:layout_height="wrap_content"
         android:layout_gravity="bottom" />
 
+     <TextView
+        android:id="@+id/blurText"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:textSize="18sp"
+        android:layout_marginLeft="50dip"
+        android:layout_marginBottom="12dip"
+        android:textColor="#000"
+        android:layout_gravity="bottom"
+        android:text="@string/blur_description"/>
+
 </merge>
\ No newline at end of file
diff --git a/libs/rs/java/ImageProcessing/res/raw/threshold.rs b/libs/rs/java/ImageProcessing/res/raw/threshold.rs
deleted file mode 100644
index 888f0cd..0000000
--- a/libs/rs/java/ImageProcessing/res/raw/threshold.rs
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
-// block of defines matching what RS will insert at runtime.
-struct Params_s{
-    int inHeight;
-    int inWidth;
-    int outHeight;
-    int outWidth;
-    float threshold;
-};
-struct Params_s * Params;
-struct InPixel_s{
-    char a;
-    char b;
-    char g;
-    char r;
-};
-struct InPixel_s * InPixel;
-struct OutPixel_s{
-    char a;
-    char b;
-    char g;
-    char r;
-};
-struct OutPixel_s * OutPixel;
-*/
-
-struct color_s {
-    char b;
-    char g;
-    char r;
-    char a;
-};
-
-void main() {
-    int t = uptimeMillis();
-
-    struct color_s *in = (struct color_s *) InPixel;
-    struct color_s *out = (struct color_s *) 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->r +
-                          0.7154f * in->g +
-                          0.0721f * in->b;
-        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);
-}
diff --git a/libs/rs/java/ImageProcessing/res/values/strings.xml b/libs/rs/java/ImageProcessing/res/values/strings.xml
new file mode 100644
index 0000000..cc5cc4d
--- /dev/null
+++ b/libs/rs/java/ImageProcessing/res/values/strings.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+* 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.
+*/
+-->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- General -->
+    <skip />
+    <!--slider label -->
+    <string name="blur_description">Blur Radius</string>
+    <string name="in_white">In White</string>
+    <string name="out_white">Out White</string>
+    <string name="in_black">In Black</string>
+    <string name="out_black">Out Black</string>
+    <string name="gamma">Gamma</string>
+    <string name="saturation">Saturation</string>
+    <string name="benchmark">Benchmark</string>
+
+</resources>
diff --git a/libs/rs/java/ImageProcessing/src/com/android/rs/image/ImageProcessingActivity.java b/libs/rs/java/ImageProcessing/src/com/android/rs/image/ImageProcessingActivity.java
index 9ce53d8..24ef547 100644
--- a/libs/rs/java/ImageProcessing/src/com/android/rs/image/ImageProcessingActivity.java
+++ b/libs/rs/java/ImageProcessing/src/com/android/rs/image/ImageProcessingActivity.java
@@ -31,53 +31,56 @@
 import android.view.SurfaceHolder;
 import android.widget.ImageView;
 import android.widget.SeekBar;
+import android.widget.TextView;
+import android.view.View;
 import java.lang.Math;
 
-public class ImageProcessingActivity extends Activity implements SurfaceHolder.Callback {
-    private Bitmap mBitmap;
-    private Params mParams;
-    private Script.Invokable mInvokable;
-    private int[] mInData;
-    private int[] mOutData;
+public class ImageProcessingActivity extends Activity
+                                       implements SurfaceHolder.Callback,
+                                       SeekBar.OnSeekBarChangeListener {
+    private Bitmap mBitmapIn;
+    private Bitmap mBitmapOut;
+    private Bitmap mBitmapScratch;
+    private ScriptC_Threshold mScript;
+    private ScriptC_Vertical_blur mScriptVBlur;
+    private ScriptC_Horizontal_blur mScriptHBlur;
+    private ScriptC_Levels mScriptLevels;
+    private int mRadius = 0;
+    private SeekBar mRadiusSeekBar;
+
+    private float mInBlack = 0.0f;
+    private SeekBar mInBlackSeekBar;
+    private float mOutBlack = 0.0f;
+    private SeekBar mOutBlackSeekBar;
+    private float mInWhite = 255.0f;
+    private SeekBar mInWhiteSeekBar;
+    private float mOutWhite = 255.0f;
+    private SeekBar mOutWhiteSeekBar;
+    private float mGamma = 1.0f;
+    private SeekBar mGammaSeekBar;
+
+    private float mSaturation = 1.0f;
+    private SeekBar mSaturationSeekBar;
+
+    private TextView mBenchmarkResult;
 
     @SuppressWarnings({"FieldCanBeLocal"})
     private RenderScript mRS;
     @SuppressWarnings({"FieldCanBeLocal"})
-    private Type mParamsType;
-    @SuppressWarnings({"FieldCanBeLocal"})
-    private Allocation mParamsAllocation;
-    @SuppressWarnings({"FieldCanBeLocal"})
     private Type mPixelType;
     @SuppressWarnings({"FieldCanBeLocal"})
     private Allocation mInPixelsAllocation;
     @SuppressWarnings({"FieldCanBeLocal"})
     private Allocation mOutPixelsAllocation;
+    @SuppressWarnings({"FieldCanBeLocal"})
+    private Allocation mScratchPixelsAllocation;
 
     private SurfaceView mSurfaceView;
     private ImageView mDisplayView;
 
-    static class Params {
-        public int inWidth;
-        public int outWidth;
-        public int inHeight;
-        public int outHeight;
-
-        public float threshold;
-    }
-
-    static class Pixel {
-        public byte a;
-        public byte r;
-        public byte g;
-        public byte b;
-    }
-
     class FilterCallback extends RenderScript.RSMessage {
         private Runnable mAction = new Runnable() {
             public void run() {
-                mOutPixelsAllocation.readData(mOutData);
-                mBitmap.setPixels(mOutData, 0, mParams.outWidth, 0, 0,
-                        mParams.outWidth, mParams.outHeight);
                 mDisplayView.invalidate();
             }
         };
@@ -89,29 +92,218 @@
         }
     }
 
-    private void javaFilter() {
+    int in[];
+    int interm[];
+    int out[];
+    int MAX_RADIUS = 25;
+    // Store our coefficients here
+    float gaussian[];
+
+    private long javaFilter() {
+        final int width = mBitmapIn.getWidth();
+        final int height = mBitmapIn.getHeight();
+        final int count = width * height;
+
+        if (in == null) {
+            in = new int[count];
+            interm = new int[count];
+            out = new int[count];
+            gaussian = new float[MAX_RADIUS * 2 + 1];
+            mBitmapIn.getPixels(in, 0, width, 0, 0, width, height);
+        }
+
         long t = java.lang.System.currentTimeMillis();
-        int count = mParams.inWidth * mParams.inHeight;
-        float threshold = mParams.threshold * 255.f;
 
-        for (int i = 0; i < count; i++) {
-            final float r = (float)((mInData[i] >> 0) & 0xff);
-            final float g = (float)((mInData[i] >> 8) & 0xff);
-            final float b = (float)((mInData[i] >> 16) & 0xff);
+        int w, h, r;
 
-            final float luminance = 0.2125f * r +
-                              0.7154f * g +
-                              0.0721f * b;
-            if (luminance > threshold) {
-                mOutData[i] = mInData[i];
-            } else {
-                mOutData[i] = mInData[i] & 0xff000000;
+        float fRadius = (float)mRadius;
+        int radius = (int)mRadius;
+
+        // Compute gaussian weights for the blur
+        // e is the euler's number
+        float e = 2.718281828459045f;
+        float pi = 3.1415926535897932f;
+        // g(x) = ( 1 / sqrt( 2 * pi ) * sigma) * e ^ ( -x^2 / 2 * sigma^2 )
+        // x is of the form [-radius .. 0 .. radius]
+        // and sigma varies with radius.
+        // Based on some experimental radius values and sigma's
+        // we approximately fit sigma = f(radius) as
+        // sigma = radius * 0.4  + 0.6
+        // The larger the radius gets, the more our gaussian blur
+        // will resemble a box blur since with large sigma
+        // the gaussian curve begins to lose its shape
+        float sigma = 0.4f * fRadius + 0.6f;
+        // Now compute the coefficints
+        // We will store some redundant values to save some math during
+        // the blur calculations
+        // precompute some values
+        float coeff1 = 1.0f / (float)(Math.sqrt( 2.0f * pi ) * sigma);
+        float coeff2 = - 1.0f / (2.0f * sigma * sigma);
+        float normalizeFactor = 0.0f;
+        float floatR = 0.0f;
+        for(r = -radius; r <= radius; r ++) {
+            floatR = (float)r;
+            gaussian[r + radius] = coeff1 * (float)Math.pow(e, floatR * floatR * coeff2);
+            normalizeFactor += gaussian[r + radius];
+        }
+
+        //Now we need to normalize the weights because all our coefficients need to add up to one
+        normalizeFactor = 1.0f / normalizeFactor;
+        for(r = -radius; r <= radius; r ++) {
+            floatR = (float)r;
+            gaussian[r + radius] *= normalizeFactor;
+        }
+
+        float blurredPixelR = 0.0f;
+        float blurredPixelG = 0.0f;
+        float blurredPixelB = 0.0f;
+        float blurredPixelA = 0.0f;
+
+        for(h = 0; h < height; h ++) {
+            for(w = 0; w < width; w ++) {
+
+                blurredPixelR = 0.0f;
+                blurredPixelG = 0.0f;
+                blurredPixelB = 0.0f;
+                blurredPixelA = 0.0f;
+
+                for(r = -radius; r <= radius; r ++) {
+                    // Stepping left and right away from the pixel
+                    int validW = w + r;
+                    // Clamp to zero and width max() isn't exposed for ints yet
+                    if(validW < 0) {
+                        validW = 0;
+                    }
+                    if(validW > width - 1) {
+                        validW = width - 1;
+                    }
+
+                    int input = in[h*width + validW];
+
+                    int R = ((input >> 24) & 0xff);
+                    int G = ((input >> 16) & 0xff);
+                    int B = ((input >> 8) & 0xff);
+                    int A = (input & 0xff);
+
+                    float weight = gaussian[r + radius];
+
+                    blurredPixelR += (float)(R)*weight;
+                    blurredPixelG += (float)(G)*weight;
+                    blurredPixelB += (float)(B)*weight;
+                    blurredPixelA += (float)(A)*weight;
+                }
+
+                int R = (int)blurredPixelR;
+                int G = (int)blurredPixelG;
+                int B = (int)blurredPixelB;
+                int A = (int)blurredPixelA;
+
+                interm[h*width + w] = (R << 24) | (G << 16) | (B << 8) | (A);
+            }
+        }
+
+        for(h = 0; h < height; h ++) {
+            for(w = 0; w < width; w ++) {
+
+                blurredPixelR = 0.0f;
+                blurredPixelG = 0.0f;
+                blurredPixelB = 0.0f;
+                blurredPixelA = 0.0f;
+                for(r = -radius; r <= radius; r ++) {
+                    int validH = h + r;
+                    // Clamp to zero and width
+                    if(validH < 0) {
+                        validH = 0;
+                    }
+                    if(validH > height - 1) {
+                        validH = height - 1;
+                    }
+
+                    int input = interm[validH*width + w];
+
+                    int R = ((input >> 24) & 0xff);
+                    int G = ((input >> 16) & 0xff);
+                    int B = ((input >> 8) & 0xff);
+                    int A = (input & 0xff);
+
+                    float weight = gaussian[r + radius];
+
+                    blurredPixelR += (float)(R)*weight;
+                    blurredPixelG += (float)(G)*weight;
+                    blurredPixelB += (float)(B)*weight;
+                    blurredPixelA += (float)(A)*weight;
+                }
+
+                int R = (int)blurredPixelR;
+                int G = (int)blurredPixelG;
+                int B = (int)blurredPixelB;
+                int A = (int)blurredPixelA;
+
+                out[h*width + w] = (R << 24) | (G << 16) | (B << 8) | (A);
             }
         }
 
         t = java.lang.System.currentTimeMillis() - t;
+        android.util.Log.v("Img", "Java frame time ms " + t);
+        mBitmapOut.setPixels(out, 0, width, 0, 0, width, height);
+        return t;
+    }
 
-        android.util.Log.v("Img", "frame time ms " + t);
+    public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+        if (fromUser) {
+
+            if(seekBar == mRadiusSeekBar) {
+                float fRadius = progress / 100.0f;
+                fRadius *= (float)(MAX_RADIUS);
+                mRadius = (int)fRadius;
+
+                mScript.set_radius(mRadius);
+            }
+            else if(seekBar == mInBlackSeekBar) {
+                mInBlack = (float)progress;
+                mScriptLevels.invoke_setLevels(mInBlack, mOutBlack, mInWhite, mOutWhite);
+            }
+            else if(seekBar == mOutBlackSeekBar) {
+                mOutBlack = (float)progress;
+                mScriptLevels.invoke_setLevels(mInBlack, mOutBlack, mInWhite, mOutWhite);
+            }
+            else if(seekBar == mInWhiteSeekBar) {
+                mInWhite = (float)progress + 127.0f;
+                mScriptLevels.invoke_setLevels(mInBlack, mOutBlack, mInWhite, mOutWhite);
+            }
+            else if(seekBar == mOutWhiteSeekBar) {
+                mOutWhite = (float)progress + 127.0f;
+                mScriptLevels.invoke_setLevels(mInBlack, mOutBlack, mInWhite, mOutWhite);
+            }
+            else if(seekBar == mGammaSeekBar) {
+                mGamma = (float)progress/100.0f;
+                mGamma = Math.max(mGamma, 0.1f);
+                mGamma = 1.0f / mGamma;
+                mScriptLevels.invoke_setGamma(mGamma);
+            }
+            else if(seekBar == mSaturationSeekBar) {
+                mSaturation = (float)progress / 50.0f;
+                mScriptLevels.invoke_setSaturation(mSaturation);
+            }
+
+            long t = java.lang.System.currentTimeMillis();
+            if (true) {
+                mScript.invoke_filter();
+                mRS.finish();
+            } else {
+                javaFilter();
+                mDisplayView.invalidate();
+            }
+
+            t = java.lang.System.currentTimeMillis() - t;
+            android.util.Log.v("Img", "Renderscript frame time core ms " + t);
+        }
+    }
+
+    public void onStartTrackingTouch(SeekBar seekBar) {
+    }
+
+    public void onStopTrackingTouch(SeekBar seekBar) {
     }
 
     @Override
@@ -119,45 +311,54 @@
         super.onCreate(savedInstanceState);
         setContentView(R.layout.main);
 
-        mBitmap = loadBitmap(R.drawable.data);
+        mBitmapIn = loadBitmap(R.drawable.data);
+        mBitmapOut = loadBitmap(R.drawable.data);
+        mBitmapScratch = loadBitmap(R.drawable.data);
 
         mSurfaceView = (SurfaceView) findViewById(R.id.surface);
         mSurfaceView.getHolder().addCallback(this);
 
         mDisplayView = (ImageView) findViewById(R.id.display);
-        mDisplayView.setImageBitmap(mBitmap);
+        mDisplayView.setImageBitmap(mBitmapOut);
 
-        ((SeekBar) findViewById(R.id.threshold)).setOnSeekBarChangeListener(
-                new SeekBar.OnSeekBarChangeListener() {
-            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
-                if (fromUser) {
-                    mParams.threshold = progress / 100.0f;
-                    mParamsAllocation.data(mParams);
+        mRadiusSeekBar = (SeekBar) findViewById(R.id.radius);
+        mRadiusSeekBar.setOnSeekBarChangeListener(this);
 
-                    if (true) {
-                        mInvokable.execute();
-                    } else {
-                        javaFilter();
-                        mBitmap.setPixels(mOutData, 0, mParams.outWidth, 0, 0,
-                                mParams.outWidth, mParams.outHeight);
-                        mDisplayView.invalidate();
-                    }
-                }
-            }
+        mInBlackSeekBar = (SeekBar)findViewById(R.id.inBlack);
+        mInBlackSeekBar.setOnSeekBarChangeListener(this);
+        mInBlackSeekBar.setMax(128);
+        mInBlackSeekBar.setProgress(0);
+        mOutBlackSeekBar = (SeekBar)findViewById(R.id.outBlack);
+        mOutBlackSeekBar.setOnSeekBarChangeListener(this);
+        mOutBlackSeekBar.setMax(128);
+        mOutBlackSeekBar.setProgress(0);
 
-            public void onStartTrackingTouch(SeekBar seekBar) {
-            }
+        mInWhiteSeekBar = (SeekBar)findViewById(R.id.inWhite);
+        mInWhiteSeekBar.setOnSeekBarChangeListener(this);
+        mInWhiteSeekBar.setMax(128);
+        mInWhiteSeekBar.setProgress(128);
+        mOutWhiteSeekBar = (SeekBar)findViewById(R.id.outWhite);
+        mOutWhiteSeekBar.setOnSeekBarChangeListener(this);
+        mOutWhiteSeekBar.setMax(128);
+        mOutWhiteSeekBar.setProgress(128);
 
-            public void onStopTrackingTouch(SeekBar seekBar) {
-            }
-        });
+        mGammaSeekBar = (SeekBar)findViewById(R.id.inGamma);
+        mGammaSeekBar.setOnSeekBarChangeListener(this);
+        mGammaSeekBar.setMax(150);
+        mGammaSeekBar.setProgress(100);
+
+        mSaturationSeekBar = (SeekBar)findViewById(R.id.inSaturation);
+        mSaturationSeekBar.setOnSeekBarChangeListener(this);
+        mSaturationSeekBar.setProgress(50);
+
+        mBenchmarkResult = (TextView) findViewById(R.id.benchmarkText);
+        mBenchmarkResult.setText("Benchmark no yet run");
     }
 
     public void surfaceCreated(SurfaceHolder holder) {
-        mParams = createParams();
-        mInvokable = createScript();
-
-        mInvokable.execute();
+        createScript();
+        mScript.invoke_filter();
+        mRS.finish();
     }
 
     public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
@@ -166,54 +367,34 @@
     public void surfaceDestroyed(SurfaceHolder holder) {
     }
 
-    private Script.Invokable createScript() {
+    private void createScript() {
         mRS = RenderScript.create();
         mRS.mMessageCallback = new FilterCallback();
 
-        mParamsType = Type.createFromClass(mRS, Params.class, 1, "Parameters");
-        mParamsAllocation = Allocation.createTyped(mRS, mParamsType);
-        mParamsAllocation.data(mParams);
+        mInPixelsAllocation = Allocation.createBitmapRef(mRS, mBitmapIn);
+        mOutPixelsAllocation = Allocation.createBitmapRef(mRS, mBitmapOut);
+        mScratchPixelsAllocation = Allocation.createBitmapRef(mRS, mBitmapScratch);
 
-        final int pixelCount = mParams.inWidth * mParams.inHeight;
+        mScriptVBlur = new ScriptC_Vertical_blur(mRS, getResources(), R.raw.vertical_blur, false);
+        mScriptHBlur = new ScriptC_Horizontal_blur(mRS, getResources(), R.raw.horizontal_blur, false);
+        mScriptLevels = new ScriptC_Levels(mRS, getResources(), R.raw.levels, false);
 
-        mPixelType = Type.createFromClass(mRS, Pixel.class, 1, "Pixel");
-        mInPixelsAllocation = Allocation.createSized(mRS,
-                Element.createUser(mRS, Element.DataType.SIGNED_32),
-                pixelCount);
-        mOutPixelsAllocation = Allocation.createSized(mRS,
-                Element.createUser(mRS, Element.DataType.SIGNED_32),
-                pixelCount);
+        mScript = new ScriptC_Threshold(mRS, getResources(), R.raw.threshold, false);
+        mScript.set_width(mBitmapIn.getWidth());
+        mScript.set_height(mBitmapIn.getHeight());
+        mScript.set_radius(mRadius);
 
-        mInData = new int[pixelCount];
-        mBitmap.getPixels(mInData, 0, mParams.inWidth, 0, 0, mParams.inWidth, mParams.inHeight);
-        mInPixelsAllocation.data(mInData);
+        mScriptLevels.invoke_setLevels(mInBlack, mOutBlack, mInWhite, mOutWhite);
+        mScriptLevels.invoke_setGamma(mGamma);
+        mScriptLevels.invoke_setSaturation(mSaturation);
 
-        mOutData = new int[pixelCount];
-        mOutPixelsAllocation.data(mOutData);
+        mScript.bind_InPixel(mInPixelsAllocation);
+        mScript.bind_OutPixel(mOutPixelsAllocation);
+        mScript.bind_ScratchPixel(mScratchPixelsAllocation);
 
-        ScriptC.Builder sb = new ScriptC.Builder(mRS);
-        sb.setType(mParamsType, "Params", 0);
-        sb.setType(mPixelType, "InPixel", 1);
-        sb.setType(mPixelType, "OutPixel", 2);
-        sb.setType(true, 2);
-        Script.Invokable invokable = sb.addInvokable("main");
-        sb.setScript(getResources(), R.raw.threshold);
-        //sb.setRoot(true);
-
-        ScriptC script = sb.create();
-        script.bindAllocation(mParamsAllocation, 0);
-        script.bindAllocation(mInPixelsAllocation, 1);
-        script.bindAllocation(mOutPixelsAllocation, 2);
-
-        return invokable;
-    }
-
-    private Params createParams() {
-        final Params params = new Params();
-        params.inWidth = params.outWidth = mBitmap.getWidth();
-        params.inHeight = params.outHeight = mBitmap.getHeight();
-        params.threshold = 0.5f;
-        return params;
+        mScript.set_vBlurScript(mScriptVBlur);
+        mScript.set_hBlurScript(mScriptHBlur);
+        mScript.set_levelsScript(mScriptLevels);
     }
 
     private Bitmap loadBitmap(int resource) {
@@ -229,4 +410,30 @@
         source.recycle();
         return b;
     }
+
+    // button hook
+    public void benchmark(View v) {
+        android.util.Log.v("Img", "Benchmarking");
+        int oldRadius = mRadius;
+        mRadius = MAX_RADIUS;
+        mScript.set_radius(mRadius);
+
+        long t = java.lang.System.currentTimeMillis();
+
+        mScript.invoke_filterBenchmark();
+        mRS.finish();
+
+        t = java.lang.System.currentTimeMillis() - t;
+        android.util.Log.v("Img", "Renderscript frame time core ms " + t);
+
+        long javaTime = javaFilter();
+        mBenchmarkResult.setText("RS: " + t + " ms  Java: " + javaTime + " ms");
+        //mBenchmarkResult.setText("RS: " + t + " ms");
+
+        mRadius = oldRadius;
+        mScript.set_radius(mRadius);
+
+        mScript.invoke_filter();
+        mRS.finish();
+    }
 }
diff --git a/libs/rs/java/ImageProcessing/src/com/android/rs/image/horizontal_blur.rs b/libs/rs/java/ImageProcessing/src/com/android/rs/image/horizontal_blur.rs
new file mode 100644
index 0000000..4ed5aba
--- /dev/null
+++ b/libs/rs/java/ImageProcessing/src/com/android/rs/image/horizontal_blur.rs
@@ -0,0 +1,50 @@
+#pragma version(1)
+
+#include "ip.rsh"
+
+void root(const void *v_in, void *v_out, const void *usrData, uint32_t x, uint32_t y) {
+    uchar4 *output = (uchar4 *)v_out;
+    const FilterStruct *fs = (const FilterStruct *)usrData;
+    const uchar4 *input = (const uchar4 *)rsGetElementAt(fs->ain, 0, y);
+
+    float3 blurredPixel = 0;
+    float3 currentPixel = 0;
+
+    const float *gPtr = fs->gaussian;
+    if ((x > fs->radius) && (x < (fs->width - fs->radius))) {
+        const uchar4 *i = input + (x - fs->radius);
+        for(int r = -fs->radius; r <= fs->radius; r ++) {
+            currentPixel.x = (float)(i->x);
+            currentPixel.y = (float)(i->y);
+            currentPixel.z = (float)(i->z);
+            blurredPixel += currentPixel * gPtr[0];
+            gPtr++;
+            i++;
+        }
+    } else {
+        for(int r = -fs->radius; r <= fs->radius; r ++) {
+            // Stepping left and right away from the pixel
+            int validW = x + r;
+            // Clamp to zero and width max() isn't exposed for ints yet
+            if(validW < 0) {
+                validW = 0;
+            }
+            if(validW > fs->width - 1) {
+                validW = fs->width - 1;
+            }
+            //int validW = rsClamp(w + r, 0, width - 1);
+
+            currentPixel.x = (float)(input[validW].x);
+            currentPixel.y = (float)(input[validW].y);
+            currentPixel.z = (float)(input[validW].z);
+
+            blurredPixel += currentPixel * gPtr[0];
+            gPtr++;
+        }
+    }
+
+    output->x = (uint8_t)blurredPixel.x;
+    output->y = (uint8_t)blurredPixel.y;
+    output->z = (uint8_t)blurredPixel.z;
+}
+
diff --git a/libs/rs/java/ImageProcessing/src/com/android/rs/image/ip.rsh b/libs/rs/java/ImageProcessing/src/com/android/rs/image/ip.rsh
new file mode 100644
index 0000000..34213f5
--- /dev/null
+++ b/libs/rs/java/ImageProcessing/src/com/android/rs/image/ip.rsh
@@ -0,0 +1,15 @@
+#pragma rs java_package_name(com.android.rs.image)
+
+#define MAX_RADIUS 25
+
+typedef struct {
+    rs_allocation ain;
+
+    float *gaussian; //[MAX_RADIUS * 2 + 1];
+    int height;
+    int width;
+    int radius;
+
+} FilterStruct;
+
+
diff --git a/libs/rs/java/ImageProcessing/src/com/android/rs/image/levels.rs b/libs/rs/java/ImageProcessing/src/com/android/rs/image/levels.rs
new file mode 100644
index 0000000..bb8a6fc
--- /dev/null
+++ b/libs/rs/java/ImageProcessing/src/com/android/rs/image/levels.rs
@@ -0,0 +1,86 @@
+#pragma version(1)
+
+#include "ip.rsh"
+
+
+static float inBlack;
+static float outBlack;
+static float inWhite;
+static float outWhite;
+static float3 gamma;
+static float saturation;
+
+static float inWMinInB;
+static float outWMinOutB;
+static float overInWMinInB;
+static rs_matrix3x3 colorMat;
+
+//#pragma rs export_var(height, width, radius, InPixel, OutPixel, ScratchPixel, inBlack, outBlack, inWhite, outWhite, gamma, saturation, InPixel, OutPixel, ScratchPixel, vBlurScript, hBlurScript)
+#pragma rs export_func(setLevels, setSaturation, setGamma);
+
+void setLevels(float iBlk, float oBlk, float iWht, float oWht) {
+    inBlack = iBlk;
+    outBlack = oBlk;
+    inWhite = iWht;
+    outWhite = oWht;
+
+    inWMinInB = inWhite - inBlack;
+    outWMinOutB = outWhite - outBlack;
+    overInWMinInB = 1.f / inWMinInB;
+}
+
+void setSaturation(float sat) {
+    saturation = sat;
+
+    // Saturation
+    // Linear weights
+    //float rWeight = 0.3086f;
+    //float gWeight = 0.6094f;
+    //float bWeight = 0.0820f;
+
+    // Gamma 2.2 weights (we haven't converted our image to linear space yet for perf reasons)
+    float rWeight = 0.299f;
+    float gWeight = 0.587f;
+    float bWeight = 0.114f;
+
+    float oneMinusS = 1.0f - saturation;
+    rsMatrixSet(&colorMat, 0, 0, oneMinusS * rWeight + saturation);
+    rsMatrixSet(&colorMat, 0, 1, oneMinusS * rWeight);
+    rsMatrixSet(&colorMat, 0, 2, oneMinusS * rWeight);
+    rsMatrixSet(&colorMat, 1, 0, oneMinusS * gWeight);
+    rsMatrixSet(&colorMat, 1, 1, oneMinusS * gWeight + saturation);
+    rsMatrixSet(&colorMat, 1, 2, oneMinusS * gWeight);
+    rsMatrixSet(&colorMat, 2, 0, oneMinusS * bWeight);
+    rsMatrixSet(&colorMat, 2, 1, oneMinusS * bWeight);
+    rsMatrixSet(&colorMat, 2, 2, oneMinusS * bWeight + saturation);
+}
+
+void setGamma(float g) {
+    gamma = (float3)g;
+}
+
+
+void root(const void *v_in, void *v_out, const void *usrData, uint32_t x, uint32_t y) {
+    const uchar4 *input = v_in;
+    uchar4 *output = v_out;
+
+    float3 currentPixel = 0;
+
+    //currentPixel.xyz = convert_float3(input.xyz);
+    currentPixel.x = (float)(input->x);
+    currentPixel.y = (float)(input->y);
+    currentPixel.z = (float)(input->z);
+
+    float3 temp = rsMatrixMultiply(&colorMat, currentPixel);
+    temp = (clamp(temp, 0.f, 255.f) - inBlack) * overInWMinInB;
+    if (gamma.x != 1.0f)
+        temp = pow(temp, (float3)gamma);
+    currentPixel = clamp(temp * outWMinOutB + outBlack, 0.f, 255.f);
+
+    //output.xyz = convert_uchar3(currentPixel.xyz);
+    output->x = (uint8_t)currentPixel.x;
+    output->y = (uint8_t)currentPixel.y;
+    output->z = (uint8_t)currentPixel.z;
+    output->w = input->w;
+}
+
diff --git a/libs/rs/java/ImageProcessing/src/com/android/rs/image/threshold.rs b/libs/rs/java/ImageProcessing/src/com/android/rs/image/threshold.rs
new file mode 100644
index 0000000..8e198c7
--- /dev/null
+++ b/libs/rs/java/ImageProcessing/src/com/android/rs/image/threshold.rs
@@ -0,0 +1,100 @@
+#pragma version(1)
+
+#include "ip.rsh"
+
+int height;
+int width;
+int radius;
+
+uchar4 * InPixel;
+uchar4 * OutPixel;
+uchar4 * ScratchPixel;
+
+#pragma rs export_var(height, width, radius, InPixel, OutPixel, ScratchPixel, vBlurScript, hBlurScript, levelsScript)
+#pragma rs export_func(filter, filterBenchmark);
+
+rs_script vBlurScript;
+rs_script hBlurScript;
+rs_script levelsScript;
+
+const int CMD_FINISHED = 1;
+
+// Store our coefficients here
+static float gaussian[MAX_RADIUS * 2 + 1];
+
+
+static void computeGaussianWeights() {
+    // Compute gaussian weights for the blur
+    // e is the euler's number
+    float e = 2.718281828459045f;
+    float pi = 3.1415926535897932f;
+    // g(x) = ( 1 / sqrt( 2 * pi ) * sigma) * e ^ ( -x^2 / 2 * sigma^2 )
+    // x is of the form [-radius .. 0 .. radius]
+    // and sigma varies with radius.
+    // Based on some experimental radius values and sigma's
+    // we approximately fit sigma = f(radius) as
+    // sigma = radius * 0.4  + 0.6
+    // The larger the radius gets, the more our gaussian blur
+    // will resemble a box blur since with large sigma
+    // the gaussian curve begins to lose its shape
+    float sigma = 0.4f * (float)radius + 0.6f;
+
+    // Now compute the coefficints
+    // We will store some redundant values to save some math during
+    // the blur calculations
+    // precompute some values
+    float coeff1 = 1.0f / (sqrt( 2.0f * pi ) * sigma);
+    float coeff2 = - 1.0f / (2.0f * sigma * sigma);
+
+    float normalizeFactor = 0.0f;
+    float floatR = 0.0f;
+    int r;
+    for(r = -radius; r <= radius; r ++) {
+        floatR = (float)r;
+        gaussian[r + radius] = coeff1 * pow(e, floatR * floatR * coeff2);
+        normalizeFactor += gaussian[r + radius];
+    }
+
+    //Now we need to normalize the weights because all our coefficients need to add up to one
+    normalizeFactor = 1.0f / normalizeFactor;
+    for(r = -radius; r <= radius; r ++) {
+        floatR = (float)r;
+        gaussian[r + radius] *= normalizeFactor;
+    }
+}
+
+
+static void blur() {
+    computeGaussianWeights();
+
+    FilterStruct fs;
+    fs.gaussian = gaussian;
+    fs.width = width;
+    fs.height = height;
+    fs.radius = radius;
+
+    fs.ain = rsGetAllocation(InPixel);
+    rsForEach(hBlurScript, fs.ain, rsGetAllocation(ScratchPixel), &fs);
+
+    fs.ain = rsGetAllocation(ScratchPixel);
+    rsForEach(vBlurScript, fs.ain, rsGetAllocation(OutPixel), &fs);
+}
+
+void filter() {
+    //RS_DEBUG(radius);
+
+    if(radius > 0) {
+        blur();
+        rsForEach(levelsScript, rsGetAllocation(OutPixel), rsGetAllocation(OutPixel), 0);
+    } else {
+        rsForEach(levelsScript, rsGetAllocation(InPixel), rsGetAllocation(OutPixel), 0);
+    }
+
+    rsSendToClientBlocking(CMD_FINISHED);
+}
+
+void filterBenchmark() {
+    blur();
+    rsSendToClientBlocking(CMD_FINISHED);
+}
+
diff --git a/libs/rs/java/ImageProcessing/src/com/android/rs/image/vertical_blur.rs b/libs/rs/java/ImageProcessing/src/com/android/rs/image/vertical_blur.rs
new file mode 100644
index 0000000..008c8b5
--- /dev/null
+++ b/libs/rs/java/ImageProcessing/src/com/android/rs/image/vertical_blur.rs
@@ -0,0 +1,59 @@
+#pragma version(1)
+
+#include "ip.rsh"
+
+void root(const void *v_in, void *v_out, const void *usrData, uint32_t x, uint32_t y) {
+    uchar4 *output = (uchar4 *)v_out;
+    const FilterStruct *fs = (const FilterStruct *)usrData;
+    const uchar4 *input = (const uchar4 *)rsGetElementAt(fs->ain, x, 0);
+
+    float3 blurredPixel = 0;
+    float3 currentPixel = 0;
+
+    const float *gPtr = fs->gaussian;
+    if ((y > fs->radius) && (y < (fs->height - fs->radius))) {
+        const uchar4 *i = input + ((y - fs->radius) * fs->width);
+        for(int r = -fs->radius; r <= fs->radius; r ++) {
+            currentPixel.x = (float)(i->x);
+            currentPixel.y = (float)(i->y);
+            currentPixel.z = (float)(i->z);
+            blurredPixel += currentPixel * gPtr[0];
+            gPtr++;
+            i += fs->width;
+        }
+    } else {
+        for(int r = -fs->radius; r <= fs->radius; r ++) {
+    #if 1
+            int validH = y + r;
+            // Clamp to zero and width
+            if(validH < 0) {
+                validH = 0;
+            }
+            if(validH > fs->height - 1) {
+                validH = fs->height - 1;
+            }
+
+            const uchar4 *i = input + validH * fs->width;
+            //const uchar4 *i = (const uchar4 *)rsGetElementAt(fs->ain, x, validH);
+
+            currentPixel.x = (float)(i->x);
+            currentPixel.y = (float)(i->y);
+            currentPixel.z = (float)(i->z);
+            blurredPixel += currentPixel * gPtr[0];
+            gPtr++;
+    #else
+            int validH = rsClamp(y + r, 0, height - 1);
+            validH -= y;
+            uchar4 *i = input + validH * width + x;
+            blurredPixel.xyz += convert_float3(i->xyz) * gPtr[0];
+            gPtr++;
+    #endif
+        }
+    }
+
+    //output->xyz = convert_uchar3(blurredPixel.xyz);
+    output->x = (uint8_t)blurredPixel.x;
+    output->y = (uint8_t)blurredPixel.y;
+    output->z = (uint8_t)blurredPixel.z;
+}
+
diff --git a/libs/rs/java/ModelViewer/Android.mk b/libs/rs/java/ModelViewer/Android.mk
new file mode 100644
index 0000000..efe77d7
--- /dev/null
+++ b/libs/rs/java/ModelViewer/Android.mk
@@ -0,0 +1,31 @@
+#
+# 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.
+#
+
+ifneq ($(TARGET_SIMULATOR),true)
+
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src) $(call all-renderscript-files-under, src)
+#LOCAL_STATIC_JAVA_LIBRARIES := android.renderscript
+
+LOCAL_PACKAGE_NAME := ModelViewer
+
+include $(BUILD_PACKAGE)
+
+endif
diff --git a/libs/rs/java/ModelViewer/AndroidManifest.xml b/libs/rs/java/ModelViewer/AndroidManifest.xml
new file mode 100644
index 0000000..ebbe743
--- /dev/null
+++ b/libs/rs/java/ModelViewer/AndroidManifest.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.modelviewer">
+    <application android:label="ModelViewer">
+        <activity android:name="ModelViewer"
+                  android:screenOrientation="portrait"
+                  android:theme="@android:style/Theme.Black.NoTitleBar">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
diff --git a/libs/rs/java/ModelViewer/res/drawable/robot.png b/libs/rs/java/ModelViewer/res/drawable/robot.png
new file mode 100644
index 0000000..7c85e56
--- /dev/null
+++ b/libs/rs/java/ModelViewer/res/drawable/robot.png
Binary files differ
diff --git a/libs/rs/java/ModelViewer/res/raw/robot.a3d b/libs/rs/java/ModelViewer/res/raw/robot.a3d
new file mode 100644
index 0000000..2d7d32b
--- /dev/null
+++ b/libs/rs/java/ModelViewer/res/raw/robot.a3d
Binary files differ
diff --git a/libs/rs/java/ModelViewer/src/com/android/modelviewer/ModelViewer.java b/libs/rs/java/ModelViewer/src/com/android/modelviewer/ModelViewer.java
new file mode 100644
index 0000000..7491744
--- /dev/null
+++ b/libs/rs/java/ModelViewer/src/com/android/modelviewer/ModelViewer.java
@@ -0,0 +1,71 @@
+/*
+ * 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.
+ */
+
+package com.android.modelviewer;
+
+import android.renderscript.RSSurfaceView;
+import android.renderscript.RenderScript;
+
+import android.app.Activity;
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.provider.Settings.System;
+import android.util.Config;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.Window;
+import android.widget.Button;
+import android.widget.ListView;
+
+import java.lang.Runtime;
+
+public class ModelViewer extends Activity {
+
+    private ModelViewerView mView;
+
+    @Override
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+
+        // Create our Preview view and set it as the content of our
+        // Activity
+        mView = new ModelViewerView(this);
+        setContentView(mView);
+    }
+
+    @Override
+    protected void onResume() {
+        // Ideally a game should implement onResume() and onPause()
+        // to take appropriate action when the activity looses focus
+        super.onResume();
+        mView.onResume();
+    }
+
+    @Override
+    protected void onPause() {
+        // Ideally a game should implement onResume() and onPause()
+        // to take appropriate action when the activity looses focus
+        super.onPause();
+        mView.onPause();
+    }
+
+}
+
diff --git a/libs/rs/java/ModelViewer/src/com/android/modelviewer/ModelViewerRS.java b/libs/rs/java/ModelViewer/src/com/android/modelviewer/ModelViewerRS.java
new file mode 100644
index 0000000..479aaf3
--- /dev/null
+++ b/libs/rs/java/ModelViewer/src/com/android/modelviewer/ModelViewerRS.java
@@ -0,0 +1,170 @@
+/*
+ * 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.
+ */
+
+package com.android.modelviewer;
+
+import java.io.Writer;
+
+import android.content.res.Resources;
+import android.renderscript.*;
+import android.renderscript.ProgramStore.DepthFunc;
+import android.util.Log;
+
+
+public class ModelViewerRS {
+
+    private final int STATE_LAST_FOCUS = 1;
+
+    int mWidth;
+    int mHeight;
+    int mRotation;
+
+    public ModelViewerRS() {
+    }
+
+    public void init(RenderScriptGL rs, Resources res, int width, int height) {
+        mRS = rs;
+        mRes = res;
+        mWidth = width;
+        mHeight = height;
+        mRotation = 0;
+        initRS();
+    }
+
+    private Resources mRes;
+    private RenderScriptGL mRS;
+    private Sampler mSampler;
+    private ProgramStore mPSBackground;
+    private ProgramFragment mPFBackground;
+    private ProgramVertex mPVBackground;
+    private ProgramVertex.MatrixAllocation mPVA;
+
+    private Allocation mGridImage;
+    private Allocation mAllocPV;
+
+    private Mesh mMesh;
+
+    private Font mItalic;
+    private Allocation mTextAlloc;
+
+    private ScriptC_Modelviewer mScript;
+
+    int mLastX;
+    int mLastY;
+
+    public void touchEvent(int x, int y) {
+        int dx = mLastX - x;
+        if(Math.abs(dx) > 50 || Math.abs(dx) < 3) {
+            dx = 0;
+        }
+
+        mRotation -= dx;
+        if(mRotation > 360) {
+            mRotation -= 360;
+        }
+        if(mRotation < 0) {
+            mRotation += 360;
+        }
+
+        mScript.set_gRotate(-(float)mRotation);
+
+        mLastX = x;
+        mLastY = y;
+    }
+
+    private void initPFS() {
+        ProgramStore.Builder b = new ProgramStore.Builder(mRS, null, null);
+
+        b.setDepthFunc(ProgramStore.DepthFunc.LESS);
+        b.setDitherEnable(false);
+        b.setDepthMask(true);
+        mPSBackground = b.create();
+
+        mScript.set_gPFSBackground(mPSBackground);
+    }
+
+    private void initPF() {
+        Sampler.Builder bs = new Sampler.Builder(mRS);
+        bs.setMin(Sampler.Value.LINEAR);
+        bs.setMag(Sampler.Value.LINEAR);
+        bs.setWrapS(Sampler.Value.CLAMP);
+        bs.setWrapT(Sampler.Value.WRAP);
+        mSampler = bs.create();
+
+        ProgramFragment.Builder b = new ProgramFragment.Builder(mRS);
+        b.setTexture(ProgramFragment.Builder.EnvMode.REPLACE,
+                     ProgramFragment.Builder.Format.RGBA, 0);
+        mPFBackground = b.create();
+        mPFBackground.bindSampler(mSampler, 0);
+
+        mScript.set_gPFBackground(mPFBackground);
+    }
+
+    private void initPV() {
+        ProgramVertex.Builder pvb = new ProgramVertex.Builder(mRS, null, null);
+        mPVBackground = pvb.create();
+
+        mPVA = new ProgramVertex.MatrixAllocation(mRS);
+        mPVBackground.bindAllocation(mPVA);
+        mPVA.setupProjectionNormalized(mWidth, mHeight);
+
+        mScript.set_gPVBackground(mPVBackground);
+    }
+
+    private void loadImage() {
+        mGridImage = Allocation.createFromBitmapResourceBoxed(mRS, mRes, R.drawable.robot, Element.RGB_565(mRS), true);
+        mGridImage.uploadToTexture(0);
+
+        mScript.set_gTGrid(mGridImage);
+    }
+
+    private void initTextAllocation() {
+        String allocString = "Displaying file: R.raw.robot";
+        mTextAlloc = Allocation.createFromString(mRS, allocString);
+        mScript.set_gTextAlloc(mTextAlloc);
+    }
+
+    private void initRS() {
+
+        mScript = new ScriptC_Modelviewer(mRS, mRes, R.raw.modelviewer, true);
+
+        initPFS();
+        initPF();
+        initPV();
+
+        loadImage();
+
+        FileA3D model = FileA3D.createFromResource(mRS, mRes, R.raw.robot);
+        FileA3D.IndexEntry entry = model.getIndexEntry(0);
+        if(entry == null || entry.getClassID() != FileA3D.ClassID.MESH) {
+            Log.e("rs", "could not load model");
+        }
+        else {
+            mMesh = (Mesh)entry.getObject();
+            mScript.set_gTestMesh(mMesh);
+        }
+
+        mItalic = Font.create(mRS, mRes, "DroidSerif-Italic.ttf", 8);
+        mScript.set_gItalic(mItalic);
+
+        initTextAllocation();
+
+        mRS.contextBindRootScript(mScript);
+    }
+}
+
+
+
diff --git a/libs/rs/java/ModelViewer/src/com/android/modelviewer/ModelViewerView.java b/libs/rs/java/ModelViewer/src/com/android/modelviewer/ModelViewerView.java
new file mode 100644
index 0000000..061cf8e
--- /dev/null
+++ b/libs/rs/java/ModelViewer/src/com/android/modelviewer/ModelViewerView.java
@@ -0,0 +1,94 @@
+/*
+ * 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.
+ */
+
+package com.android.modelviewer;
+
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.concurrent.Semaphore;
+
+import android.renderscript.RSSurfaceView;
+import android.renderscript.RenderScript;
+import android.renderscript.RenderScriptGL;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.os.Message;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.Surface;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+
+public class ModelViewerView extends RSSurfaceView {
+
+    public ModelViewerView(Context context) {
+        super(context);
+        //setFocusable(true);
+    }
+
+    private RenderScriptGL mRS;
+    private ModelViewerRS mRender;
+
+
+    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
+        super.surfaceChanged(holder, format, w, h);
+        if (mRS == null) {
+            mRS = createRenderScript(true);
+            mRS.contextSetSurface(w, h, holder.getSurface());
+            mRender = new ModelViewerRS();
+            mRender.init(mRS, getResources(), w, h);
+        }
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        if(mRS != null) {
+            mRS = null;
+            destroyRenderScript();
+        }
+    }
+
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event)
+    {
+        // break point at here
+        // this method doesn't work when 'extends View' include 'extends ScrollView'.
+        return super.onKeyDown(keyCode, event);
+    }
+
+
+    @Override
+    public boolean onTouchEvent(MotionEvent ev)
+    {
+        boolean ret = true;
+        int act = ev.getAction();
+        if (act == ev.ACTION_UP) {
+            ret = false;
+        }
+
+        mRender.touchEvent((int)ev.getX(), (int)ev.getY());
+        return ret;
+    }
+}
+
+
diff --git a/libs/rs/java/ModelViewer/src/com/android/modelviewer/modelviewer.rs b/libs/rs/java/ModelViewer/src/com/android/modelviewer/modelviewer.rs
new file mode 100644
index 0000000..adb609c
--- /dev/null
+++ b/libs/rs/java/ModelViewer/src/com/android/modelviewer/modelviewer.rs
@@ -0,0 +1,72 @@
+// 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.
+
+#pragma version(1)
+
+#pragma rs java_package_name(com.android.modelviewer)
+
+#include "rs_graphics.rsh"
+
+rs_program_vertex gPVBackground;
+rs_program_fragment gPFBackground;
+
+rs_allocation gTGrid;
+rs_mesh gTestMesh;
+
+rs_program_store gPFSBackground;
+
+float gRotate;
+
+rs_font gItalic;
+rs_allocation gTextAlloc;
+
+#pragma rs export_var(gPVBackground, gPFBackground, gTGrid, gTestMesh, gPFSBackground, gRotate, gItalic, gTextAlloc)
+
+float gDT;
+int64_t gLastTime;
+
+void init() {
+    gRotate = 0.0f;
+}
+
+int root(int launchID) {
+
+    rsgClearColor(1.0f, 1.0f, 1.0f, 1.0f);
+    rsgClearDepth(1.0f);
+
+    rsgBindProgramVertex(gPVBackground);
+
+    rsgBindProgramFragment(gPFBackground);
+    rsgBindProgramStore(gPFSBackground);
+    rsgBindTexture(gPFBackground, 0, gTGrid);
+
+    rs_matrix4x4 matrix;
+    rsMatrixLoadIdentity(&matrix);
+    // Position our model on the screen
+    rsMatrixTranslate(&matrix, 0.0f, -0.3f, 1.2f);
+    rsMatrixScale(&matrix, 0.2f, 0.2f, 0.2f);
+    rsMatrixRotate(&matrix, -25.0f, 1.0f, 0.0f, 0.0f);
+    rsMatrixRotate(&matrix, gRotate, 0.0f, 1.0f, 0.0f);
+    rsgProgramVertexLoadModelMatrix(&matrix);
+
+    rsgDrawMesh(gTestMesh);
+
+    rsgFontColor(0.3f, 0.3f, 0.3f, 1.0f);
+    rsgDrawText("Renderscript model test", 30, 695);
+
+    rsgBindFont(gItalic);
+    rsgDrawText(gTextAlloc, 30, 730);
+
+    return 10;
+}
diff --git a/libs/rs/rs.spec b/libs/rs/rs.spec
index 5ae8d01..571b145 100644
--- a/libs/rs/rs.spec
+++ b/libs/rs/rs.spec
@@ -1,11 +1,14 @@
 
+ContextFinish {
+	handcodeApi
+	}
 
 ContextBindRootScript {
 	param RsScript sampler
 	}
 
-ContextBindProgramFragmentStore {
-	param RsProgramFragmentStore pgm
+ContextBindProgramStore {
+	param RsProgramStore pgm
 	}
 
 ContextBindProgramFragment {
@@ -20,6 +23,10 @@
 	param RsProgramRaster pgm
 	}
 
+ContextBindFont {
+	param RsFont pgm
+	}
+
 ContextPause {
 	}
 
@@ -51,6 +58,11 @@
 	param size_t len
 	}
 
+GetName {
+	param void *obj
+	param const char **name
+	}
+
 ObjDestroy {
 	param void *obj
 	}
@@ -71,6 +83,19 @@
 	ret RsElement
 	}
 
+ElementGetNativeData {
+	param RsElement elem
+	param uint32_t *elemData
+	param uint32_t elemDataSize
+	}
+
+ElementGetSubElements {
+	param RsElement elem
+	param uint32_t *ids
+	param const char **names
+	param uint32_t dataSize
+	}
+
 TypeBegin {
 	param RsElement type
 	}
@@ -84,6 +109,12 @@
 	ret RsType
 	}
 
+TypeGetNativeData {
+	param RsType type
+	param uint32_t * typeData
+	param uint32_t typeDataSize
+	}
+
 AllocationCreateTyped {
 	param RsType type
 	ret RsAllocation
@@ -224,6 +255,11 @@
 	param const void *data
 	}
 
+AllocationGetType {
+	param RsAllocation va
+	ret const void*
+	}
+
 SamplerBegin {
 	}
 
@@ -248,13 +284,6 @@
 ScriptCBegin {
 	}
 
-ScriptSetClearColor {
-	param RsScript s
-	param float r
-	param float g
-	param float b
-	param float a
-	}
 
 ScriptSetTimeZone {
 	param RsScript s
@@ -262,37 +291,41 @@
 	param uint32_t length
 	}
 
-ScriptSetClearDepth {
-	param RsScript s
-	param float depth
-	}
-
-ScriptSetClearStencil {
-	param RsScript s
-	param uint32_t stencil
-	}
-
-ScriptSetType {
-	param RsType type
-	param uint32_t slot
-	param bool isWritable
-	param const char * name
-	}
-
-ScriptSetInvoke {
-	param const char * name
-	param uint32_t slot
-	}
 
 ScriptInvoke {
 	param RsScript s
 	param uint32_t slot
 	}
 
-ScriptSetRoot {
-	param bool isRoot
+ScriptInvokeV {
+	param RsScript s
+	param uint32_t slot
+	param const void * data
+	param uint32_t dataLen
+	handcodeApi
+	togglePlay
 	}
 
+ScriptSetVarI {
+	param RsScript s
+	param uint32_t slot
+	param int value
+	}
+
+ScriptSetVarF {
+	param RsScript s
+	param uint32_t slot
+	param float value
+	}
+
+ScriptSetVarV {
+	param RsScript s
+	param uint32_t slot
+	param const void * data
+	param uint32_t dataLen
+	handcodeApi
+	togglePlay
+	}
 
 
 ScriptCSetScript {
@@ -308,52 +341,41 @@
 	ret RsScript
 	}
 
-ScriptCSetDefineF {
-    param const char* name
-    param float value
-    }
 
-ScriptCSetDefineI32 {
-    param const char* name
-    param int32_t value
-    }
-
-ProgramFragmentStoreBegin {
+ProgramStoreBegin {
 	param RsElement in
 	param RsElement out
 	}
 
-ProgramFragmentStoreColorMask {
+ProgramStoreColorMask {
 	param bool r
 	param bool g
 	param bool b
 	param bool a
 	}
 
-ProgramFragmentStoreBlendFunc {
+ProgramStoreBlendFunc {
 	param RsBlendSrcFunc srcFunc
 	param RsBlendDstFunc destFunc
 	}
 
-ProgramFragmentStoreDepthMask {
+ProgramStoreDepthMask {
 	param bool enable
 }
 
-ProgramFragmentStoreDither {
+ProgramStoreDither {
 	param bool enable
 }
 
-ProgramFragmentStoreDepthFunc {
+ProgramStoreDepthFunc {
 	param RsDepthFunc func
 }
 
-ProgramFragmentStoreCreate {
-	ret RsProgramFragmentStore
+ProgramStoreCreate {
+	ret RsProgramStore
 	}
 
 ProgramRasterCreate {
-	param RsElement in
-	param RsElement out
 	param bool pointSmooth
 	param bool lineSmooth
 	param bool pointSprite
@@ -365,12 +387,11 @@
 	param float lw
 }
 
-ProgramRasterSetPointSize{
+ProgramRasterSetCullMode {
 	param RsProgramRaster pr
-	param float ps
+	param RsCullMode mode
 }
 
-
 ProgramBindConstants {
 	param RsProgram vp
 	param uint32_t slot
@@ -447,36 +468,91 @@
 	param float b
 	}
 
+FileA3DCreateFromAssetStream {
+	param const void * data
+	param size_t len
+	ret RsFile
+	}
+
 FileOpen {
 	ret RsFile
 	param const char *name
 	param size_t len
 	}
 
+FileA3DGetNumIndexEntries {
+	param int32_t * numEntries
+	param RsFile file
+	}
 
-SimpleMeshCreate {
-	ret RsSimpleMesh
-	param RsAllocation prim
-	param RsAllocation index
-	param RsAllocation *vtx
+FileA3DGetIndexEntries {
+	param RsFileIndexEntry * fileEntries
+	param uint32_t numEntries
+	param RsFile fileA3D
+	}
+
+FileA3DGetEntryByIndex {
+	param uint32_t index
+	param RsFile file
+	ret RsObjectBase
+	}
+
+FontCreateFromFile {
+	param const char *name
+	param uint32_t fontSize
+	param uint32_t dpi
+	ret RsFont
+	}
+
+MeshCreate {
+	ret RsMesh
 	param uint32_t vtxCount
-	param uint32_t primType
+	param uint32_t idxCount
 	}
 
-
-SimpleMeshBindIndex {
-	param RsSimpleMesh mesh
+MeshBindIndex {
+	param RsMesh mesh
 	param RsAllocation idx
+	param uint32_t primType
+	param uint32_t slot
 	}
 
-SimpleMeshBindPrimitive {
-	param RsSimpleMesh mesh
-	param RsAllocation prim
-	}
-
-SimpleMeshBindVertex {
-	param RsSimpleMesh mesh
+MeshBindVertex {
+	param RsMesh mesh
 	param RsAllocation vtx
 	param uint32_t slot
 	}
 
+MeshGetVertexBufferCount {
+	param RsMesh mesh
+	param int32_t *numVtx
+	}
+
+MeshGetIndexCount {
+	param RsMesh mesh
+	param int32_t *numIdx
+	}
+
+MeshGetVertices {
+	param RsMesh mv
+	param RsAllocation *vtxData
+	param uint32_t vtxDataCount
+	}
+
+MeshGetIndices {
+	param RsMesh mv
+	param RsAllocation *va
+	param uint32_t *primType
+	param uint32_t idxDataCount
+	}
+
+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/rsAdapter.cpp b/libs/rs/rsAdapter.cpp
index 0d31fac..b4ec250 100644
--- a/libs/rs/rsAdapter.cpp
+++ b/libs/rs/rsAdapter.cpp
@@ -15,7 +15,11 @@
  * limitations under the License.
  */
 
+#ifndef ANDROID_RS_BUILD_FOR_HOST
 #include "rsContext.h"
+#else
+#include "rsContextHostStub.h"
+#endif
 
 using namespace android;
 using namespace android::renderscript;
@@ -70,6 +74,16 @@
            mAllocation.get()->getType()->getSizeBytes());
 }
 
+void Adapter1D::serialize(OStream *stream) const
+{
+    
+}
+
+Adapter1D *Adapter1D::createFromStream(Context *rsc, IStream *stream)
+{
+    return NULL;
+}
+
 namespace android {
 namespace renderscript {
 
@@ -185,6 +199,15 @@
            mAllocation.get()->getType()->getSizeBytes());
 }
 
+void Adapter2D::serialize(OStream *stream) const
+{
+    
+}
+
+Adapter2D *Adapter2D::createFromStream(Context *rsc, IStream *stream)
+{
+    return NULL;
+}
 
 
 namespace android {
diff --git a/libs/rs/rsAdapter.h b/libs/rs/rsAdapter.h
index cb2872e..449e7ad 100644
--- a/libs/rs/rsAdapter.h
+++ b/libs/rs/rsAdapter.h
@@ -50,6 +50,10 @@
     void subData(uint32_t xoff, uint32_t count, const void *data);
     void data(const void *data);
 
+    virtual void serialize(OStream *stream) const;
+    virtual RsA3DClassID getClassId() const { return RS_A3D_CLASS_ID_ADAPTER_1D; }
+    static Adapter1D *createFromStream(Context *rsc, IStream *stream);
+
 protected:
     ObjectBaseRef<Allocation> mAllocation;
     uint32_t mY;
@@ -82,6 +86,10 @@
     void data(const void *data);
     void subData(uint32_t xoff, uint32_t yoff, uint32_t w, uint32_t h, const void *data);
 
+    virtual void serialize(OStream *stream) const;
+    virtual RsA3DClassID getClassId() const { return RS_A3D_CLASS_ID_ADAPTER_2D; }
+    static Adapter2D *createFromStream(Context *rsc, IStream *stream);
+
 protected:
     ObjectBaseRef<Allocation> mAllocation;
     uint32_t mZ;
diff --git a/libs/rs/rsAllocation.cpp b/libs/rs/rsAllocation.cpp
index 4e8278d..6560101 100644
--- a/libs/rs/rsAllocation.cpp
+++ b/libs/rs/rsAllocation.cpp
@@ -13,12 +13,18 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
+#ifndef ANDROID_RS_BUILD_FOR_HOST
 #include "rsContext.h"
 
 #include <GLES/gl.h>
 #include <GLES2/gl2.h>
 #include <GLES/glext.h>
+#else
+#include "rsContextHostStub.h"
+
+#include <OpenGL/gl.h>
+#include <OpenGl/glext.h>
+#endif
 
 using namespace android;
 using namespace android::renderscript;
@@ -167,9 +173,12 @@
                      0, format, type, ptr);
     }
     if (mTextureGenMipmap) {
+#ifndef ANDROID_RS_BUILD_FOR_HOST
         glGenerateMipmap(GL_TEXTURE_2D);
+#endif //ANDROID_RS_BUILD_FOR_HOST
     }
 
+    rsc->checkError("Allocation::uploadToTexture");
 }
 
 void Allocation::deferedUploadToBufferObject(const Context *rsc)
@@ -201,6 +210,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)
@@ -284,7 +294,7 @@
 
 void Allocation::addProgramToDirty(const Program *p)
 {
-    mToDirtyList.add(p);
+    mToDirtyList.push(p);
 }
 
 void Allocation::removeProgramToDirty(const Program *p)
@@ -316,6 +326,60 @@
 
 }
 
+void Allocation::serialize(OStream *stream) const
+{
+    // Need to identify ourselves
+    stream->addU32((uint32_t)getClassId());
+
+    String8 name(getName());
+    stream->addString(&name);
+
+    // First thing we need to serialize is the type object since it will be needed
+    // to initialize the class
+    mType->serialize(stream);
+
+    uint32_t dataSize = mType->getSizeBytes();
+    // Write how much data we are storing
+    stream->addU32(dataSize);
+    // Now write the data
+    stream->addByteArray(mPtr, dataSize);
+}
+
+Allocation *Allocation::createFromStream(Context *rsc, IStream *stream)
+{
+    // First make sure we are reading the correct object
+    RsA3DClassID classID = (RsA3DClassID)stream->loadU32();
+    if(classID != RS_A3D_CLASS_ID_ALLOCATION) {
+        LOGE("allocation loading skipped due to invalid class id\n");
+        return NULL;
+    }
+
+    String8 name;
+    stream->loadString(&name);
+
+    Type *type = Type::createFromStream(rsc, stream);
+    if(!type) {
+        return NULL;
+    }
+    type->compute();
+
+    // Number of bytes we wrote out for this allocation
+    uint32_t dataSize = stream->loadU32();
+    if(dataSize != type->getSizeBytes()) {
+        LOGE("failed to read allocation because numbytes written is not the same loaded type wants\n");
+        delete type;
+        return NULL;
+    }
+
+    Allocation *alloc = new Allocation(rsc, type);
+    alloc->setName(name.string(), name.size());
+
+    // Read in all of our allocation data
+    stream->loadByteArray(alloc->getPtr(), dataSize);
+
+    return alloc;
+}
+
 void Allocation::sendDirty() const
 {
     for (size_t ct=0; ct < mToDirtyList.size(); ct++) {
@@ -495,7 +559,7 @@
     if (srcGLType == GL_UNSIGNED_BYTE &&
         srcGLFmt == GL_RGB &&
         dstGLType == GL_UNSIGNED_SHORT_5_6_5 &&
-        dstGLType == GL_RGB) {
+        dstGLFmt == GL_RGB) {
 
         return elementConverter_888_to_565;
     }
@@ -503,15 +567,21 @@
     if (srcGLType == GL_UNSIGNED_BYTE &&
         srcGLFmt == GL_RGBA &&
         dstGLType == GL_UNSIGNED_SHORT_5_6_5 &&
-        dstGLType == GL_RGB) {
+        dstGLFmt == GL_RGB) {
 
         return elementConverter_8888_to_565;
     }
 
     LOGE("pickConverter, unsuported combo, src %p,  dst %p", src, dst);
+    LOGE("pickConverter, srcGLType = %x,  srcGLFmt = %x", srcGLType, srcGLFmt);
+    LOGE("pickConverter, dstGLType = %x,  dstGLFmt = %x", dstGLType, dstGLFmt);
+    src->dumpLOGV("SRC ");
+    dst->dumpLOGV("DST ");
     return 0;
 }
 
+#ifndef ANDROID_RS_BUILD_FOR_HOST
+
 RsAllocation rsi_AllocationCreateBitmapRef(Context *rsc, RsType vtype,
                                            void *bmp, void *callbackData, RsBitmapCallback_t callback)
 {
@@ -613,6 +683,15 @@
     a->read(data);
 }
 
+const void* rsi_AllocationGetType(Context *rsc, RsAllocation va)
+{
+    Allocation *a = static_cast<Allocation *>(va);
+    a->getType()->incUserRef();
+
+    return a->getType();
+}
+
+#endif //ANDROID_RS_BUILD_FOR_HOST
 
 }
 }
diff --git a/libs/rs/rsAllocation.h b/libs/rs/rsAllocation.h
index 516f8b7..8273165 100644
--- a/libs/rs/rsAllocation.h
+++ b/libs/rs/rsAllocation.h
@@ -72,9 +72,15 @@
     void removeProgramToDirty(const Program *);
 
     virtual void dumpLOGV(const char *prefix) const;
+    virtual void serialize(OStream *stream) const;
+    virtual RsA3DClassID getClassId() const { return RS_A3D_CLASS_ID_ALLOCATION; }
+    static Allocation *createFromStream(Context *rsc, IStream *stream);
 
     virtual void uploadCheck(const Context *rsc);
 
+    bool getIsTexture() const {return mIsTexture;}
+    bool getIsBufferObject() const {return mIsVertexBuffer;}
+
 protected:
     void sendDirty() const;
 
diff --git a/libs/rs/rsAnimation.cpp b/libs/rs/rsAnimation.cpp
new file mode 100644
index 0000000..6200715
--- /dev/null
+++ b/libs/rs/rsAnimation.cpp
@@ -0,0 +1,149 @@
+/*
+ * 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_BUILD_FOR_HOST
+#include "rsContext.h"
+#else
+#include "rsContextHostStub.h"
+#endif //ANDROID_RS_BUILD_FOR_HOST
+
+#include "rsAnimation.h"
+
+
+using namespace android;
+using namespace android::renderscript;
+
+void Animation::serialize(OStream *stream) const
+{
+    
+}
+
+Animation *Animation::createFromStream(Context *rsc, IStream *stream)
+{
+    return NULL;
+}
+
+/*
+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..340314e
--- /dev/null
+++ b/libs/rs/rsAnimation.h
@@ -0,0 +1,71 @@
+/*
+ * 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;
+
+    virtual void serialize(OStream *stream) const;
+    virtual RsA3DClassID getClassId() const { return RS_A3D_CLASS_ID_ANIMATION; }
+    static Animation *createFromStream(Context *rsc, IStream *stream);
+
+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/rsComponent.cpp b/libs/rs/rsComponent.cpp
index 15a56f7..8e509ad 100644
--- a/libs/rs/rsComponent.cpp
+++ b/libs/rs/rsComponent.cpp
@@ -16,7 +16,11 @@
 
 #include "rsComponent.h"
 
+#ifndef ANDROID_RS_BUILD_FOR_HOST
 #include <GLES/gl.h>
+#else
+#include <OpenGL/gl.h>
+#endif
 
 using namespace android;
 using namespace android::renderscript;
@@ -148,6 +152,10 @@
     case RS_TYPE_UNSIGNED_64:
         mTypeBits = 64;
         break;
+
+    case RS_TYPE_BOOLEAN:
+        mTypeBits = 8;
+        break;
     }
 
     mBits = mTypeBits * mVectorSize;
@@ -188,82 +196,6 @@
     return 0;
 }
 
-static const char * gCTypeStrings[] = {
-    0,
-    0,//"F16",
-    "float",
-    "double",
-    "char",
-    "short",
-    "int",
-    0,//"S64",
-    "char",//U8",
-    "short",//U16",
-    "int",//U32",
-    0,//"U64",
-    0,//"UP_565",
-    0,//"UP_5551",
-    0,//"UP_4444",
-    0,//"ELEMENT",
-    0,//"TYPE",
-    0,//"ALLOCATION",
-    0,//"SAMPLER",
-    0,//"SCRIPT",
-    0,//"MESH",
-    0,//"PROGRAM_FRAGMENT",
-    0,//"PROGRAM_VERTEX",
-    0,//"PROGRAM_RASTER",
-    0,//"PROGRAM_STORE",
-};
-
-static const char * gCVecTypeStrings[] = {
-    0,
-    0,//"F16",
-    "vecF32",
-    "vecF64",
-    "vecI8",
-    "vecI16",
-    "vecI32",
-    0,//"S64",
-    "vecU8",//U8",
-    "vecU16",//U16",
-    "vecU32",//U32",
-    0,//"U64",
-    0,//"UP_565",
-    0,//"UP_5551",
-    0,//"UP_4444",
-    0,//"ELEMENT",
-    0,//"TYPE",
-    0,//"ALLOCATION",
-    0,//"SAMPLER",
-    0,//"SCRIPT",
-    0,//"MESH",
-    0,//"PROGRAM_FRAGMENT",
-    0,//"PROGRAM_VERTEX",
-    0,//"PROGRAM_RASTER",
-    0,//"PROGRAM_STORE",
-};
-
-String8 Component::getCType() const
-{
-    char buf[64];
-    if (mVectorSize == 1) {
-        return String8(gCTypeStrings[mType]);
-    }
-
-    // Yuck, acc WAR
-    // Appears to have problems packing chars
-    if (mVectorSize == 4 && mType == RS_TYPE_UNSIGNED_8) {
-        return String8("int");
-    }
-
-
-    String8 s(gCVecTypeStrings[mType]);
-    sprintf(buf, "_%i_t", mVectorSize);
-    s.append(buf);
-    return s;
-}
-
 String8 Component::getGLSLType() const
 {
     if (mType == RS_TYPE_SIGNED_32) {
@@ -298,6 +230,7 @@
     "U16",
     "U32",
     "U64",
+    "BOOLEAN",
     "UP_565",
     "UP_5551",
     "UP_4444",
@@ -334,4 +267,25 @@
          prefix, gTypeStrings[mType], gKindStrings[mKind], mVectorSize, mBits);
 }
 
+void Component::serialize(OStream *stream) const
+{
+    stream->addU8((uint8_t)mType);
+    stream->addU8((uint8_t)mKind);
+    stream->addU8((uint8_t)(mNormalized ? 1 : 0));
+    stream->addU32(mVectorSize);
+}
+
+void Component::loadFromStream(IStream *stream)
+{
+    mType = (RsDataType)stream->loadU8();
+    mKind = (RsDataKind)stream->loadU8();
+    uint8_t temp = stream->loadU8();
+    mNormalized = temp != 0;
+    mVectorSize = stream->loadU32();
+
+    set(mType, mKind, mNormalized, mVectorSize);
+}
+
+
+
 
diff --git a/libs/rs/rsComponent.h b/libs/rs/rsComponent.h
index 71de324..15fd5dd 100644
--- a/libs/rs/rsComponent.h
+++ b/libs/rs/rsComponent.h
@@ -35,7 +35,6 @@
 
     uint32_t getGLType() const;
     uint32_t getGLFormat() const;
-    String8 getCType() const;
     String8 getGLSLType() const;
     void dumpLOGV(const char *prefix) const;
 
@@ -48,6 +47,10 @@
     bool getIsSigned() const {return mIsSigned;}
     uint32_t getBits() const {return mBits;}
 
+    // Helpers for reading / writing this class out
+    void serialize(OStream *stream) const;
+    void loadFromStream(IStream *stream);
+
 protected:
     RsDataType mType;
     RsDataKind mKind;
diff --git a/libs/rs/rsContext.cpp b/libs/rs/rsContext.cpp
index 596f533..dfdeb98 100644
--- a/libs/rs/rsContext.cpp
+++ b/libs/rs/rsContext.cpp
@@ -23,6 +23,7 @@
 
 #include <sys/types.h>
 #include <sys/resource.h>
+#include <sched.h>
 
 #include <cutils/properties.h>
 
@@ -127,19 +128,21 @@
 }
 
 
-uint32_t Context::runScript(Script *s, uint32_t launchID)
+uint32_t Context::runScript(Script *s)
 {
     ObjectBaseRef<ProgramFragment> frag(mFragment);
     ObjectBaseRef<ProgramVertex> vtx(mVertex);
-    ObjectBaseRef<ProgramFragmentStore> store(mFragmentStore);
+    ObjectBaseRef<ProgramStore> store(mFragmentStore);
     ObjectBaseRef<ProgramRaster> raster(mRaster);
+    ObjectBaseRef<Font> font(mFont);
 
-    uint32_t ret = s->run(this, launchID);
+    uint32_t ret = s->run(this);
 
     mFragment.set(frag);
     mVertex.set(vtx);
     mFragmentStore.set(store);
     mRaster.set(raster);
+    mFont.set(font);
     return ret;
 }
 
@@ -153,29 +156,11 @@
 
 uint32_t Context::runRootScript()
 {
-    timerSet(RS_TIMER_CLEAR_SWAP);
-    rsAssert(mRootScript->mEnviroment.mIsRoot);
-
-    eglQuerySurface(mEGL.mDisplay, mEGL.mSurface, EGL_WIDTH, &mEGL.mWidth);
-    eglQuerySurface(mEGL.mDisplay, mEGL.mSurface, EGL_HEIGHT, &mEGL.mHeight);
-    glViewport(0, 0, mEGL.mWidth, mEGL.mHeight);
-    glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
-
-    glClearColor(mRootScript->mEnviroment.mClearColor[0],
-                 mRootScript->mEnviroment.mClearColor[1],
-                 mRootScript->mEnviroment.mClearColor[2],
-                 mRootScript->mEnviroment.mClearColor[3]);
-    if (mUseDepth) {
-        glDepthMask(GL_TRUE);
-        glClearDepthf(mRootScript->mEnviroment.mClearDepth);
-        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
-    } else {
-        glClear(GL_COLOR_BUFFER_BIT);
-    }
+    glViewport(0, 0, mWidth, mHeight);
 
     timerSet(RS_TIMER_SCRIPT);
     mStateFragmentStore.mLast.clear();
-    uint32_t ret = runScript(mRootScript.get(), 0);
+    uint32_t ret = runScript(mRootScript.get());
 
     checkError("runRootScript");
     if (mError != RS_ERROR_NONE) {
@@ -274,6 +259,24 @@
     return 0 != strcmp(buf, "0");
 }
 
+void Context::displayDebugStats()
+{
+    char buffer[128];
+    sprintf(buffer, "Frame %i ms, Script %i ms", mTimeMSLastFrame, mTimeMSLastScript);
+    float oldR, oldG, oldB, oldA;
+    mStateFont.getFontColor(&oldR, &oldG, &oldB, &oldA);
+
+    float shadowCol = 0.2f;
+    mStateFont.setFontColor(shadowCol, shadowCol, shadowCol, 1.0f);
+    mStateFont.renderText(buffer, 5, getHeight() - 5);
+
+    float textCol = 0.9f;
+    mStateFont.setFontColor(textCol, textCol, textCol, 1.0f);
+    mStateFont.renderText(buffer, 4, getHeight() - 6);
+
+    mStateFont.setFontColor(oldR, oldG, oldB, oldA);
+}
+
 void * Context::threadProc(void *vrsc)
 {
      Context *rsc = static_cast<Context *>(vrsc);
@@ -286,6 +289,7 @@
      rsc->props.mLogScripts = getProp("debug.rs.script");
      rsc->props.mLogObjects = getProp("debug.rs.object");
      rsc->props.mLogShaders = getProp("debug.rs.shader");
+     rsc->props.mLogVisual = getProp("debug.rs.visual");
 
      ScriptTLSStruct *tlsStruct = new ScriptTLSStruct;
      if (!tlsStruct) {
@@ -300,14 +304,16 @@
      }
 
      if (rsc->mIsGraphicsContext) {
-         rsc->mStateRaster.init(rsc, rsc->mEGL.mWidth, rsc->mEGL.mHeight);
+         rsc->mStateRaster.init(rsc);
          rsc->setRaster(NULL);
-         rsc->mStateVertex.init(rsc, rsc->mEGL.mWidth, rsc->mEGL.mHeight);
+         rsc->mStateVertex.init(rsc);
          rsc->setVertex(NULL);
-         rsc->mStateFragment.init(rsc, rsc->mEGL.mWidth, rsc->mEGL.mHeight);
+         rsc->mStateFragment.init(rsc);
          rsc->setFragment(NULL);
-         rsc->mStateFragmentStore.init(rsc, rsc->mEGL.mWidth, rsc->mEGL.mHeight);
+         rsc->mStateFragmentStore.init(rsc);
          rsc->setFragmentStore(NULL);
+         rsc->mStateFont.init(rsc);
+         rsc->setFont(NULL);
          rsc->mStateVertexArray.init(rsc);
      }
 
@@ -321,6 +327,11 @@
          uint32_t targetTime = 0;
          if (mDraw && rsc->mIsGraphicsContext) {
              targetTime = rsc->runRootScript();
+
+             if(rsc->props.mLogVisual) {
+                 rsc->displayDebugStats();
+             }
+
              mDraw = targetTime && !rsc->mPaused;
              rsc->timerSet(RS_TIMER_CLEAR_SWAP);
              eglSwapBuffers(rsc->mEGL.mDisplay, rsc->mEGL.mSurface);
@@ -346,11 +357,13 @@
          rsc->mFragment.clear();
          rsc->mVertex.clear();
          rsc->mFragmentStore.clear();
+         rsc->mFont.clear();
          rsc->mRootScript.clear();
          rsc->mStateRaster.deinit(rsc);
          rsc->mStateVertex.deinit(rsc);
          rsc->mStateFragment.deinit(rsc);
          rsc->mStateFragmentStore.deinit(rsc);
+         rsc->mStateFont.deinit(rsc);
      }
      ObjectBase::zeroAllUserRef(rsc);
 
@@ -367,6 +380,49 @@
      return NULL;
 }
 
+void * Context::helperThreadProc(void *vrsc)
+{
+     Context *rsc = static_cast<Context *>(vrsc);
+     uint32_t idx = (uint32_t)android_atomic_inc(&rsc->mWorkers.mLaunchCount);
+
+     LOGV("RS helperThread starting %p idx=%i", rsc, idx);
+
+     rsc->mWorkers.mLaunchSignals[idx].init();
+     rsc->mWorkers.mNativeThreadId[idx] = gettid();
+
+     //cpu_set_t cpset[16];
+     //int ret = sched_getaffinity(rsc->mWorkers.mNativeThreadId[idx], sizeof(cpset), &cpset);
+     //LOGE("ret = %i", ret);
+
+//sched_setaffinity
+
+     setpriority(PRIO_PROCESS, rsc->mWorkers.mNativeThreadId[idx], rsc->mThreadPriority);
+     while(rsc->mRunning) {
+         rsc->mWorkers.mLaunchSignals[idx].wait();
+         if (rsc->mWorkers.mLaunchCallback) {
+            rsc->mWorkers.mLaunchCallback(rsc->mWorkers.mLaunchData, idx);
+         }
+         android_atomic_dec(&rsc->mWorkers.mRunningCount);
+         rsc->mWorkers.mCompleteSignal.set();
+     }
+
+     LOGV("RS helperThread exiting %p idx=%i", rsc, idx);
+     return NULL;
+}
+
+void Context::launchThreads(WorkerCallback_t cbk, void *data)
+{
+    mWorkers.mLaunchData = data;
+    mWorkers.mLaunchCallback = cbk;
+    mWorkers.mRunningCount = (int)mWorkers.mCount;
+    for (uint32_t ct = 0; ct < mWorkers.mCount; ct++) {
+        mWorkers.mLaunchSignals[ct].set();
+    }
+    while(mWorkers.mRunningCount) {
+        mWorkers.mCompleteSignal.wait();
+    }
+}
+
 void Context::setPriority(int32_t p)
 {
     // Note: If we put this in the proper "background" policy
@@ -383,7 +439,10 @@
         // success; reset the priority as well
     }
 #else
-        setpriority(PRIO_PROCESS, mNativeThreadId, p);
+    setpriority(PRIO_PROCESS, mNativeThreadId, p);
+    for (uint32_t ct=0; ct < mWorkers.mCount; ct++) {
+        setpriority(PRIO_PROCESS, mWorkers.mNativeThreadId[ct], p);
+    }
 #endif
 }
 
@@ -433,16 +492,36 @@
     timerInit();
     timerSet(RS_TIMER_INTERNAL);
 
-    LOGV("RS Launching thread");
+    int cpu = sysconf(_SC_NPROCESSORS_ONLN);
+    LOGV("RS Launching thread(s), reported CPU count %i", cpu);
+    if (cpu < 2) cpu = 0;
+
+    mWorkers.mCount = (uint32_t)cpu;
+    mWorkers.mThreadId = (pthread_t *) calloc(mWorkers.mCount, sizeof(pthread_t));
+    mWorkers.mNativeThreadId = (pid_t *) calloc(mWorkers.mCount, sizeof(pid_t));
+    mWorkers.mLaunchSignals = new Signal[mWorkers.mCount];
+    mWorkers.mLaunchCallback = NULL;
     status = pthread_create(&mThreadId, &threadAttr, threadProc, this);
     if (status) {
         LOGE("Failed to start rs context thread.");
+        return;
     }
-
     while(!mRunning) {
         usleep(100);
     }
 
+    mWorkers.mRunningCount = 0;
+    mWorkers.mLaunchCount = 0;
+    for (uint32_t ct=0; ct < mWorkers.mCount; ct++) {
+        status = pthread_create(&mWorkers.mThreadId[ct], &threadAttr, helperThreadProc, this);
+        if (status) {
+            mWorkers.mCount = ct;
+            LOGE("Created fewer than expected number of RS threads.");
+            break;
+        }
+    }
+
+
     pthread_attr_destroy(&threadAttr);
 }
 
@@ -486,14 +565,14 @@
         checkEglError("eglDestroySurface", ret);
 
         mEGL.mSurface = NULL;
-        mEGL.mWidth = 0;
-        mEGL.mHeight = 0;
         mWidth = 0;
         mHeight = 0;
     }
 
     mWndSurface = sur;
     if (mWndSurface != NULL) {
+        mWidth = w;
+        mHeight = h;
         bool first = false;
         if (!mEGL.mContext) {
             first = true;
@@ -511,15 +590,7 @@
         ret = eglMakeCurrent(mEGL.mDisplay, mEGL.mSurface, mEGL.mSurface, mEGL.mContext);
         checkEglError("eglMakeCurrent", ret);
 
-        eglQuerySurface(mEGL.mDisplay, mEGL.mSurface, EGL_WIDTH, &mEGL.mWidth);
-        eglQuerySurface(mEGL.mDisplay, mEGL.mSurface, EGL_HEIGHT, &mEGL.mHeight);
-        mWidth = w;
-        mHeight = h;
-        mStateVertex.updateSize(this, w, h);
-
-        if ((int)mWidth != mEGL.mWidth || (int)mHeight != mEGL.mHeight) {
-            LOGE("EGL/Surface mismatch  EGL (%i x %i)  SF (%i x %i)", mEGL.mWidth, mEGL.mHeight, mWidth, mHeight);
-        }
+        mStateVertex.updateSize(this);
 
         if (first) {
             mGL.mVersion = glGetString(GL_VERSION);
@@ -583,7 +654,7 @@
     mRootScript.set(s);
 }
 
-void Context::setFragmentStore(ProgramFragmentStore *pfs)
+void Context::setFragmentStore(ProgramStore *pfs)
 {
     rsAssert(mIsGraphicsContext);
     if (pfs == NULL) {
@@ -623,6 +694,16 @@
     }
 }
 
+void Context::setFont(Font *f)
+{
+    rsAssert(mIsGraphicsContext);
+    if (f == NULL) {
+        mFont.set(mStateFont.mDefault);
+    } else {
+        mFont.set(f);
+    }
+}
+
 void Context::assignName(ObjectBase *obj, const char *name, uint32_t len)
 {
     rsAssert(!obj->getName());
@@ -640,32 +721,9 @@
     }
 }
 
-ObjectBase * Context::lookupName(const char *name) const
-{
-    for(size_t ct=0; ct < mNames.size(); ct++) {
-        if (!strcmp(name, mNames[ct]->getName())) {
-            return mNames[ct];
-        }
-    }
-    return NULL;
-}
-
-void Context::appendNameDefines(String8 *str) const
-{
-    char buf[256];
-    for (size_t ct=0; ct < mNames.size(); ct++) {
-        str->append("#define NAMED_");
-        str->append(mNames[ct]->getName());
-        str->append(" ");
-        sprintf(buf, "%i\n", (int)mNames[ct]);
-        str->append(buf);
-    }
-}
-
 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 +733,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 +743,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)
@@ -757,9 +804,13 @@
         }
     }
     //LOGE("sendMessageToClient 2");
-    void *p = mIO.mToClient.reserve(len);
-    memcpy(p, data, len);
-    mIO.mToClient.commit(cmdID, len);
+    if (len > 0) {
+        void *p = mIO.mToClient.reserve(len);
+        memcpy(p, data, len);
+        mIO.mToClient.commit(cmdID, len);
+    } else {
+        mIO.mToClient.commit(cmdID, 0);
+    }
     //LOGE("sendMessageToClient 3");
     return true;
 }
@@ -799,8 +850,7 @@
     LOGE("RS Context debug");
 
     LOGE(" EGL ver %i %i", mEGL.mMajorVersion, mEGL.mMinorVersion);
-    LOGE(" EGL context %p  surface %p,  w=%i h=%i  Display=%p", mEGL.mContext,
-         mEGL.mSurface, mEGL.mWidth, mEGL.mHeight, mEGL.mDisplay);
+    LOGE(" EGL context %p  surface %p,  Display=%p", mEGL.mContext, mEGL.mSurface, mEGL.mDisplay);
     LOGE(" GL vendor: %s", mGL.mVendor);
     LOGE(" GL renderer: %s", mGL.mRenderer);
     LOGE(" GL Version: %s", mGL.mVersion);
@@ -822,6 +872,9 @@
 namespace android {
 namespace renderscript {
 
+void rsi_ContextFinish(Context *rsc)
+{
+}
 
 void rsi_ContextBindRootScript(Context *rsc, RsScript vs)
 {
@@ -841,9 +894,9 @@
     s->bindToContext(&rsc->mStateSampler, slot);
 }
 
-void rsi_ContextBindProgramFragmentStore(Context *rsc, RsProgramFragmentStore vpfs)
+void rsi_ContextBindProgramStore(Context *rsc, RsProgramStore vpfs)
 {
-    ProgramFragmentStore *pfs = static_cast<ProgramFragmentStore *>(vpfs);
+    ProgramStore *pfs = static_cast<ProgramStore *>(vpfs);
     rsc->setFragmentStore(pfs);
 }
 
@@ -865,12 +918,24 @@
     rsc->setVertex(pv);
 }
 
+void rsi_ContextBindFont(Context *rsc, RsFont vfont)
+{
+    Font *font = static_cast<Font *>(vfont);
+    rsc->setFont(font);
+}
+
 void rsi_AssignName(Context *rsc, void * obj, const char *name, uint32_t len)
 {
     ObjectBase *ob = static_cast<ObjectBase *>(obj);
     rsc->assignName(ob, name, len);
 }
 
+void rsi_GetName(Context *rsc, void * obj, const char **name)
+{
+    ObjectBase *ob = static_cast<ObjectBase *>(obj);
+    (*name) = ob->getName();
+}
+
 void rsi_ObjDestroy(Context *rsc, void *obj)
 {
     ObjectBase *ob = static_cast<ObjectBase *>(obj);
diff --git a/libs/rs/rsContext.h b/libs/rs/rsContext.h
index 709730e..2da3ab5 100644
--- a/libs/rs/rsContext.h
+++ b/libs/rs/rsContext.h
@@ -18,12 +18,12 @@
 #define ANDROID_RS_CONTEXT_H
 
 #include "rsUtils.h"
+#include "rsMutex.h"
 
 #include "rsThreadIO.h"
 #include "rsType.h"
 #include "rsMatrix.h"
 #include "rsAllocation.h"
-#include "rsSimpleMesh.h"
 #include "rsMesh.h"
 #include "rsDevice.h"
 #include "rsScriptC.h"
@@ -31,8 +31,9 @@
 #include "rsAdapter.h"
 #include "rsSampler.h"
 #include "rsLight.h"
+#include "rsFont.h"
 #include "rsProgramFragment.h"
-#include "rsProgramFragmentStore.h"
+#include "rsProgramStore.h"
 #include "rsProgramRaster.h"
 #include "rsProgramVertex.h"
 #include "rsShaderCache.h"
@@ -64,17 +65,19 @@
         Script * mScript;
     };
 
+    typedef void (*WorkerCallback_t)(void *usr, uint32_t idx);
 
     //StructuredAllocationContext mStateAllocation;
     ElementState mStateElement;
     TypeState mStateType;
     SamplerState mStateSampler;
     ProgramFragmentState mStateFragment;
-    ProgramFragmentStoreState mStateFragmentStore;
+    ProgramStoreState mStateFragmentStore;
     ProgramRasterState mStateRaster;
     ProgramVertexState mStateVertex;
     LightState mStateLight;
     VertexArrayState mStateVertexArray;
+    FontState mStateFont;
 
     ScriptCState mScriptC;
     ShaderCache mShaderCache;
@@ -84,14 +87,16 @@
     void setRaster(ProgramRaster *);
     void setVertex(ProgramVertex *);
     void setFragment(ProgramFragment *);
-    void setFragmentStore(ProgramFragmentStore *);
+    void setFragmentStore(ProgramStore *);
+    void setFont(Font *);
 
     void updateSurface(void *sur);
 
     const ProgramFragment * getFragment() {return mFragment.get();}
-    const ProgramFragmentStore * getFragmentStore() {return mFragmentStore.get();}
+    const ProgramStore * getFragmentStore() {return mFragmentStore.get();}
     const ProgramRaster * getRaster() {return mRaster.get();}
     const ProgramVertex * getVertex() {return mVertex.get();}
+    Font * getFont() {return mFont.get();}
 
     bool setupCheck();
     bool checkDriver() const {return mEGL.mSurface != 0;}
@@ -103,12 +108,10 @@
 
     void assignName(ObjectBase *obj, const char *name, uint32_t len);
     void removeName(ObjectBase *obj);
-    ObjectBase * lookupName(const char *name) const;
-    void appendNameDefines(String8 *str) const;
 
     uint32_t getMessageToClient(void *data, size_t *receiveLen, size_t bufferLen, bool wait);
     bool sendMessageToClient(void *data, uint32_t cmdID, size_t len, bool waitForSpace);
-    uint32_t runScript(Script *s, uint32_t launchID);
+    uint32_t runScript(Script *s);
 
     void initToClient();
     void deinitToClient();
@@ -119,15 +122,18 @@
     ProgramVertex * getDefaultProgramVertex() const {
         return mStateVertex.mDefault.get();
     }
-    ProgramFragmentStore * getDefaultProgramFragmentStore() const {
+    ProgramStore * getDefaultProgramStore() const {
         return mStateFragmentStore.mDefault.get();
     }
     ProgramRaster * getDefaultProgramRaster() const {
         return mStateRaster.mDefault.get();
     }
+    Font* getDefaultFont() const {
+        return mStateFont.mDefault.get();
+    }
 
-    uint32_t getWidth() const {return mEGL.mWidth;}
-    uint32_t getHeight() const {return mEGL.mHeight;}
+    uint32_t getWidth() const {return mWidth;}
+    uint32_t getHeight() const {return mHeight;}
 
 
     ThreadIO mIO;
@@ -156,17 +162,21 @@
         bool mLogScripts;
         bool mLogObjects;
         bool mLogShaders;
+        bool mLogVisual;
     } props;
 
     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;
 
     bool ext_OES_texture_npot() const {return mGL.OES_texture_npot;}
 
+    void launchThreads(WorkerCallback_t cbk, void *data);
+    uint32_t getWorkerPoolSize() const {return (uint32_t)mWorkers.mRunningCount;}
+
 protected:
     Device *mDev;
 
@@ -177,8 +187,6 @@
         EGLConfig mConfig;
         EGLContext mContext;
         EGLSurface mSurface;
-        EGLint mWidth;
-        EGLint mHeight;
         EGLDisplay mDisplay;
     } mEGL;
 
@@ -219,15 +227,29 @@
     pthread_t mThreadId;
     pid_t mNativeThreadId;
 
+    struct Workers {
+        volatile int mRunningCount;
+        volatile int mLaunchCount;
+        uint32_t mCount;
+        pthread_t *mThreadId;
+        pid_t *mNativeThreadId;
+        Signal mCompleteSignal;
+
+        Signal *mLaunchSignals;
+        WorkerCallback_t mLaunchCallback;
+        void *mLaunchData;
+    };
+    Workers mWorkers;
+
     ObjectBaseRef<Script> mRootScript;
     ObjectBaseRef<ProgramFragment> mFragment;
     ObjectBaseRef<ProgramVertex> mVertex;
-    ObjectBaseRef<ProgramFragmentStore> mFragmentStore;
+    ObjectBaseRef<ProgramStore> mFragmentStore;
     ObjectBaseRef<ProgramRaster> mRaster;
-
+    ObjectBaseRef<Font> mFont;
 
     struct ObjDestroyOOB {
-        pthread_mutex_t mMutex;
+        Mutex mMutex;
         Vector<ObjectBase *> mDestroyList;
         bool mNeedToEmpty;
     };
@@ -236,6 +258,8 @@
     void objDestroyOOBRun();
     void objDestroyOOBDestroy();
 
+    void displayDebugStats();
+
 private:
     Context();
 
@@ -245,6 +269,7 @@
     uint32_t runRootScript();
 
     static void * threadProc(void *);
+    static void * helperThreadProc(void *);
 
     ANativeWindow *mWndSurface;
 
diff --git a/libs/rs/rsContextHostStub.h b/libs/rs/rsContextHostStub.h
new file mode 100644
index 0000000..c437606
--- /dev/null
+++ b/libs/rs/rsContextHostStub.h
@@ -0,0 +1,152 @@
+/*
+ * 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_CONTEXT_HOST_STUB_H
+#define ANDROID_RS_CONTEXT_HOST_STUB_H
+
+#include "rsUtils.h"
+//#include "rsMutex.h"
+
+//#include "rsThreadIO.h"
+#include "rsType.h"
+#include "rsMatrix.h"
+#include "rsAllocation.h"
+#include "rsMesh.h"
+//#include "rsDevice.h"
+#include "rsScriptC.h"
+#include "rsAllocation.h"
+#include "rsAdapter.h"
+#include "rsSampler.h"
+#include "rsLight.h"
+#include "rsProgramFragment.h"
+#include "rsProgramStore.h"
+#include "rsProgramRaster.h"
+#include "rsProgramVertex.h"
+#include "rsShaderCache.h"
+#include "rsVertexArray.h"
+
+//#include "rsgApiStructs.h"
+//#include "rsLocklessFifo.h"
+
+//#include <ui/egl/android_natives.h>
+
+// ---------------------------------------------------------------------------
+namespace android {
+
+namespace renderscript {
+
+class Device;
+
+class Context
+{
+public:
+    Context(Device *, bool isGraphics, bool useDepth) {
+        mObjHead = NULL;
+    }
+    ~Context() {
+    }
+
+
+    //StructuredAllocationContext mStateAllocation;
+    ElementState mStateElement;
+    TypeState mStateType;
+    SamplerState mStateSampler;
+    //ProgramFragmentState mStateFragment;
+    ProgramStoreState mStateFragmentStore;
+    //ProgramRasterState mStateRaster;
+    //ProgramVertexState mStateVertex;
+    LightState mStateLight;
+    VertexArrayState mStateVertexArray;
+
+    //ScriptCState mScriptC;
+    ShaderCache mShaderCache;
+
+
+    //bool setupCheck();
+    bool checkDriver() const {return false;}
+
+    ProgramFragment * getDefaultProgramFragment() const {
+        return NULL;
+    }
+    ProgramVertex * getDefaultProgramVertex() const {
+        return NULL;
+    }
+    ProgramStore * getDefaultProgramStore() const {
+        return NULL;
+    }
+    ProgramRaster * getDefaultProgramRaster() const {
+        return NULL;
+    }
+
+    uint32_t getWidth() const {return 0;}
+    uint32_t getHeight() const {return 0;}
+
+    // Timers
+    enum Timers {
+        RS_TIMER_IDLE,
+        RS_TIMER_INTERNAL,
+        RS_TIMER_SCRIPT,
+        RS_TIMER_CLEAR_SWAP,
+        _RS_TIMER_TOTAL
+    };
+
+    bool checkVersion1_1() const {return false; }
+    bool checkVersion2_0() const {return false; }
+
+    struct {
+        bool mLogTimes;
+        bool mLogScripts;
+        bool mLogObjects;
+        bool mLogShaders;
+    } props;
+
+    void dumpDebug() const {    }
+    void checkError(const char *) const {  };
+    void setError(RsError e, const char *msg = NULL) {  }
+
+    mutable const ObjectBase * mObjHead;
+
+    bool ext_OES_texture_npot() const {return mGL.OES_texture_npot;}
+
+protected:
+
+    struct {
+        const uint8_t * mVendor;
+        const uint8_t * mRenderer;
+        const uint8_t * mVersion;
+        const uint8_t * mExtensions;
+
+        uint32_t mMajorVersion;
+        uint32_t mMinorVersion;
+
+        int32_t mMaxVaryingVectors;
+        int32_t mMaxTextureImageUnits;
+
+        int32_t mMaxFragmentTextureImageUnits;
+        int32_t mMaxFragmentUniformVectors;
+
+        int32_t mMaxVertexAttribs;
+        int32_t mMaxVertexUniformVectors;
+        int32_t mMaxVertexTextureUnits;
+
+        bool OES_texture_npot;
+    } mGL;
+
+};
+
+}
+}
+#endif
diff --git a/libs/rs/rsDevice.cpp b/libs/rs/rsDevice.cpp
index b670ad4..a96b114 100644
--- a/libs/rs/rsDevice.cpp
+++ b/libs/rs/rsDevice.cpp
@@ -15,7 +15,11 @@
  */
 
 #include "rsDevice.h"
+#ifndef ANDROID_RS_BUILD_FOR_HOST
 #include "rsContext.h"
+#else
+#include "rsContextHostStub.h"
+#endif
 
 using namespace android;
 using namespace android::renderscript;
@@ -33,7 +37,7 @@
 
 void Device::addContext(Context *rsc)
 {
-    mContexts.add(rsc);
+    mContexts.push(rsc);
 }
 
 void Device::removeContext(Context *rsc)
diff --git a/libs/rs/rsElement.cpp b/libs/rs/rsElement.cpp
index 6288bc4..37b8bd6 100644
--- a/libs/rs/rsElement.cpp
+++ b/libs/rs/rsElement.cpp
@@ -14,9 +14,14 @@
  * limitations under the License.
  */
 
-#include "rsContext.h"
 
+#ifndef ANDROID_RS_BUILD_FOR_HOST
+#include "rsContext.h"
 #include <GLES/gl.h>
+#else
+#include "rsContextHostStub.h"
+#include <OpenGL/gl.h>
+#endif
 
 using namespace android;
 using namespace android::renderscript;
@@ -83,6 +88,90 @@
     }
 }
 
+void Element::serialize(OStream *stream) const
+{
+    // Need to identify ourselves
+    stream->addU32((uint32_t)getClassId());
+
+    String8 name(getName());
+    stream->addString(&name);
+
+    mComponent.serialize(stream);
+
+    // Now serialize all the fields
+    stream->addU32(mFieldCount);
+    for(uint32_t ct = 0; ct < mFieldCount; ct++) {
+        stream->addString(&mFields[ct].name);
+        mFields[ct].e->serialize(stream);
+    }
+}
+
+Element *Element::createFromStream(Context *rsc, IStream *stream)
+{
+    // First make sure we are reading the correct object
+    RsA3DClassID classID = (RsA3DClassID)stream->loadU32();
+    if(classID != RS_A3D_CLASS_ID_ELEMENT) {
+        LOGE("element loading skipped due to invalid class id\n");
+        return NULL;
+    }
+
+    String8 name;
+    stream->loadString(&name);
+
+    Element *elem = new Element(rsc);
+    elem->mComponent.loadFromStream(stream);
+    elem->mBits = elem->mComponent.getBits();
+
+    elem->mFieldCount = stream->loadU32();
+    if(elem->mFieldCount) {
+        elem->mFields = new ElementField_t [elem->mFieldCount];
+        for(uint32_t ct = 0; ct < elem->mFieldCount; ct ++) {
+            stream->loadString(&elem->mFields[ct].name);
+            Element *fieldElem = Element::createFromStream(rsc, stream);
+            elem->mFields[ct].e.set(fieldElem);
+        }
+    }
+
+    // We need to check if this already exists
+    for (uint32_t ct=0; ct < rsc->mStateElement.mElements.size(); ct++) {
+        Element *ee = rsc->mStateElement.mElements[ct];
+
+        if (!ee->getFieldCount() ) {
+
+            if((ee->getComponent().getType() == elem->getComponent().getType()) &&
+               (ee->getComponent().getKind() == elem->getComponent().getKind()) &&
+               (ee->getComponent().getIsNormalized() == elem->getComponent().getIsNormalized()) &&
+               (ee->getComponent().getVectorSize() == elem->getComponent().getVectorSize())) {
+                // Match
+                delete elem;
+                ee->incUserRef();
+                return ee;
+            }
+
+        } else if (ee->getFieldCount() == elem->mFieldCount) {
+
+            bool match = true;
+            for (uint32_t i=0; i < elem->mFieldCount; i++) {
+                if ((ee->mFields[i].e.get() != elem->mFields[i].e.get()) ||
+                    (ee->mFields[i].name.length() != elem->mFields[i].name.length()) ||
+                    (ee->mFields[i].name != elem->mFields[i].name)) {
+                    match = false;
+                    break;
+                }
+            }
+            if (match) {
+                delete elem;
+                ee->incUserRef();
+                return ee;
+            }
+
+        }
+    }
+
+    rsc->mStateElement.mElements.push(elem);
+    return elem;
+}
+
 
 const Element * Element::create(Context *rsc, RsDataType dt, RsDataKind dk,
                             bool isNorm, uint32_t vecSize)
@@ -143,45 +232,6 @@
     return e;
 }
 
-String8 Element::getCStructBody(uint32_t indent) const
-{
-    String8 si;
-    for (uint32_t ct=0; ct < indent; ct++) {
-        si.append(" ");
-    }
-
-    String8 s(si);
-    s.append("{\n");
-    for (uint32_t ct = 0; ct < mFieldCount; ct++) {
-        s.append(si);
-        s.append(mFields[ct].e->getCType(indent+4));
-        s.append(" ");
-        s.append(mFields[ct].name);
-        s.append(";\n");
-    }
-    s.append(si);
-    s.append("}");
-    return s;
-}
-
-String8 Element::getCType(uint32_t indent) const
-{
-    String8 s;
-    for (uint32_t ct=0; ct < indent; ct++) {
-        s.append(" ");
-    }
-
-    if (!mFieldCount) {
-        // Basic component.
-        s.append(mComponent.getCType());
-    } else {
-        s.append("struct ");
-        s.append(getCStructBody(indent));
-    }
-
-    return s;
-}
-
 String8 Element::getGLSLType(uint32_t indent) const
 {
     String8 s;
@@ -243,6 +293,32 @@
     return (RsElement)e;
 }
 
+void rsi_ElementGetNativeData(Context *rsc, RsElement elem, uint32_t *elemData, uint32_t elemDataSize)
+{
+    rsAssert(elemDataSize == 5);
+    // we will pack mType; mKind; mNormalized; mVectorSize; NumSubElements
+    Element *e = static_cast<Element *>(elem);
+
+    (*elemData++) = (uint32_t)e->getType();
+    (*elemData++) = (uint32_t)e->getKind();
+    (*elemData++) = e->getComponent().getIsNormalized() ? 1 : 0;
+    (*elemData++) = e->getComponent().getVectorSize();
+    (*elemData++) = e->getFieldCount();
+
+}
+
+void rsi_ElementGetSubElements(Context *rsc, RsElement elem, uint32_t *ids, const char **names, uint32_t dataSize)
+{
+    Element *e = static_cast<Element *>(elem);
+    rsAssert(e->getFieldCount() == dataSize);
+
+    for(uint32_t i = 0; i < dataSize; i ++) {
+        ids[i] = (uint32_t)e->getField(i);
+        names[i] = e->getFieldName(i);
+    }
+
+}
+
 
 }
 }
diff --git a/libs/rs/rsElement.h b/libs/rs/rsElement.h
index 02a1ca2..90e7cc8 100644
--- a/libs/rs/rsElement.h
+++ b/libs/rs/rsElement.h
@@ -54,11 +54,12 @@
     RsDataKind getKind() const {return mComponent.getKind();}
     uint32_t getBits() const {return mBits;}
 
-    String8 getCType(uint32_t indent=0) const;
-    String8 getCStructBody(uint32_t indent=0) const;
     String8 getGLSLType(uint32_t indent=0) const;
 
     void dumpLOGV(const char *prefix) const;
+    virtual void serialize(OStream *stream) const;
+    virtual RsA3DClassID getClassId() const { return RS_A3D_CLASS_ID_ELEMENT; }
+    static Element *createFromStream(Context *rsc, IStream *stream);
 
     static const Element * create(Context *rsc, RsDataType dt, RsDataKind dk,
                             bool isNorm, uint32_t vecSize);
@@ -90,7 +91,7 @@
     ~ElementState();
 
     // Cache of all existing elements.
-    Vector<const Element *> mElements;
+    Vector<Element *> mElements;
 };
 
 
diff --git a/libs/rs/rsFileA3D.cpp b/libs/rs/rsFileA3D.cpp
index e3272c5..5709f2a 100644
--- a/libs/rs/rsFileA3D.cpp
+++ b/libs/rs/rsFileA3D.cpp
@@ -15,29 +15,156 @@
  * limitations under the License.
  */
 
+#ifndef ANDROID_RS_BUILD_FOR_HOST
 #include "rsContext.h"
+#else
+#include "rsContextHostStub.h"
+#endif
 
-
-#include <utils/String8.h>
 #include "rsFileA3D.h"
 
 #include "rsMesh.h"
+#include "rsAnimation.h"
+
 
 using namespace android;
 using namespace android::renderscript;
 
-
-
-FileA3D::FileA3D()
+FileA3D::FileA3D(Context *rsc) : ObjectBase(rsc)
 {
-    mRsc = NULL;
+    mAlloc = NULL;
+    mData = NULL;
+    mWriteStream = NULL;
+    mReadStream = NULL;
+
+    mMajorVersion = 0;
+    mMinorVersion = 1;
+    mDataSize = 0;
 }
 
 FileA3D::~FileA3D()
 {
+    for(size_t i = 0; i < mIndex.size(); i ++) {
+        delete mIndex[i];
+    }
+    for(size_t i = 0; i < mWriteIndex.size(); i ++) {
+        delete mWriteIndex[i];
+    }
+    if(mWriteStream) {
+        delete mWriteStream;
+    }
+    if(mReadStream) {
+        delete mWriteStream;
+    }
+    if(mAlloc) {
+        free(mAlloc);
+    }
 }
 
-bool FileA3D::load(Context *rsc, FILE *f)
+void FileA3D::parseHeader(IStream *headerStream)
+{
+    mMajorVersion = headerStream->loadU32();
+    mMinorVersion = headerStream->loadU32();
+    uint32_t flags = headerStream->loadU32();
+    mUse64BitOffsets = (flags & 1) != 0;
+
+    LOGE("file open 64bit = %i", mUse64BitOffsets);
+
+    uint32_t numIndexEntries = headerStream->loadU32();
+    for(uint32_t i = 0; i < numIndexEntries; i ++) {
+        A3DIndexEntry *entry = new A3DIndexEntry();
+        headerStream->loadString(&entry->mObjectName);
+        LOGE("Header data, entry name = %s", entry->mObjectName.string());
+        entry->mType = (RsA3DClassID)headerStream->loadU32();
+        if(mUse64BitOffsets){
+            entry->mOffset = headerStream->loadOffset();
+            entry->mLength = headerStream->loadOffset();
+        }
+        else {
+            entry->mOffset = headerStream->loadU32();
+            entry->mLength = headerStream->loadU32();
+        }
+        entry->mRsObj = NULL;
+        mIndex.push(entry);
+    }
+}
+
+bool FileA3D::load(const void *data, size_t length)
+{
+    LOGE("Loading data. Size: %u", length);
+    const uint8_t *localData = (const uint8_t *)data;
+
+    size_t lengthRemaining = length;
+    size_t magicStrLen = 12;
+    if ((length < magicStrLen) ||
+        memcmp(data, "Android3D_ff", magicStrLen)) {
+        return false;
+    }
+
+    localData += magicStrLen;
+    lengthRemaining -= magicStrLen;
+
+    // Next we get our header size
+    uint64_t headerSize = 0;
+    if(lengthRemaining < sizeof(headerSize)) {
+        return false;
+    }
+
+    memcpy(&headerSize, localData, sizeof(headerSize));
+    localData += sizeof(headerSize);
+    lengthRemaining -= sizeof(headerSize);
+
+    LOGE("Loading data, headerSize = %lli", headerSize);
+
+    if(lengthRemaining < headerSize) {
+        return false;
+    }
+
+    uint8_t *headerData = (uint8_t *)malloc(headerSize);
+    if(!headerData) {
+        return false;
+    }
+
+    memcpy(headerData, localData, headerSize);
+
+    // Now open the stream to parse the header
+    IStream headerStream(headerData, false);
+    parseHeader(&headerStream);
+
+    free(headerData);
+
+    localData += headerSize;
+    lengthRemaining -= headerSize;
+
+    if(lengthRemaining < sizeof(mDataSize)) {
+        return false;
+    }
+
+    // Read the size of the data
+    memcpy(&mDataSize, localData, sizeof(mDataSize));
+    localData += sizeof(mDataSize);
+    lengthRemaining -= sizeof(mDataSize);
+
+    LOGE("Loading data, mDataSize = %lli", mDataSize);
+
+    if(lengthRemaining < mDataSize) {
+        return false;
+    }
+
+    // We should know enough to read the file in at this point.
+    mAlloc = malloc(mDataSize);
+    if (!mAlloc) {
+        return false;
+    }
+    mData = (uint8_t *)mAlloc;
+    memcpy(mAlloc, localData, mDataSize);
+
+    mReadStream = new IStream(mData, mUse64BitOffsets);
+
+    return true;
+}
+
+bool FileA3D::load(FILE *f)
 {
     char magicString[12];
     size_t len;
@@ -49,47 +176,39 @@
         return false;
     }
 
-    LOGE("file open 2");
-    len = fread(&mMajorVersion, 1, sizeof(mMajorVersion), f);
-    if (len != sizeof(mMajorVersion)) {
+    // Next thing is the size of the header
+    uint64_t headerSize = 0;
+    len = fread(&headerSize, 1, sizeof(headerSize), f);
+    if (len != sizeof(headerSize) || headerSize == 0) {
         return false;
     }
 
-    LOGE("file open 3");
-    len = fread(&mMinorVersion, 1, sizeof(mMinorVersion), f);
-    if (len != sizeof(mMinorVersion)) {
+    uint8_t *headerData = (uint8_t *)malloc(headerSize);
+    if(!headerData) {
         return false;
     }
 
-    LOGE("file open 4");
-    uint32_t flags;
-    len = fread(&flags, 1, sizeof(flags), f);
-    if (len != sizeof(flags)) {
+    len = fread(headerData, 1, headerSize, f);
+    if (len != headerSize) {
         return false;
     }
-    mUse64BitOffsets = (flags & 1) != 0;
 
-    LOGE("file open 64bit = %i", mUse64BitOffsets);
+    // Now open the stream to parse the header
+    IStream headerStream(headerData, false);
+    parseHeader(&headerStream);
 
-    if (mUse64BitOffsets) {
-        len = fread(&mDataSize, 1, sizeof(mDataSize), f);
-        if (len != sizeof(mDataSize)) {
-            return false;
-        }
-    } else {
-        uint32_t tmp;
-        len = fread(&tmp, 1, sizeof(tmp), f);
-        if (len != sizeof(tmp)) {
-            return false;
-        }
-        mDataSize = tmp;
+    free(headerData);
+
+    // Next thing is the size of the header
+    len = fread(&mDataSize, 1, sizeof(mDataSize), f);
+    if (len != sizeof(mDataSize) || mDataSize == 0) {
+        return false;
     }
 
     LOGE("file open size = %lli", mDataSize);
 
     // We should know enough to read the file in at this point.
-    fseek(f, SEEK_SET, 0);
-    mAlloc= malloc(mDataSize);
+    mAlloc = malloc(mDataSize);
     if (!mAlloc) {
         return false;
     }
@@ -99,282 +218,255 @@
         return false;
     }
 
-    LOGE("file start processing");
-    return process(rsc);
+    mReadStream = new IStream(mData, mUse64BitOffsets);
+
+    LOGE("Header is read an stream initialized");
+    return true;
 }
 
-bool FileA3D::processIndex(Context *rsc, A3DIndexEntry *ie)
-{
-    bool ret = false;
-    IO io(mData + ie->mOffset, mUse64BitOffsets);
+size_t FileA3D::getNumIndexEntries() const {
+    return mIndex.size();
+}
 
-    LOGE("process index, type %i", ie->mType);
-
-    switch(ie->mType) {
-    case CHUNK_ELEMENT:
-        processChunk_Element(rsc, &io, ie);
-        break;
-    case CHUNK_ELEMENT_SOURCE:
-        processChunk_ElementSource(rsc, &io, ie);
-        break;
-    case CHUNK_VERTICIES:
-        processChunk_Verticies(rsc, &io, ie);
-        break;
-    case CHUNK_MESH:
-        processChunk_Mesh(rsc, &io, ie);
-        break;
-    case CHUNK_PRIMITIVE:
-        processChunk_Primitive(rsc, &io, ie);
-        break;
-    default:
-        LOGE("FileA3D Unknown chunk type");
-        break;
+const FileA3D::A3DIndexEntry *FileA3D::getIndexEntry(size_t index) const {
+    if(index < mIndex.size()) {
+        return mIndex[index];
     }
-    return (ie->mRsObj != NULL);
+    return NULL;
 }
 
-bool FileA3D::process(Context *rsc)
-{
-    LOGE("process");
-    IO io(mData + 12, mUse64BitOffsets);
-    bool ret = true;
-
-    // Build the index first
-    LOGE("process 1");
-    io.loadU32(); // major version, already loaded
-    io.loadU32(); // minor version, already loaded
-    LOGE("process 2");
-
-    io.loadU32();  // flags
-    io.loadOffset(); // filesize, already loaded.
-    LOGE("process 4");
-    uint64_t mIndexOffset = io.loadOffset();
-    uint64_t mStringOffset = io.loadOffset();
-
-    LOGE("process mIndexOffset= 0x%016llx", mIndexOffset);
-    LOGE("process mStringOffset= 0x%016llx", mStringOffset);
-
-    IO index(mData + mIndexOffset, mUse64BitOffsets);
-    IO stringTable(mData + mStringOffset, mUse64BitOffsets);
-
-    uint32_t stringEntryCount = stringTable.loadU32();
-    LOGE("stringEntryCount %i", stringEntryCount);
-    mStrings.setCapacity(stringEntryCount);
-    mStringIndexValues.setCapacity(stringEntryCount);
-    if (stringEntryCount) {
-        uint32_t stringType = stringTable.loadU32();
-        LOGE("stringType %i", stringType);
-        rsAssert(stringType==0);
-        for (uint32_t ct = 0; ct < stringEntryCount; ct++) {
-            uint64_t offset = stringTable.loadOffset();
-            LOGE("string offset 0x%016llx", offset);
-            IO tmp(mData + offset, mUse64BitOffsets);
-            String8 s;
-            tmp.loadString(&s);
-            LOGE("string %s", s.string());
-            mStrings.push(s);
-        }
+ObjectBase *FileA3D::initializeFromEntry(size_t index) {
+    if(index >= mIndex.size()) {
+        return NULL;
     }
 
-    LOGE("strings done");
-    uint32_t indexEntryCount = index.loadU32();
-    LOGE("index count %i", indexEntryCount);
-    mIndex.setCapacity(indexEntryCount);
-    for (uint32_t ct = 0; ct < indexEntryCount; ct++) {
-        A3DIndexEntry e;
-        uint32_t stringIndex = index.loadU32();
-        LOGE("index %i", ct);
-        LOGE("  string index %i", stringIndex);
-        e.mType = (A3DChunkType)index.loadU32();
-        LOGE("  type %i", e.mType);
-        e.mOffset = index.loadOffset();
-        LOGE("  offset 0x%016llx", e.mOffset);
-
-        if (stringIndex && (stringIndex < mStrings.size())) {
-            e.mID = mStrings[stringIndex];
-            mStringIndexValues.editItemAt(stringIndex) = ct;
-            LOGE("  id %s", e.mID.string());
-        }
-
-        mIndex.push(e);
-    }
-    LOGE("index done");
-
-    // At this point the index should be fully populated.
-    // We can now walk though it and load all the objects.
-    for (uint32_t ct = 0; ct < indexEntryCount; ct++) {
-        LOGE("processing index entry %i", ct);
-        processIndex(rsc, &mIndex.editItemAt(ct));
+    FileA3D::A3DIndexEntry *entry = mIndex[index];
+    if(!entry) {
+        return NULL;
     }
 
-    return ret;
-}
-
-
-FileA3D::IO::IO(const uint8_t *buf, bool use64)
-{
-    mData = buf;
-    mPos = 0;
-    mUse64 = use64;
-}
-
-uint64_t FileA3D::IO::loadOffset()
-{
-    uint64_t tmp;
-    if (mUse64) {
-        mPos = (mPos + 7) & (~7);
-        tmp = reinterpret_cast<const uint64_t *>(&mData[mPos])[0];
-        mPos += sizeof(uint64_t);
-        return tmp;
+    if(entry->mRsObj) {
+        entry->mRsObj->incUserRef();
+        return entry->mRsObj;
     }
-    return loadU32();
-}
 
-void FileA3D::IO::loadString(String8 *s)
-{
-    LOGE("loadString");
-    uint32_t len = loadU32();
-    LOGE("loadString len %i", len);
-    s->setTo((const char *)&mData[mPos], len);
-    mPos += len;
-}
-
-
-void FileA3D::processChunk_Mesh(Context *rsc, IO *io, A3DIndexEntry *ie)
-{
-    Mesh * m = new Mesh(rsc);
-
-    m->mPrimitivesCount = io->loadU32();
-    m->mPrimitives = new Mesh::Primitive_t *[m->mPrimitivesCount];
-
-    for (uint32_t ct = 0; ct < m->mPrimitivesCount; ct++) {
-        uint32_t index = io->loadU32();
-
-        m->mPrimitives[ct] = (Mesh::Primitive_t *)mIndex[index].mRsObj;
-    }
-    ie->mRsObj = m;
-}
-
-void FileA3D::processChunk_Primitive(Context *rsc, IO *io, A3DIndexEntry *ie)
-{
-    Mesh::Primitive_t * p = new Mesh::Primitive_t;
-
-    p->mIndexCount = io->loadU32();
-    uint32_t vertIdx = io->loadU32();
-    p->mRestartCounts = io->loadU16();
-    uint32_t bits = io->loadU8();
-    p->mType = (RsPrimitive)io->loadU8();
-
-    LOGE("processChunk_Primitive count %i, bits %i", p->mIndexCount, bits);
-
-    p->mVerticies = (Mesh::Verticies_t *)mIndex[vertIdx].mRsObj;
-
-    p->mIndicies = new uint16_t[p->mIndexCount];
-    for (uint32_t ct = 0; ct < p->mIndexCount; ct++) {
-        switch(bits) {
-        case 8:
-            p->mIndicies[ct] = io->loadU8();
+    // Seek to the beginning of object
+    mReadStream->reset(entry->mOffset);
+    switch (entry->mType) {
+        case RS_A3D_CLASS_ID_UNKNOWN:
+            return NULL;
+        case RS_A3D_CLASS_ID_MESH:
+            entry->mRsObj = Mesh::createFromStream(mRSC, mReadStream);
             break;
-        case 16:
-            p->mIndicies[ct] = io->loadU16();
+        case RS_A3D_CLASS_ID_TYPE:
+            entry->mRsObj = Type::createFromStream(mRSC, mReadStream);
             break;
-        case 32:
-            p->mIndicies[ct] = io->loadU32();
+        case RS_A3D_CLASS_ID_ELEMENT:
+            entry->mRsObj = Element::createFromStream(mRSC, mReadStream);
             break;
+        case RS_A3D_CLASS_ID_ALLOCATION:
+            entry->mRsObj = Allocation::createFromStream(mRSC, mReadStream);
+            break;
+        case RS_A3D_CLASS_ID_PROGRAM_VERTEX:
+            entry->mRsObj = ProgramVertex::createFromStream(mRSC, mReadStream);
+            break;
+        case RS_A3D_CLASS_ID_PROGRAM_RASTER:
+            entry->mRsObj = ProgramRaster::createFromStream(mRSC, mReadStream);
+            break;
+        case RS_A3D_CLASS_ID_PROGRAM_FRAGMENT:
+            entry->mRsObj = ProgramFragment::createFromStream(mRSC, mReadStream);
+            break;
+        case RS_A3D_CLASS_ID_PROGRAM_STORE:
+            entry->mRsObj = ProgramStore::createFromStream(mRSC, mReadStream);
+            break;
+        case RS_A3D_CLASS_ID_SAMPLER:
+            entry->mRsObj = Sampler::createFromStream(mRSC, mReadStream);
+            break;
+        case RS_A3D_CLASS_ID_ANIMATION:
+            entry->mRsObj = Animation::createFromStream(mRSC, mReadStream);
+            break;
+        case RS_A3D_CLASS_ID_LIGHT:
+            entry->mRsObj = Light::createFromStream(mRSC, mReadStream);
+            break;
+        case RS_A3D_CLASS_ID_ADAPTER_1D:
+            entry->mRsObj = Adapter1D::createFromStream(mRSC, mReadStream);
+            break;
+        case RS_A3D_CLASS_ID_ADAPTER_2D:
+            entry->mRsObj = Adapter2D::createFromStream(mRSC, mReadStream);
+            break;
+        case RS_A3D_CLASS_ID_SCRIPT_C:
+            return NULL;
+    }
+    if(entry->mRsObj) {
+        entry->mRsObj->incUserRef();
+    }
+    return entry->mRsObj;
+}
+
+bool FileA3D::writeFile(const char *filename)
+{
+    if(!mWriteStream) {
+        LOGE("No objects to write\n");
+        return false;
+    }
+    if(mWriteStream->getPos() == 0) {
+        LOGE("No objects to write\n");
+        return false;
+    }
+
+    FILE *writeHandle = fopen(filename, "wb");
+    if(!writeHandle) {
+        LOGE("Couldn't open the file for writing\n");
+        return false;
+    }
+
+    // Open a new stream to make writing the header easier
+    OStream headerStream(5*1024, false);
+    headerStream.addU32(mMajorVersion);
+    headerStream.addU32(mMinorVersion);
+    uint32_t is64Bit = 0;
+    headerStream.addU32(is64Bit);
+
+    uint32_t writeIndexSize = mWriteIndex.size();
+    headerStream.addU32(writeIndexSize);
+    for(uint32_t i = 0; i < writeIndexSize; i ++) {
+        headerStream.addString(&mWriteIndex[i]->mObjectName);
+        headerStream.addU32((uint32_t)mWriteIndex[i]->mType);
+        if(mUse64BitOffsets){
+            headerStream.addOffset(mWriteIndex[i]->mOffset);
+            headerStream.addOffset(mWriteIndex[i]->mLength);
         }
-        LOGE("  idx %i", p->mIndicies[ct]);
-    }
-
-    if (p->mRestartCounts) {
-        p->mRestarts = new uint16_t[p->mRestartCounts];
-        for (uint32_t ct = 0; ct < p->mRestartCounts; ct++) {
-            switch(bits) {
-            case 8:
-                p->mRestarts[ct] = io->loadU8();
-                break;
-            case 16:
-                p->mRestarts[ct] = io->loadU16();
-                break;
-            case 32:
-                p->mRestarts[ct] = io->loadU32();
-                break;
-            }
-            LOGE("  idx %i", p->mRestarts[ct]);
+        else {
+            uint32_t offset = (uint32_t)mWriteIndex[i]->mOffset;
+            headerStream.addU32(offset);
+            offset = (uint32_t)mWriteIndex[i]->mLength;
+            headerStream.addU32(offset);
         }
-    } else {
-        p->mRestarts = NULL;
     }
 
-    ie->mRsObj = p;
+    // Write our magic string so we know we are reading the right file
+    String8 magicString(A3D_MAGIC_KEY);
+    fwrite(magicString.string(), sizeof(char), magicString.size(), writeHandle);
+
+    // Store the size of the header to make it easier to parse when we read it
+    uint64_t headerSize = headerStream.getPos();
+    fwrite(&headerSize, sizeof(headerSize), 1, writeHandle);
+
+    // Now write our header
+    fwrite(headerStream.getPtr(), sizeof(uint8_t), headerStream.getPos(), writeHandle);
+
+    // Now write the size of the data part of the file for easier parsing later
+    uint64_t fileDataSize = mWriteStream->getPos();
+    fwrite(&fileDataSize, sizeof(fileDataSize), 1, writeHandle);
+
+    fwrite(mWriteStream->getPtr(), sizeof(uint8_t), mWriteStream->getPos(), writeHandle);
+
+    int status = fclose(writeHandle);
+
+    if(status != 0) {
+        LOGE("Couldn't close file\n");
+        return false;
+    }
+
+    return true;
 }
 
-void FileA3D::processChunk_Verticies(Context *rsc, IO *io, A3DIndexEntry *ie)
-{
-    Mesh::Verticies_t *cv = new Mesh::Verticies_t;
-    cv->mAllocationCount = io->loadU32();
-    cv->mAllocations = new Allocation *[cv->mAllocationCount];
-    LOGE("processChunk_Verticies count %i", cv->mAllocationCount);
-    for (uint32_t ct = 0; ct < cv->mAllocationCount; ct++) {
-        uint32_t i = io->loadU32();
-        cv->mAllocations[ct] = (Allocation *)mIndex[i].mRsObj;
-        LOGE("  idx %i", i);
+void FileA3D::appendToFile(ObjectBase *obj) {
+    if(!obj) {
+        return;
     }
-    ie->mRsObj = cv;
-}
-
-void FileA3D::processChunk_Element(Context *rsc, IO *io, A3DIndexEntry *ie)
-{
-    /*
-    rsi_ElementBegin(rsc);
-
-    uint32_t count = io->loadU32();
-    LOGE("processChunk_Element count %i", count);
-    while (count--) {
-        RsDataKind dk = (RsDataKind)io->loadU8();
-        RsDataType dt = (RsDataType)io->loadU8();
-        uint32_t bits = io->loadU8();
-        bool isNorm = io->loadU8() != 0;
-        LOGE("  %i %i %i %i", dk, dt, bits, isNorm);
-        rsi_ElementAdd(rsc, dk, dt, isNorm, bits, 0);
+    if(!mWriteStream) {
+        const uint64_t initialStreamSize = 256*1024;
+        mWriteStream = new OStream(initialStreamSize, false);
     }
-    LOGE("processChunk_Element create");
-    ie->mRsObj = rsi_ElementCreate(rsc);
-    */
-}
-
-void FileA3D::processChunk_ElementSource(Context *rsc, IO *io, A3DIndexEntry *ie)
-{
-    uint32_t index = io->loadU32();
-    uint32_t count = io->loadU32();
-
-    LOGE("processChunk_ElementSource count %i, index %i", count, index);
-
-    RsElement e = (RsElement)mIndex[index].mRsObj;
-
-    RsAllocation a = rsi_AllocationCreateSized(rsc, e, count);
-    Allocation * alloc = static_cast<Allocation *>(a);
-
-    float * data = (float *)alloc->getPtr();
-    while(count--) {
-        *data = io->loadF();
-        LOGE("  %f", *data);
-        data++;
-    }
-    ie->mRsObj = alloc;
+    A3DIndexEntry *indexEntry = new A3DIndexEntry();
+    indexEntry->mObjectName.setTo(obj->getName());
+    indexEntry->mType = obj->getClassId();
+    indexEntry->mOffset = mWriteStream->getPos();
+    indexEntry->mRsObj = obj;
+    mWriteIndex.push(indexEntry);
+    obj->serialize(mWriteStream);
+    indexEntry->mLength = mWriteStream->getPos() - indexEntry->mOffset;
+    mWriteStream->align(4);
 }
 
 namespace android {
 namespace renderscript {
 
+void rsi_FileA3DGetNumIndexEntries(Context *rsc, int32_t *numEntries, RsFile file)
+{
+    FileA3D *fa3d = static_cast<FileA3D *>(file);
+
+    if(fa3d) {
+        *numEntries = fa3d->getNumIndexEntries();
+    }
+    else {
+        *numEntries = 0;
+    }
+}
+
+void rsi_FileA3DGetIndexEntries(Context *rsc, RsFileIndexEntry *fileEntries, uint32_t numEntries, RsFile file)
+{
+    FileA3D *fa3d = static_cast<FileA3D *>(file);
+
+    if(!fa3d) {
+        LOGE("Can't load index entries. No valid file");
+        return;
+    }
+
+    uint32_t numFileEntries = fa3d->getNumIndexEntries();
+    if(numFileEntries != numEntries || numEntries == 0 || fileEntries == NULL) {
+        LOGE("Can't load index entries. Invalid number requested");
+        return;
+    }
+
+    for(uint32_t i = 0; i < numFileEntries; i ++) {
+        const FileA3D::A3DIndexEntry *entry = fa3d->getIndexEntry(i);
+        fileEntries[i].classID = entry->getType();
+        fileEntries[i].objectName = entry->getObjectName().string();
+    }
+
+}
+
+RsObjectBase rsi_FileA3DGetEntryByIndex(Context *rsc, uint32_t index, RsFile file)
+{
+    FileA3D *fa3d = static_cast<FileA3D *>(file);
+    if(!fa3d) {
+        LOGE("Can't load entry. No valid file");
+        return NULL;
+    }
+
+    ObjectBase *obj = fa3d->initializeFromEntry(index);
+    LOGE("Returning object with name %s", obj->getName());
+
+    return obj;
+}
+
+RsFile rsi_FileA3DCreateFromAssetStream(Context *rsc, const void *data, uint32_t len)
+{
+    if (data == NULL) {
+        LOGE("File load failed. Asset stream is NULL");
+        return NULL;
+    }
+
+    FileA3D *fa3d = new FileA3D(rsc);
+
+    fa3d->load(data, len);
+    fa3d->incUserRef();
+
+    return fa3d;
+}
+
 
 RsFile rsi_FileOpen(Context *rsc, char const *path, unsigned int len)
 {
-    FileA3D *fa3d = new FileA3D;
+    FileA3D *fa3d = new FileA3D(rsc);
 
     FILE *f = fopen("/sdcard/test.a3d", "rb");
     if (f) {
-        fa3d->load(rsc, f);
+        fa3d->load(f);
         fclose(f);
+        fa3d->incUserRef();
         return fa3d;
     }
     delete fa3d;
diff --git a/libs/rs/rsFileA3D.h b/libs/rs/rsFileA3D.h
index 9ee08ec..b985907 100644
--- a/libs/rs/rsFileA3D.h
+++ b/libs/rs/rsFileA3D.h
@@ -18,20 +18,23 @@
 #define ANDROID_RS_FILE_A3D_H
 
 #include "RenderScript.h"
-#include "rsFileA3DDecls.h"
 #include "rsMesh.h"
 
 #include <utils/String8.h>
+#include "rsStream.h"
 #include <stdio.h>
 
+#define A3D_MAGIC_KEY "Android3D_ff"
+
 // ---------------------------------------------------------------------------
 namespace android {
+
 namespace renderscript {
 
-class FileA3D
+class FileA3D : public ObjectBase
 {
 public:
-    FileA3D();
+    FileA3D(Context *rsc);
     ~FileA3D();
 
     uint32_t mMajorVersion;
@@ -40,78 +43,53 @@
     uint64_t mStringTableOffset;
     bool mUse64BitOffsets;
 
-    struct A3DIndexEntry {
-        String8 mID;
-        A3DChunkType mType;
+    class A3DIndexEntry {
+        String8 mObjectName;
+        RsA3DClassID mType;
         uint64_t mOffset;
-        void * mRsObj;
+        uint64_t mLength;
+        ObjectBase *mRsObj;
+    public:
+        friend class FileA3D;
+        const String8 &getObjectName() const {
+            return mObjectName;
+        }
+        RsA3DClassID getType() const {
+            return mType;
+        }
     };
 
-    bool load(Context *rsc, FILE *f);
+    bool load(FILE *f);
+    bool load(const void *data, size_t length);
+
+    size_t getNumIndexEntries() const;
+    const A3DIndexEntry* getIndexEntry(size_t index) const;
+    ObjectBase *initializeFromEntry(size_t index);
+
+    void appendToFile(ObjectBase *obj);
+    bool writeFile(const char *filename);
+
+    // Currently files do not get serialized,
+    // but we need to inherit from ObjectBase for ref tracking
+    virtual void serialize(OStream *stream) const {
+    }
+    virtual RsA3DClassID getClassId() const {
+        return RS_A3D_CLASS_ID_UNKNOWN;
+    }
 
 protected:
-    class IO
-    {
-    public:
-        IO(const uint8_t *, bool use64);
-    
-        float loadF() {
-            mPos = (mPos + 3) & (~3);
-            float tmp = reinterpret_cast<const float *>(&mData[mPos])[0];
-            mPos += sizeof(float);
-            return tmp;
-        }
-        int32_t loadI32() {
-            mPos = (mPos + 3) & (~3);
-            int32_t tmp = reinterpret_cast<const int32_t *>(&mData[mPos])[0];
-            mPos += sizeof(int32_t);
-            return tmp;
-        }
-        uint32_t loadU32() {
-            mPos = (mPos + 3) & (~3);
-            uint32_t tmp = reinterpret_cast<const uint32_t *>(&mData[mPos])[0];
-            mPos += sizeof(uint32_t);
-            return tmp;
-        }
-        uint16_t loadU16() {
-            mPos = (mPos + 1) & (~1);
-            uint16_t tmp = reinterpret_cast<const uint16_t *>(&mData[mPos])[0];
-            mPos += sizeof(uint16_t);
-            return tmp;
-        }
-        uint8_t loadU8() {
-            uint8_t tmp = reinterpret_cast<const uint8_t *>(&mData[mPos])[0];
-            mPos += sizeof(uint8_t);
-            return tmp;
-        }
-        uint64_t loadOffset();
-        void loadString(String8 *s);
-        uint64_t getPos() const {return mPos;}
-        const uint8_t * getPtr() const;
-    protected:
-        const uint8_t * mData;
-        uint64_t mPos;
-        bool mUse64;
-    };
 
-
-    bool process(Context *rsc);
-    bool processIndex(Context *rsc, A3DIndexEntry *);
-    void processChunk_Mesh(Context *rsc, IO *io, A3DIndexEntry *ie);
-    void processChunk_Primitive(Context *rsc, IO *io, A3DIndexEntry *ie);
-    void processChunk_Verticies(Context *rsc, IO *io, A3DIndexEntry *ie);
-    void processChunk_Element(Context *rsc, IO *io, A3DIndexEntry *ie);
-    void processChunk_ElementSource(Context *rsc, IO *io, A3DIndexEntry *ie);
+    void parseHeader(IStream *headerStream);
 
     const uint8_t * mData;
     void * mAlloc;
     uint64_t mDataSize;
-    Context * mRsc;
 
-    Vector<A3DIndexEntry> mIndex;
-    Vector<String8> mStrings;
-    Vector<uint32_t> mStringIndexValues;
+    OStream *mWriteStream;
+    Vector<A3DIndexEntry*> mWriteIndex;
 
+    IStream *mReadStream;
+    Vector<A3DIndexEntry*> mIndex;
 };
 
 
diff --git a/libs/rs/rsFileA3DDecls.h b/libs/rs/rsFileA3DDecls.h
deleted file mode 100644
index 2a08bd3..0000000
--- a/libs/rs/rsFileA3DDecls.h
+++ /dev/null
@@ -1,44 +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.
- */
-
-#ifndef ANDROID_RS_FILE_A3D_DECLS_H
-#define ANDROID_RS_FILE_A3D_DECLS_H
-
-
-#define A3D_MAGIC_KEY "Android3D_ff"
-
-namespace android {
-namespace renderscript {
-
-    enum A3DChunkType {
-        CHUNK_EMPTY,
-
-        CHUNK_ELEMENT,
-        CHUNK_ELEMENT_SOURCE,
-        CHUNK_VERTICIES,
-        CHUNK_MESH,
-        CHUNK_PRIMITIVE,
-
-        CHUNK_LAST
-    };
-
-
-}
-}
-#endif //ANDROID_RS_FILE_A3D_H
-
-
-
diff --git a/libs/rs/rsFont.cpp b/libs/rs/rsFont.cpp
new file mode 100644
index 0000000..833bee0
--- /dev/null
+++ b/libs/rs/rsFont.cpp
@@ -0,0 +1,727 @@
+
+/*
+ * 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_BUILD_FOR_HOST
+#include "rsContext.h"
+#else
+#include "rsContextHostStub.h"
+#endif
+
+#include "rsFont.h"
+#include "rsProgramFragment.h"
+#include FT_BITMAP_H
+
+#include <GLES/gl.h>
+#include <GLES/glext.h>
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
+
+using namespace android;
+using namespace android::renderscript;
+
+Font::Font(Context *rsc) : ObjectBase(rsc), mCachedGlyphs(NULL)
+{
+    mAllocFile = __FILE__;
+    mAllocLine = __LINE__;
+    mInitialized = false;
+    mHasKerning = false;
+    mFace = NULL;
+}
+
+bool Font::init(const char *name, uint32_t fontSize, uint32_t dpi)
+{
+    if(mInitialized) {
+        LOGE("Reinitialization of fonts not supported");
+        return false;
+    }
+
+    String8 fontsDir("/fonts/");
+    String8 fullPath(getenv("ANDROID_ROOT"));
+    fullPath += fontsDir;
+    fullPath += name;
+
+    FT_Error error = FT_New_Face(mRSC->mStateFont.getLib(), fullPath.string(), 0, &mFace);
+    if(error) {
+        LOGE("Unable to initialize font %s", fullPath.string());
+        return false;
+    }
+
+    mFontName = name;
+    mFontSize = fontSize;
+    mDpi = dpi;
+
+    error = FT_Set_Char_Size(mFace, fontSize * 64, 0, dpi, 0);
+    if(error) {
+        LOGE("Unable to set font size on %s", fullPath.string());
+        return false;
+    }
+
+    mHasKerning = FT_HAS_KERNING(mFace);
+
+    mInitialized = true;
+    return true;
+}
+
+void Font::invalidateTextureCache()
+{
+    for(uint32_t i = 0; i < mCachedGlyphs.size(); i ++) {
+        mCachedGlyphs.valueAt(i)->mIsValid = false;
+    }
+}
+
+void Font::drawCachedGlyph(CachedGlyphInfo *glyph, int x, int y)
+{
+    FontState *state = &mRSC->mStateFont;
+
+    int nPenX = x + glyph->mBitmapLeft;
+    int nPenY = y - glyph->mBitmapTop + glyph->mBitmapHeight;
+
+    state->appendMeshQuad(nPenX, nPenY, 0,
+                            glyph->mBitmapMinU, glyph->mBitmapMaxV,
+
+                            nPenX + (int)glyph->mBitmapWidth, nPenY, 0,
+                            glyph->mBitmapMaxU, glyph->mBitmapMaxV,
+
+                            nPenX + (int)glyph->mBitmapWidth, nPenY - (int)glyph->mBitmapHeight, 0,
+                            glyph->mBitmapMaxU, glyph->mBitmapMinV,
+
+                            nPenX, nPenY - (int)glyph->mBitmapHeight, 0,
+                            glyph->mBitmapMinU, glyph->mBitmapMinV);
+}
+
+void Font::renderUTF(const char *text, uint32_t len, uint32_t start, int numGlyphs, int x, int y)
+{
+    if(!mInitialized || numGlyphs == 0 || text == NULL || len == 0) {
+        return;
+    }
+
+    int penX = x, penY = y;
+    int glyphsLeft = 1;
+    if(numGlyphs > 0) {
+        glyphsLeft = numGlyphs;
+    }
+
+    size_t index = start;
+    size_t nextIndex = 0;
+
+    while (glyphsLeft > 0) {
+
+        int32_t utfChar = utf32_at(text, len, index, &nextIndex);
+
+        // Reached the end of the string or encountered
+        if(utfChar < 0) {
+            break;
+        }
+
+        // Move to the next character in the array
+        index = nextIndex;
+
+        CachedGlyphInfo *cachedGlyph = mCachedGlyphs.valueFor((uint32_t)utfChar);
+
+        if(cachedGlyph == NULL) {
+            cachedGlyph = cacheGlyph((uint32_t)utfChar);
+        }
+        // Is the glyph still in texture cache?
+        if(!cachedGlyph->mIsValid) {
+            updateGlyphCache(cachedGlyph);
+        }
+
+        // If it's still not valid, we couldn't cache it, so we shouldn't draw garbage
+        if(cachedGlyph->mIsValid) {
+            drawCachedGlyph(cachedGlyph, penX, penY);
+        }
+
+        penX += (cachedGlyph->mAdvance.x >> 6);
+
+        // If we were given a specific number of glyphs, decrement
+        if(numGlyphs > 0) {
+            glyphsLeft --;
+        }
+    }
+}
+
+void Font::updateGlyphCache(CachedGlyphInfo *glyph)
+{
+    FT_Error error = FT_Load_Glyph( mFace, glyph->mGlyphIndex, FT_LOAD_RENDER );
+    if(error) {
+        LOGE("Couldn't load glyph.");
+        return;
+    }
+
+    glyph->mAdvance = mFace->glyph->advance;
+    glyph->mBitmapLeft = mFace->glyph->bitmap_left;
+    glyph->mBitmapTop = mFace->glyph->bitmap_top;
+
+    FT_Bitmap *bitmap = &mFace->glyph->bitmap;
+
+    // Now copy the bitmap into the cache texture
+    uint32_t startX = 0;
+    uint32_t startY = 0;
+
+    // Let the font state figure out where to put the bitmap
+    FontState *state = &mRSC->mStateFont;
+    glyph->mIsValid = state->cacheBitmap(bitmap, &startX, &startY);
+
+    if(!glyph->mIsValid) {
+        return;
+    }
+
+    uint32_t endX = startX + bitmap->width;
+    uint32_t endY = startY + bitmap->rows;
+
+    glyph->mBitmapMinX = startX;
+    glyph->mBitmapMinY = startY;
+    glyph->mBitmapWidth = bitmap->width;
+    glyph->mBitmapHeight = bitmap->rows;
+
+    uint32_t cacheWidth = state->getCacheTextureType()->getDimX();
+    uint32_t cacheHeight = state->getCacheTextureType()->getDimY();
+
+    glyph->mBitmapMinU = (float)startX / (float)cacheWidth;
+    glyph->mBitmapMinV = (float)startY / (float)cacheHeight;
+    glyph->mBitmapMaxU = (float)endX / (float)cacheWidth;
+    glyph->mBitmapMaxV = (float)endY / (float)cacheHeight;
+}
+
+Font::CachedGlyphInfo *Font::cacheGlyph(uint32_t glyph)
+{
+    CachedGlyphInfo *newGlyph = new CachedGlyphInfo();
+    mCachedGlyphs.add(glyph, newGlyph);
+
+    newGlyph->mGlyphIndex = FT_Get_Char_Index(mFace, glyph);
+    newGlyph->mIsValid = false;
+
+    updateGlyphCache(newGlyph);
+
+    return newGlyph;
+}
+
+Font * Font::create(Context *rsc, const char *name, uint32_t fontSize, uint32_t dpi)
+{
+    Vector<Font*> &activeFonts = rsc->mStateFont.mActiveFonts;
+
+    for(uint32_t i = 0; i < activeFonts.size(); i ++) {
+        Font *ithFont = activeFonts[i];
+        if(ithFont->mFontName == name && ithFont->mFontSize == fontSize && ithFont->mDpi == dpi) {
+            return ithFont;
+        }
+    }
+
+    Font *newFont = new Font(rsc);
+    bool isInitialized = newFont->init(name, fontSize, dpi);
+    if(isInitialized) {
+        activeFonts.push(newFont);
+        return newFont;
+    }
+
+    delete newFont;
+    return NULL;
+
+}
+
+Font::~Font()
+{
+    if(mFace) {
+        FT_Done_Face(mFace);
+    }
+
+    for (uint32_t ct = 0; ct < mRSC->mStateFont.mActiveFonts.size(); ct++) {
+        if (mRSC->mStateFont.mActiveFonts[ct] == this) {
+            mRSC->mStateFont.mActiveFonts.removeAt(ct);
+            break;
+        }
+    }
+
+    for(uint32_t i = 0; i < mCachedGlyphs.size(); i ++) {
+        CachedGlyphInfo *glyph = mCachedGlyphs.valueAt(i);
+        delete glyph;
+    }
+}
+
+FontState::FontState()
+{
+    mInitialized = false;
+    mMaxNumberOfQuads = 1024;
+    mCurrentQuadIndex = 0;
+    mRSC = NULL;
+    mLibrary = NULL;
+    setFontColor(0.1f, 0.1f, 0.1f, 1.0f);
+}
+
+FontState::~FontState()
+{
+    for(uint32_t i = 0; i < mCacheLines.size(); i ++) {
+        delete mCacheLines[i];
+    }
+
+    rsAssert(!mActiveFonts.size());
+}
+
+FT_Library FontState::getLib()
+{
+    if(!mLibrary) {
+        FT_Error error = FT_Init_FreeType(&mLibrary);
+        if(error) {
+            LOGE("Unable to initialize freetype");
+            return NULL;
+        }
+    }
+
+    return mLibrary;
+}
+
+void FontState::init(Context *rsc)
+{
+    mRSC = rsc;
+}
+
+void FontState::flushAllAndInvalidate()
+{
+    if(mCurrentQuadIndex != 0) {
+        issueDrawCommand();
+        mCurrentQuadIndex = 0;
+    }
+    for(uint32_t i = 0; i < mActiveFonts.size(); i ++) {
+        mActiveFonts[i]->invalidateTextureCache();
+    }
+    for(uint32_t i = 0; i < mCacheLines.size(); i ++) {
+        mCacheLines[i]->mCurrentCol = 0;
+    }
+}
+
+bool FontState::cacheBitmap(FT_Bitmap *bitmap, uint32_t *retOriginX, uint32_t *retOriginY)
+{
+    // If the glyph is too tall, don't cache it
+    if((uint32_t)bitmap->rows > mCacheLines[mCacheLines.size()-1]->mMaxHeight) {
+        LOGE("Font size to large to fit in cache. width, height = %i, %i", (int)bitmap->width, (int)bitmap->rows);
+        return false;
+    }
+
+    // Now copy the bitmap into the cache texture
+    uint32_t startX = 0;
+    uint32_t startY = 0;
+
+    bool bitmapFit = false;
+    for(uint32_t i = 0; i < mCacheLines.size(); i ++) {
+        bitmapFit = mCacheLines[i]->fitBitmap(bitmap, &startX, &startY);
+        if(bitmapFit) {
+            break;
+        }
+    }
+
+    // If the new glyph didn't fit, flush the state so far and invalidate everything
+    if(!bitmapFit) {
+        flushAllAndInvalidate();
+
+        // Try to fit it again
+        for(uint32_t i = 0; i < mCacheLines.size(); i ++) {
+            bitmapFit = mCacheLines[i]->fitBitmap(bitmap, &startX, &startY);
+            if(bitmapFit) {
+                break;
+            }
+        }
+
+        // if we still don't fit, something is wrong and we shouldn't draw
+        if(!bitmapFit) {
+            LOGE("Bitmap doesn't fit in cache. width, height = %i, %i", (int)bitmap->width, (int)bitmap->rows);
+            return false;
+        }
+    }
+
+    *retOriginX = startX;
+    *retOriginY = startY;
+
+    uint32_t endX = startX + bitmap->width;
+    uint32_t endY = startY + bitmap->rows;
+
+    uint32_t cacheWidth = getCacheTextureType()->getDimX();
+
+    unsigned char *cacheBuffer = (unsigned char*)mTextTexture->getPtr();
+    unsigned char *bitmapBuffer = bitmap->buffer;
+
+    uint32_t cacheX = 0, bX = 0, cacheY = 0, bY = 0;
+    for(cacheX = startX, bX = 0; cacheX < endX; cacheX ++, bX ++) {
+        for(cacheY = startY, bY = 0; cacheY < endY; cacheY ++, bY ++) {
+            unsigned char tempCol = bitmapBuffer[bY * bitmap->width + bX];
+            cacheBuffer[cacheY*cacheWidth + cacheX] = tempCol;
+        }
+    }
+
+    // This will dirty the texture and the shader so next time
+    // we draw it will upload the data
+    mTextTexture->deferedUploadToTexture(mRSC, false, 0);
+    mFontShaderF->bindTexture(0, mTextTexture.get());
+
+    // Some debug code
+    /*for(uint32_t i = 0; i < mCacheLines.size(); i ++) {
+        LOGE("Cache Line: H: %u Empty Space: %f",
+             mCacheLines[i]->mMaxHeight,
+              (1.0f - (float)mCacheLines[i]->mCurrentCol/(float)mCacheLines[i]->mMaxWidth)*100.0f);
+
+    }*/
+
+    return true;
+}
+
+void FontState::initRenderState()
+{
+    uint32_t tmp[] = {
+        RS_TEX_ENV_MODE_REPLACE, 1,
+        RS_TEX_ENV_MODE_NONE, 0,
+        0, 0
+    };
+    ProgramFragment *pf = new ProgramFragment(mRSC, tmp, 6);
+    mFontShaderF.set(pf);
+    mFontShaderF->init(mRSC);
+
+    Sampler *sampler = new Sampler(mRSC, RS_SAMPLER_NEAREST, RS_SAMPLER_NEAREST,
+                                      RS_SAMPLER_CLAMP, RS_SAMPLER_CLAMP, RS_SAMPLER_CLAMP);
+    mFontSampler.set(sampler);
+    mFontShaderF->bindSampler(0, sampler);
+
+    ProgramStore *fontStore = new ProgramStore(mRSC);
+    mFontProgramStore.set(fontStore);
+    mFontProgramStore->setDepthFunc(RS_DEPTH_FUNC_ALWAYS);
+    mFontProgramStore->setBlendFunc(RS_BLEND_SRC_SRC_ALPHA, RS_BLEND_DST_ONE_MINUS_SRC_ALPHA);
+    mFontProgramStore->setDitherEnable(false);
+    mFontProgramStore->setDepthMask(false);
+}
+
+void FontState::initTextTexture()
+{
+    const Element *alphaElem = Element::create(mRSC, RS_TYPE_UNSIGNED_8, RS_KIND_PIXEL_A, true, 1);
+
+    // We will allocate a texture to initially hold 32 character bitmaps
+    Type *texType = new Type(mRSC);
+    texType->setElement(alphaElem);
+    texType->setDimX(1024);
+    texType->setDimY(256);
+    texType->compute();
+
+    Allocation *cacheAlloc = new Allocation(mRSC, texType);
+    mTextTexture.set(cacheAlloc);
+    mTextTexture->deferedUploadToTexture(mRSC, false, 0);
+
+    // Split up our cache texture into lines of certain widths
+    int nextLine = 0;
+    mCacheLines.push(new CacheTextureLine(16, texType->getDimX(), nextLine, 0));
+    nextLine += mCacheLines.top()->mMaxHeight;
+    mCacheLines.push(new CacheTextureLine(24, texType->getDimX(), nextLine, 0));
+    nextLine += mCacheLines.top()->mMaxHeight;
+    mCacheLines.push(new CacheTextureLine(32, texType->getDimX(), nextLine, 0));
+    nextLine += mCacheLines.top()->mMaxHeight;
+    mCacheLines.push(new CacheTextureLine(32, texType->getDimX(), nextLine, 0));
+    nextLine += mCacheLines.top()->mMaxHeight;
+    mCacheLines.push(new CacheTextureLine(40, texType->getDimX(), nextLine, 0));
+    nextLine += mCacheLines.top()->mMaxHeight;
+    mCacheLines.push(new CacheTextureLine(texType->getDimY() - nextLine, texType->getDimX(), nextLine, 0));
+}
+
+// Avoid having to reallocate memory and render quad by quad
+void FontState::initVertexArrayBuffers()
+{
+    // Now lets write index data
+    const Element *indexElem = Element::create(mRSC, RS_TYPE_UNSIGNED_16, RS_KIND_USER, false, 1);
+    Type *indexType = new Type(mRSC);
+    uint32_t numIndicies = mMaxNumberOfQuads * 6;
+    indexType->setDimX(numIndicies);
+    indexType->setElement(indexElem);
+    indexType->compute();
+
+    Allocation *indexAlloc = new Allocation(mRSC, indexType);
+    uint16_t *indexPtr = (uint16_t*)indexAlloc->getPtr();
+
+    // Four verts, two triangles , six indices per quad
+    for(uint32_t i = 0; i < mMaxNumberOfQuads; i ++) {
+        int i6 = i * 6;
+        int i4 = i * 4;
+
+        indexPtr[i6 + 0] = i4 + 0;
+        indexPtr[i6 + 1] = i4 + 1;
+        indexPtr[i6 + 2] = i4 + 2;
+
+        indexPtr[i6 + 3] = i4 + 0;
+        indexPtr[i6 + 4] = i4 + 2;
+        indexPtr[i6 + 5] = i4 + 3;
+    }
+
+    indexAlloc->deferedUploadToBufferObject(mRSC);
+    mIndexBuffer.set(indexAlloc);
+
+    const Element *posElem = Element::create(mRSC, RS_TYPE_FLOAT_32, RS_KIND_USER, false, 3);
+    const Element *texElem = Element::create(mRSC, RS_TYPE_FLOAT_32, RS_KIND_USER, false, 2);
+
+    const Element *elemArray[2];
+    elemArray[0] = posElem;
+    elemArray[1] = texElem;
+
+    String8 posName("position");
+    String8 texName("texture0");
+
+    const char *nameArray[2];
+    nameArray[0] = posName.string();
+    nameArray[1] = texName.string();
+    size_t lengths[2];
+    lengths[0] = posName.size();
+    lengths[1] = texName.size();
+
+    const Element *vertexDataElem = Element::create(mRSC, 2, elemArray, nameArray, lengths);
+
+    Type *vertexDataType = new Type(mRSC);
+    vertexDataType->setDimX(mMaxNumberOfQuads * 4);
+    vertexDataType->setElement(vertexDataElem);
+    vertexDataType->compute();
+
+    Allocation *vertexAlloc = new Allocation(mRSC, vertexDataType);
+    mTextMeshPtr = (float*)vertexAlloc->getPtr();
+
+    mVertexArray.set(vertexAlloc);
+}
+
+// We don't want to allocate anything unless we actually draw text
+void FontState::checkInit()
+{
+    if(mInitialized) {
+        return;
+    }
+
+    initTextTexture();
+    initRenderState();
+
+    initVertexArrayBuffers();
+
+    mInitialized = true;
+}
+
+void FontState::issueDrawCommand() {
+
+    ObjectBaseRef<const ProgramVertex> tmpV(mRSC->getVertex());
+    mRSC->setVertex(mRSC->getDefaultProgramVertex());
+
+    ObjectBaseRef<const ProgramRaster> tmpR(mRSC->getRaster());
+    mRSC->setRaster(mRSC->getDefaultProgramRaster());
+
+    ObjectBaseRef<const ProgramFragment> tmpF(mRSC->getFragment());
+    mRSC->setFragment(mFontShaderF.get());
+
+    ObjectBaseRef<const ProgramStore> tmpPS(mRSC->getFragmentStore());
+    mRSC->setFragmentStore(mFontProgramStore.get());
+
+    if(mFontColorDirty) {
+        mFontShaderF->setConstantColor(mFontColor[0], mFontColor[1], mFontColor[2], mFontColor[3]);
+        mFontColorDirty = false;
+    }
+
+    if (!mRSC->setupCheck()) {
+        mRSC->setVertex((ProgramVertex *)tmpV.get());
+        mRSC->setRaster((ProgramRaster *)tmpR.get());
+        mRSC->setFragment((ProgramFragment *)tmpF.get());
+        mRSC->setFragmentStore((ProgramStore *)tmpPS.get());
+        return;
+    }
+
+    float *vtx = (float*)mVertexArray->getPtr();
+    float *tex = vtx + 3;
+
+    VertexArray va;
+    va.add(GL_FLOAT, 3, 20, false, (uint32_t)vtx, "position");
+    va.add(GL_FLOAT, 2, 20, false, (uint32_t)tex, "texture0");
+    va.setupGL2(mRSC, &mRSC->mStateVertexArray, &mRSC->mShaderCache);
+
+    mIndexBuffer->uploadCheck(mRSC);
+    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mIndexBuffer->getBufferObjectID());
+    glDrawElements(GL_TRIANGLES, mCurrentQuadIndex * 6, GL_UNSIGNED_SHORT, (uint16_t *)(0));
+
+    // Reset the state
+    mRSC->setVertex((ProgramVertex *)tmpV.get());
+    mRSC->setRaster((ProgramRaster *)tmpR.get());
+    mRSC->setFragment((ProgramFragment *)tmpF.get());
+    mRSC->setFragmentStore((ProgramStore *)tmpPS.get());
+}
+
+void FontState::appendMeshQuad(float x1, float y1, float z1,
+                                  float u1, float v1,
+                                  float x2, float y2, float z2,
+                                  float u2, float v2,
+                                  float x3, float y3, float z3,
+                                  float u3, float v3,
+                                  float x4, float y4, float z4,
+                                  float u4, float v4)
+{
+    const uint32_t vertsPerQuad = 4;
+    const uint32_t floatsPerVert = 5;
+    float *currentPos = mTextMeshPtr + mCurrentQuadIndex * vertsPerQuad * floatsPerVert;
+
+    // Cull things that are off the screen
+    float width = (float)mRSC->getWidth();
+    float height = (float)mRSC->getHeight();
+
+    if(x1 > width || y1 < 0.0f || x2 < 0 || y4 > height) {
+        return;
+    }
+
+    /*LOGE("V0 x: %f y: %f z: %f", x1, y1, z1);
+    LOGE("V1 x: %f y: %f z: %f", x2, y2, z2);
+    LOGE("V2 x: %f y: %f z: %f", x3, y3, z3);
+    LOGE("V3 x: %f y: %f z: %f", x4, y4, z4);*/
+
+    (*currentPos++) = x1;
+    (*currentPos++) = y1;
+    (*currentPos++) = z1;
+    (*currentPos++) = u1;
+    (*currentPos++) = v1;
+
+    (*currentPos++) = x2;
+    (*currentPos++) = y2;
+    (*currentPos++) = z2;
+    (*currentPos++) = u2;
+    (*currentPos++) = v2;
+
+    (*currentPos++) = x3;
+    (*currentPos++) = y3;
+    (*currentPos++) = z3;
+    (*currentPos++) = u3;
+    (*currentPos++) = v3;
+
+    (*currentPos++) = x4;
+    (*currentPos++) = y4;
+    (*currentPos++) = z4;
+    (*currentPos++) = u4;
+    (*currentPos++) = v4;
+
+    mCurrentQuadIndex ++;
+
+    if(mCurrentQuadIndex == mMaxNumberOfQuads) {
+        issueDrawCommand();
+        mCurrentQuadIndex = 0;
+    }
+}
+
+void FontState::renderText(const char *text, uint32_t len, uint32_t startIndex, int numGlyphs, int x, int y)
+{
+    checkInit();
+
+    //String8 text8(text);
+
+    // Render code here
+    Font *currentFont = mRSC->getFont();
+    if(!currentFont) {
+        if(!mDefault.get()) {
+            mDefault.set(Font::create(mRSC, "DroidSans.ttf", 16, 96));
+        }
+        currentFont = mDefault.get();
+    }
+    if(!currentFont) {
+        LOGE("Unable to initialize any fonts");
+        return;
+    }
+
+    currentFont->renderUTF(text, len, startIndex, numGlyphs, x, y);
+
+    if(mCurrentQuadIndex != 0) {
+        issueDrawCommand();
+        mCurrentQuadIndex = 0;
+    }
+}
+
+void FontState::renderText(const char *text, int x, int y)
+{
+    size_t textLen = strlen(text);
+    renderText(text, textLen, 0, -1, x, y);
+}
+
+void FontState::renderText(Allocation *alloc, int x, int y)
+{
+    if(!alloc) {
+        return;
+    }
+
+    const char *text = (const char *)alloc->getPtr();
+    size_t allocSize = alloc->getType()->getSizeBytes();
+    renderText(text, allocSize, 0, -1, x, y);
+}
+
+void FontState::renderText(Allocation *alloc, uint32_t start, int len, int x, int y)
+{
+    if(!alloc) {
+        return;
+    }
+
+    const char *text = (const char *)alloc->getPtr();
+    size_t allocSize = alloc->getType()->getSizeBytes();
+    renderText(text, allocSize, start, len, x, y);
+}
+
+void FontState::setFontColor(float r, float g, float b, float a) {
+    mFontColor[0] = r;
+    mFontColor[1] = g;
+    mFontColor[2] = b;
+    mFontColor[3] = a;
+    mFontColorDirty = true;
+}
+
+void FontState::getFontColor(float *r, float *g, float *b, float *a) const {
+    *r = mFontColor[0];
+    *g = mFontColor[1];
+    *b = mFontColor[2];
+    *a = mFontColor[3];
+}
+
+void FontState::deinit(Context *rsc)
+{
+    mInitialized = false;
+
+    mIndexBuffer.clear();
+    mVertexArray.clear();
+
+    mFontShaderF.clear();
+    mFontSampler.clear();
+    mFontProgramStore.clear();
+
+    mTextTexture.clear();
+    for(uint32_t i = 0; i < mCacheLines.size(); i ++) {
+        delete mCacheLines[i];
+    }
+    mCacheLines.clear();
+
+    mDefault.clear();
+
+    Vector<Font*> fontsToDereference = mActiveFonts;
+    for(uint32_t i = 0; i < fontsToDereference.size(); i ++) {
+        fontsToDereference[i]->zeroUserRef();
+    }
+
+    if(mLibrary) {
+        FT_Done_FreeType( mLibrary );
+        mLibrary = NULL;
+    }
+}
+
+namespace android {
+namespace renderscript {
+
+RsFont rsi_FontCreateFromFile(Context *rsc, char const *name, uint32_t fontSize, uint32_t dpi)
+{
+    Font *newFont = Font::create(rsc, name, fontSize, dpi);
+    if(newFont) {
+        newFont->incUserRef();
+    }
+    return newFont;
+}
+
+} // renderscript
+} // android
diff --git a/libs/rs/rsFont.h b/libs/rs/rsFont.h
new file mode 100644
index 0000000..ab229be
--- /dev/null
+++ b/libs/rs/rsFont.h
@@ -0,0 +1,213 @@
+/*
+ * 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_FONT_H
+#define ANDROID_RS_FONT_H
+
+#include "RenderScript.h"
+#include "rsStream.h"
+#include <utils/String8.h>
+#include <utils/Vector.h>
+#include <utils/KeyedVector.h>
+
+#include <ft2build.h>
+#include FT_FREETYPE_H
+
+// ---------------------------------------------------------------------------
+namespace android {
+
+namespace renderscript {
+
+class FontState;
+
+class Font : public ObjectBase
+{
+public:
+    ~Font();
+
+    // Pointer to the utf data, length of data, where to start, number of glyphs ot read
+    // (each glyph may be longer than a char because we are dealing with utf data)
+    // Last two variables are the initial pen position
+    void renderUTF(const char *text, uint32_t len, uint32_t start, int numGlyphs, int x, int y);
+
+    // Currently files do not get serialized,
+    // but we need to inherit from ObjectBase for ref tracking
+    virtual void serialize(OStream *stream) const {
+    }
+    virtual RsA3DClassID getClassId() const {
+        return RS_A3D_CLASS_ID_UNKNOWN;
+    }
+
+    static Font * create(Context *rsc, const char *name, uint32_t fontSize, uint32_t dpi);
+
+protected:
+
+    friend class FontState;
+
+    void invalidateTextureCache();
+    struct CachedGlyphInfo
+    {
+        // Has the cache been invalidated?
+        bool mIsValid;
+        // Location of the cached glyph in the bitmap
+        // in case we need to resize the texture
+        uint32_t mBitmapMinX;
+        uint32_t mBitmapMinY;
+        uint32_t mBitmapWidth;
+        uint32_t mBitmapHeight;
+        // Also cache texture coords for the quad
+        float mBitmapMinU;
+        float mBitmapMinV;
+        float mBitmapMaxU;
+        float mBitmapMaxV;
+        // Minimize how much we call freetype
+        FT_UInt mGlyphIndex;
+        FT_Vector mAdvance;
+        // Values below contain a glyph's origin in the bitmap
+        FT_Int mBitmapLeft;
+        FT_Int mBitmapTop;
+    };
+
+    String8 mFontName;
+    uint32_t mFontSize;
+    uint32_t mDpi;
+
+    Font(Context *rsc);
+    bool init(const char *name, uint32_t fontSize, uint32_t dpi);
+
+    FT_Face mFace;
+    bool mInitialized;
+    bool mHasKerning;
+
+    DefaultKeyedVector<uint32_t, CachedGlyphInfo* > mCachedGlyphs;
+
+    CachedGlyphInfo *cacheGlyph(uint32_t glyph);
+    void updateGlyphCache(CachedGlyphInfo *glyph);
+    void drawCachedGlyph(CachedGlyphInfo *glyph, int x, int y);
+};
+
+class FontState
+{
+public:
+    FontState();
+    ~FontState();
+
+    void init(Context *rsc);
+    void deinit(Context *rsc);
+
+    ObjectBaseRef<Font> mDefault;
+    ObjectBaseRef<Font> mLast;
+
+    void renderText(const char *text, uint32_t len, uint32_t startIndex, int numGlyphs, int x, int y);
+    void renderText(const char *text, int x, int y);
+    void renderText(Allocation *alloc, int x, int y);
+    void renderText(Allocation *alloc, uint32_t start, int len, int x, int y);
+
+    void setFontColor(float r, float g, float b, float a);
+    void getFontColor(float *r, float *g, float *b, float *a) const;
+
+protected:
+
+    friend class Font;
+
+    struct CacheTextureLine
+    {
+        uint32_t mMaxHeight;
+        uint32_t mMaxWidth;
+        uint32_t mCurrentRow;
+        uint32_t mCurrentCol;
+
+        CacheTextureLine(uint32_t maxHeight, uint32_t maxWidth, uint32_t currentRow, uint32_t currentCol) :
+            mMaxHeight(maxHeight), mMaxWidth(maxWidth), mCurrentRow(currentRow), mCurrentCol(currentCol) {
+        }
+
+        bool fitBitmap(FT_Bitmap *bitmap, uint32_t *retOriginX, uint32_t *retOriginY) {
+            if((uint32_t)bitmap->rows > mMaxHeight) {
+                return false;
+            }
+
+            if(mCurrentCol + (uint32_t)bitmap->width < mMaxWidth) {
+               *retOriginX = mCurrentCol;
+               *retOriginY = mCurrentRow;
+               mCurrentCol += bitmap->width;
+               return true;
+            }
+
+            return false;
+        }
+    };
+
+    Vector<CacheTextureLine*> mCacheLines;
+
+    Context *mRSC;
+
+    float mFontColor[4];
+    bool mFontColorDirty;
+
+    // Free type library, we only need one copy
+    FT_Library mLibrary;
+    FT_Library getLib();
+    Vector<Font*> mActiveFonts;
+
+    // Render state for the font
+    ObjectBaseRef<ProgramFragment> mFontShaderF;
+    ObjectBaseRef<Sampler> mFontSampler;
+    ObjectBaseRef<ProgramStore> mFontProgramStore;
+    void initRenderState();
+
+    // Texture to cache glyph bitmaps
+    ObjectBaseRef<Allocation> mTextTexture;
+    void initTextTexture();
+
+    bool cacheBitmap(FT_Bitmap *bitmap, uint32_t *retOriginX, uint32_t *retOriginY);
+    const Type* getCacheTextureType() {
+        return mTextTexture->getType();
+    }
+
+    void flushAllAndInvalidate();
+
+    // Pointer to vertex data to speed up frame to frame work
+    float *mTextMeshPtr;
+    uint32_t mCurrentQuadIndex;
+    uint32_t mMaxNumberOfQuads;
+
+    void initVertexArrayBuffers();
+    ObjectBaseRef<Allocation> mIndexBuffer;
+    ObjectBaseRef<Allocation> mVertexArray;
+
+
+    bool mInitialized;
+
+    void checkInit();
+
+    void issueDrawCommand();
+
+    void appendMeshQuad(float x1, float y1, float z1,
+                          float u1, float v1,
+                          float x2, float y2, float z2,
+                          float u2, float v2,
+                          float x3, float y3, float z3,
+                          float u3, float v3,
+                          float x4, float y4, float z4,
+                          float u4, float v4);
+
+};
+
+
+}
+}
+
+#endif
diff --git a/libs/rs/rsHandcode.h b/libs/rs/rsHandcode.h
index 800eddd..353b73a 100644
--- a/libs/rs/rsHandcode.h
+++ b/libs/rs/rsHandcode.h
@@ -1,6 +1,57 @@
 
 #define DATA_SYNC_SIZE 1024
 
+static inline void rsHCAPI_ContextFinish (RsContext rsc)
+{
+    ThreadIO *io = &((Context *)rsc)->mIO;
+    uint32_t size = sizeof(RS_CMD_ContextFinish);
+    RS_CMD_ContextFinish *cmd = static_cast<RS_CMD_ContextFinish *>(io->mToCore.reserve(size));
+    io->mToCore.commitSync(RS_CMD_ID_ContextFinish, size);
+}
+
+static inline void rsHCAPI_ScriptInvokeV (RsContext rsc, RsScript va, uint32_t slot, const void * data, uint32_t sizeBytes)
+{
+    ThreadIO *io = &((Context *)rsc)->mIO;
+    uint32_t size = sizeof(RS_CMD_ScriptInvokeV);
+    if (sizeBytes < DATA_SYNC_SIZE) {
+        size += (sizeBytes + 3) & ~3;
+    }
+    RS_CMD_ScriptInvokeV *cmd = static_cast<RS_CMD_ScriptInvokeV *>(io->mToCore.reserve(size));
+    cmd->s = va;
+    cmd->slot = slot;
+    cmd->dataLen = sizeBytes;
+    cmd->data = data;
+    if (sizeBytes < DATA_SYNC_SIZE) {
+        cmd->data = (void *)(cmd+1);
+        memcpy(cmd+1, data, sizeBytes);
+        io->mToCore.commit(RS_CMD_ID_ScriptInvokeV, size);
+    } else {
+        io->mToCore.commitSync(RS_CMD_ID_ScriptInvokeV, size);
+    }
+}
+
+
+static inline void rsHCAPI_ScriptSetVarV (RsContext rsc, RsScript va, uint32_t slot, const void * data, uint32_t sizeBytes)
+{
+    ThreadIO *io = &((Context *)rsc)->mIO;
+    uint32_t size = sizeof(RS_CMD_ScriptSetVarV);
+    if (sizeBytes < DATA_SYNC_SIZE) {
+        size += (sizeBytes + 3) & ~3;
+    }
+    RS_CMD_ScriptSetVarV *cmd = static_cast<RS_CMD_ScriptSetVarV *>(io->mToCore.reserve(size));
+    cmd->s = va;
+    cmd->slot = slot;
+    cmd->dataLen = sizeBytes;
+    cmd->data = data;
+    if (sizeBytes < DATA_SYNC_SIZE) {
+        cmd->data = (void *)(cmd+1);
+        memcpy(cmd+1, data, sizeBytes);
+        io->mToCore.commit(RS_CMD_ID_ScriptSetVarV, size);
+    } else {
+        io->mToCore.commitSync(RS_CMD_ID_ScriptSetVarV, size);
+    }
+}
+
 static inline void rsHCAPI_AllocationData (RsContext rsc, RsAllocation va, const void * data, uint32_t sizeBytes)
 {
     ThreadIO *io = &((Context *)rsc)->mIO;
diff --git a/libs/rs/rsLight.cpp b/libs/rs/rsLight.cpp
index 6f2cf3e..eab9a07 100644
--- a/libs/rs/rsLight.cpp
+++ b/libs/rs/rsLight.cpp
@@ -14,9 +14,13 @@
  * limitations under the License.
  */
 
+#ifndef ANDROID_RS_BUILD_FOR_HOST
 #include "rsContext.h"
-
 #include <GLES/gl.h>
+#else
+#include "rsContextHostStub.h"
+#include <OpenGL/gl.h>
+#endif //ANDROID_RS_BUILD_FOR_HOST
 
 using namespace android;
 using namespace android::renderscript;
@@ -65,6 +69,16 @@
     glLightfv(GL_LIGHT0 + num, GL_POSITION, mPosition);
 }
 
+void Light::serialize(OStream *stream) const
+{
+    
+}
+
+Light *Light::createFromStream(Context *rsc, IStream *stream)
+{
+    return NULL;
+}
+
 ////////////////////////////////////////////
 
 LightState::LightState()
diff --git a/libs/rs/rsLight.h b/libs/rs/rsLight.h
index d8796e6..bd58979 100644
--- a/libs/rs/rsLight.h
+++ b/libs/rs/rsLight.h
@@ -37,6 +37,9 @@
     void setColor(float r, float g, float b);
 
     void setupGL(uint32_t num) const;
+    virtual void serialize(OStream *stream) const;
+    virtual RsA3DClassID getClassId() const { return RS_A3D_CLASS_ID_LIGHT; }
+    static Light *createFromStream(Context *rsc, IStream *stream);
 
 protected:
     float mColor[4];
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/rsMesh.cpp b/libs/rs/rsMesh.cpp
index d595b4e..9026578 100644
--- a/libs/rs/rsMesh.cpp
+++ b/libs/rs/rsMesh.cpp
@@ -14,28 +14,215 @@
  * limitations under the License.
  */
 
+#ifndef ANDROID_RS_BUILD_FOR_HOST
 #include "rsContext.h"
 
+#include <GLES/gl.h>
+#include <GLES2/gl2.h>
+#include <GLES/glext.h>
+#else
+#include "rsContextHostStub.h"
+
+#include <OpenGL/gl.h>
+#include <OpenGl/glext.h>
+#endif
+
+
 using namespace android;
 using namespace android::renderscript;
 
-#include <GLES/gl.h>
-#include <GLES/glext.h>
-
 Mesh::Mesh(Context *rsc) : ObjectBase(rsc)
 {
     mAllocFile = __FILE__;
     mAllocLine = __LINE__;
-    mVerticies = NULL;
-    mVerticiesCount = 0;
     mPrimitives = NULL;
     mPrimitivesCount = 0;
+    mVertexBuffers = NULL;
+    mVertexBufferCount = 0;
 }
 
 Mesh::~Mesh()
 {
+    if(mVertexBuffers) {
+        delete[] mVertexBuffers;
+    }
+
+    if(mPrimitives) {
+        for(uint32_t i = 0; i < mPrimitivesCount; i ++) {
+            delete mPrimitives[i];
+        }
+        delete[] mPrimitives;
+    }
 }
 
+void Mesh::render(Context *rsc) const
+{
+    for(uint32_t ct = 0; ct < mPrimitivesCount; ct ++) {
+        renderPrimitive(rsc, ct);
+    }
+}
+
+void Mesh::renderPrimitive(Context *rsc, uint32_t primIndex) const {
+    if (primIndex >= mPrimitivesCount) {
+        LOGE("Invalid primitive index");
+        return;
+    }
+
+    Primitive_t *prim = mPrimitives[primIndex];
+
+    if (prim->mIndexBuffer.get()) {
+        renderPrimitiveRange(rsc, primIndex, 0, prim->mIndexBuffer->getType()->getDimX());
+        return;
+    }
+
+    renderPrimitiveRange(rsc, primIndex, 0, mVertexBuffers[0]->getType()->getDimX());
+}
+
+void Mesh::renderPrimitiveRange(Context *rsc, uint32_t primIndex, uint32_t start, uint32_t len) const
+{
+    if (len < 1 || primIndex >= mPrimitivesCount) {
+        return;
+    }
+
+    rsc->checkError("Mesh::renderPrimitiveRange 1");
+    VertexArray va;
+    for (uint32_t ct=0; ct < mVertexBufferCount; ct++) {
+        mVertexBuffers[ct]->uploadCheck(rsc);
+        if (mVertexBuffers[ct]->getIsBufferObject()) {
+            va.setActiveBuffer(mVertexBuffers[ct]->getBufferObjectID());
+        } else {
+            va.setActiveBuffer(mVertexBuffers[ct]->getPtr());
+        }
+        mVertexBuffers[ct]->getType()->enableGLVertexBuffer(&va);
+    }
+    va.setupGL2(rsc, &rsc->mStateVertexArray, &rsc->mShaderCache);
+
+    rsc->checkError("Mesh::renderPrimitiveRange 2");
+    Primitive_t *prim = mPrimitives[primIndex];
+    if (prim->mIndexBuffer.get()) {
+        prim->mIndexBuffer->uploadCheck(rsc);
+        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, prim->mIndexBuffer->getBufferObjectID());
+        glDrawElements(prim->mGLPrimitive, len, GL_UNSIGNED_SHORT, (uint16_t *)(start * 2));
+    } else {
+        glDrawArrays(prim->mGLPrimitive, start, len);
+    }
+
+    rsc->checkError("Mesh::renderPrimitiveRange");
+}
+
+
+void Mesh::uploadAll(Context *rsc)
+{
+    for (uint32_t ct = 0; ct < mVertexBufferCount; ct ++) {
+        if (mVertexBuffers[ct].get()) {
+            mVertexBuffers[ct]->deferedUploadToBufferObject(rsc);
+        }
+    }
+
+    for (uint32_t ct = 0; ct < mPrimitivesCount; ct ++) {
+        if (mPrimitives[ct]->mIndexBuffer.get()) {
+            mPrimitives[ct]->mIndexBuffer->deferedUploadToBufferObject(rsc);
+        }
+    }
+
+    rsc->checkError("Mesh::uploadAll");
+}
+
+void Mesh::updateGLPrimitives()
+{
+    for(uint32_t i = 0; i < mPrimitivesCount; i ++) {
+        switch(mPrimitives[i]->mPrimitive) {
+            case RS_PRIMITIVE_POINT:          mPrimitives[i]->mGLPrimitive = GL_POINTS; break;
+            case RS_PRIMITIVE_LINE:           mPrimitives[i]->mGLPrimitive = GL_LINES; break;
+            case RS_PRIMITIVE_LINE_STRIP:     mPrimitives[i]->mGLPrimitive = GL_LINE_STRIP; break;
+            case RS_PRIMITIVE_TRIANGLE:       mPrimitives[i]->mGLPrimitive = GL_TRIANGLES; break;
+            case RS_PRIMITIVE_TRIANGLE_STRIP: mPrimitives[i]->mGLPrimitive = GL_TRIANGLE_STRIP; break;
+            case RS_PRIMITIVE_TRIANGLE_FAN:   mPrimitives[i]->mGLPrimitive = GL_TRIANGLE_FAN; break;
+        }
+    }
+}
+
+void Mesh::serialize(OStream *stream) const
+{
+    // Need to identify ourselves
+    stream->addU32((uint32_t)getClassId());
+
+    String8 name(getName());
+    stream->addString(&name);
+
+    // Store number of vertex streams
+    stream->addU32(mVertexBufferCount);
+    for(uint32_t vCount = 0; vCount < mVertexBufferCount; vCount ++) {
+        mVertexBuffers[vCount]->serialize(stream);
+    }
+
+    stream->addU32(mPrimitivesCount);
+    // Store the primitives
+    for (uint32_t pCount = 0; pCount < mPrimitivesCount; pCount ++) {
+        Primitive_t * prim = mPrimitives[pCount];
+
+        stream->addU8((uint8_t)prim->mPrimitive);
+
+        if(prim->mIndexBuffer.get()) {
+            stream->addU32(1);
+            prim->mIndexBuffer->serialize(stream);
+        }
+        else {
+            stream->addU32(0);
+        }
+    }
+}
+
+Mesh *Mesh::createFromStream(Context *rsc, IStream *stream)
+{
+    // First make sure we are reading the correct object
+    RsA3DClassID classID = (RsA3DClassID)stream->loadU32();
+    if(classID != RS_A3D_CLASS_ID_MESH) {
+        LOGE("mesh loading skipped due to invalid class id");
+        return NULL;
+    }
+
+    Mesh * mesh = new Mesh(rsc);
+
+    String8 name;
+    stream->loadString(&name);
+    mesh->setName(name.string(), name.size());
+
+    mesh->mVertexBufferCount = stream->loadU32();
+    if(mesh->mVertexBufferCount) {
+        mesh->mVertexBuffers = new ObjectBaseRef<Allocation>[mesh->mVertexBufferCount];
+
+        for(uint32_t vCount = 0; vCount < mesh->mVertexBufferCount; vCount ++) {
+            Allocation *vertexAlloc = Allocation::createFromStream(rsc, stream);
+            mesh->mVertexBuffers[vCount].set(vertexAlloc);
+        }
+    }
+
+    mesh->mPrimitivesCount = stream->loadU32();
+    if(mesh->mPrimitivesCount) {
+        mesh->mPrimitives = new Primitive_t *[mesh->mPrimitivesCount];
+
+        // load all primitives
+        for (uint32_t pCount = 0; pCount < mesh->mPrimitivesCount; pCount ++) {
+            Primitive_t * prim = new Primitive_t;
+            mesh->mPrimitives[pCount] = prim;
+
+            prim->mPrimitive = (RsPrimitive)stream->loadU8();
+
+            // Check to see if the index buffer was stored
+            uint32_t isIndexPresent = stream->loadU32();
+            if(isIndexPresent) {
+                Allocation *indexAlloc = Allocation::createFromStream(rsc, stream);
+                prim->mIndexBuffer.set(indexAlloc);
+            }
+        }
+    }
+
+    mesh->updateGLPrimitives();
+    mesh->uploadAll(rsc);
+
+    return mesh;
+}
 
 
 MeshContext::MeshContext()
@@ -46,3 +233,83 @@
 {
 }
 
+namespace android {
+namespace renderscript {
+
+RsMesh rsi_MeshCreate(Context *rsc, uint32_t vtxCount, uint32_t idxCount)
+{
+    Mesh *sm = new Mesh(rsc);
+    sm->incUserRef();
+
+    sm->mPrimitivesCount = idxCount;
+    sm->mPrimitives = new Mesh::Primitive_t *[sm->mPrimitivesCount];
+    for(uint32_t ct = 0; ct < idxCount; ct ++) {
+        sm->mPrimitives[ct] = new Mesh::Primitive_t;
+    }
+
+    sm->mVertexBufferCount = vtxCount;
+    sm->mVertexBuffers = new ObjectBaseRef<Allocation>[vtxCount];
+
+    return sm;
+}
+
+void rsi_MeshBindVertex(Context *rsc, RsMesh mv, RsAllocation va, uint32_t slot)
+{
+    Mesh *sm = static_cast<Mesh *>(mv);
+    rsAssert(slot < sm->mVertexBufferCount);
+
+    sm->mVertexBuffers[slot].set((Allocation *)va);
+}
+
+void rsi_MeshBindIndex(Context *rsc, RsMesh mv, RsAllocation va, uint32_t primType, uint32_t slot)
+{
+    Mesh *sm = static_cast<Mesh *>(mv);
+    rsAssert(slot < sm->mPrimitivesCount);
+
+    sm->mPrimitives[slot]->mIndexBuffer.set((Allocation *)va);
+    sm->mPrimitives[slot]->mPrimitive = (RsPrimitive)primType;
+    sm->updateGLPrimitives();
+}
+
+void rsi_MeshGetVertexBufferCount(Context *rsc, RsMesh mv, int32_t *numVtx)
+{
+    Mesh *sm = static_cast<Mesh *>(mv);
+    *numVtx = sm->mVertexBufferCount;
+}
+
+void rsi_MeshGetIndexCount(Context *rsc, RsMesh mv, int32_t *numIdx)
+{
+    Mesh *sm = static_cast<Mesh *>(mv);
+    *numIdx = sm->mPrimitivesCount;
+}
+
+void rsi_MeshGetVertices(Context *rsc, RsMesh mv, RsAllocation *vtxData, uint32_t vtxDataCount)
+{
+    Mesh *sm = static_cast<Mesh *>(mv);
+    rsAssert(vtxDataCount == sm->mVertexBufferCount);
+
+    for(uint32_t ct = 0; ct < vtxDataCount; ct ++) {
+        vtxData[ct] = sm->mVertexBuffers[ct].get();
+        sm->mVertexBuffers[ct]->incUserRef();
+    }
+}
+
+void rsi_MeshGetIndices(Context *rsc, RsMesh mv, RsAllocation *va, uint32_t *primType, uint32_t idxDataCount)
+{
+    Mesh *sm = static_cast<Mesh *>(mv);
+    rsAssert(idxDataCount == sm->mPrimitivesCount);
+
+    for(uint32_t ct = 0; ct < idxDataCount; ct ++) {
+        va[ct] = sm->mPrimitives[ct]->mIndexBuffer.get();
+        primType[ct] = sm->mPrimitives[ct]->mPrimitive;
+        if(sm->mPrimitives[ct]->mIndexBuffer.get()) {
+            sm->mPrimitives[ct]->mIndexBuffer->incUserRef();
+        }
+    }
+
+}
+
+
+
+
+}}
diff --git a/libs/rs/rsMesh.h b/libs/rs/rsMesh.h
index 5201abd..765a971 100644
--- a/libs/rs/rsMesh.h
+++ b/libs/rs/rsMesh.h
@@ -32,45 +32,35 @@
     Mesh(Context *);
     ~Mesh();
 
-    struct Verticies_t
-    {
-        Allocation ** mAllocations;
-        uint32_t mAllocationCount;
+    // Contains vertex data
+    // Position, normal, texcoord, etc could either be strided in one allocation
+    // of provided separetely in multiple ones
+    ObjectBaseRef<Allocation> *mVertexBuffers;
+    uint32_t mVertexBufferCount;
 
-        size_t mVertexDataSize;
-
-        size_t mOffsetCoord;
-        size_t mOffsetTex;
-        size_t mOffsetNorm;
-
-        size_t mSizeCoord;
-        size_t mSizeTex;
-        size_t mSizeNorm;
-
-        uint32_t mBufferObject;
-    };
-
+    // Either mIndexBuffer, mPrimitiveBuffer or both could have a NULL reference
+    // If both are null, mPrimitive only would be used to render the mesh
     struct Primitive_t
     {
-        RsPrimitive mType;
-        Verticies_t *mVerticies;
+        ObjectBaseRef<Allocation> mIndexBuffer;
 
-        uint32_t mIndexCount;
-        uint16_t *mIndicies;
-
-        uint32_t mRestartCounts;
-        uint16_t *mRestarts;
+        RsPrimitive mPrimitive;
+        uint32_t mGLPrimitive;
     };
 
-    Verticies_t * mVerticies;
-    uint32_t mVerticiesCount;
-
     Primitive_t ** mPrimitives;
     uint32_t mPrimitivesCount;
 
+    void render(Context *) const;
+    void renderPrimitive(Context *, uint32_t primIndex) const;
+    void renderPrimitiveRange(Context *, uint32_t primIndex, uint32_t start, uint32_t len) const;
+    void uploadAll(Context *);
+    void updateGLPrimitives();
 
+    virtual void serialize(OStream *stream) const;
+    virtual RsA3DClassID getClassId() const { return RS_A3D_CLASS_ID_MESH; }
+    static Mesh *createFromStream(Context *rsc, IStream *stream);
 
-    void analyzeElement();
 protected:
 };
 
@@ -88,3 +78,4 @@
 #endif //ANDROID_RS_TRIANGLE_MESH_H
 
 
+
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/libs/rs/rsMutex.h b/libs/rs/rsMutex.h
new file mode 100644
index 0000000..47725d7
--- /dev/null
+++ b/libs/rs/rsMutex.h
@@ -0,0 +1,43 @@
+/*
+ * 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_MUTEX_H
+#define ANDROID_RS_MUTEX_H
+
+
+#include "rsUtils.h"
+
+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/rsNoise.cpp b/libs/rs/rsNoise.cpp
deleted file mode 100644
index 4b67586..0000000
--- a/libs/rs/rsNoise.cpp
+++ /dev/null
@@ -1,256 +0,0 @@
-/*
- * This implementation of the noise functions was ported from the Java
- * implementation by Jerry Huxtable (http://www.jhlabs.com) under
- * Apache License 2.0 (see http://jhlabs.com/ip/filters/download.html)
- *
- * Original header:
- *
- * Copyright 2006 Jerry Huxtable
- *
- * 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 "rsNoise.h"
-
-#include <math.h>
-#include <stdlib.h>
-#include <time.h>
-
-namespace android {
-namespace renderscript {
-
-#define B 0x100
-#define BM 0xff
-#define N 0x1000
-
-static int p[B + B + 2];
-static float g3[B + B + 2][3];
-static float g2[B + B + 2][2];
-static float g1[B + B + 2];
-static bool noise_start = true;
-
-#define lerpf(start, stop, amount) start + (stop - start) * amount
-
-static inline float noise_sCurve(float t)
-{
-    return t * t * (3.0f - 2.0f * t);
-}
-
-inline void SC_normalizef2(float v[])
-{
-    float s = (float)sqrtf(v[0] * v[0] + v[1] * v[1]);
-    v[0] = v[0] / s;
-    v[1] = v[1] / s;
-}
-
-inline void SC_normalizef3(float v[])
-{
-    float s = (float)sqrtf(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
-    v[0] = v[0] / s;
-    v[1] = v[1] / s;
-    v[2] = v[2] / s;
-}
-
-static void noise_init()
-{
-    int i, j, k;
-    
-    for (i = 0; i < B; i++) {
-        p[i] = i;
-        
-        g1[i] = (float)((rand() % (B + B)) - B) / B;
-        
-        for (j = 0; j < 2; j++)
-            g2[i][j] = (float)((rand() % (B + B)) - B) / B;
-        SC_normalizef2(g2[i]);
-        
-        for (j = 0; j < 3; j++)
-            g3[i][j] = (float)((rand() % (B + B)) - B) / B;
-        SC_normalizef3(g3[i]);
-    }
-    
-    for (i = B-1; i >= 0; i--) {
-        k = p[i];
-        p[i] = p[j = rand() % B];
-        p[j] = k;
-    }
-    
-    for (i = 0; i < B + 2; i++) {
-        p[B + i] = p[i];
-        g1[B + i] = g1[i];
-        for (j = 0; j < 2; j++)
-            g2[B + i][j] = g2[i][j];
-        for (j = 0; j < 3; j++)
-            g3[B + i][j] = g3[i][j];
-    }
-}
-
-float SC_noisef(float x)
-{
-    srand(time(NULL));
-    int bx0, bx1;
-    float rx0, rx1, sx, t, u, v;
-    
-    if (noise_start) {
-        noise_start = false;
-        noise_init();
-    }
-    
-    t = x + N;
-    bx0 = ((int)t) & BM;
-    bx1 = (bx0+1) & BM;
-    rx0 = t - (int)t;
-    rx1 = rx0 - 1.0f;
-    
-    sx = noise_sCurve(rx0);
-    
-    u = rx0 * g1[p[bx0]];
-    v = rx1 * g1[p[bx1]];
-    return 2.3f * lerpf(u, v, sx);
-}
-
-float SC_noisef2(float x, float y)
-{
-    srand(time(NULL));
-    int bx0, bx1, by0, by1, b00, b10, b01, b11;
-    float rx0, rx1, ry0, ry1, sx, sy, a, b, t, u, v;
-    float *q;
-    int i, j;
-    
-    if (noise_start) {
-        noise_start = false;
-        noise_init();
-    }
-    
-    t = x + N;
-    bx0 = ((int)t) & BM;
-    bx1 = (bx0+1) & BM;
-    rx0 = t - (int)t;
-    rx1 = rx0 - 1.0f;
-	
-    t = y + N;
-    by0 = ((int)t) & BM;
-    by1 = (by0+1) & BM;
-    ry0 = t - (int)t;
-    ry1 = ry0 - 1.0f;
-	
-    i = p[bx0];
-    j = p[bx1];
-    
-    b00 = p[i + by0];
-    b10 = p[j + by0];
-    b01 = p[i + by1];
-    b11 = p[j + by1];
-    
-    sx = noise_sCurve(rx0);
-    sy = noise_sCurve(ry0);
-    
-    q = g2[b00]; u = rx0 * q[0] + ry0 * q[1];
-    q = g2[b10]; v = rx1 * q[0] + ry0 * q[1];
-    a = lerpf(u, v, sx);
-    
-    q = g2[b01]; u = rx0 * q[0] + ry1 * q[1];
-    q = g2[b11]; v = rx1 * q[0] + ry1 * q[1];
-    b = lerpf(u, v, sx);
-    
-    return 1.5f*lerpf(a, b, sy);
-}
-
-float SC_noisef3(float x, float y, float z)
-{
-    srand(time(NULL));
-    int bx0, bx1, by0, by1, bz0, bz1, b00, b10, b01, b11;
-    float rx0, rx1, ry0, ry1, rz0, rz1, sy, sz, a, b, c, d, t, u, v;
-    float *q;
-    int i, j;
-    
-    if (noise_start) {
-        noise_start = false;
-        noise_init();
-    }
-    
-    t = x + N;
-    bx0 = ((int)t) & BM;
-    bx1 = (bx0+1) & BM;
-    rx0 = t - (int)t;
-    rx1 = rx0 - 1.0f;
-    
-    t = y + N;
-    by0 = ((int)t) & BM;
-    by1 = (by0+1) & BM;
-    ry0 = t - (int)t;
-    ry1 = ry0 - 1.0f;
-	
-    t = z + N;
-    bz0 = ((int)t) & BM;
-    bz1 = (bz0+1) & BM;
-    rz0 = t - (int)t;
-    rz1 = rz0 - 1.0f;
-	
-    i = p[bx0];
-    j = p[bx1];
-    
-    b00 = p[i + by0];
-    b10 = p[j + by0];
-    b01 = p[i + by1];
-    b11 = p[j + by1];
-    
-    t  = noise_sCurve(rx0);
-    sy = noise_sCurve(ry0);
-    sz = noise_sCurve(rz0);
-    
-    q = g3[b00 + bz0]; u = rx0 * q[0] + ry0 * q[1] + rz0 * q[2];
-    q = g3[b10 + bz0]; v = rx1 * q[0] + ry0 * q[1] + rz0 * q[2];
-    a = lerpf(u, v, t);
-    
-    q = g3[b01 + bz0]; u = rx0 * q[0] + ry1 * q[1] + rz0 * q[2];
-    q = g3[b11 + bz0]; v = rx1 * q[0] + ry1 * q[1] + rz0 * q[2];
-    b = lerpf(u, v, t);
-    
-    c = lerpf(a, b, sy);
-    
-    q = g3[b00 + bz1]; u = rx0 * q[0] + ry0 * q[1] + rz1 * q[2];
-    q = g3[b10 + bz1]; v = rx1 * q[0] + ry0 * q[1] + rz1 * q[2];
-    a = lerpf(u, v, t);
-    
-    q = g3[b01 + bz1]; u = rx0 * q[0] + ry1 * q[1] + rz1 * q[2];
-    q = g3[b11 + bz1]; v = rx1 * q[0] + ry1 * q[1] + rz1 * q[2];
-    b = lerpf(u, v, t);
-    
-    d = lerpf(a, b, sy);
-    
-    return 1.5f*lerpf(c, d, sz);
-}
-
-float SC_turbulencef2(float x, float y, float octaves)
-{
-    srand(time(NULL));
-    float t = 0.0f;
-    
-    for (float f = 1.0f; f <= octaves; f *= 2)
-        t += fabs(SC_noisef2(f * x, f * y)) / f;
-    return t;
-}
-
-float SC_turbulencef3(float x, float y, float z, float octaves)
-{
-    srand(time(NULL));
-    float t = 0.0f;
-    
-    for (float f = 1.0f; f <= octaves; f *= 2)
-        t += fabs(SC_noisef3(f * x, f * y, f * z)) / f;
-    return t;
-}
-
-}
-}
diff --git a/libs/rs/rsNoise.h b/libs/rs/rsNoise.h
deleted file mode 100644
index 9040751..0000000
--- a/libs/rs/rsNoise.h
+++ /dev/null
@@ -1,35 +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.
- */
-
-#ifndef ANDROID_RS_NOISE_H
-#define ANDROID_RS_NOISE_H
-
-// ---------------------------------------------------------------------------
-namespace android {
-namespace renderscript {
-
-void SC_normalizef2(float v[]);
-void SC_normalizef3(float v[]);
-float SC_noisef(float x);
-float SC_noisef2(float x, float y);
-float SC_noisef3(float x, float y, float z);
-float SC_turbulencef2(float x, float y, float octaves);
-float SC_turbulencef3(float x, float y, float z, float octaves);
-
-}
-}
-
-#endif
diff --git a/libs/rs/rsObjectBase.cpp b/libs/rs/rsObjectBase.cpp
index 677413e..48f1fee 100644
--- a/libs/rs/rsObjectBase.cpp
+++ b/libs/rs/rsObjectBase.cpp
@@ -15,7 +15,12 @@
  */
 
 #include "rsObjectBase.h"
+
+#ifndef ANDROID_RS_BUILD_FOR_HOST
 #include "rsContext.h"
+#else
+#include "rsContextHostStub.h"
+#endif
 
 using namespace android;
 using namespace android::renderscript;
@@ -24,7 +29,6 @@
 {
     mUserRefCount = 0;
     mSysRefCount = 0;
-    mName = NULL;
     mRSC = NULL;
     mNext = NULL;
     mPrev = NULL;
@@ -39,14 +43,13 @@
     rsAssert(!mUserRefCount);
     rsAssert(!mSysRefCount);
     remove();
-    delete[] mName;
 }
 
 void ObjectBase::dumpLOGV(const char *op) const
 {
-    if (mName) {
+    if (mName.size()) {
         LOGV("%s RSobj %p, name %s, refs %i,%i  from %s,%i links %p,%p,%p",
-             op, this, mName, mUserRefCount, mSysRefCount, mAllocFile, mAllocLine, mNext, mPrev, mRSC);
+             op, this, mName.string(), mUserRefCount, mSysRefCount, mAllocFile, mAllocLine, mNext, mPrev, mRSC);
     } else {
         LOGV("%s RSobj %p, no-name, refs %i,%i  from %s,%i links %p,%p,%p",
              op, this, mUserRefCount, mSysRefCount, mAllocFile, mAllocLine, mNext, mPrev, mRSC);
@@ -113,18 +116,12 @@
 
 void ObjectBase::setName(const char *name)
 {
-    setName(name, strlen(name));
+    mName.setTo(name);
 }
 
 void ObjectBase::setName(const char *name, uint32_t len)
 {
-    delete mName;
-    mName = NULL;
-    if (name) {
-        mName = new char[len + 1];
-        memcpy(mName, name, len);
-        mName[len] = 0;
-    }
+    mName.setTo(name, len);
 }
 
 void ObjectBase::add() const
diff --git a/libs/rs/rsObjectBase.h b/libs/rs/rsObjectBase.h
index bb03b87..ad95b81 100644
--- a/libs/rs/rsObjectBase.h
+++ b/libs/rs/rsObjectBase.h
@@ -24,6 +24,7 @@
 namespace renderscript {
 
 class Context;
+class OStream;
 
 // An element is a group of Components that occupies one cell in a structure.
 class ObjectBase
@@ -40,7 +41,7 @@
     bool zeroUserRef() const;
 
     const char * getName() const {
-        return mName;
+        return mName.string();
     }
     void setName(const char *);
     void setName(const char *, uint32_t len);
@@ -52,6 +53,8 @@
     static void dumpAll(Context *rsc);
 
     virtual void dumpLOGV(const char *prefix) const;
+    virtual void serialize(OStream *stream) const = 0;
+    virtual RsA3DClassID getClassId() const = 0;
 
 protected:
     const char *mAllocFile;
@@ -64,7 +67,7 @@
 
     bool checkDelete() const;
 
-    char * mName;
+    String8 mName;
     mutable int32_t mSysRefCount;
     mutable int32_t mUserRefCount;
 
diff --git a/libs/rs/rsProgram.cpp b/libs/rs/rsProgram.cpp
index 70e2868..c4f8b2e 100644
--- a/libs/rs/rsProgram.cpp
+++ b/libs/rs/rsProgram.cpp
@@ -14,11 +14,17 @@
  * limitations under the License.
  */
 
+#ifndef ANDROID_RS_BUILD_FOR_HOST
 #include "rsContext.h"
-#include "rsProgram.h"
-
 #include <GLES2/gl2.h>
 #include <GLES2/gl2ext.h>
+#else
+#include "rsContextHostStub.h"
+#include <OpenGL/gl.h>
+#include <OpenGL/glext.h>
+#endif //ANDROID_RS_BUILD_FOR_HOST
+
+#include "rsProgram.h"
 
 using namespace android;
 using namespace android::renderscript;
diff --git a/libs/rs/rsProgramFragment.cpp b/libs/rs/rsProgramFragment.cpp
index c17b94c..056863c 100644
--- a/libs/rs/rsProgramFragment.cpp
+++ b/libs/rs/rsProgramFragment.cpp
@@ -14,13 +14,19 @@
  * limitations under the License.
  */
 
+#ifndef ANDROID_RS_BUILD_FOR_HOST
 #include "rsContext.h"
-#include "rsProgramFragment.h"
-
 #include <GLES/gl.h>
 #include <GLES/glext.h>
 #include <GLES2/gl2.h>
 #include <GLES2/gl2ext.h>
+#else
+#include "rsContextHostStub.h"
+#include <OpenGL/gl.h>
+#include <OpenGL/glext.h>
+#endif //ANDROID_RS_BUILD_FOR_HOST
+
+#include "rsProgramFragment.h"
 
 using namespace android;
 using namespace android::renderscript;
@@ -32,13 +38,21 @@
 {
     mAllocFile = __FILE__;
     mAllocLine = __LINE__;
-    rsAssert(paramLength = 5);
+    rsAssert(paramLength == 6);
+
+    mConstantColor[0] = 1.f;
+    mConstantColor[1] = 1.f;
+    mConstantColor[2] = 1.f;
+    mConstantColor[3] = 1.f;
 
     mEnvModes[0] = (RsTexEnvMode)params[0];
     mTextureFormats[0] = params[1];
     mEnvModes[1] = (RsTexEnvMode)params[2];
     mTextureFormats[1] = params[3];
     mPointSpriteEnable = params[4] != 0;
+    mVaryingColor = false;
+    if (paramLength > 5)
+        mVaryingColor = params[5] != 0;
 
     mTextureEnableMask = 0;
     if (mEnvModes[0]) {
@@ -47,7 +61,17 @@
     if (mEnvModes[1]) {
         mTextureEnableMask |= 2;
     }
-    init(rsc);
+
+    mUniformCount = 0;
+    mUniformNames[mUniformCount++].setTo("uni_Tex0");
+    mUniformNames[mUniformCount++].setTo("uni_Tex1");
+
+    mConstantColorUniformIndex = -1;
+    //if (!mVaryingColor) {
+        mConstantColorUniformIndex = mUniformCount;
+        mUniformNames[mUniformCount++].setTo("uni_Color");
+    //}
+    createShader();
 }
 
 ProgramFragment::ProgramFragment(Context *rsc, const char * shaderText,
@@ -58,7 +82,19 @@
     mAllocFile = __FILE__;
     mAllocLine = __LINE__;
 
-    init(rsc);
+    mConstantColor[0] = 1.f;
+    mConstantColor[1] = 1.f;
+    mConstantColor[2] = 1.f;
+    mConstantColor[3] = 1.f;
+
+    LOGE("Custom FP");
+
+    mUniformCount = 2;
+    mUniformNames[0].setTo("uni_Tex0");
+    mUniformNames[1].setTo("uni_Tex1");
+
+    createShader();
+
     mTextureEnableMask = (1 << mTextureCount) -1;
 }
 
@@ -67,76 +103,17 @@
 {
 }
 
+void ProgramFragment::setConstantColor(float r, float g, float b, float a)
+{
+    mConstantColor[0] = r;
+    mConstantColor[1] = g;
+    mConstantColor[2] = b;
+    mConstantColor[3] = a;
+    mDirty = true;
+}
+
 void ProgramFragment::setupGL(const Context *rsc, ProgramFragmentState *state)
 {
-    if ((state->mLast.get() == this) && !mDirty) {
-        return;
-    }
-    state->mLast.set(this);
-
-    for (uint32_t ct=0; ct < MAX_TEXTURE; ct++) {
-        glActiveTexture(GL_TEXTURE0 + ct);
-        if (!(mTextureEnableMask & (1 << ct)) || !mTextures[ct].get()) {
-            glDisable(GL_TEXTURE_2D);
-            continue;
-        }
-
-        glEnable(GL_TEXTURE_2D);
-        if (rsc->checkVersion1_1()) {
-            if (mPointSpriteEnable) {
-                glEnable(GL_POINT_SPRITE_OES);
-            } else {
-                glDisable(GL_POINT_SPRITE_OES);
-            }
-            glTexEnvi(GL_POINT_SPRITE_OES, GL_COORD_REPLACE_OES, mPointSpriteEnable);
-        }
-        mTextures[ct]->uploadCheck(rsc);
-        glBindTexture(GL_TEXTURE_2D, mTextures[ct]->getTextureID());
-
-        switch(mEnvModes[ct]) {
-        case RS_TEX_ENV_MODE_NONE:
-            rsAssert(0);
-            break;
-        case RS_TEX_ENV_MODE_REPLACE:
-            glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
-            break;
-        case RS_TEX_ENV_MODE_MODULATE:
-            glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
-            break;
-        case RS_TEX_ENV_MODE_DECAL:
-            glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
-            break;
-        }
-
-        if (mSamplers[ct].get()) {
-            mSamplers[ct]->setupGL(rsc, mTextures[ct]->getType()->getIsNp2());
-        } else {
-            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
-            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
-            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
-            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
-        }
-
-        // Gross hack.
-        if (ct == 2) {
-            glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE);
-
-            glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_ADD);
-            glTexEnvi(GL_TEXTURE_ENV, GL_SRC0_RGB, GL_PREVIOUS);
-            glTexEnvi(GL_TEXTURE_ENV, GL_SRC1_RGB, GL_TEXTURE);
-            glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR);
-            glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR);
-
-            glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_ADD);
-            glTexEnvi(GL_TEXTURE_ENV, GL_SRC0_ALPHA, GL_PREVIOUS);
-            glTexEnvi(GL_TEXTURE_ENV, GL_SRC1_ALPHA, GL_TEXTURE);
-            glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA, GL_SRC_ALPHA);
-            glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_ALPHA, GL_SRC_ALPHA);
-        }
-    }
-    glActiveTexture(GL_TEXTURE0);
-    mDirty = false;
-    rsc->checkError("ProgramFragment::setupGL");
 }
 
 void ProgramFragment::setupGL2(const Context *rsc, ProgramFragmentState *state, ShaderCache *sc)
@@ -144,11 +121,19 @@
 
     //LOGE("sgl2 frag1 %x", glGetError());
     if ((state->mLast.get() == this) && !mDirty) {
-        //return;
+        return;
     }
     state->mLast.set(this);
 
     rsc->checkError("ProgramFragment::setupGL2 start");
+
+    if (!mVaryingColor &&
+        (sc->fragUniformSlot(mConstantColorUniformIndex) >= 0)) {
+        //LOGE("mConstantColorUniformIndex %i %i", mConstantColorUniformIndex, sc->fragUniformSlot(mConstantColorUniformIndex));
+        glUniform4fv(sc->fragUniformSlot(mConstantColorUniformIndex), 1, mConstantColor);
+        rsc->checkError("ProgramFragment::color setup");
+    }
+
     for (uint32_t ct=0; ct < MAX_TEXTURE; ct++) {
         glActiveTexture(GL_TEXTURE0 + ct);
         if (!(mTextureEnableMask & (1 << ct)) || !mTextures[ct].get()) {
@@ -186,6 +171,7 @@
     mShader.setTo("precision mediump float;\n");
     mShader.append("varying vec4 varColor;\n");
     mShader.append("varying vec4 varTex0;\n");
+    mShader.append("uniform vec4 uni_Color;\n");
 
     if (mUserShader.length() > 1) {
         for (uint32_t ct=0; ct < mTextureCount; ct++) {
@@ -212,7 +198,11 @@
 
 
         mShader.append("void main() {\n");
-        mShader.append("  vec4 col = varColor;\n");
+        if (mVaryingColor) {
+            mShader.append("  vec4 col = varColor;\n");
+        } else {
+            mShader.append("  vec4 col = uni_Color;\n");
+        }
 
         if (mTextureEnableMask) {
             if (mPointSpriteEnable) {
@@ -282,11 +272,16 @@
 
 void ProgramFragment::init(Context *rsc)
 {
-    mUniformCount = 2;
-    mUniformNames[0].setTo("uni_Tex0");
-    mUniformNames[1].setTo("uni_Tex1");
+}
 
-    createShader();
+void ProgramFragment::serialize(OStream *stream) const
+{
+
+}
+
+ProgramFragment *ProgramFragment::createFromStream(Context *rsc, IStream *stream)
+{
+    return NULL;
 }
 
 ProgramFragmentState::ProgramFragmentState()
@@ -300,14 +295,14 @@
 
 }
 
-void ProgramFragmentState::init(Context *rsc, int32_t w, int32_t h)
+void ProgramFragmentState::init(Context *rsc)
 {
-    uint32_t tmp[5] = {
+    uint32_t tmp[] = {
         RS_TEX_ENV_MODE_NONE, 0,
         RS_TEX_ENV_MODE_NONE, 0,
-        0
+        0, 0
     };
-    ProgramFragment *pf = new ProgramFragment(rsc, tmp, 5);
+    ProgramFragment *pf = new ProgramFragment(rsc, tmp, 6);
     mDefault.set(pf);
     pf->init(rsc);
 }
@@ -328,6 +323,7 @@
 {
     ProgramFragment *pf = new ProgramFragment(rsc, params, paramLength);
     pf->incUserRef();
+    //LOGE("rsi_ProgramFragmentCreate %p", pf);
     return pf;
 }
 
@@ -337,6 +333,7 @@
 {
     ProgramFragment *pf = new ProgramFragment(rsc, shaderText, shaderLength, params, paramLength);
     pf->incUserRef();
+    //LOGE("rsi_ProgramFragmentCreate2 %p", pf);
     return pf;
 }
 
diff --git a/libs/rs/rsProgramFragment.h b/libs/rs/rsProgramFragment.h
index 9fa565d..7c1598e 100644
--- a/libs/rs/rsProgramFragment.h
+++ b/libs/rs/rsProgramFragment.h
@@ -40,6 +40,11 @@
     virtual void createShader();
     virtual void loadShader(Context *rsc);
     virtual void init(Context *rsc);
+    virtual void serialize(OStream *stream) const;
+    virtual RsA3DClassID getClassId() const { return RS_A3D_CLASS_ID_PROGRAM_FRAGMENT; }
+    static ProgramFragment *createFromStream(Context *rsc, IStream *stream);
+
+    void setConstantColor(float, float, float, float);
 
 protected:
     // Hacks to create a program for now
@@ -48,6 +53,10 @@
     RsTexEnvMode mEnvModes[MAX_TEXTURE];
     uint32_t mTextureEnableMask;
     bool mPointSpriteEnable;
+    bool mVaryingColor;
+
+    float mConstantColor[4];
+    int32_t mConstantColorUniformIndex;
 };
 
 class ProgramFragmentState
@@ -57,7 +66,7 @@
     ~ProgramFragmentState();
 
     ProgramFragment *mPF;
-    void init(Context *rsc, int32_t w, int32_t h);
+    void init(Context *rsc);
     void deinit(Context *rsc);
 
     ObjectBaseRef<Type> mTextureTypes[ProgramFragment::MAX_TEXTURE];
diff --git a/libs/rs/rsProgramFragmentStore.cpp b/libs/rs/rsProgramFragmentStore.cpp
deleted file mode 100644
index 8a2157f..0000000
--- a/libs/rs/rsProgramFragmentStore.cpp
+++ /dev/null
@@ -1,308 +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.
- */
-
-#include "rsContext.h"
-#include "rsProgramFragmentStore.h"
-
-#include <GLES/gl.h>
-#include <GLES/glext.h>
-
-using namespace android;
-using namespace android::renderscript;
-
-
-ProgramFragmentStore::ProgramFragmentStore(Context *rsc) :
-    Program(rsc)
-{
-    mAllocFile = __FILE__;
-    mAllocLine = __LINE__;
-    mDitherEnable = true;
-    mBlendEnable = false;
-    mColorRWriteEnable = true;
-    mColorGWriteEnable = true;
-    mColorBWriteEnable = true;
-    mColorAWriteEnable = true;
-    mBlendSrc = GL_ONE;
-    mBlendDst = GL_ZERO;
-
-
-    mDepthTestEnable = false;
-    mDepthWriteEnable = true;
-    mDepthFunc = GL_LESS;
-
-
-}
-
-ProgramFragmentStore::~ProgramFragmentStore()
-{
-}
-
-void ProgramFragmentStore::setupGL(const Context *rsc, ProgramFragmentStoreState *state)
-{
-    if (state->mLast.get() == this) {
-        return;
-    }
-    state->mLast.set(this);
-
-    glColorMask(mColorRWriteEnable,
-                mColorGWriteEnable,
-                mColorBWriteEnable,
-                mColorAWriteEnable);
-    if (mBlendEnable) {
-        glEnable(GL_BLEND);
-        glBlendFunc(mBlendSrc, mBlendDst);
-    } else {
-        glDisable(GL_BLEND);
-    }
-
-    //LOGE("pfs  %i, %i, %x", mDepthWriteEnable, mDepthTestEnable, mDepthFunc);
-
-    glDepthMask(mDepthWriteEnable);
-    if(mDepthTestEnable || mDepthWriteEnable) {
-        glEnable(GL_DEPTH_TEST);
-        glDepthFunc(mDepthFunc);
-    } else {
-        glDisable(GL_DEPTH_TEST);
-    }
-
-    if (mDitherEnable) {
-        glEnable(GL_DITHER);
-    } else {
-        glDisable(GL_DITHER);
-    }
-}
-
-void ProgramFragmentStore::setupGL2(const Context *rsc, ProgramFragmentStoreState *state)
-{
-    if (state->mLast.get() == this) {
-        return;
-    }
-    state->mLast.set(this);
-
-    glColorMask(mColorRWriteEnable,
-                mColorGWriteEnable,
-                mColorBWriteEnable,
-                mColorAWriteEnable);
-    if (mBlendEnable) {
-        glEnable(GL_BLEND);
-        glBlendFunc(mBlendSrc, mBlendDst);
-    } else {
-        glDisable(GL_BLEND);
-    }
-
-    //LOGE("pfs  %i, %i, %x", mDepthWriteEnable, mDepthTestEnable, mDepthFunc);
-
-    glDepthMask(mDepthWriteEnable);
-    if(mDepthTestEnable || mDepthWriteEnable) {
-        glEnable(GL_DEPTH_TEST);
-        glDepthFunc(mDepthFunc);
-    } else {
-        glDisable(GL_DEPTH_TEST);
-    }
-
-    if (mDitherEnable) {
-        glEnable(GL_DITHER);
-    } else {
-        glDisable(GL_DITHER);
-    }
-}
-
-
-void ProgramFragmentStore::setDitherEnable(bool enable)
-{
-    mDitherEnable = enable;
-}
-
-void ProgramFragmentStore::setDepthFunc(RsDepthFunc func)
-{
-    mDepthTestEnable = true;
-
-    switch(func) {
-    case RS_DEPTH_FUNC_ALWAYS:
-        mDepthTestEnable = false;
-        mDepthFunc = GL_ALWAYS;
-        break;
-    case RS_DEPTH_FUNC_LESS:
-        mDepthFunc = GL_LESS;
-        break;
-    case RS_DEPTH_FUNC_LEQUAL:
-        mDepthFunc = GL_LEQUAL;
-        break;
-    case RS_DEPTH_FUNC_GREATER:
-        mDepthFunc = GL_GREATER;
-        break;
-    case RS_DEPTH_FUNC_GEQUAL:
-        mDepthFunc = GL_GEQUAL;
-        break;
-    case RS_DEPTH_FUNC_EQUAL:
-        mDepthFunc = GL_EQUAL;
-        break;
-    case RS_DEPTH_FUNC_NOTEQUAL:
-        mDepthFunc = GL_NOTEQUAL;
-        break;
-    }
-}
-
-void ProgramFragmentStore::setDepthMask(bool mask)
-{
-    mDepthWriteEnable = mask;
-}
-
-void ProgramFragmentStore::setBlendFunc(RsBlendSrcFunc src, RsBlendDstFunc dst)
-{
-    mBlendEnable = true;
-    if ((src == RS_BLEND_SRC_ONE) &&
-        (dst == RS_BLEND_DST_ZERO)) {
-        mBlendEnable = false;
-    }
-
-    switch(src) {
-    case RS_BLEND_SRC_ZERO:
-        mBlendSrc = GL_ZERO;
-        break;
-    case RS_BLEND_SRC_ONE:
-        mBlendSrc = GL_ONE;
-        break;
-    case RS_BLEND_SRC_DST_COLOR:
-        mBlendSrc = GL_DST_COLOR;
-        break;
-    case RS_BLEND_SRC_ONE_MINUS_DST_COLOR:
-        mBlendSrc = GL_ONE_MINUS_DST_COLOR;
-        break;
-    case RS_BLEND_SRC_SRC_ALPHA:
-        mBlendSrc = GL_SRC_ALPHA;
-        break;
-    case RS_BLEND_SRC_ONE_MINUS_SRC_ALPHA:
-        mBlendSrc = GL_ONE_MINUS_SRC_ALPHA;
-        break;
-    case RS_BLEND_SRC_DST_ALPHA:
-        mBlendSrc = GL_DST_ALPHA;
-        break;
-    case RS_BLEND_SRC_ONE_MINUS_DST_ALPHA:
-        mBlendSrc = GL_ONE_MINUS_DST_ALPHA;
-        break;
-    case RS_BLEND_SRC_SRC_ALPHA_SATURATE:
-        mBlendSrc = GL_SRC_ALPHA_SATURATE;
-        break;
-    }
-
-    switch(dst) {
-    case RS_BLEND_DST_ZERO:
-        mBlendDst = GL_ZERO;
-        break;
-    case RS_BLEND_DST_ONE:
-        mBlendDst = GL_ONE;
-        break;
-    case RS_BLEND_DST_SRC_COLOR:
-        mBlendDst = GL_SRC_COLOR;
-        break;
-    case RS_BLEND_DST_ONE_MINUS_SRC_COLOR:
-        mBlendDst = GL_ONE_MINUS_SRC_COLOR;
-        break;
-    case RS_BLEND_DST_SRC_ALPHA:
-        mBlendDst = GL_SRC_ALPHA;
-        break;
-    case RS_BLEND_DST_ONE_MINUS_SRC_ALPHA:
-        mBlendDst = GL_ONE_MINUS_SRC_ALPHA;
-        break;
-    case RS_BLEND_DST_DST_ALPHA:
-        mBlendDst = GL_DST_ALPHA;
-        break;
-    case RS_BLEND_DST_ONE_MINUS_DST_ALPHA:
-        mBlendDst = GL_ONE_MINUS_DST_ALPHA;
-        break;
-    }
-}
-
-void ProgramFragmentStore::setColorMask(bool r, bool g, bool b, bool a)
-{
-    mColorRWriteEnable = r;
-    mColorGWriteEnable = g;
-    mColorBWriteEnable = b;
-    mColorAWriteEnable = a;
-}
-
-
-ProgramFragmentStoreState::ProgramFragmentStoreState()
-{
-    mPFS = NULL;
-}
-
-ProgramFragmentStoreState::~ProgramFragmentStoreState()
-{
-    delete mPFS;
-
-}
-
-void ProgramFragmentStoreState::init(Context *rsc, int32_t w, int32_t h)
-{
-    ProgramFragmentStore *pfs = new ProgramFragmentStore(rsc);
-    mDefault.set(pfs);
-}
-
-void ProgramFragmentStoreState::deinit(Context *rsc)
-{
-    mDefault.clear();
-    mLast.clear();
-}
-
-
-namespace android {
-namespace renderscript {
-
-void rsi_ProgramFragmentStoreBegin(Context * rsc, RsElement in, RsElement out)
-{
-    delete rsc->mStateFragmentStore.mPFS;
-    rsc->mStateFragmentStore.mPFS = new ProgramFragmentStore(rsc);
-
-}
-
-void rsi_ProgramFragmentStoreDepthFunc(Context *rsc, RsDepthFunc func)
-{
-    rsc->mStateFragmentStore.mPFS->setDepthFunc(func);
-}
-
-void rsi_ProgramFragmentStoreDepthMask(Context *rsc, bool mask)
-{
-    rsc->mStateFragmentStore.mPFS->setDepthMask(mask);
-}
-
-void rsi_ProgramFragmentStoreColorMask(Context *rsc, bool r, bool g, bool b, bool a)
-{
-    rsc->mStateFragmentStore.mPFS->setColorMask(r, g, b, a);
-}
-
-void rsi_ProgramFragmentStoreBlendFunc(Context *rsc, RsBlendSrcFunc src, RsBlendDstFunc dst)
-{
-    rsc->mStateFragmentStore.mPFS->setBlendFunc(src, dst);
-}
-
-RsProgramFragmentStore rsi_ProgramFragmentStoreCreate(Context *rsc)
-{
-    ProgramFragmentStore *pfs = rsc->mStateFragmentStore.mPFS;
-    pfs->incUserRef();
-    rsc->mStateFragmentStore.mPFS = 0;
-    return pfs;
-}
-
-void rsi_ProgramFragmentStoreDither(Context *rsc, bool enable)
-{
-    rsc->mStateFragmentStore.mPFS->setDitherEnable(enable);
-}
-
-
-}
-}
diff --git a/libs/rs/rsProgramFragmentStore.h b/libs/rs/rsProgramFragmentStore.h
deleted file mode 100644
index 3412c99..0000000
--- a/libs/rs/rsProgramFragmentStore.h
+++ /dev/null
@@ -1,84 +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.
- */
-
-#ifndef ANDROID_RS_PROGRAM_FRAGMENT_STORE_H
-#define ANDROID_RS_PROGRAM_FRAGMENT_STORE_H
-
-#include "rsProgram.h"
-
-// ---------------------------------------------------------------------------
-namespace android {
-namespace renderscript {
-
-class ProgramFragmentStoreState;
-
-class ProgramFragmentStore : public Program
-{
-public:
-    ProgramFragmentStore(Context *);
-    virtual ~ProgramFragmentStore();
-
-    virtual void setupGL(const Context *, ProgramFragmentStoreState *);
-    virtual void setupGL2(const Context *, ProgramFragmentStoreState *);
-
-    void setDepthFunc(RsDepthFunc);
-    void setDepthMask(bool);
-
-    void setBlendFunc(RsBlendSrcFunc src, RsBlendDstFunc dst);
-    void setColorMask(bool, bool, bool, bool);
-
-    void setDitherEnable(bool);
-
-protected:
-    bool mDitherEnable;
-
-    bool mBlendEnable;
-    bool mColorRWriteEnable;
-    bool mColorGWriteEnable;
-    bool mColorBWriteEnable;
-    bool mColorAWriteEnable;
-    int32_t mBlendSrc;
-    int32_t mBlendDst;
-
-    bool mDepthTestEnable;
-    bool mDepthWriteEnable;
-    int32_t mDepthFunc;
-
-    bool mStencilTestEnable;
-};
-
-class ProgramFragmentStoreState
-{
-public:
-    ProgramFragmentStoreState();
-    ~ProgramFragmentStoreState();
-    void init(Context *rsc, int32_t w, int32_t h);
-    void deinit(Context *rsc);
-
-    ObjectBaseRef<ProgramFragmentStore> mDefault;
-    ObjectBaseRef<ProgramFragmentStore> mLast;
-
-
-    ProgramFragmentStore *mPFS;
-};
-
-
-}
-}
-#endif
-
-
-
diff --git a/libs/rs/rsProgramRaster.cpp b/libs/rs/rsProgramRaster.cpp
index 13887d1..5b69370 100644
--- a/libs/rs/rsProgramRaster.cpp
+++ b/libs/rs/rsProgramRaster.cpp
@@ -14,11 +14,17 @@
  * limitations under the License.
  */
 
+#ifndef ANDROID_RS_BUILD_FOR_HOST
 #include "rsContext.h"
-#include "rsProgramRaster.h"
-
 #include <GLES/gl.h>
 #include <GLES/glext.h>
+#else
+#include "rsContextHostStub.h"
+#include <OpenGL/gl.h>
+#include <OpenGl/glext.h>
+#endif //ANDROID_RS_BUILD_FOR_HOST
+
+#include "rsProgramRaster.h"
 
 using namespace android;
 using namespace android::renderscript;
@@ -35,9 +41,8 @@
     mPointSmooth = pointSmooth;
     mLineSmooth = lineSmooth;
     mPointSprite = pointSprite;
-
-    mPointSize = 1.0f;
     mLineWidth = 1.0f;
+    mCull = RS_CULL_BACK;
 }
 
 ProgramRaster::~ProgramRaster()
@@ -47,21 +52,23 @@
 void ProgramRaster::setLineWidth(float s)
 {
     mLineWidth = s;
+    mDirty = true;
 }
 
-void ProgramRaster::setPointSize(float s)
+void ProgramRaster::setCullMode(RsCullMode mode)
 {
-    mPointSize = s;
+    mCull = mode;
+    mDirty = true;
 }
 
 void ProgramRaster::setupGL(const Context *rsc, ProgramRasterState *state)
 {
-    if (state->mLast.get() == this) {
+    if (state->mLast.get() == this && !mDirty) {
         return;
     }
     state->mLast.set(this);
+    mDirty = false;
 
-    glPointSize(mPointSize);
     if (mPointSmooth) {
         glEnable(GL_POINT_SMOOTH);
     } else {
@@ -76,23 +83,62 @@
     }
 
     if (rsc->checkVersion1_1()) {
+#ifndef ANDROID_RS_BUILD_FOR_HOST
         if (mPointSprite) {
             glEnable(GL_POINT_SPRITE_OES);
         } else {
             glDisable(GL_POINT_SPRITE_OES);
         }
+#endif //ANDROID_RS_BUILD_FOR_HOST
+    }
+
+    switch(mCull) {
+        case RS_CULL_BACK:
+            glEnable(GL_CULL_FACE);
+            glCullFace(GL_BACK);
+            break;
+        case RS_CULL_FRONT:
+            glEnable(GL_CULL_FACE);
+            glCullFace(GL_FRONT);
+            break;
+        case RS_CULL_NONE:
+            glDisable(GL_CULL_FACE);
+            break;
     }
 }
 
 void ProgramRaster::setupGL2(const Context *rsc, ProgramRasterState *state)
 {
-    if (state->mLast.get() == this) {
+    if (state->mLast.get() == this && !mDirty) {
         return;
     }
     state->mLast.set(this);
+    mDirty = false;
+
+    switch(mCull) {
+        case RS_CULL_BACK:
+            glEnable(GL_CULL_FACE);
+            glCullFace(GL_BACK);
+            break;
+        case RS_CULL_FRONT:
+            glEnable(GL_CULL_FACE);
+            glCullFace(GL_FRONT);
+            break;
+        case RS_CULL_NONE:
+            glDisable(GL_CULL_FACE);
+            break;
+    }
 }
 
+void ProgramRaster::serialize(OStream *stream) const
+{
 
+}
+
+ProgramRaster *ProgramRaster::createFromStream(Context *rsc, IStream *stream)
+{
+    return NULL;
+}
 
 ProgramRasterState::ProgramRasterState()
 {
@@ -102,7 +148,7 @@
 {
 }
 
-void ProgramRasterState::init(Context *rsc, int32_t w, int32_t h)
+void ProgramRasterState::init(Context *rsc)
 {
     ProgramRaster *pr = new ProgramRaster(rsc, false, false, false);
     mDefault.set(pr);
@@ -118,7 +164,7 @@
 namespace android {
 namespace renderscript {
 
-RsProgramRaster rsi_ProgramRasterCreate(Context * rsc, RsElement in, RsElement out,
+RsProgramRaster rsi_ProgramRasterCreate(Context * rsc,
                                       bool pointSmooth,
                                       bool lineSmooth,
                                       bool pointSprite)
@@ -131,18 +177,18 @@
     return pr;
 }
 
-void rsi_ProgramRasterSetPointSize(Context * rsc, RsProgramRaster vpr, float s)
-{
-    ProgramRaster *pr = static_cast<ProgramRaster *>(vpr);
-    pr->setPointSize(s);
-}
-
 void rsi_ProgramRasterSetLineWidth(Context * rsc, RsProgramRaster vpr, float s)
 {
     ProgramRaster *pr = static_cast<ProgramRaster *>(vpr);
     pr->setLineWidth(s);
 }
 
+void rsi_ProgramRasterSetCullMode(Context * rsc, RsProgramRaster vpr, RsCullMode mode)
+{
+    ProgramRaster *pr = static_cast<ProgramRaster *>(vpr);
+    pr->setCullMode(mode);
+}
+
 
 }
 }
diff --git a/libs/rs/rsProgramRaster.h b/libs/rs/rsProgramRaster.h
index c3a9c90..801ab2a 100644
--- a/libs/rs/rsProgramRaster.h
+++ b/libs/rs/rsProgramRaster.h
@@ -36,19 +36,19 @@
 
     virtual void setupGL(const Context *, ProgramRasterState *);
     virtual void setupGL2(const Context *, ProgramRasterState *);
+    virtual void serialize(OStream *stream) const;
+    virtual RsA3DClassID getClassId() const { return RS_A3D_CLASS_ID_PROGRAM_RASTER; }
+    static ProgramRaster *createFromStream(Context *rsc, IStream *stream);
 
     void setLineWidth(float w);
-    void setPointSize(float s);
+    void setCullMode(RsCullMode mode);
 
 protected:
     bool mPointSmooth;
     bool mLineSmooth;
     bool mPointSprite;
-
-    float mPointSize;
     float mLineWidth;
-
-
+    RsCullMode mCull;
 };
 
 class ProgramRasterState
@@ -56,7 +56,7 @@
 public:
     ProgramRasterState();
     ~ProgramRasterState();
-    void init(Context *rsc, int32_t w, int32_t h);
+    void init(Context *rsc);
     void deinit(Context *rsc);
 
     ObjectBaseRef<ProgramRaster> mDefault;
diff --git a/libs/rs/rsProgramStore.cpp b/libs/rs/rsProgramStore.cpp
new file mode 100644
index 0000000..e741c0a
--- /dev/null
+++ b/libs/rs/rsProgramStore.cpp
@@ -0,0 +1,325 @@
+/*
+ * 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_BUILD_FOR_HOST
+#include "rsContext.h"
+#include <GLES/gl.h>
+#include <GLES/glext.h>
+#else
+#include "rsContextHostStub.h"
+#include <OpenGL/gl.h>
+#include <OpenGl/glext.h>
+#endif //ANDROID_RS_BUILD_FOR_HOST
+
+#include "rsProgramStore.h"
+
+using namespace android;
+using namespace android::renderscript;
+
+
+ProgramStore::ProgramStore(Context *rsc) :
+    Program(rsc)
+{
+    mAllocFile = __FILE__;
+    mAllocLine = __LINE__;
+    mDitherEnable = true;
+    mBlendEnable = false;
+    mColorRWriteEnable = true;
+    mColorGWriteEnable = true;
+    mColorBWriteEnable = true;
+    mColorAWriteEnable = true;
+    mBlendSrc = GL_ONE;
+    mBlendDst = GL_ZERO;
+
+
+    mDepthTestEnable = false;
+    mDepthWriteEnable = true;
+    mDepthFunc = GL_LESS;
+
+
+}
+
+ProgramStore::~ProgramStore()
+{
+}
+
+void ProgramStore::setupGL(const Context *rsc, ProgramStoreState *state)
+{
+    if (state->mLast.get() == this) {
+        return;
+    }
+    state->mLast.set(this);
+
+    glColorMask(mColorRWriteEnable,
+                mColorGWriteEnable,
+                mColorBWriteEnable,
+                mColorAWriteEnable);
+    if (mBlendEnable) {
+        glEnable(GL_BLEND);
+        glBlendFunc(mBlendSrc, mBlendDst);
+    } else {
+        glDisable(GL_BLEND);
+    }
+
+    //LOGE("pfs  %i, %i, %x", mDepthWriteEnable, mDepthTestEnable, mDepthFunc);
+
+    glDepthMask(mDepthWriteEnable);
+    if(mDepthTestEnable || mDepthWriteEnable) {
+        glEnable(GL_DEPTH_TEST);
+        glDepthFunc(mDepthFunc);
+    } else {
+        glDisable(GL_DEPTH_TEST);
+    }
+
+    if (mDitherEnable) {
+        glEnable(GL_DITHER);
+    } else {
+        glDisable(GL_DITHER);
+    }
+}
+
+void ProgramStore::setupGL2(const Context *rsc, ProgramStoreState *state)
+{
+    if (state->mLast.get() == this) {
+        return;
+    }
+    state->mLast.set(this);
+
+    glColorMask(mColorRWriteEnable,
+                mColorGWriteEnable,
+                mColorBWriteEnable,
+                mColorAWriteEnable);
+    if (mBlendEnable) {
+        glEnable(GL_BLEND);
+        glBlendFunc(mBlendSrc, mBlendDst);
+    } else {
+        glDisable(GL_BLEND);
+    }
+
+    //LOGE("pfs  %i, %i, %x", mDepthWriteEnable, mDepthTestEnable, mDepthFunc);
+
+    glDepthMask(mDepthWriteEnable);
+    if(mDepthTestEnable || mDepthWriteEnable) {
+        glEnable(GL_DEPTH_TEST);
+        glDepthFunc(mDepthFunc);
+    } else {
+        glDisable(GL_DEPTH_TEST);
+    }
+
+    if (mDitherEnable) {
+        glEnable(GL_DITHER);
+    } else {
+        glDisable(GL_DITHER);
+    }
+}
+
+
+void ProgramStore::setDitherEnable(bool enable)
+{
+    mDitherEnable = enable;
+}
+
+void ProgramStore::serialize(OStream *stream) const
+{
+
+}
+
+ProgramStore *ProgramStore::createFromStream(Context *rsc, IStream *stream)
+{
+    return NULL;
+}
+
+
+void ProgramStore::setDepthFunc(RsDepthFunc func)
+{
+    mDepthTestEnable = true;
+
+    switch(func) {
+    case RS_DEPTH_FUNC_ALWAYS:
+        mDepthTestEnable = false;
+        mDepthFunc = GL_ALWAYS;
+        break;
+    case RS_DEPTH_FUNC_LESS:
+        mDepthFunc = GL_LESS;
+        break;
+    case RS_DEPTH_FUNC_LEQUAL:
+        mDepthFunc = GL_LEQUAL;
+        break;
+    case RS_DEPTH_FUNC_GREATER:
+        mDepthFunc = GL_GREATER;
+        break;
+    case RS_DEPTH_FUNC_GEQUAL:
+        mDepthFunc = GL_GEQUAL;
+        break;
+    case RS_DEPTH_FUNC_EQUAL:
+        mDepthFunc = GL_EQUAL;
+        break;
+    case RS_DEPTH_FUNC_NOTEQUAL:
+        mDepthFunc = GL_NOTEQUAL;
+        break;
+    }
+}
+
+void ProgramStore::setDepthMask(bool mask)
+{
+    mDepthWriteEnable = mask;
+}
+
+void ProgramStore::setBlendFunc(RsBlendSrcFunc src, RsBlendDstFunc dst)
+{
+    mBlendEnable = true;
+    if ((src == RS_BLEND_SRC_ONE) &&
+        (dst == RS_BLEND_DST_ZERO)) {
+        mBlendEnable = false;
+    }
+
+    switch(src) {
+    case RS_BLEND_SRC_ZERO:
+        mBlendSrc = GL_ZERO;
+        break;
+    case RS_BLEND_SRC_ONE:
+        mBlendSrc = GL_ONE;
+        break;
+    case RS_BLEND_SRC_DST_COLOR:
+        mBlendSrc = GL_DST_COLOR;
+        break;
+    case RS_BLEND_SRC_ONE_MINUS_DST_COLOR:
+        mBlendSrc = GL_ONE_MINUS_DST_COLOR;
+        break;
+    case RS_BLEND_SRC_SRC_ALPHA:
+        mBlendSrc = GL_SRC_ALPHA;
+        break;
+    case RS_BLEND_SRC_ONE_MINUS_SRC_ALPHA:
+        mBlendSrc = GL_ONE_MINUS_SRC_ALPHA;
+        break;
+    case RS_BLEND_SRC_DST_ALPHA:
+        mBlendSrc = GL_DST_ALPHA;
+        break;
+    case RS_BLEND_SRC_ONE_MINUS_DST_ALPHA:
+        mBlendSrc = GL_ONE_MINUS_DST_ALPHA;
+        break;
+    case RS_BLEND_SRC_SRC_ALPHA_SATURATE:
+        mBlendSrc = GL_SRC_ALPHA_SATURATE;
+        break;
+    }
+
+    switch(dst) {
+    case RS_BLEND_DST_ZERO:
+        mBlendDst = GL_ZERO;
+        break;
+    case RS_BLEND_DST_ONE:
+        mBlendDst = GL_ONE;
+        break;
+    case RS_BLEND_DST_SRC_COLOR:
+        mBlendDst = GL_SRC_COLOR;
+        break;
+    case RS_BLEND_DST_ONE_MINUS_SRC_COLOR:
+        mBlendDst = GL_ONE_MINUS_SRC_COLOR;
+        break;
+    case RS_BLEND_DST_SRC_ALPHA:
+        mBlendDst = GL_SRC_ALPHA;
+        break;
+    case RS_BLEND_DST_ONE_MINUS_SRC_ALPHA:
+        mBlendDst = GL_ONE_MINUS_SRC_ALPHA;
+        break;
+    case RS_BLEND_DST_DST_ALPHA:
+        mBlendDst = GL_DST_ALPHA;
+        break;
+    case RS_BLEND_DST_ONE_MINUS_DST_ALPHA:
+        mBlendDst = GL_ONE_MINUS_DST_ALPHA;
+        break;
+    }
+}
+
+void ProgramStore::setColorMask(bool r, bool g, bool b, bool a)
+{
+    mColorRWriteEnable = r;
+    mColorGWriteEnable = g;
+    mColorBWriteEnable = b;
+    mColorAWriteEnable = a;
+}
+
+
+ProgramStoreState::ProgramStoreState()
+{
+    mPFS = NULL;
+}
+
+ProgramStoreState::~ProgramStoreState()
+{
+    delete mPFS;
+
+}
+
+void ProgramStoreState::init(Context *rsc)
+{
+    ProgramStore *pfs = new ProgramStore(rsc);
+    mDefault.set(pfs);
+}
+
+void ProgramStoreState::deinit(Context *rsc)
+{
+    mDefault.clear();
+    mLast.clear();
+}
+
+
+namespace android {
+namespace renderscript {
+
+void rsi_ProgramStoreBegin(Context * rsc, RsElement in, RsElement out)
+{
+    delete rsc->mStateFragmentStore.mPFS;
+    rsc->mStateFragmentStore.mPFS = new ProgramStore(rsc);
+
+}
+
+void rsi_ProgramStoreDepthFunc(Context *rsc, RsDepthFunc func)
+{
+    rsc->mStateFragmentStore.mPFS->setDepthFunc(func);
+}
+
+void rsi_ProgramStoreDepthMask(Context *rsc, bool mask)
+{
+    rsc->mStateFragmentStore.mPFS->setDepthMask(mask);
+}
+
+void rsi_ProgramStoreColorMask(Context *rsc, bool r, bool g, bool b, bool a)
+{
+    rsc->mStateFragmentStore.mPFS->setColorMask(r, g, b, a);
+}
+
+void rsi_ProgramStoreBlendFunc(Context *rsc, RsBlendSrcFunc src, RsBlendDstFunc dst)
+{
+    rsc->mStateFragmentStore.mPFS->setBlendFunc(src, dst);
+}
+
+RsProgramStore rsi_ProgramStoreCreate(Context *rsc)
+{
+    ProgramStore *pfs = rsc->mStateFragmentStore.mPFS;
+    pfs->incUserRef();
+    rsc->mStateFragmentStore.mPFS = 0;
+    return pfs;
+}
+
+void rsi_ProgramStoreDither(Context *rsc, bool enable)
+{
+    rsc->mStateFragmentStore.mPFS->setDitherEnable(enable);
+}
+
+
+}
+}
diff --git a/libs/rs/rsProgramStore.h b/libs/rs/rsProgramStore.h
new file mode 100644
index 0000000..fe8d78e
--- /dev/null
+++ b/libs/rs/rsProgramStore.h
@@ -0,0 +1,89 @@
+/*
+ * 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_PROGRAM_FRAGMENT_STORE_H
+#define ANDROID_RS_PROGRAM_FRAGMENT_STORE_H
+
+#include "rsProgram.h"
+#include "rsStream.h"
+
+// ---------------------------------------------------------------------------
+namespace android {
+namespace renderscript {
+
+class ProgramStoreState;
+
+class ProgramStore : public Program
+{
+public:
+    ProgramStore(Context *);
+    virtual ~ProgramStore();
+
+    virtual void setupGL(const Context *, ProgramStoreState *);
+    virtual void setupGL2(const Context *, ProgramStoreState *);
+
+    void setDepthFunc(RsDepthFunc);
+    void setDepthMask(bool);
+
+    void setBlendFunc(RsBlendSrcFunc src, RsBlendDstFunc dst);
+    void setColorMask(bool, bool, bool, bool);
+
+    void setDitherEnable(bool);
+
+    virtual void serialize(OStream *stream) const;
+    virtual RsA3DClassID getClassId() const { return RS_A3D_CLASS_ID_PROGRAM_STORE; }
+    static ProgramStore *createFromStream(Context *rsc, IStream *stream);
+
+protected:
+    bool mDitherEnable;
+
+    bool mBlendEnable;
+    bool mColorRWriteEnable;
+    bool mColorGWriteEnable;
+    bool mColorBWriteEnable;
+    bool mColorAWriteEnable;
+    int32_t mBlendSrc;
+    int32_t mBlendDst;
+
+    bool mDepthTestEnable;
+    bool mDepthWriteEnable;
+    int32_t mDepthFunc;
+
+    bool mStencilTestEnable;
+};
+
+class ProgramStoreState
+{
+public:
+    ProgramStoreState();
+    ~ProgramStoreState();
+    void init(Context *rsc);
+    void deinit(Context *rsc);
+
+    ObjectBaseRef<ProgramStore> mDefault;
+    ObjectBaseRef<ProgramStore> mLast;
+
+
+    ProgramStore *mPFS;
+};
+
+
+}
+}
+#endif
+
+
+
diff --git a/libs/rs/rsProgramVertex.cpp b/libs/rs/rsProgramVertex.cpp
index a2b2df4..60de04a 100644
--- a/libs/rs/rsProgramVertex.cpp
+++ b/libs/rs/rsProgramVertex.cpp
@@ -14,13 +14,19 @@
  * limitations under the License.
  */
 
+#ifndef ANDROID_RS_BUILD_FOR_HOST
 #include "rsContext.h"
-#include "rsProgramVertex.h"
-
 #include <GLES/gl.h>
 #include <GLES/glext.h>
 #include <GLES2/gl2.h>
 #include <GLES2/gl2ext.h>
+#else
+#include "rsContextHostStub.h"
+#include <OpenGL/gl.h>
+#include <OpenGL/glext.h>
+#endif //ANDROID_RS_BUILD_FOR_HOST
+
+#include "rsProgramVertex.h"
 
 using namespace android;
 using namespace android::renderscript;
@@ -81,9 +87,12 @@
     glMatrixMode(GL_MODELVIEW);
     glLoadIdentity();
     if (mLightCount) {
+#ifndef ANDROID_RS_BUILD_FOR_HOST // GLES Only
         int v = 0;
         glEnable(GL_LIGHTING);
+
         glLightModelxv(GL_LIGHT_MODEL_TWO_SIDE, &v);
+
         for (uint32_t ct = 0; ct < mLightCount; ct++) {
             const Light *l = mLights[ct].get();
             glEnable(GL_LIGHT0 + ct);
@@ -92,6 +101,7 @@
         for (uint32_t ct = mLightCount; ct < MAX_LIGHTS; ct++) {
             glDisable(GL_LIGHT0 + ct);
         }
+#endif //ANDROID_RS_BUILD_FOR_HOST
     } else {
         glDisable(GL_LIGHTING);
     }
@@ -128,6 +138,11 @@
             const Element *e = mConstantTypes[ct]->getElement();
             for (uint32_t field=0; field < e->getFieldCount(); field++) {
                 const Element *f = e->getField(field);
+                const char *fn = e->getFieldName(field);
+
+                if (fn[0] == '#') {
+                    continue;
+                }
 
                 // Cannot be complex
                 rsAssert(!f->getFieldCount());
@@ -140,7 +155,7 @@
                     rsAssert(0);
                 }
 
-                mShader.append(e->getFieldName(field));
+                mShader.append(fn);
                 mShader.append(";\n");
             }
         }
@@ -150,6 +165,11 @@
             const Element *e = mInputElements[ct].get();
             for (uint32_t field=0; field < e->getFieldCount(); field++) {
                 const Element *f = e->getField(field);
+                const char *fn = e->getFieldName(field);
+
+                if (fn[0] == '#') {
+                    continue;
+                }
 
                 // Cannot be complex
                 rsAssert(!f->getFieldCount());
@@ -162,17 +182,16 @@
                     rsAssert(0);
                 }
 
-                mShader.append(e->getFieldName(field));
+                mShader.append(fn);
                 mShader.append(";\n");
             }
         }
         mShader.append(mUserShader);
     } else {
-        mShader.append("attribute vec4 ATTRIB_LegacyPosition;\n");
-        mShader.append("attribute vec4 ATTRIB_LegacyColor;\n");
-        mShader.append("attribute vec3 ATTRIB_LegacyNormal;\n");
-        mShader.append("attribute float ATTRIB_LegacyPointSize;\n");
-        mShader.append("attribute vec4 ATTRIB_LegacyTexture;\n");
+        mShader.append("attribute vec4 ATTRIB_position;\n");
+        mShader.append("attribute vec4 ATTRIB_color;\n");
+        mShader.append("attribute vec3 ATTRIB_normal;\n");
+        mShader.append("attribute vec4 ATTRIB_texture0;\n");
 
         for (uint32_t ct=0; ct < mUniformCount; ct++) {
             mShader.append("uniform mat4 ");
@@ -181,18 +200,15 @@
         }
 
         mShader.append("void main() {\n");
-        mShader.append("  gl_Position = UNI_MVP * ATTRIB_LegacyPosition;\n");
-        mShader.append("  gl_PointSize = ATTRIB_LegacyPointSize;\n");
+        mShader.append("  gl_Position = UNI_MVP * ATTRIB_position;\n");
+        mShader.append("  gl_PointSize = 1.0;\n");
 
-        mShader.append("  varColor = ATTRIB_LegacyColor;\n");
+        mShader.append("  varColor = ATTRIB_color;\n");
         if (mTextureMatrixEnable) {
-            mShader.append("  varTex0 = UNI_TexMatrix * ATTRIB_LegacyTexture;\n");
+            mShader.append("  varTex0 = UNI_TexMatrix * ATTRIB_texture0;\n");
         } else {
-            mShader.append("  varTex0 = ATTRIB_LegacyTexture;\n");
+            mShader.append("  varTex0 = ATTRIB_texture0;\n");
         }
-        //mShader.append("  pos.x = pos.x / 480.0;\n");
-        //mShader.append("  pos.y = pos.y / 800.0;\n");
-        //mShader.append("  gl_Position = pos;\n");
         mShader.append("}\n");
     }
 }
@@ -201,11 +217,10 @@
 {
     //LOGE("sgl2 vtx1 %x", glGetError());
     if ((state->mLast.get() == this) && !mDirty) {
-        //return;
+        return;
     }
 
     rsc->checkError("ProgramVertex::setupGL2 start");
-    glVertexAttrib4f(1, state->color[0], state->color[1], state->color[2], state->color[3]);
 
     const float *f = static_cast<const float *>(mConstants[0]->getPtr());
 
@@ -351,6 +366,16 @@
     createShader();
 }
 
+void ProgramVertex::serialize(OStream *stream) const
+{
+
+}
+
+ProgramVertex *ProgramVertex::createFromStream(Context *rsc, IStream *stream)
+{
+    return NULL;
+}
+
 
 ///////////////////////////////////////////////////////////////////////
 
@@ -362,8 +387,9 @@
 {
 }
 
-void ProgramVertexState::init(Context *rsc, int32_t w, int32_t h)
+void ProgramVertexState::init(Context *rsc)
 {
+#ifndef ANDROID_RS_BUILD_FOR_HOST
     RsElement e = (RsElement) Element::create(rsc, RS_TYPE_FLOAT_32, RS_KIND_USER, false, 1);
 
     rsi_TypeBegin(rsc, e);
@@ -372,23 +398,21 @@
 
     ProgramVertex *pv = new ProgramVertex(rsc, false);
     Allocation *alloc = (Allocation *)rsi_AllocationCreateTyped(rsc, mAllocType.get());
+
     mDefaultAlloc.set(alloc);
     mDefault.set(pv);
     pv->init(rsc);
     pv->bindAllocation(alloc, 0);
 
-    color[0] = 1.f;
-    color[1] = 1.f;
-    color[2] = 1.f;
-    color[3] = 1.f;
+    updateSize(rsc);
+#endif //ANDROID_RS_BUILD_FOR_HOST
 
-    updateSize(rsc, w, h);
 }
 
-void ProgramVertexState::updateSize(Context *rsc, int32_t w, int32_t h)
+void ProgramVertexState::updateSize(Context *rsc)
 {
     Matrix m;
-    m.loadOrtho(0,w, h,0, -1,1);
+    m.loadOrtho(0,rsc->getWidth(), rsc->getHeight(),0, -1,1);
     mDefaultAlloc->subData(RS_PROGRAM_VERTEX_PROJECTION_OFFSET, 16, &m.m[0], 16*4);
 
     m.loadIdentity();
diff --git a/libs/rs/rsProgramVertex.h b/libs/rs/rsProgramVertex.h
index 28554cc..1c8b9c8 100644
--- a/libs/rs/rsProgramVertex.h
+++ b/libs/rs/rsProgramVertex.h
@@ -52,6 +52,9 @@
     virtual void loadShader(Context *);
     virtual void init(Context *);
 
+    virtual void serialize(OStream *stream) const;
+    virtual RsA3DClassID getClassId() const { return RS_A3D_CLASS_ID_PROGRAM_VERTEX; }
+    static ProgramVertex *createFromStream(Context *rsc, IStream *stream);
 
 protected:
     uint32_t mLightCount;
@@ -71,18 +74,15 @@
     ProgramVertexState();
     ~ProgramVertexState();
 
-    void init(Context *rsc, int32_t w, int32_t h);
+    void init(Context *rsc);
     void deinit(Context *rsc);
-    void updateSize(Context *rsc, int32_t w, int32_t h);
+    void updateSize(Context *rsc);
 
     ObjectBaseRef<ProgramVertex> mDefault;
     ObjectBaseRef<ProgramVertex> mLast;
     ObjectBaseRef<Allocation> mDefaultAlloc;
 
     ObjectBaseRef<Type> mAllocType;
-
-
-    float color[4];
 };
 
 
diff --git a/libs/rs/rsSampler.cpp b/libs/rs/rsSampler.cpp
index 71f508f..f41f295 100644
--- a/libs/rs/rsSampler.cpp
+++ b/libs/rs/rsSampler.cpp
@@ -14,10 +14,16 @@
  * limitations under the License.
  */
 
+#ifndef ANDROID_RS_BUILD_FOR_HOST
 #include <GLES/gl.h>
 #include <GLES/glext.h>
-
 #include "rsContext.h"
+#else
+#include "rsContextHostStub.h"
+#include <OpenGL/gl.h>
+#include <OpenGL/glext.h>
+#endif //ANDROID_RS_BUILD_FOR_HOST
+
 #include "rsSampler.h"
 
 
@@ -94,6 +100,17 @@
     mBoundSlot = -1;
     ss->mSamplers[slot].clear();
 }
+
+void Sampler::serialize(OStream *stream) const
+{
+    
+}
+
+Sampler *Sampler::createFromStream(Context *rsc, IStream *stream)
+{
+    return NULL;
+}
+
 /*
 void SamplerState::setupGL()
 {
diff --git a/libs/rs/rsSampler.h b/libs/rs/rsSampler.h
index 0506081..3786439 100644
--- a/libs/rs/rsSampler.h
+++ b/libs/rs/rsSampler.h
@@ -46,6 +46,10 @@
     void bindToContext(SamplerState *, uint32_t slot);
     void unbindFromContext(SamplerState *);
 
+    virtual void serialize(OStream *stream) const;
+    virtual RsA3DClassID getClassId() const { return RS_A3D_CLASS_ID_SAMPLER; }
+    static Sampler *createFromStream(Context *rsc, IStream *stream);
+
 protected:
     RsSamplerValue mMagFilter;
     RsSamplerValue mMinFilter;
diff --git a/libs/rs/rsScript.cpp b/libs/rs/rsScript.cpp
index a33933b..fc22fc3 100644
--- a/libs/rs/rsScript.cpp
+++ b/libs/rs/rsScript.cpp
@@ -24,19 +24,25 @@
     mAllocFile = __FILE__;
     mAllocLine = __LINE__;
     memset(&mEnviroment, 0, sizeof(mEnviroment));
-    mEnviroment.mClearColor[0] = 0;
-    mEnviroment.mClearColor[1] = 0;
-    mEnviroment.mClearColor[2] = 0;
-    mEnviroment.mClearColor[3] = 1;
-    mEnviroment.mClearDepth = 1;
-    mEnviroment.mClearStencil = 0;
-    mEnviroment.mIsRoot = false;
 }
 
 Script::~Script()
 {
 }
 
+void Script::setVar(uint32_t slot, const void *val, uint32_t len)
+{
+    int32_t *destPtr = ((int32_t **)mEnviroment.mFieldAddress)[slot];
+    if (destPtr) {
+        //LOGE("setVar f1  %f", ((const float *)destPtr)[0]);
+        //LOGE("setVar %p %i", destPtr, len);
+        memcpy(destPtr, val, len);
+        //LOGE("setVar f2  %f", ((const float *)destPtr)[0]);
+    } else {
+        LOGE("Calling setVar on slot = %i which is null", slot);
+    }
+}
+
 namespace android {
 namespace renderscript {
 
@@ -44,16 +50,9 @@
 void rsi_ScriptBindAllocation(Context * rsc, RsScript vs, RsAllocation va, uint32_t slot)
 {
     Script *s = static_cast<Script *>(vs);
-    s->mSlots[slot].set(static_cast<Allocation *>(va));
-}
-
-void rsi_ScriptSetClearColor(Context * rsc, RsScript vs, float r, float g, float b, float a)
-{
-    Script *s = static_cast<Script *>(vs);
-    s->mEnviroment.mClearColor[0] = r;
-    s->mEnviroment.mClearColor[1] = g;
-    s->mEnviroment.mClearColor[2] = b;
-    s->mEnviroment.mClearColor[3] = a;
+    Allocation *a = static_cast<Allocation *>(va);
+    s->mSlots[slot].set(a);
+    //LOGE("rsi_ScriptBindAllocation %i  %p  %p", slot, a, a->getPtr());
 }
 
 void rsi_ScriptSetTimeZone(Context * rsc, RsScript vs, const char * timeZone, uint32_t length)
@@ -62,53 +61,51 @@
     s->mEnviroment.mTimeZone = timeZone;
 }
 
-void rsi_ScriptSetClearDepth(Context * rsc, RsScript vs, float v)
-{
-    Script *s = static_cast<Script *>(vs);
-    s->mEnviroment.mClearDepth = v;
-}
-
-void rsi_ScriptSetClearStencil(Context * rsc, RsScript vs, uint32_t v)
-{
-    Script *s = static_cast<Script *>(vs);
-    s->mEnviroment.mClearStencil = v;
-}
-
 void rsi_ScriptSetType(Context * rsc, RsType vt, uint32_t slot, bool writable, const char *name)
 {
     ScriptCState *ss = &rsc->mScriptC;
     const Type *t = static_cast<const Type *>(vt);
     ss->mConstantBufferTypes[slot].set(t);
     ss->mSlotWritable[slot] = writable;
-    if (name) {
-        ss->mSlotNames[slot].setTo(name);
-    } else {
-        ss->mSlotNames[slot].setTo("");
-    }
-}
-
-void rsi_ScriptSetInvoke(Context *rsc, const char *name, uint32_t slot)
-{
-    ScriptCState *ss = &rsc->mScriptC;
-    ss->mInvokableNames[slot] = name;
+    LOGE("rsi_ScriptSetType");
 }
 
 void rsi_ScriptInvoke(Context *rsc, RsScript vs, uint32_t slot)
 {
     Script *s = static_cast<Script *>(vs);
-    if (s->mEnviroment.mInvokables[slot] == NULL) {
-        rsc->setError(RS_ERROR_BAD_SCRIPT, "Calling invoke on bad script");
-        return;
-    }
-    s->setupScript();
-    s->mEnviroment.mInvokables[slot]();
+    s->Invoke(rsc, slot, NULL, 0);
 }
 
 
-void rsi_ScriptSetRoot(Context * rsc, bool isRoot)
+void rsi_ScriptInvokeData(Context *rsc, RsScript vs, uint32_t slot, void *data)
 {
-    ScriptCState *ss = &rsc->mScriptC;
-    ss->mScript->mEnviroment.mIsRoot = isRoot;
+    Script *s = static_cast<Script *>(vs);
+    s->Invoke(rsc, slot, NULL, 0);
+}
+
+void rsi_ScriptInvokeV(Context *rsc, RsScript vs, uint32_t slot, const void *data, uint32_t len)
+{
+    Script *s = static_cast<Script *>(vs);
+    s->Invoke(rsc, slot, data, len);
+}
+
+void rsi_ScriptSetVarI(Context *rsc, RsScript vs, uint32_t slot, int value)
+{
+    Script *s = static_cast<Script *>(vs);
+    s->setVar(slot, &value, sizeof(value));
+}
+
+void rsi_ScriptSetVarF(Context *rsc, RsScript vs, uint32_t slot, float value)
+{
+    Script *s = static_cast<Script *>(vs);
+    s->setVar(slot, &value, sizeof(value));
+}
+
+void rsi_ScriptSetVarV(Context *rsc, RsScript vs, uint32_t slot, const void *data, uint32_t len)
+{
+    const float *fp = (const float *)data;
+    Script *s = static_cast<Script *>(vs);
+    s->setVar(slot, data, len);
 }
 
 
diff --git a/libs/rs/rsScript.h b/libs/rs/rsScript.h
index 5f4a536..455ece7 100644
--- a/libs/rs/rsScript.h
+++ b/libs/rs/rsScript.h
@@ -27,9 +27,9 @@
 class ProgramVertex;
 class ProgramFragment;
 class ProgramRaster;
-class ProgramFragmentStore;
+class ProgramStore;
 
-#define MAX_SCRIPT_BANKS 16
+#define MAX_SCRIPT_BANKS 32
 
 class Script : public ObjectBase
 {
@@ -39,38 +39,41 @@
     Script(Context *);
     virtual ~Script();
 
-
     struct Enviroment_t {
-        bool mIsRoot;
-        float mClearColor[4];
-        float mClearDepth;
-        uint32_t mClearStencil;
-
-        uint32_t mStartTimeMillis;
+        int64_t mStartTimeMillis;
+        int64_t mLastDtTime;
         const char* mTimeZone;
 
         ObjectBaseRef<ProgramVertex> mVertex;
         ObjectBaseRef<ProgramFragment> mFragment;
         ObjectBaseRef<ProgramRaster> mRaster;
-        ObjectBaseRef<ProgramFragmentStore> mFragmentStore;
-        InvokeFunc_t mInvokables[MAX_SCRIPT_BANKS];
+        ObjectBaseRef<ProgramStore> mFragmentStore;
+
+        uint32_t mInvokeFunctionCount;
+        InvokeFunc_t *mInvokeFunctions;
+        uint32_t mFieldCount;
+        void ** mFieldAddress;
+
         char * mScriptText;
         uint32_t mScriptTextLength;
     };
     Enviroment_t mEnviroment;
 
-    uint32_t mCounstantBufferCount;
-
-
     ObjectBaseRef<Allocation> mSlots[MAX_SCRIPT_BANKS];
     ObjectBaseRef<const Type> mTypes[MAX_SCRIPT_BANKS];
-    String8 mSlotNames[MAX_SCRIPT_BANKS];
     bool mSlotWritable[MAX_SCRIPT_BANKS];
 
+    void setVar(uint32_t slot, const void *val, uint32_t len);
 
+    virtual void runForEach(Context *rsc,
+                            const Allocation * ain,
+                            Allocation * aout,
+                            const void * usr,
+                            const RsScriptCall *sc = NULL) = 0;
 
-    virtual void setupScript() = 0;
-    virtual uint32_t run(Context *, uint32_t launchID) = 0;
+    virtual void Invoke(Context *rsc, uint32_t slot, const void *data, uint32_t len) = 0;
+    virtual void setupScript(Context *rsc) = 0;
+    virtual uint32_t run(Context *) = 0;
 };
 
 
diff --git a/libs/rs/rsScriptC.cpp b/libs/rs/rsScriptC.cpp
index f4d2451..7c7b037 100644
--- a/libs/rs/rsScriptC.cpp
+++ b/libs/rs/rsScriptC.cpp
@@ -17,8 +17,7 @@
 #include "rsContext.h"
 #include "rsScriptC.h"
 #include "rsMatrix.h"
-
-#include "acc/acc.h"
+#include "../../../external/llvm/libbcc/include/bcc/bcc.h"
 #include "utils/Timers.h"
 
 #include <GLES/gl.h>
@@ -37,40 +36,74 @@
 {
     mAllocFile = __FILE__;
     mAllocLine = __LINE__;
-    mAccScript = NULL;
+    mBccScript = NULL;
     memset(&mProgram, 0, sizeof(mProgram));
 }
 
 ScriptC::~ScriptC()
 {
-    if (mAccScript) {
-        accDeleteScript(mAccScript);
+    if (mBccScript) {
+        bccDeleteScript(mBccScript);
     }
     free(mEnviroment.mScriptText);
     mEnviroment.mScriptText = NULL;
 }
 
-void ScriptC::setupScript()
+void ScriptC::setupScript(Context *rsc)
 {
-    for (int ct=0; ct < MAX_SCRIPT_BANKS; ct++) {
-        if (mProgram.mSlotPointers[ct]) {
-            *mProgram.mSlotPointers[ct] = mSlots[ct]->getPtr();
+    setupGLState(rsc);
+    mEnviroment.mStartTimeMillis
+                = nanoseconds_to_milliseconds(systemTime(SYSTEM_TIME_MONOTONIC));
+
+    for (uint32_t ct=0; ct < mEnviroment.mFieldCount; ct++) {
+        if (!mSlots[ct].get())
+            continue;
+        void *ptr = mSlots[ct]->getPtr();
+        void **dest = ((void ***)mEnviroment.mFieldAddress)[ct];
+        //LOGE("setupScript %i %p = %p    %p %i", ct, dest, ptr, mSlots[ct]->getType(), mSlots[ct]->getType()->getDimX());
+
+        //const uint32_t *p32 = (const uint32_t *)ptr;
+        //for (uint32_t ct2=0; ct2 < mSlots[ct]->getType()->getDimX(); ct2++) {
+            //LOGE("  %i = 0x%08x ", ct2, p32[ct2]);
+        //}
+
+        if (dest) {
+            *dest = ptr;
+        } else {
+            LOGE("ScriptC::setupScript, NULL var binding address.");
         }
     }
 }
 
-
-uint32_t ScriptC::run(Context *rsc, uint32_t launchIndex)
+const Allocation *ScriptC::ptrToAllocation(const void *ptr) const
 {
-    if (mProgram.mScript == NULL) {
-        rsc->setError(RS_ERROR_BAD_SCRIPT, "Attempted to run bad script");
-        return 0;
+    if (!ptr) {
+        return NULL;
     }
+    for (uint32_t ct=0; ct < mEnviroment.mFieldCount; ct++) {
+        if (!mSlots[ct].get())
+            continue;
+        if (mSlots[ct]->getPtr() == ptr) {
+            return mSlots[ct].get();
+        }
+    }
+    LOGE("ScriptC::ptrToAllocation, failed to find %p", ptr);
+    return NULL;
+}
 
-    Context::ScriptTLSStruct * tls =
-    (Context::ScriptTLSStruct *)pthread_getspecific(Context::gThreadTLSKey);
+Script * ScriptC::setTLS(Script *sc)
+{
+    Context::ScriptTLSStruct * tls = (Context::ScriptTLSStruct *)
+                                  pthread_getspecific(Context::gThreadTLSKey);
     rsAssert(tls);
+    Script *old = tls->mScript;
+    tls->mScript = sc;
+    return old;
+}
 
+
+void ScriptC::setupGLState(Context *rsc)
+{
     if (mEnviroment.mFragmentStore.get()) {
         rsc->setFragmentStore(mEnviroment.mFragmentStore.get());
     }
@@ -83,20 +116,214 @@
     if (mEnviroment.mRaster.get()) {
         rsc->setRaster(mEnviroment.mRaster.get());
     }
+}
 
-    if (launchIndex == 0) {
-        mEnviroment.mStartTimeMillis
-                = nanoseconds_to_milliseconds(systemTime(SYSTEM_TIME_MONOTONIC));
+uint32_t ScriptC::run(Context *rsc)
+{
+    if (mProgram.mRoot == NULL) {
+        rsc->setError(RS_ERROR_BAD_SCRIPT, "Attempted to run bad script");
+        return 0;
     }
-    setupScript();
+
+    setupScript(rsc);
 
     uint32_t ret = 0;
-    tls->mScript = this;
-    ret = mProgram.mScript(launchIndex);
-    tls->mScript = NULL;
+    Script * oldTLS = setTLS(this);
+    //LOGE("ScriptC::run %p", mProgram.mRoot);
+    ret = mProgram.mRoot();
+    setTLS(oldTLS);
+    //LOGE("ScriptC::run ret %i", ret);
     return ret;
 }
 
+
+typedef struct {
+    Context *rsc;
+    ScriptC *script;
+    const Allocation * ain;
+    Allocation * aout;
+    const void * usr;
+
+    uint32_t mSliceSize;
+    volatile int mSliceNum;
+
+    const uint8_t *ptrIn;
+    uint32_t eStrideIn;
+    uint8_t *ptrOut;
+    uint32_t eStrideOut;
+
+    uint32_t xStart;
+    uint32_t xEnd;
+    uint32_t yStart;
+    uint32_t yEnd;
+    uint32_t zStart;
+    uint32_t zEnd;
+    uint32_t arrayStart;
+    uint32_t arrayEnd;
+
+    uint32_t dimX;
+    uint32_t dimY;
+    uint32_t dimZ;
+    uint32_t dimArray;
+} MTLaunchStruct;
+typedef int (*rs_t)(const void *, void *, const void *, uint32_t, uint32_t, uint32_t, uint32_t);
+
+static void wc_xy(void *usr, uint32_t idx)
+{
+    MTLaunchStruct *mtls = (MTLaunchStruct *)usr;
+
+    while (1) {
+        uint32_t slice = (uint32_t)android_atomic_inc(&mtls->mSliceNum);
+        uint32_t yStart = mtls->yStart + slice * mtls->mSliceSize;
+        uint32_t yEnd = yStart + mtls->mSliceSize;
+        yEnd = rsMin(yEnd, mtls->yEnd);
+        if (yEnd <= yStart) {
+            return;
+        }
+
+        //LOGE("usr idx %i, x %i,%i  y %i,%i", idx, mtls->xStart, mtls->xEnd, yStart, yEnd);
+
+        for (uint32_t y = yStart; y < yEnd; y++) {
+            uint32_t offset = mtls->dimX * y;
+            uint8_t *xPtrOut = mtls->ptrOut + (mtls->eStrideOut * offset);
+            const uint8_t *xPtrIn = mtls->ptrIn + (mtls->eStrideIn * offset);
+
+            for (uint32_t x = mtls->xStart; x < mtls->xEnd; x++) {
+                ((rs_t)mtls->script->mProgram.mRoot) (xPtrIn, xPtrOut, mtls->usr, x, y, 0, 0);
+                xPtrIn += mtls->eStrideIn;
+                xPtrOut += mtls->eStrideOut;
+            }
+        }
+    }
+
+}
+
+void ScriptC::runForEach(Context *rsc,
+                         const Allocation * ain,
+                         Allocation * aout,
+                         const void * usr,
+                         const RsScriptCall *sc)
+{
+    MTLaunchStruct mtls;
+    memset(&mtls, 0, sizeof(mtls));
+
+    if (ain) {
+        mtls.dimX = ain->getType()->getDimX();
+        mtls.dimY = ain->getType()->getDimY();
+        mtls.dimZ = ain->getType()->getDimZ();
+        //mtls.dimArray = ain->getType()->getDimArray();
+    } else if (aout) {
+        mtls.dimX = aout->getType()->getDimX();
+        mtls.dimY = aout->getType()->getDimY();
+        mtls.dimZ = aout->getType()->getDimZ();
+        //mtls.dimArray = aout->getType()->getDimArray();
+    } else {
+        rsc->setError(RS_ERROR_BAD_SCRIPT, "rsForEach called with null allocations");
+        return;
+    }
+
+    if (!sc || (sc->xEnd == 0)) {
+        mtls.xEnd = mtls.dimX;
+    } else {
+        rsAssert(sc->xStart < mtls.dimX);
+        rsAssert(sc->xEnd <= mtls.dimX);
+        rsAssert(sc->xStart < sc->xEnd);
+        mtls.xStart = rsMin(mtls.dimX, sc->xStart);
+        mtls.xEnd = rsMin(mtls.dimX, sc->xEnd);
+        if (mtls.xStart >= mtls.xEnd) return;
+    }
+
+    if (!sc || (sc->yEnd == 0)) {
+        mtls.yEnd = mtls.dimY;
+    } else {
+        rsAssert(sc->yStart < mtls.dimY);
+        rsAssert(sc->yEnd <= mtls.dimY);
+        rsAssert(sc->yStart < sc->yEnd);
+        mtls.yStart = rsMin(mtls.dimY, sc->yStart);
+        mtls.yEnd = rsMin(mtls.dimY, sc->yEnd);
+        if (mtls.yStart >= mtls.yEnd) return;
+    }
+
+    mtls.xEnd = rsMax((uint32_t)1, mtls.xEnd);
+    mtls.yEnd = rsMax((uint32_t)1, mtls.yEnd);
+    mtls.zEnd = rsMax((uint32_t)1, mtls.zEnd);
+    mtls.arrayEnd = rsMax((uint32_t)1, mtls.arrayEnd);
+
+    rsAssert(ain->getType()->getDimZ() == 0);
+
+    setupScript(rsc);
+    Script * oldTLS = setTLS(this);
+
+
+    mtls.rsc = rsc;
+    mtls.ain = ain;
+    mtls.aout = aout;
+    mtls.script = this;
+    mtls.usr = usr;
+    mtls.mSliceSize = 10;
+    mtls.mSliceNum = 0;
+
+    mtls.ptrIn = NULL;
+    mtls.eStrideIn = 0;
+    if (ain) {
+        mtls.ptrIn = (const uint8_t *)ain->getPtr();
+        mtls.eStrideIn = ain->getType()->getElementSizeBytes();
+    }
+
+    mtls.ptrOut = NULL;
+    mtls.eStrideOut = 0;
+    if (aout) {
+        mtls.ptrOut = (uint8_t *)aout->getPtr();
+        mtls.eStrideOut = aout->getType()->getElementSizeBytes();
+    }
+
+
+    if ((rsc->getWorkerPoolSize() > 1) &&
+        ((mtls.dimY * mtls.dimZ * mtls.dimArray) > 1)) {
+
+        //LOGE("launch 1");
+        rsc->launchThreads(wc_xy, &mtls);
+        //LOGE("launch 2");
+    } else {
+        for (uint32_t ar = mtls.arrayStart; ar < mtls.arrayEnd; ar++) {
+            for (uint32_t z = mtls.zStart; z < mtls.zEnd; z++) {
+                for (uint32_t y = mtls.yStart; y < mtls.yEnd; y++) {
+                    uint32_t offset = mtls.dimX * mtls.dimY * mtls.dimZ * ar +
+                                      mtls.dimX * mtls.dimY * z +
+                                      mtls.dimX * y;
+                    uint8_t *xPtrOut = mtls.ptrOut + (mtls.eStrideOut * offset);
+                    const uint8_t *xPtrIn = mtls.ptrIn + (mtls.eStrideIn * offset);
+
+                    for (uint32_t x = mtls.xStart; x < mtls.xEnd; x++) {
+                        ((rs_t)mProgram.mRoot) (xPtrIn, xPtrOut, usr, x, y, z, ar);
+                        xPtrIn += mtls.eStrideIn;
+                        xPtrOut += mtls.eStrideOut;
+                    }
+                }
+            }
+        }
+    }
+
+    setTLS(oldTLS);
+}
+
+void ScriptC::Invoke(Context *rsc, uint32_t slot, const void *data, uint32_t len)
+{
+    //LOGE("rsi_ScriptInvoke %i", slot);
+    if ((slot >= mEnviroment.mInvokeFunctionCount) ||
+        (mEnviroment.mInvokeFunctions[slot] == NULL)) {
+        rsc->setError(RS_ERROR_BAD_SCRIPT, "Calling invoke on bad script");
+        return;
+    }
+    setupScript(rsc);
+    Script * oldTLS = setTLS(this);
+
+    ((void (*)(const void *, uint32_t))
+        mEnviroment.mInvokeFunctions[slot])(data, len);
+
+    setTLS(oldTLS);
+}
+
 ScriptCState::ScriptCState()
 {
     mScript = NULL;
@@ -113,21 +340,25 @@
 {
     for (uint32_t ct=0; ct < MAX_SCRIPT_BANKS; ct++) {
         mConstantBufferTypes[ct].clear();
-        mSlotNames[ct].setTo("");
-        mInvokableNames[ct].setTo("");
         mSlotWritable[ct] = false;
     }
 
     delete mScript;
     mScript = new ScriptC(NULL);
-
-    mInt32Defines.clear();
-    mFloatDefines.clear();
 }
 
-static ACCvoid* symbolLookup(ACCvoid* pContext, const ACCchar* name)
+static BCCvoid* symbolLookup(BCCvoid* pContext, const BCCchar* name)
 {
-    const ScriptCState::SymbolTable_t *sym = ScriptCState::lookupSymbol(name);
+    const ScriptCState::SymbolTable_t *sym;
+    sym = ScriptCState::lookupSymbol(name);
+    if (sym) {
+        return sym->mPtr;
+    }
+    sym = ScriptCState::lookupSymbolCL(name);
+    if (sym) {
+        return sym->mPtr;
+    }
+    sym = ScriptCState::lookupSymbolGL(name);
     if (sym) {
         return sym->mPtr;
     }
@@ -137,65 +368,52 @@
 
 void ScriptCState::runCompiler(Context *rsc, ScriptC *s)
 {
-    s->mAccScript = accCreateScript();
-    String8 tmp;
+    LOGV("ScriptCState::runCompiler ");
 
-    rsc->appendNameDefines(&tmp);
-    appendDecls(&tmp);
-    appendVarDefines(rsc, &tmp);
-    appendTypes(rsc, &tmp);
-    tmp.append("#line 1\n");
-
-    const char* scriptSource[] = {tmp.string(), s->mEnviroment.mScriptText};
-    int scriptLength[] = {tmp.length(), s->mEnviroment.mScriptTextLength} ;
-    accScriptSource(s->mAccScript, sizeof(scriptLength) / sizeof(int), scriptSource, scriptLength);
-    accRegisterSymbolCallback(s->mAccScript, symbolLookup, NULL);
-    accCompileScript(s->mAccScript);
-    accGetScriptLabel(s->mAccScript, "main", (ACCvoid**) &s->mProgram.mScript);
-    accGetScriptLabel(s->mAccScript, "init", (ACCvoid**) &s->mProgram.mInit);
-    rsAssert(s->mProgram.mScript);
-
-    if (!s->mProgram.mScript) {
-        ACCchar buf[4096];
-        ACCsizei len;
-        accGetScriptInfoLog(s->mAccScript, sizeof(buf), &len, buf);
-        LOGE("%s", buf);
-        rsc->setError(RS_ERROR_BAD_SCRIPT, "Error compiling user script.");
-        return;
-    }
+    s->mBccScript = bccCreateScript();
+    bccScriptBitcode(s->mBccScript, s->mEnviroment.mScriptText, s->mEnviroment.mScriptTextLength);
+    bccRegisterSymbolCallback(s->mBccScript, symbolLookup, NULL);
+    bccCompileScript(s->mBccScript);
+    bccGetScriptLabel(s->mBccScript, "root", (BCCvoid**) &s->mProgram.mRoot);
+    bccGetScriptLabel(s->mBccScript, "init", (BCCvoid**) &s->mProgram.mInit);
+    LOGV("root %p,  init %p", s->mProgram.mRoot, s->mProgram.mInit);
 
     if (s->mProgram.mInit) {
         s->mProgram.mInit();
     }
 
-    for (int ct=0; ct < MAX_SCRIPT_BANKS; ct++) {
-        if (mSlotNames[ct].length() > 0) {
-            accGetScriptLabel(s->mAccScript,
-                              mSlotNames[ct].string(),
-                              (ACCvoid**) &s->mProgram.mSlotPointers[ct]);
-        }
+    bccGetExportFuncs(s->mBccScript, (BCCsizei*) &s->mEnviroment.mInvokeFunctionCount, 0, NULL);
+    if(s->mEnviroment.mInvokeFunctionCount <= 0)
+        s->mEnviroment.mInvokeFunctions = NULL;
+    else {
+        s->mEnviroment.mInvokeFunctions = (Script::InvokeFunc_t*) calloc(s->mEnviroment.mInvokeFunctionCount, sizeof(Script::InvokeFunc_t));
+        bccGetExportFuncs(s->mBccScript, NULL, s->mEnviroment.mInvokeFunctionCount, (BCCvoid **) s->mEnviroment.mInvokeFunctions);
     }
 
-    for (int ct=0; ct < MAX_SCRIPT_BANKS; ct++) {
-        if (mInvokableNames[ct].length() > 0) {
-            accGetScriptLabel(s->mAccScript,
-                              mInvokableNames[ct].string(),
-                              (ACCvoid**) &s->mEnviroment.mInvokables[ct]);
-        }
+    bccGetExportVars(s->mBccScript, (BCCsizei*) &s->mEnviroment.mFieldCount, 0, NULL);
+    if(s->mEnviroment.mFieldCount <= 0)
+        s->mEnviroment.mFieldAddress = NULL;
+    else {
+        s->mEnviroment.mFieldAddress = (void **) calloc(s->mEnviroment.mFieldCount, sizeof(void *));
+        bccGetExportVars(s->mBccScript, NULL, s->mEnviroment.mFieldCount, (BCCvoid **) s->mEnviroment.mFieldAddress);
     }
+    //for (int ct2=0; ct2 < s->mEnviroment.mFieldCount; ct2++ ) {
+        //LOGE("Script field %i = %p", ct2, s->mEnviroment.mFieldAddress[ct2]);
+    //}
 
     s->mEnviroment.mFragment.set(rsc->getDefaultProgramFragment());
     s->mEnviroment.mVertex.set(rsc->getDefaultProgramVertex());
-    s->mEnviroment.mFragmentStore.set(rsc->getDefaultProgramFragmentStore());
+    s->mEnviroment.mFragmentStore.set(rsc->getDefaultProgramStore());
     s->mEnviroment.mRaster.set(rsc->getDefaultProgramRaster());
 
-    if (s->mProgram.mScript) {
+    if (s->mProgram.mRoot) {
         const static int pragmaMax = 16;
-        ACCsizei pragmaCount;
-        ACCchar * str[pragmaMax];
-        accGetPragmas(s->mAccScript, &pragmaCount, pragmaMax, &str[0]);
+        BCCsizei pragmaCount;
+        BCCchar * str[pragmaMax];
+        bccGetPragmas(s->mBccScript, &pragmaCount, pragmaMax, &str[0]);
 
         for (int ct=0; ct < pragmaCount; ct+=2) {
+            //LOGE("pragme %s %s", str[ct], str[ct+1]);
             if (!strcmp(str[ct], "version")) {
                 continue;
             }
@@ -208,11 +426,6 @@
                     s->mEnviroment.mVertex.clear();
                     continue;
                 }
-                ProgramVertex * pv = (ProgramVertex *)rsc->lookupName(str[ct+1]);
-                if (pv != NULL) {
-                    s->mEnviroment.mVertex.set(pv);
-                    continue;
-                }
                 LOGE("Unreconized value %s passed to stateVertex", str[ct+1]);
             }
 
@@ -224,11 +437,6 @@
                     s->mEnviroment.mRaster.clear();
                     continue;
                 }
-                ProgramRaster * pr = (ProgramRaster *)rsc->lookupName(str[ct+1]);
-                if (pr != NULL) {
-                    s->mEnviroment.mRaster.set(pr);
-                    continue;
-                }
                 LOGE("Unreconized value %s passed to stateRaster", str[ct+1]);
             }
 
@@ -240,11 +448,6 @@
                     s->mEnviroment.mFragment.clear();
                     continue;
                 }
-                ProgramFragment * pf = (ProgramFragment *)rsc->lookupName(str[ct+1]);
-                if (pf != NULL) {
-                    s->mEnviroment.mFragment.set(pf);
-                    continue;
-                }
                 LOGE("Unreconized value %s passed to stateFragment", str[ct+1]);
             }
 
@@ -256,12 +459,6 @@
                     s->mEnviroment.mFragmentStore.clear();
                     continue;
                 }
-                ProgramFragmentStore * pfs =
-                    (ProgramFragmentStore *)rsc->lookupName(str[ct+1]);
-                if (pfs != NULL) {
-                    s->mEnviroment.mFragmentStore.set(pfs);
-                    continue;
-                }
                 LOGE("Unreconized value %s passed to stateStore", str[ct+1]);
             }
 
@@ -273,111 +470,6 @@
     }
 }
 
-static void appendElementBody(String8 *s, const Element *e)
-{
-    s->append(" {\n");
-    for (size_t ct2=0; ct2 < e->getFieldCount(); ct2++) {
-        const Element *c = e->getField(ct2);
-        s->append("    ");
-        s->append(c->getCType());
-        s->append(" ");
-        s->append(e->getFieldName(ct2));
-        s->append(";\n");
-    }
-    s->append("}");
-}
-
-void ScriptCState::appendVarDefines(const Context *rsc, String8 *str)
-{
-    char buf[256];
-    if (rsc->props.mLogScripts) {
-        LOGD("appendVarDefines mInt32Defines.size()=%d mFloatDefines.size()=%d\n",
-                mInt32Defines.size(), mFloatDefines.size());
-    }
-    for (size_t ct=0; ct < mInt32Defines.size(); ct++) {
-        str->append("#define ");
-        str->append(mInt32Defines.keyAt(ct));
-        str->append(" ");
-        sprintf(buf, "%i\n", (int)mInt32Defines.valueAt(ct));
-        str->append(buf);
-    }
-    for (size_t ct=0; ct < mFloatDefines.size(); ct++) {
-        str->append("#define ");
-        str->append(mFloatDefines.keyAt(ct));
-        str->append(" ");
-        sprintf(buf, "%ff\n", mFloatDefines.valueAt(ct));
-        str->append(buf);
-    }
-}
-
-
-
-void ScriptCState::appendTypes(const Context *rsc, String8 *str)
-{
-    char buf[256];
-    String8 tmp;
-
-    str->append("struct vecF32_2_s {float x; float y;};\n");
-    str->append("struct vecF32_3_s {float x; float y; float z;};\n");
-    str->append("struct vecF32_4_s {float x; float y; float z; float w;};\n");
-    str->append("struct vecU8_4_s {char r; char g; char b; char a;};\n");
-    str->append("#define vecF32_2_t struct vecF32_2_s\n");
-    str->append("#define vecF32_3_t struct vecF32_3_s\n");
-    str->append("#define vecF32_4_t struct vecF32_4_s\n");
-    str->append("#define vecU8_4_t struct vecU8_4_s\n");
-    str->append("#define vecI8_4_t struct vecU8_4_s\n");
-
-    for (size_t ct=0; ct < MAX_SCRIPT_BANKS; ct++) {
-        const Type *t = mConstantBufferTypes[ct].get();
-        if (!t) {
-            continue;
-        }
-        const Element *e = t->getElement();
-        if (e->getName() && (e->getFieldCount() > 1)) {
-            String8 s("struct struct_");
-            s.append(e->getName());
-            s.append(e->getCStructBody());
-            s.append(";\n");
-
-            s.append("#define ");
-            s.append(e->getName());
-            s.append("_t struct struct_");
-            s.append(e->getName());
-            s.append("\n\n");
-            if (rsc->props.mLogScripts) {
-                LOGV("%s", static_cast<const char*>(s));
-            }
-            str->append(s);
-        }
-
-        if (mSlotNames[ct].length() > 0) {
-            String8 s;
-            if (e->getName()) {
-                // Use the named struct
-                s.setTo(e->getName());
-            } else {
-                // create an struct named from the slot.
-                s.setTo("struct ");
-                s.append(mSlotNames[ct]);
-                s.append("_s");
-                s.append(e->getCStructBody());
-                //appendElementBody(&s, e);
-                s.append(";\n");
-                s.append("struct ");
-                s.append(mSlotNames[ct]);
-                s.append("_s");
-            }
-
-            s.append(" * ");
-            s.append(mSlotNames[ct]);
-            s.append(";\n");
-            if (rsc->props.mLogScripts) {
-                LOGV("%s", static_cast<const char*>(s));
-            }
-            str->append(s);
-        }
-    }
-}
 
 
 namespace android {
@@ -420,7 +512,6 @@
     s->setContext(rsc);
     for (int ct=0; ct < MAX_SCRIPT_BANKS; ct++) {
         s->mTypes[ct].set(ss->mConstantBufferTypes[ct].get());
-        s->mSlotNames[ct] = ss->mSlotNames[ct];
         s->mSlotWritable[ct] = ss->mSlotWritable[ct];
     }
 
@@ -428,18 +519,6 @@
     return s;
 }
 
-void rsi_ScriptCSetDefineF(Context *rsc, const char* name, float value)
-{
-    ScriptCState *ss = &rsc->mScriptC;
-    ss->mFloatDefines.add(String8(name), value);
-}
-
-void rsi_ScriptCSetDefineI32(Context *rsc, const char* name, int32_t value)
-{
-    ScriptCState *ss = &rsc->mScriptC;
-    ss->mInt32Defines.add(String8(name), value);
-}
-
 }
 }
 
diff --git a/libs/rs/rsScriptC.h b/libs/rs/rsScriptC.h
index 35abadf..9d09b0b 100644
--- a/libs/rs/rsScriptC.h
+++ b/libs/rs/rsScriptC.h
@@ -21,9 +21,7 @@
 
 #include "RenderScriptEnv.h"
 
-#include <utils/KeyedVector.h>
-
-struct ACCscript;
+struct BCCscript;
 
 // ---------------------------------------------------------------------------
 namespace android {
@@ -34,7 +32,7 @@
 class ScriptC : public Script
 {
 public:
-    typedef int (*RunScript_t)(uint32_t launchIndex);
+    typedef int (*RunScript_t)();
     typedef void (*VoidFunc_t)();
 
     ScriptC(Context *);
@@ -44,18 +42,35 @@
         int mVersionMajor;
         int mVersionMinor;
 
-        RunScript_t mScript;
+        RunScript_t mRoot;
         VoidFunc_t mInit;
-
-        void ** mSlotPointers[MAX_SCRIPT_BANKS];
     };
 
     Program_t mProgram;
 
-    ACCscript*    mAccScript;
+    BCCscript*    mBccScript;
 
-    virtual void setupScript();
-    virtual uint32_t run(Context *, uint32_t launchID);
+    const Allocation *ptrToAllocation(const void *) const;
+
+
+    virtual void Invoke(Context *rsc, uint32_t slot, const void *data, uint32_t len);
+
+    virtual uint32_t run(Context *);
+
+    virtual void runForEach(Context *rsc,
+                            const Allocation * ain,
+                            Allocation * aout,
+                            const void * usr,
+                            const RsScriptCall *sc = NULL);
+
+    virtual void serialize(OStream *stream) const {    }
+    virtual RsA3DClassID getClassId() const { return RS_A3D_CLASS_ID_SCRIPT_C; }
+    static Type *createFromStream(Context *rsc, IStream *stream) { return NULL; }
+
+protected:
+    void setupScript(Context *);
+    void setupGLState(Context *);
+    Script * setTLS(Script *);
 };
 
 class ScriptCState
@@ -67,27 +82,21 @@
     ScriptC *mScript;
 
     ObjectBaseRef<const Type> mConstantBufferTypes[MAX_SCRIPT_BANKS];
-    String8 mSlotNames[MAX_SCRIPT_BANKS];
+    //String8 mSlotNames[MAX_SCRIPT_BANKS];
     bool mSlotWritable[MAX_SCRIPT_BANKS];
-    String8 mInvokableNames[MAX_SCRIPT_BANKS];
+    //String8 mInvokableNames[MAX_SCRIPT_BANKS];
 
     void clear();
     void runCompiler(Context *rsc, ScriptC *s);
-    void appendVarDefines(const Context *rsc, String8 *str);
-    void appendTypes(const Context *rsc, String8 *str);
 
     struct SymbolTable_t {
         const char * mName;
         void * mPtr;
-        const char * mRet;
-        const char * mParam;
     };
-    static SymbolTable_t gSyms[];
+    //static SymbolTable_t gSyms[];
     static const SymbolTable_t * lookupSymbol(const char *);
-    static void appendDecls(String8 *str);
-
-    KeyedVector<String8,int> mInt32Defines;
-    KeyedVector<String8,float> mFloatDefines;
+    static const SymbolTable_t * lookupSymbolCL(const char *);
+    static const SymbolTable_t * lookupSymbolGL(const char *);
 };
 
 
diff --git a/libs/rs/rsScriptC_Lib.cpp b/libs/rs/rsScriptC_Lib.cpp
index 202ca3d..dc30afc 100644
--- a/libs/rs/rsScriptC_Lib.cpp
+++ b/libs/rs/rsScriptC_Lib.cpp
@@ -17,18 +17,10 @@
 #include "rsContext.h"
 #include "rsScriptC.h"
 #include "rsMatrix.h"
-#include "rsNoise.h"
 
 #include "acc/acc.h"
 #include "utils/Timers.h"
 
-#define GL_GLEXT_PROTOTYPES
-
-#include <GLES/gl.h>
-#include <GLES/glext.h>
-#include <GLES2/gl2.h>
-#include <GLES2/gl2ext.h>
-
 #include <time.h>
 
 using namespace android;
@@ -39,252 +31,11 @@
     Context * rsc = tls->mContext; \
     ScriptC * sc = (ScriptC *) tls->mScript
 
-typedef struct {
-    float x;
-    float y;
-    float z;
-} vec3_t;
-
-typedef struct {
-    float x;
-    float y;
-    float z;
-    float w;
-} vec4_t;
-
-typedef struct {
-    float x;
-    float y;
-} vec2_t;
-
-//////////////////////////////////////////////////////////////////////////////
-// IO routines
-//////////////////////////////////////////////////////////////////////////////
-
-static float SC_loadF(uint32_t bank, uint32_t offset)
-{
-    GET_TLS();
-    const void *vp = sc->mSlots[bank]->getPtr();
-    const float *f = static_cast<const float *>(vp);
-    //LOGE("loadF %i %i = %f %x", bank, offset, f, ((int *)&f)[0]);
-    return f[offset];
-}
-
-static int32_t SC_loadI32(uint32_t bank, uint32_t offset)
-{
-    GET_TLS();
-    const void *vp = sc->mSlots[bank]->getPtr();
-    const int32_t *i = static_cast<const int32_t *>(vp);
-    //LOGE("loadI32 %i %i = %i", bank, offset, t);
-    return i[offset];
-}
-
-static float* SC_loadArrayF(uint32_t bank, uint32_t offset)
-{
-    GET_TLS();
-    void *vp = sc->mSlots[bank]->getPtr();
-    float *f = static_cast<float *>(vp);
-    return f + offset;
-}
-
-static int32_t* SC_loadArrayI32(uint32_t bank, uint32_t offset)
-{
-    GET_TLS();
-    void *vp = sc->mSlots[bank]->getPtr();
-    int32_t *i = static_cast<int32_t *>(vp);
-    return i + offset;
-}
-
-static float* SC_loadSimpleMeshVerticesF(RsSimpleMesh mesh, uint32_t idx)
-{
-    SimpleMesh *tm = static_cast<SimpleMesh *>(mesh);
-    void *vp = tm->mVertexBuffers[idx]->getPtr();;
-    return static_cast<float *>(vp);
-}
-
-static void SC_updateSimpleMesh(RsSimpleMesh mesh)
-{
-    GET_TLS();
-    SimpleMesh *sm = static_cast<SimpleMesh *>(mesh);
-    sm->uploadAll(rsc);
-}
-
-static uint32_t SC_loadU32(uint32_t bank, uint32_t offset)
-{
-    GET_TLS();
-    const void *vp = sc->mSlots[bank]->getPtr();
-    const uint32_t *i = static_cast<const uint32_t *>(vp);
-    return i[offset];
-}
-
-static void SC_loadVec4(uint32_t bank, uint32_t offset, rsc_Vector4 *v)
-{
-    GET_TLS();
-    const void *vp = sc->mSlots[bank]->getPtr();
-    const float *f = static_cast<const float *>(vp);
-    memcpy(v, &f[offset], sizeof(rsc_Vector4));
-}
-
-static void SC_loadMatrix(uint32_t bank, uint32_t offset, rsc_Matrix *m)
-{
-    GET_TLS();
-    const void *vp = sc->mSlots[bank]->getPtr();
-    const float *f = static_cast<const float *>(vp);
-    memcpy(m, &f[offset], sizeof(rsc_Matrix));
-}
-
-
-static void SC_storeF(uint32_t bank, uint32_t offset, float v)
-{
-    //LOGE("storeF %i %i %f", bank, offset, v);
-    GET_TLS();
-    void *vp = sc->mSlots[bank]->getPtr();
-    float *f = static_cast<float *>(vp);
-    f[offset] = v;
-}
-
-static void SC_storeI32(uint32_t bank, uint32_t offset, int32_t v)
-{
-    GET_TLS();
-    void *vp = sc->mSlots[bank]->getPtr();
-    int32_t *f = static_cast<int32_t *>(vp);
-    static_cast<int32_t *>(sc->mSlots[bank]->getPtr())[offset] = v;
-}
-
-static void SC_storeU32(uint32_t bank, uint32_t offset, uint32_t v)
-{
-    GET_TLS();
-    void *vp = sc->mSlots[bank]->getPtr();
-    uint32_t *f = static_cast<uint32_t *>(vp);
-    static_cast<uint32_t *>(sc->mSlots[bank]->getPtr())[offset] = v;
-}
-
-static void SC_storeVec4(uint32_t bank, uint32_t offset, const rsc_Vector4 *v)
-{
-    GET_TLS();
-    void *vp = sc->mSlots[bank]->getPtr();
-    float *f = static_cast<float *>(vp);
-    memcpy(&f[offset], v, sizeof(rsc_Vector4));
-}
-
-static void SC_storeMatrix(uint32_t bank, uint32_t offset, const rsc_Matrix *m)
-{
-    GET_TLS();
-    void *vp = sc->mSlots[bank]->getPtr();
-    float *f = static_cast<float *>(vp);
-    memcpy(&f[offset], m, sizeof(rsc_Matrix));
-}
-
-//////////////////////////////////////////////////////////////////////////////
-// Vec3 routines
-//////////////////////////////////////////////////////////////////////////////
-
-static void SC_vec3Norm(vec3_t *v)
-{
-    float len = sqrtf(v->x * v->x + v->y * v->y + v->z * v->z);
-    len = 1 / len;
-    v->x *= len;
-    v->y *= len;
-    v->z *= len;
-}
-
-static float SC_vec3Length(const vec3_t *v)
-{
-    return sqrtf(v->x * v->x + v->y * v->y + v->z * v->z);
-}
-
-static void SC_vec3Add(vec3_t *dest, const vec3_t *lhs, const vec3_t *rhs)
-{
-    dest->x = lhs->x + rhs->x;
-    dest->y = lhs->y + rhs->y;
-    dest->z = lhs->z + rhs->z;
-}
-
-static void SC_vec3Sub(vec3_t *dest, const vec3_t *lhs, const vec3_t *rhs)
-{
-    dest->x = lhs->x - rhs->x;
-    dest->y = lhs->y - rhs->y;
-    dest->z = lhs->z - rhs->z;
-}
-
-static void SC_vec3Cross(vec3_t *dest, const vec3_t *lhs, const vec3_t *rhs)
-{
-    float x = lhs->y * rhs->z  - lhs->z * rhs->y;
-    float y = lhs->z * rhs->x  - lhs->x * rhs->z;
-    float z = lhs->x * rhs->y  - lhs->y * rhs->x;
-    dest->x = x;
-    dest->y = y;
-    dest->z = z;
-}
-
-static float SC_vec3Dot(const vec3_t *lhs, const vec3_t *rhs)
-{
-    return lhs->x * rhs->x + lhs->y * rhs->y + lhs->z * rhs->z;
-}
-
-static void SC_vec3Scale(vec3_t *lhs, float scale)
-{
-    lhs->x *= scale;
-    lhs->y *= scale;
-    lhs->z *= scale;
-}
-
-//////////////////////////////////////////////////////////////////////////////
-// Vec4 routines
-//////////////////////////////////////////////////////////////////////////////
-
-static void SC_vec4Norm(vec4_t *v)
-{
-    float len = sqrtf(v->x * v->x + v->y * v->y + v->z * v->z + v->w * v->w);
-    len = 1 / len;
-    v->x *= len;
-    v->y *= len;
-    v->z *= len;
-    v->w *= len;
-}
-
-static float SC_vec4Length(const vec4_t *v)
-{
-    return sqrtf(v->x * v->x + v->y * v->y + v->z * v->z + v->w * v->w);
-}
-
-static void SC_vec4Add(vec4_t *dest, const vec4_t *lhs, const vec4_t *rhs)
-{
-    dest->x = lhs->x + rhs->x;
-    dest->y = lhs->y + rhs->y;
-    dest->z = lhs->z + rhs->z;
-    dest->w = lhs->w + rhs->w;
-}
-
-static void SC_vec4Sub(vec4_t *dest, const vec4_t *lhs, const vec4_t *rhs)
-{
-    dest->x = lhs->x - rhs->x;
-    dest->y = lhs->y - rhs->y;
-    dest->z = lhs->z - rhs->z;
-    dest->w = lhs->w - rhs->w;
-}
-
-static float SC_vec4Dot(const vec4_t *lhs, const vec4_t *rhs)
-{
-    return lhs->x * rhs->x + lhs->y * rhs->y + lhs->z * rhs->z + lhs->w * rhs->w;
-}
-
-static void SC_vec4Scale(vec4_t *lhs, float scale)
-{
-    lhs->x *= scale;
-    lhs->y *= scale;
-    lhs->z *= scale;
-    lhs->w *= scale;
-}
 
 //////////////////////////////////////////////////////////////////////////////
 // Math routines
 //////////////////////////////////////////////////////////////////////////////
 
-#define PI 3.1415926f
-#define DEG_TO_RAD PI / 180.0f
-#define RAD_TO_DEG 180.0f / PI
-
 static float SC_sinf_fast(float x)
 {
     const float A =   1.0f / (2.0f * M_PI);
@@ -323,6 +74,7 @@
     return 0.2215f * (y * fabsf(y) - y) + y;
 }
 
+
 static float SC_randf(float max)
 {
     float r = (float)rand();
@@ -335,104 +87,20 @@
     return r / RAND_MAX * (max - min) + min;
 }
 
-static int SC_sign(int value)
+static int SC_randi(int max)
 {
-    return (value > 0) - (value < 0);
+    return (int)SC_randf(max);
 }
 
-static float SC_signf(float value)
+static int SC_randi2(int min, int max)
 {
-    return (value > 0) - (value < 0);
+    return (int)SC_randf2(min, max);
 }
 
-static float SC_clampf(float amount, float low, float high)
+static float SC_frac(float v)
 {
-    return amount < low ? low : (amount > high ? high : amount);
-}
-
-static int SC_clamp(int amount, int low, int high)
-{
-    return amount < low ? low : (amount > high ? high : amount);
-}
-
-static float SC_maxf(float a, float b)
-{
-    return a > b ? a : b;
-}
-
-static float SC_minf(float a, float b)
-{
-    return a < b ? a : b;
-}
-
-static float SC_sqrf(float v)
-{
-    return v * v;
-}
-
-static int SC_sqr(int v)
-{
-    return v * v;
-}
-
-static float SC_fracf(float v)
-{
-    return v - floorf(v);
-}
-
-static float SC_roundf(float v)
-{
-    return floorf(v + 0.4999999999);
-}
-
-static float SC_distf2(float x1, float y1, float x2, float y2)
-{
-    float x = x2 - x1;
-    float y = y2 - y1;
-    return sqrtf(x * x + y * y);
-}
-
-static float SC_distf3(float x1, float y1, float z1, float x2, float y2, float z2)
-{
-    float x = x2 - x1;
-    float y = y2 - y1;
-    float z = z2 - z1;
-    return sqrtf(x * x + y * y + z * z);
-}
-
-static float SC_magf2(float a, float b)
-{
-    return sqrtf(a * a + b * b);
-}
-
-static float SC_magf3(float a, float b, float c)
-{
-    return sqrtf(a * a + b * b + c * c);
-}
-
-static float SC_radf(float degrees)
-{
-    return degrees * DEG_TO_RAD;
-}
-
-static float SC_degf(float radians)
-{
-    return radians * RAD_TO_DEG;
-}
-
-static float SC_lerpf(float start, float stop, float amount)
-{
-    return start + (stop - start) * amount;
-}
-
-static float SC_normf(float start, float stop, float value)
-{
-    return (value - start) / (stop - start);
-}
-
-static float SC_mapf(float minStart, float minStop, float maxStart, float maxStop, float value)
-{
-    return maxStart + (maxStart - maxStop) * ((value - minStart) / (minStop - minStart));
+    int i = (int)floor(v);
+    return fmin(v - i, 0x1.fffffep-1f);
 }
 
 //////////////////////////////////////////////////////////////////////////////
@@ -511,348 +179,22 @@
     return timeinfo->tm_year;
 }
 
-static int32_t SC_uptimeMillis()
+static int64_t SC_uptimeMillis()
 {
     return nanoseconds_to_milliseconds(systemTime(SYSTEM_TIME_MONOTONIC));
 }
 
-static int32_t SC_startTimeMillis()
+static int64_t SC_uptimeNanos()
+{
+    return systemTime(SYSTEM_TIME_MONOTONIC);
+}
+
+static float SC_getDt()
 {
     GET_TLS();
-    return sc->mEnviroment.mStartTimeMillis;
-}
-
-static int32_t SC_elapsedTimeMillis()
-{
-    GET_TLS();
-    return nanoseconds_to_milliseconds(systemTime(SYSTEM_TIME_MONOTONIC))
-            - sc->mEnviroment.mStartTimeMillis;
-}
-
-//////////////////////////////////////////////////////////////////////////////
-// Matrix routines
-//////////////////////////////////////////////////////////////////////////////
-
-
-static void SC_matrixLoadIdentity(rsc_Matrix *mat)
-{
-    Matrix *m = reinterpret_cast<Matrix *>(mat);
-    m->loadIdentity();
-}
-
-static void SC_matrixLoadFloat(rsc_Matrix *mat, const float *f)
-{
-    Matrix *m = reinterpret_cast<Matrix *>(mat);
-    m->load(f);
-}
-
-static void SC_matrixLoadMat(rsc_Matrix *mat, const rsc_Matrix *newmat)
-{
-    Matrix *m = reinterpret_cast<Matrix *>(mat);
-    m->load(reinterpret_cast<const Matrix *>(newmat));
-}
-
-static void SC_matrixLoadRotate(rsc_Matrix *mat, float rot, float x, float y, float z)
-{
-    Matrix *m = reinterpret_cast<Matrix *>(mat);
-    m->loadRotate(rot, x, y, z);
-}
-
-static void SC_matrixLoadScale(rsc_Matrix *mat, float x, float y, float z)
-{
-    Matrix *m = reinterpret_cast<Matrix *>(mat);
-    m->loadScale(x, y, z);
-}
-
-static void SC_matrixLoadTranslate(rsc_Matrix *mat, float x, float y, float z)
-{
-    Matrix *m = reinterpret_cast<Matrix *>(mat);
-    m->loadTranslate(x, y, z);
-}
-
-static void SC_matrixLoadMultiply(rsc_Matrix *mat, const rsc_Matrix *lhs, const rsc_Matrix *rhs)
-{
-    Matrix *m = reinterpret_cast<Matrix *>(mat);
-    m->loadMultiply(reinterpret_cast<const Matrix *>(lhs),
-                    reinterpret_cast<const Matrix *>(rhs));
-}
-
-static void SC_matrixMultiply(rsc_Matrix *mat, const rsc_Matrix *rhs)
-{
-    Matrix *m = reinterpret_cast<Matrix *>(mat);
-    m->multiply(reinterpret_cast<const Matrix *>(rhs));
-}
-
-static void SC_matrixRotate(rsc_Matrix *mat, float rot, float x, float y, float z)
-{
-    Matrix *m = reinterpret_cast<Matrix *>(mat);
-    m->rotate(rot, x, y, z);
-}
-
-static void SC_matrixScale(rsc_Matrix *mat, float x, float y, float z)
-{
-    Matrix *m = reinterpret_cast<Matrix *>(mat);
-    m->scale(x, y, z);
-}
-
-static void SC_matrixTranslate(rsc_Matrix *mat, float x, float y, float z)
-{
-    Matrix *m = reinterpret_cast<Matrix *>(mat);
-    m->translate(x, y, z);
-}
-
-
-static void SC_vec2Rand(float *vec, float maxLen)
-{
-    float angle = SC_randf(PI * 2);
-    float len = SC_randf(maxLen);
-    vec[0] = len * sinf(angle);
-    vec[1] = len * cosf(angle);
-}
-
-
-
-//////////////////////////////////////////////////////////////////////////////
-// Context
-//////////////////////////////////////////////////////////////////////////////
-
-static void SC_bindTexture(RsProgramFragment vpf, uint32_t slot, RsAllocation va)
-{
-    GET_TLS();
-    rsi_ProgramBindTexture(rsc,
-                           static_cast<ProgramFragment *>(vpf),
-                           slot,
-                           static_cast<Allocation *>(va));
-
-}
-
-static void SC_bindSampler(RsProgramFragment vpf, uint32_t slot, RsSampler vs)
-{
-    GET_TLS();
-    rsi_ProgramBindSampler(rsc,
-                           static_cast<ProgramFragment *>(vpf),
-                           slot,
-                           static_cast<Sampler *>(vs));
-
-}
-
-static void SC_bindProgramFragmentStore(RsProgramFragmentStore pfs)
-{
-    GET_TLS();
-    rsi_ContextBindProgramFragmentStore(rsc, pfs);
-
-}
-
-static void SC_bindProgramFragment(RsProgramFragment pf)
-{
-    GET_TLS();
-    rsi_ContextBindProgramFragment(rsc, pf);
-
-}
-
-static void SC_bindProgramVertex(RsProgramVertex pv)
-{
-    GET_TLS();
-    rsi_ContextBindProgramVertex(rsc, pv);
-
-}
-
-//////////////////////////////////////////////////////////////////////////////
-// VP
-//////////////////////////////////////////////////////////////////////////////
-
-static void SC_vpLoadModelMatrix(const rsc_Matrix *m)
-{
-    GET_TLS();
-    rsc->getVertex()->setModelviewMatrix(m);
-}
-
-static void SC_vpLoadTextureMatrix(const rsc_Matrix *m)
-{
-    GET_TLS();
-    rsc->getVertex()->setTextureMatrix(m);
-}
-
-
-
-//////////////////////////////////////////////////////////////////////////////
-// Drawing
-//////////////////////////////////////////////////////////////////////////////
-
-static void SC_drawLine(float x1, float y1, float z1,
-                        float x2, float y2, float z2)
-{
-    GET_TLS();
-    if (!rsc->setupCheck()) {
-        return;
-    }
-
-    float vtx[] = { x1, y1, z1, x2, y2, z2 };
-    VertexArray va;
-    va.addLegacy(GL_FLOAT, 3, 12, RS_KIND_POSITION, false, (uint32_t)vtx);
-    if (rsc->checkVersion2_0()) {
-        va.setupGL2(rsc, &rsc->mStateVertexArray, &rsc->mShaderCache);
-    } else {
-        va.setupGL(rsc, &rsc->mStateVertexArray);
-    }
-
-    glDrawArrays(GL_LINES, 0, 2);
-}
-
-static void SC_drawPoint(float x, float y, float z)
-{
-    GET_TLS();
-    if (!rsc->setupCheck()) {
-        return;
-    }
-
-    float vtx[] = { x, y, z };
-
-    VertexArray va;
-    va.addLegacy(GL_FLOAT, 3, 12, RS_KIND_POSITION, false, (uint32_t)vtx);
-    if (rsc->checkVersion2_0()) {
-        va.setupGL2(rsc, &rsc->mStateVertexArray, &rsc->mShaderCache);
-    } else {
-        va.setupGL(rsc, &rsc->mStateVertexArray);
-    }
-
-    glDrawArrays(GL_POINTS, 0, 1);
-}
-
-static void SC_drawQuadTexCoords(float x1, float y1, float z1,
-                                 float u1, float v1,
-                                 float x2, float y2, float z2,
-                                 float u2, float v2,
-                                 float x3, float y3, float z3,
-                                 float u3, float v3,
-                                 float x4, float y4, float z4,
-                                 float u4, float v4)
-{
-    GET_TLS();
-    if (!rsc->setupCheck()) {
-        return;
-    }
-
-    //LOGE("Quad");
-    //LOGE("%4.2f, %4.2f, %4.2f", x1, y1, z1);
-    //LOGE("%4.2f, %4.2f, %4.2f", x2, y2, z2);
-    //LOGE("%4.2f, %4.2f, %4.2f", x3, y3, z3);
-    //LOGE("%4.2f, %4.2f, %4.2f", x4, y4, z4);
-
-    float vtx[] = {x1,y1,z1, x2,y2,z2, x3,y3,z3, x4,y4,z4};
-    const float tex[] = {u1,v1, u2,v2, u3,v3, u4,v4};
-
-    VertexArray va;
-    va.addLegacy(GL_FLOAT, 3, 12, RS_KIND_POSITION, false, (uint32_t)vtx);
-    va.addLegacy(GL_FLOAT, 2, 8, RS_KIND_TEXTURE, false, (uint32_t)tex);
-    if (rsc->checkVersion2_0()) {
-        va.setupGL2(rsc, &rsc->mStateVertexArray, &rsc->mShaderCache);
-    } else {
-        va.setupGL(rsc, &rsc->mStateVertexArray);
-    }
-
-
-    glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
-}
-
-static void SC_drawQuad(float x1, float y1, float z1,
-                        float x2, float y2, float z2,
-                        float x3, float y3, float z3,
-                        float x4, float y4, float z4)
-{
-    SC_drawQuadTexCoords(x1, y1, z1, 0, 1,
-                         x2, y2, z2, 1, 1,
-                         x3, y3, z3, 1, 0,
-                         x4, y4, z4, 0, 0);
-}
-
-static void SC_drawSpriteScreenspace(float x, float y, float z, float w, float h)
-{
-    GET_TLS();
-    ObjectBaseRef<const ProgramVertex> tmp(rsc->getVertex());
-    rsc->setVertex(rsc->getDefaultProgramVertex());
-    //rsc->setupCheck();
-
-    //GLint crop[4] = {0, h, w, -h};
-
-    float sh = rsc->getHeight();
-
-    SC_drawQuad(x,   sh - y,     z,
-                x+w, sh - y,     z,
-                x+w, sh - (y+h), z,
-                x,   sh - (y+h), z);
-    rsc->setVertex((ProgramVertex *)tmp.get());
-}
-
-static void SC_drawSpriteScreenspaceCropped(float x, float y, float z, float w, float h,
-        float cx0, float cy0, float cx1, float cy1)
-{
-    GET_TLS();
-    if (!rsc->setupCheck()) {
-        return;
-    }
-
-    GLint crop[4] = {cx0, cy0, cx1, cy1};
-    glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_CROP_RECT_OES, crop);
-    glDrawTexfOES(x, y, z, w, h);
-}
-
-static void SC_drawSprite(float x, float y, float z, float w, float h)
-{
-    GET_TLS();
-    float vin[3] = {x, y, z};
-    float vout[4];
-
-    //LOGE("ds  in %f %f %f", x, y, z);
-    rsc->getVertex()->transformToScreen(rsc, vout, vin);
-    //LOGE("ds  out %f %f %f %f", vout[0], vout[1], vout[2], vout[3]);
-    vout[0] /= vout[3];
-    vout[1] /= vout[3];
-    vout[2] /= vout[3];
-
-    vout[0] *= rsc->getWidth() / 2;
-    vout[1] *= rsc->getHeight() / 2;
-    vout[0] += rsc->getWidth() / 2;
-    vout[1] += rsc->getHeight() / 2;
-
-    vout[0] -= w/2;
-    vout[1] -= h/2;
-
-    //LOGE("ds  out2 %f %f %f", vout[0], vout[1], vout[2]);
-
-    // U, V, W, H
-    SC_drawSpriteScreenspace(vout[0], vout[1], z, h, w);
-    //rsc->setupCheck();
-}
-
-
-static void SC_drawRect(float x1, float y1,
-                        float x2, float y2, float z)
-{
-    SC_drawQuad(x1, y2, z,
-                x2, y2, z,
-                x2, y1, z,
-                x1, y1, z);
-}
-
-static void SC_drawSimpleMesh(RsSimpleMesh vsm)
-{
-    GET_TLS();
-    SimpleMesh *sm = static_cast<SimpleMesh *>(vsm);
-    if (!rsc->setupCheck()) {
-        return;
-    }
-    sm->render(rsc);
-}
-
-static void SC_drawSimpleMeshRange(RsSimpleMesh vsm, uint32_t start, uint32_t len)
-{
-    GET_TLS();
-    SimpleMesh *sm = static_cast<SimpleMesh *>(vsm);
-    if (!rsc->setupCheck()) {
-        return;
-    }
-    sm->renderRange(rsc, start, len);
+    int64_t l = sc->mEnviroment.mLastDtTime;
+    sc->mEnviroment.mLastDtTime = systemTime(SYSTEM_TIME_MONOTONIC);
+    return ((float)(sc->mEnviroment.mLastDtTime - l)) / 1.0e9;
 }
 
 
@@ -860,531 +202,237 @@
 //
 //////////////////////////////////////////////////////////////////////////////
 
-static void SC_color(float r, float g, float b, float a)
+static uint32_t SC_allocGetDimX(RsAllocation va)
 {
     GET_TLS();
-    rsc->mStateVertex.color[0] = r;
-    rsc->mStateVertex.color[1] = g;
-    rsc->mStateVertex.color[2] = b;
-    rsc->mStateVertex.color[3] = a;
-    if (!rsc->checkVersion2_0()) {
-        glColor4f(r, g, b, a);
-    }
+    const Allocation *a = static_cast<const Allocation *>(va);
+    //LOGE("SC_allocGetDimX a=%p", a);
+    //LOGE(" type=%p", a->getType());
+    return a->getType()->getDimX();
 }
 
-static void SC_ambient(float r, float g, float b, float a)
-{
-    GLfloat params[] = { r, g, b, a };
-    glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, params);
-}
-
-static void SC_diffuse(float r, float g, float b, float a)
-{
-    GLfloat params[] = { r, g, b, a };
-    glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, params);
-}
-
-static void SC_specular(float r, float g, float b, float a)
-{
-    GLfloat params[] = { r, g, b, a };
-    glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, params);
-}
-
-static void SC_emission(float r, float g, float b, float a)
-{
-    GLfloat params[] = { r, g, b, a };
-    glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION, params);
-}
-
-static void SC_shininess(float s)
-{
-    glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, s);
-}
-
-static void SC_pointAttenuation(float a, float b, float c)
-{
-    GLfloat params[] = { a, b, c };
-    glPointParameterfv(GL_POINT_DISTANCE_ATTENUATION, params);
-}
-
-static void SC_hsbToRgb(float h, float s, float b, float* rgb)
-{
-    float red = 0.0f;
-    float green = 0.0f;
-    float blue = 0.0f;
-
-    float x = h;
-    float y = s;
-    float z = b;
-
-    float hf = (x - (int) x) * 6.0f;
-    int ihf = (int) hf;
-    float f = hf - ihf;
-    float pv = z * (1.0f - y);
-    float qv = z * (1.0f - y * f);
-    float tv = z * (1.0f - y * (1.0f - f));
-
-    switch (ihf) {
-        case 0:         // Red is the dominant color
-            red = z;
-            green = tv;
-            blue = pv;
-            break;
-        case 1:         // Green is the dominant color
-            red = qv;
-            green = z;
-            blue = pv;
-            break;
-        case 2:
-            red = pv;
-            green = z;
-            blue = tv;
-            break;
-        case 3:         // Blue is the dominant color
-            red = pv;
-            green = qv;
-            blue = z;
-            break;
-        case 4:
-            red = tv;
-            green = pv;
-            blue = z;
-            break;
-        case 5:         // Red is the dominant color
-            red = z;
-            green = pv;
-            blue = qv;
-            break;
-    }
-
-    rgb[0] = red;
-    rgb[1] = green;
-    rgb[2] = blue;
-}
-
-static int SC_hsbToAbgr(float h, float s, float b, float a)
-{
-    float rgb[3];
-    SC_hsbToRgb(h, s, b, rgb);
-    return int(a      * 255.0f) << 24 |
-           int(rgb[2] * 255.0f) << 16 |
-           int(rgb[1] * 255.0f) <<  8 |
-           int(rgb[0] * 255.0f);
-}
-
-static void SC_hsb(float h, float s, float b, float a)
+static uint32_t SC_allocGetDimY(RsAllocation va)
 {
     GET_TLS();
-    float rgb[3];
-    SC_hsbToRgb(h, s, b, rgb);
-    if (rsc->checkVersion2_0()) {
-        glVertexAttrib4f(1, rgb[0], rgb[1], rgb[2], a);
-    } else {
-        glColor4f(rgb[0], rgb[1], rgb[2], a);
-    }
+    const Allocation *a = static_cast<const Allocation *>(va);
+    return a->getType()->getDimY();
 }
 
-static void SC_uploadToTexture(RsAllocation va, uint32_t baseMipLevel)
+static uint32_t SC_allocGetDimZ(RsAllocation va)
 {
     GET_TLS();
-    rsi_AllocationUploadToTexture(rsc, va, false, baseMipLevel);
+    const Allocation *a = static_cast<const Allocation *>(va);
+    return a->getType()->getDimZ();
 }
 
-static void SC_uploadToBufferObject(RsAllocation va)
+static uint32_t SC_allocGetDimLOD(RsAllocation va)
 {
     GET_TLS();
-    rsi_AllocationUploadToBufferObject(rsc, va);
+    const Allocation *a = static_cast<const Allocation *>(va);
+    return a->getType()->getDimLOD();
 }
 
-static void SC_syncToGL(RsAllocation va)
+static uint32_t SC_allocGetDimFaces(RsAllocation va)
 {
     GET_TLS();
-    Allocation *a = static_cast<Allocation *>(va);
-
+    const Allocation *a = static_cast<const Allocation *>(va);
+    return a->getType()->getDimFaces();
 }
 
-static void SC_ClearColor(float r, float g, float b, float a)
+const void * SC_getElementAtX(RsAllocation va, uint32_t x)
 {
-    //LOGE("c %f %f %f %f", r, g, b, a);
-    GET_TLS();
-    sc->mEnviroment.mClearColor[0] = r;
-    sc->mEnviroment.mClearColor[1] = g;
-    sc->mEnviroment.mClearColor[2] = b;
-    sc->mEnviroment.mClearColor[3] = a;
+    const Allocation *a = static_cast<const Allocation *>(va);
+    const Type *t = a->getType();
+    const uint8_t *p = (const uint8_t *)a->getPtr();
+    return &p[t->getElementSizeBytes() * x];
 }
 
-static void SC_debugF(const char *s, float f)
+const void * SC_getElementAtXY(RsAllocation va, uint32_t x, uint32_t y)
 {
-    LOGE("%s %f", s, f);
+    const Allocation *a = static_cast<const Allocation *>(va);
+    const Type *t = a->getType();
+    const uint8_t *p = (const uint8_t *)a->getPtr();
+    return &p[t->getElementSizeBytes() * (x + y*t->getDimX())];
 }
 
-static void SC_debugHexF(const char *s, float f)
+const void * SC_getElementAtXYZ(RsAllocation va, uint32_t x, uint32_t y, uint32_t z)
 {
-    LOGE("%s 0x%x", s, *((int *) (&f)));
+    const Allocation *a = static_cast<const Allocation *>(va);
+    const Type *t = a->getType();
+    const uint8_t *p = (const uint8_t *)a->getPtr();
+    return &p[t->getElementSizeBytes() * (x + y*t->getDimX())];
 }
 
-static void SC_debugI32(const char *s, int32_t i)
-{
-    LOGE("%s %i", s, i);
+
+static void SC_debugF(const char *s, float f) {
+    LOGE("%s %f, 0x%08x", s, f, *((int *) (&f)));
+}
+static void SC_debugFv2(const char *s, rsvF_2 fv) {
+    float *f = (float *)&fv;
+    LOGE("%s {%f, %f}", s, f[0], f[1]);
+}
+static void SC_debugFv3(const char *s, rsvF_4 fv) {
+    float *f = (float *)&fv;
+    LOGE("%s {%f, %f, %f}", s, f[0], f[1], f[2]);
+}
+static void SC_debugFv4(const char *s, rsvF_4 fv) {
+    float *f = (float *)&fv;
+    LOGE("%s {%f, %f, %f, %f}", s, f[0], f[1], f[2], f[3]);
+}
+static void SC_debugI32(const char *s, int32_t i) {
+    LOGE("%s %i  0x%x", s, i, i);
+}
+static void SC_debugU32(const char *s, uint32_t i) {
+    LOGE("%s %i  0x%x", s, i, i);
 }
 
-static void SC_debugHexI32(const char *s, int32_t i)
-{
-    LOGE("%s 0x%x", s, i);
+static void SC_debugP(const char *s, const void *p) {
+    LOGE("%s %p", s, p);
 }
 
-static uint32_t SC_getWidth()
+static uint32_t SC_toClient2(int cmdID, void *data, int len)
 {
     GET_TLS();
-    return rsc->getWidth();
+    //LOGE("SC_toClient %i %i %i", cmdID, len);
+    return rsc->sendMessageToClient(data, cmdID, len, false);
 }
 
-static uint32_t SC_getHeight()
+static uint32_t SC_toClient(int cmdID)
 {
     GET_TLS();
-    return rsc->getHeight();
+    //LOGE("SC_toClient %i", cmdID);
+    return rsc->sendMessageToClient(NULL, cmdID, 0, false);
 }
 
-static uint32_t SC_colorFloatRGBAtoUNorm8(float r, float g, float b, float a)
-{
-    uint32_t c = 0;
-    c |= (uint32_t)(r * 255.f + 0.5f);
-    c |= ((uint32_t)(g * 255.f + 0.5f)) << 8;
-    c |= ((uint32_t)(b * 255.f + 0.5f)) << 16;
-    c |= ((uint32_t)(a * 255.f + 0.5f)) << 24;
-    return c;
-}
-
-static uint32_t SC_colorFloatRGBAto565(float r, float g, float b)
-{
-    uint32_t ir = (uint32_t)(r * 255.f + 0.5f);
-    uint32_t ig = (uint32_t)(g * 255.f + 0.5f);
-    uint32_t ib = (uint32_t)(b * 255.f + 0.5f);
-    return rs888to565(ir, ig, ib);
-}
-
-static uint32_t SC_toClient(void *data, int cmdID, int len, int waitForSpace)
+static uint32_t SC_toClientBlocking2(int cmdID, void *data, int len)
 {
     GET_TLS();
-    return rsc->sendMessageToClient(data, cmdID, len, waitForSpace != 0);
+    //LOGE("SC_toClientBlocking %i %i", cmdID, len);
+    return rsc->sendMessageToClient(data, cmdID, len, true);
 }
 
-static void SC_scriptCall(int scriptID)
+static uint32_t SC_toClientBlocking(int cmdID)
 {
     GET_TLS();
-    rsc->runScript((Script *)scriptID, 0);
+    //LOGE("SC_toClientBlocking %i", cmdID);
+    return rsc->sendMessageToClient(NULL, cmdID, 0, true);
 }
 
+int SC_divsi3(int a, int b)
+{
+    return a / b;
+}
+
+int SC_getAllocation(const void *ptr)
+{
+    GET_TLS();
+    const Allocation *alloc = sc->ptrToAllocation(ptr);
+    return (int)alloc;
+}
+
+
+void SC_ForEach(RsScript vs,
+                RsAllocation vin,
+                RsAllocation vout,
+                const void *usr)
+{
+    GET_TLS();
+    const Allocation *ain = static_cast<const Allocation *>(vin);
+    Allocation *aout = static_cast<Allocation *>(vout);
+    Script *s = static_cast<Script *>(vs);
+    s->runForEach(rsc, ain, aout, usr);
+}
+
+void SC_ForEach2(RsScript vs,
+                RsAllocation vin,
+                RsAllocation vout,
+                const void *usr,
+                const RsScriptCall *call)
+{
+    GET_TLS();
+    const Allocation *ain = static_cast<const Allocation *>(vin);
+    Allocation *aout = static_cast<Allocation *>(vout);
+    Script *s = static_cast<Script *>(vs);
+    s->runForEach(rsc, ain, aout, usr, call);
+}
 
 //////////////////////////////////////////////////////////////////////////////
 // Class implementation
 //////////////////////////////////////////////////////////////////////////////
 
-ScriptCState::SymbolTable_t ScriptCState::gSyms[] = {
-    // IO
-    { "loadI32", (void *)&SC_loadI32,
-        "int", "(int, int)" },
-    //{ "loadU32", (void *)&SC_loadU32, "unsigned int", "(int, int)" },
-    { "loadF", (void *)&SC_loadF,
-        "float", "(int, int)" },
-    { "loadArrayF", (void *)&SC_loadArrayF,
-        "float*", "(int, int)" },
-    { "loadArrayI32", (void *)&SC_loadArrayI32,
-        "int*", "(int, int)" },
-    { "loadVec4", (void *)&SC_loadVec4,
-        "void", "(int, int, float *)" },
-    { "loadMatrix", (void *)&SC_loadMatrix,
-        "void", "(int, int, float *)" },
-    { "storeI32", (void *)&SC_storeI32,
-        "void", "(int, int, int)" },
-    //{ "storeU32", (void *)&SC_storeU32, "void", "(int, int, unsigned int)" },
-    { "storeF", (void *)&SC_storeF,
-        "void", "(int, int, float)" },
-    { "storeVec4", (void *)&SC_storeVec4,
-        "void", "(int, int, float *)" },
-    { "storeMatrix", (void *)&SC_storeMatrix,
-        "void", "(int, int, float *)" },
-    { "loadSimpleMeshVerticesF", (void *)&SC_loadSimpleMeshVerticesF,
-        "float*", "(int, int)" },
-    { "updateSimpleMesh", (void *)&SC_updateSimpleMesh,
-        "void", "(int)" },
+// llvm name mangling ref
+//  <builtin-type> ::= v  # void
+//                 ::= b  # bool
+//                 ::= c  # char
+//                 ::= a  # signed char
+//                 ::= h  # unsigned char
+//                 ::= s  # short
+//                 ::= t  # unsigned short
+//                 ::= i  # int
+//                 ::= j  # unsigned int
+//                 ::= l  # long
+//                 ::= m  # unsigned long
+//                 ::= x  # long long, __int64
+//                 ::= y  # unsigned long long, __int64
+//                 ::= f  # float
+//                 ::= d  # double
 
-    // math
-    { "modf", (void *)&fmod,
-        "float", "(float, float)" },
-    { "abs", (void *)&abs,
-        "int", "(int)" },
-    { "absf", (void *)&fabsf,
-        "float", "(float)" },
-    { "sinf_fast", (void *)&SC_sinf_fast,
-        "float", "(float)" },
-    { "cosf_fast", (void *)&SC_cosf_fast,
-        "float", "(float)" },
-    { "sinf", (void *)&sinf,
-        "float", "(float)" },
-    { "cosf", (void *)&cosf,
-        "float", "(float)" },
-    { "asinf", (void *)&asinf,
-        "float", "(float)" },
-    { "acosf", (void *)&acosf,
-        "float", "(float)" },
-    { "atanf", (void *)&atanf,
-        "float", "(float)" },
-    { "atan2f", (void *)&atan2f,
-        "float", "(float, float)" },
-    { "fabsf", (void *)&fabsf,
-        "float", "(float)" },
-    { "randf", (void *)&SC_randf,
-        "float", "(float)" },
-    { "randf2", (void *)&SC_randf2,
-        "float", "(float, float)" },
-    { "floorf", (void *)&floorf,
-        "float", "(float)" },
-    { "fracf", (void *)&SC_fracf,
-        "float", "(float)" },
-    { "ceilf", (void *)&ceilf,
-        "float", "(float)" },
-    { "roundf", (void *)&SC_roundf,
-        "float", "(float)" },
-    { "expf", (void *)&expf,
-        "float", "(float)" },
-    { "logf", (void *)&logf,
-        "float", "(float)" },
-    { "powf", (void *)&powf,
-        "float", "(float, float)" },
-    { "maxf", (void *)&SC_maxf,
-        "float", "(float, float)" },
-    { "minf", (void *)&SC_minf,
-        "float", "(float, float)" },
-    { "sqrt", (void *)&sqrt,
-        "int", "(int)" },
-    { "sqrtf", (void *)&sqrtf,
-        "float", "(float)" },
-    { "sqr", (void *)&SC_sqr,
-        "int", "(int)" },
-    { "sqrf", (void *)&SC_sqrf,
-        "float", "(float)" },
-    { "sign", (void *)&SC_sign,
-        "int", "(int)" },
-    { "signf", (void *)&SC_signf,
-        "float", "(float)" },
-    { "clamp", (void *)&SC_clamp,
-        "int", "(int, int, int)" },
-    { "clampf", (void *)&SC_clampf,
-        "float", "(float, float, float)" },
-    { "distf2", (void *)&SC_distf2,
-        "float", "(float, float, float, float)" },
-    { "distf3", (void *)&SC_distf3,
-        "float", "(float, float, float, float, float, float)" },
-    { "magf2", (void *)&SC_magf2,
-        "float", "(float, float)" },
-    { "magf3", (void *)&SC_magf3,
-        "float", "(float, float, float)" },
-    { "radf", (void *)&SC_radf,
-        "float", "(float)" },
-    { "degf", (void *)&SC_degf,
-        "float", "(float)" },
-    { "lerpf", (void *)&SC_lerpf,
-        "float", "(float, float, float)" },
-    { "normf", (void *)&SC_normf,
-        "float", "(float, float, float)" },
-    { "mapf", (void *)&SC_mapf,
-        "float", "(float, float, float, float, float)" },
-    { "noisef", (void *)&SC_noisef,
-        "float", "(float)" },
-    { "noisef2", (void *)&SC_noisef2,
-        "float", "(float, float)" },
-    { "noisef3", (void *)&SC_noisef3,
-        "float", "(float, float, float)" },
-    { "turbulencef2", (void *)&SC_turbulencef2,
-        "float", "(float, float, float)" },
-    { "turbulencef3", (void *)&SC_turbulencef3,
-        "float", "(float, float, float, float)" },
+static ScriptCState::SymbolTable_t gSyms[] = {
+    { "__divsi3", (void *)&SC_divsi3 },
+
+    // allocation
+    { "_Z19rsAllocationGetDimX13rs_allocation", (void *)&SC_allocGetDimX },
+    { "_Z19rsAllocationGetDimY13rs_allocation", (void *)&SC_allocGetDimY },
+    { "_Z19rsAllocationGetDimZ13rs_allocation", (void *)&SC_allocGetDimZ },
+    { "_Z21rsAllocationGetDimLOD13rs_allocation", (void *)&SC_allocGetDimLOD },
+    { "_Z23rsAllocationGetDimFaces13rs_allocation", (void *)&SC_allocGetDimFaces },
+    { "_Z15rsGetAllocationPKv", (void *)&SC_getAllocation },
+
+    { "_Z14rsGetElementAt13rs_allocationj", (void *)&SC_getElementAtX },
+    { "_Z14rsGetElementAt13rs_allocationjj", (void *)&SC_getElementAtXY },
+    { "_Z14rsGetElementAt13rs_allocationjjj", (void *)&SC_getElementAtXYZ },
+
+    // Debug
+    { "_Z7rsDebugPKcf", (void *)&SC_debugF },
+    { "_Z7rsDebugPKcDv2_f", (void *)&SC_debugFv2 },
+    { "_Z7rsDebugPKcDv3_f", (void *)&SC_debugFv3 },
+    { "_Z7rsDebugPKcDv4_f", (void *)&SC_debugFv4 },
+    { "_Z7rsDebugPKci", (void *)&SC_debugI32 },
+    { "_Z7rsDebugPKcj", (void *)&SC_debugU32 },
+    { "_Z7rsDebugPKcPKv", (void *)&SC_debugP },
+
+    // RS Math
+    { "_Z6rsRandi", (void *)&SC_randi },
+    { "_Z6rsRandii", (void *)&SC_randi2 },
+    { "_Z6rsRandf", (void *)&SC_randf },
+    { "_Z6rsRandff", (void *)&SC_randf2 },
+    { "_Z6rsFracf", (void *)&SC_frac },
 
     // time
-    { "second", (void *)&SC_second,
-        "int", "()" },
-    { "minute", (void *)&SC_minute,
-        "int", "()" },
-    { "hour", (void *)&SC_hour,
-        "int", "()" },
-    { "day", (void *)&SC_day,
-        "int", "()" },
-    { "month", (void *)&SC_month,
-        "int", "()" },
-    { "year", (void *)&SC_year,
-        "int", "()" },
-    { "uptimeMillis", (void*)&SC_uptimeMillis,
-        "int", "()" },      // TODO: use long instead
-    { "startTimeMillis", (void*)&SC_startTimeMillis,
-        "int", "()" },      // TODO: use long instead
-    { "elapsedTimeMillis", (void*)&SC_elapsedTimeMillis,
-        "int", "()" },      // TODO: use long instead
+    { "_Z8rsSecondv", (void *)&SC_second },
+    { "_Z8rsMinutev", (void *)&SC_minute },
+    { "_Z6rsHourv", (void *)&SC_hour },
+    { "_Z5rsDayv", (void *)&SC_day },
+    { "_Z7rsMonthv", (void *)&SC_month },
+    { "_Z6rsYearv", (void *)&SC_year },
+    { "_Z14rsUptimeMillisv", (void*)&SC_uptimeMillis },
+    { "_Z13rsUptimeNanosv", (void*)&SC_uptimeNanos },
+    { "_Z7rsGetDtv", (void*)&SC_getDt },
 
-    // matrix
-    { "matrixLoadIdentity", (void *)&SC_matrixLoadIdentity,
-        "void", "(float *mat)" },
-    { "matrixLoadFloat", (void *)&SC_matrixLoadFloat,
-        "void", "(float *mat, float *f)" },
-    { "matrixLoadMat", (void *)&SC_matrixLoadMat,
-        "void", "(float *mat, float *newmat)" },
-    { "matrixLoadRotate", (void *)&SC_matrixLoadRotate,
-        "void", "(float *mat, float rot, float x, float y, float z)" },
-    { "matrixLoadScale", (void *)&SC_matrixLoadScale,
-        "void", "(float *mat, float x, float y, float z)" },
-    { "matrixLoadTranslate", (void *)&SC_matrixLoadTranslate,
-        "void", "(float *mat, float x, float y, float z)" },
-    { "matrixLoadMultiply", (void *)&SC_matrixLoadMultiply,
-        "void", "(float *mat, float *lhs, float *rhs)" },
-    { "matrixMultiply", (void *)&SC_matrixMultiply,
-        "void", "(float *mat, float *rhs)" },
-    { "matrixRotate", (void *)&SC_matrixRotate,
-        "void", "(float *mat, float rot, float x, float y, float z)" },
-    { "matrixScale", (void *)&SC_matrixScale,
-        "void", "(float *mat, float x, float y, float z)" },
-    { "matrixTranslate", (void *)&SC_matrixTranslate,
-        "void", "(float *mat, float x, float y, float z)" },
+    { "_Z14rsSendToClienti", (void *)&SC_toClient },
+    { "_Z14rsSendToClientiPKvj", (void *)&SC_toClient2 },
+    { "_Z22rsSendToClientBlockingi", (void *)&SC_toClientBlocking },
+    { "_Z22rsSendToClientBlockingiPKvj", (void *)&SC_toClientBlocking2 },
 
-    // vector
-    { "vec2Rand", (void *)&SC_vec2Rand,
-        "void", "(float *vec, float maxLen)" },
+    { "_Z9rsForEach9rs_script13rs_allocationS0_PKv", (void *)&SC_ForEach },
+    //{ "_Z9rsForEach9rs_script13rs_allocationS0_PKv", (void *)&SC_ForEach2 },
 
-    // vec3
-    { "vec3Norm", (void *)&SC_vec3Norm,
-        "void", "(struct vecF32_3_s *)" },
-    { "vec3Length", (void *)&SC_vec3Length,
-        "float", "(struct vecF32_3_s *)" },
-    { "vec3Add", (void *)&SC_vec3Add,
-        "void", "(struct vecF32_3_s *dest, struct vecF32_3_s *lhs, struct vecF32_3_s *rhs)" },
-    { "vec3Sub", (void *)&SC_vec3Sub,
-        "void", "(struct vecF32_3_s *dest, struct vecF32_3_s *lhs, struct vecF32_3_s *rhs)" },
-    { "vec3Cross", (void *)&SC_vec3Cross,
-        "void", "(struct vecF32_3_s *dest, struct vecF32_3_s *lhs, struct vecF32_3_s *rhs)" },
-    { "vec3Dot", (void *)&SC_vec3Dot,
-        "float", "(struct vecF32_3_s *lhs, struct vecF32_3_s *rhs)" },
-    { "vec3Scale", (void *)&SC_vec3Scale,
-        "void", "(struct vecF32_3_s *lhs, float scale)" },
+////////////////////////////////////////////////////////////////////
 
-    // vec4
-    { "vec4Norm", (void *)&SC_vec4Norm,
-        "void", "(struct vecF32_4_s *)" },
-    { "vec4Length", (void *)&SC_vec4Length,
-        "float", "(struct vecF32_4_s *)" },
-    { "vec4Add", (void *)&SC_vec4Add,
-        "void", "(struct vecF32_4_s *dest, struct vecF32_4_s *lhs, struct vecF32_4_s *rhs)" },
-    { "vec4Sub", (void *)&SC_vec4Sub,
-        "void", "(struct vecF32_4_s *dest, struct vecF32_4_s *lhs, struct vecF32_4_s *rhs)" },
-    { "vec4Dot", (void *)&SC_vec4Dot,
-        "float", "(struct vecF32_4_s *lhs, struct vecF32_4_s *rhs)" },
-    { "vec4Scale", (void *)&SC_vec4Scale,
-        "void", "(struct vecF32_4_s *lhs, float scale)" },
+    //{ "sinf_fast", (void *)&SC_sinf_fast },
+    //{ "cosf_fast", (void *)&SC_cosf_fast },
 
-    // context
-    { "bindProgramFragment", (void *)&SC_bindProgramFragment,
-        "void", "(int)" },
-    { "bindProgramFragmentStore", (void *)&SC_bindProgramFragmentStore,
-        "void", "(int)" },
-    { "bindProgramStore", (void *)&SC_bindProgramFragmentStore,
-        "void", "(int)" },
-    { "bindProgramVertex", (void *)&SC_bindProgramVertex,
-        "void", "(int)" },
-    { "bindSampler", (void *)&SC_bindSampler,
-        "void", "(int, int, int)" },
-    { "bindTexture", (void *)&SC_bindTexture,
-        "void", "(int, int, int)" },
-
-    // vp
-    { "vpLoadModelMatrix", (void *)&SC_vpLoadModelMatrix,
-        "void", "(void *)" },
-    { "vpLoadTextureMatrix", (void *)&SC_vpLoadTextureMatrix,
-        "void", "(void *)" },
-
-
-
-    // drawing
-    { "drawRect", (void *)&SC_drawRect,
-        "void", "(float x1, float y1, float x2, float y2, float z)" },
-    { "drawQuad", (void *)&SC_drawQuad,
-        "void", "(float x1, float y1, float z1, float x2, float y2, float z2, float x3, float y3, float z3, float x4, float y4, float z4)" },
-    { "drawQuadTexCoords", (void *)&SC_drawQuadTexCoords,
-        "void", "(float x1, float y1, float z1, float u1, float v1, float x2, float y2, float z2, float u2, float v2, float x3, float y3, float z3, float u3, float v3, float x4, float y4, float z4, float u4, float v4)" },
-    { "drawSprite", (void *)&SC_drawSprite,
-        "void", "(float x, float y, float z, float w, float h)" },
-    { "drawSpriteScreenspace", (void *)&SC_drawSpriteScreenspace,
-        "void", "(float x, float y, float z, float w, float h)" },
-    { "drawSpriteScreenspaceCropped", (void *)&SC_drawSpriteScreenspaceCropped,
-        "void", "(float x, float y, float z, float w, float h, float cx0, float cy0, float cx1, float cy1)" },
-    { "drawLine", (void *)&SC_drawLine,
-        "void", "(float x1, float y1, float z1, float x2, float y2, float z2)" },
-    { "drawPoint", (void *)&SC_drawPoint,
-        "void", "(float x1, float y1, float z1)" },
-    { "drawSimpleMesh", (void *)&SC_drawSimpleMesh,
-        "void", "(int ism)" },
-    { "drawSimpleMeshRange", (void *)&SC_drawSimpleMeshRange,
-        "void", "(int ism, int start, int len)" },
-
-
-    // misc
-    { "pfClearColor", (void *)&SC_ClearColor,
-        "void", "(float, float, float, float)" },
-    { "color", (void *)&SC_color,
-        "void", "(float, float, float, float)" },
-    { "hsb", (void *)&SC_hsb,
-        "void", "(float, float, float, float)" },
-    { "hsbToRgb", (void *)&SC_hsbToRgb,
-        "void", "(float, float, float, float*)" },
-    { "hsbToAbgr", (void *)&SC_hsbToAbgr,
-        "int", "(float, float, float, float)" },
-    { "ambient", (void *)&SC_ambient,
-        "void", "(float, float, float, float)" },
-    { "diffuse", (void *)&SC_diffuse,
-        "void", "(float, float, float, float)" },
-    { "specular", (void *)&SC_specular,
-        "void", "(float, float, float, float)" },
-    { "emission", (void *)&SC_emission,
-        "void", "(float, float, float, float)" },
-    { "shininess", (void *)&SC_shininess,
-        "void", "(float)" },
-    { "pointAttenuation", (void *)&SC_pointAttenuation,
-        "void", "(float, float, float)" },
-
-    { "uploadToTexture", (void *)&SC_uploadToTexture,
-        "void", "(int, int)" },
-    { "uploadToBufferObject", (void *)&SC_uploadToBufferObject,
-        "void", "(int)" },
-
-    { "syncToGL", (void *)&SC_syncToGL,
-        "void", "(int)" },
-
-    { "colorFloatRGBAtoUNorm8", (void *)&SC_colorFloatRGBAtoUNorm8,
-        "int", "(float, float, float, float)" },
-    { "colorFloatRGBto565", (void *)&SC_colorFloatRGBAto565,
-        "int", "(float, float, float)" },
-
-
-    { "getWidth", (void *)&SC_getWidth,
-        "int", "()" },
-    { "getHeight", (void *)&SC_getHeight,
-        "int", "()" },
-
-    { "sendToClient", (void *)&SC_toClient,
-        "int", "(void *data, int cmdID, int len, int waitForSpace)" },
-
-
-    { "debugF", (void *)&SC_debugF,
-        "void", "(void *, float)" },
-    { "debugI32", (void *)&SC_debugI32,
-        "void", "(void *, int)" },
-    { "debugHexF", (void *)&SC_debugHexF,
-        "void", "(void *, float)" },
-    { "debugHexI32", (void *)&SC_debugHexI32,
-        "void", "(void *, int)" },
-
-    { "scriptCall", (void *)&SC_scriptCall,
-        "void", "(int)" },
-
-
-    { NULL, NULL, NULL, NULL }
+    { NULL, NULL }
 };
 
 const ScriptCState::SymbolTable_t * ScriptCState::lookupSymbol(const char *sym)
@@ -1400,17 +448,3 @@
     return NULL;
 }
 
-void ScriptCState::appendDecls(String8 *str)
-{
-    ScriptCState::SymbolTable_t *syms = gSyms;
-    while (syms->mPtr) {
-        str->append(syms->mRet);
-        str->append(" ");
-        str->append(syms->mName);
-        str->append(syms->mParam);
-        str->append(";\n");
-        syms++;
-    }
-}
-
-
diff --git a/libs/rs/rsScriptC_LibCL.cpp b/libs/rs/rsScriptC_LibCL.cpp
new file mode 100644
index 0000000..ce8e7b2
--- /dev/null
+++ b/libs/rs/rsScriptC_LibCL.cpp
@@ -0,0 +1,314 @@
+/*
+ * 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 "rsScriptC.h"
+
+// Implements rs_cl.rsh
+
+
+using namespace android;
+using namespace android::renderscript;
+
+
+static float SC_acospi(float v) {
+    return acosf(v)/ M_PI;
+}
+
+static float SC_asinpi(float v) {
+    return asinf(v) / M_PI;
+}
+
+static float SC_atanpi(float v) {
+    return atanf(v) / M_PI;
+}
+
+static float SC_atan2pi(float y, float x) {
+    return atan2f(y, x) / M_PI;
+}
+
+static float SC_cospi(float v) {
+    return cosf(v * M_PI);
+}
+
+static float SC_exp10(float v) {
+    return pow(10.f, v);
+
+}
+
+static float SC_fract(float v, int *iptr) {
+    int i = (int)floor(v);
+    iptr[0] = i;
+    return fmin(v - i, 0x1.fffffep-1f);
+}
+
+static float SC_log2(float v) {
+    return log10(v) / log10(2.f);
+}
+
+static float SC_pown(float v, int p) {
+    return powf(v, (float)p);
+}
+
+static float SC_powr(float v, float p) {
+    return powf(v, p);
+}
+
+float SC_rootn(float v, int r) {
+    return pow(v, 1.f / r);
+}
+
+float SC_rsqrt(float v) {
+    return 1.f / sqrtf(v);
+}
+
+float SC_sincos(float v, float *cosptr) {
+    *cosptr = cosf(v);
+    return sinf(v);
+}
+
+static float SC_sinpi(float v) {
+    return sinf(v * M_PI);
+}
+
+static float SC_tanpi(float v) {
+    return tanf(v * M_PI);
+}
+
+    //{ "logb", (void *)& },
+    //{ "mad", (void *)& },
+    //{ "nan", (void *)& },
+    //{ "tgamma", (void *)& },
+
+//////////////////////////////////////////////////////////////////////////////
+// Integer
+//////////////////////////////////////////////////////////////////////////////
+
+
+static uint32_t SC_abs_i32(int32_t v) {return abs(v);}
+static uint16_t SC_abs_i16(int16_t v) {return (uint16_t)abs(v);}
+static uint8_t SC_abs_i8(int8_t v) {return (uint8_t)abs(v);}
+
+static uint32_t SC_clz_u32(uint32_t v) {return __builtin_clz(v);}
+static uint16_t SC_clz_u16(uint16_t v) {return (uint16_t)__builtin_clz(v);}
+static uint8_t SC_clz_u8(uint8_t v) {return (uint8_t)__builtin_clz(v);}
+static int32_t SC_clz_i32(int32_t v) {return (int32_t)__builtin_clz((uint32_t)v);}
+static int16_t SC_clz_i16(int16_t v) {return (int16_t)__builtin_clz(v);}
+static int8_t SC_clz_i8(int8_t v) {return (int8_t)__builtin_clz(v);}
+
+static uint32_t SC_max_u32(uint32_t v, uint32_t v2) {return rsMax(v, v2);}
+static uint16_t SC_max_u16(uint16_t v, uint16_t v2) {return rsMax(v, v2);}
+static uint8_t SC_max_u8(uint8_t v, uint8_t v2) {return rsMax(v, v2);}
+static int32_t SC_max_i32(int32_t v, int32_t v2) {return rsMax(v, v2);}
+static int16_t SC_max_i16(int16_t v, int16_t v2) {return rsMax(v, v2);}
+static int8_t SC_max_i8(int8_t v, int8_t v2) {return rsMax(v, v2);}
+
+static uint32_t SC_min_u32(uint32_t v, uint32_t v2) {return rsMin(v, v2);}
+static uint16_t SC_min_u16(uint16_t v, uint16_t v2) {return rsMin(v, v2);}
+static uint8_t SC_min_u8(uint8_t v, uint8_t v2) {return rsMin(v, v2);}
+static int32_t SC_min_i32(int32_t v, int32_t v2) {return rsMin(v, v2);}
+static int16_t SC_min_i16(int16_t v, int16_t v2) {return rsMin(v, v2);}
+static int8_t SC_min_i8(int8_t v, int8_t v2) {return rsMin(v, v2);}
+
+//////////////////////////////////////////////////////////////////////////////
+// Float util
+//////////////////////////////////////////////////////////////////////////////
+
+static float SC_clamp_f32(float amount, float low, float high)
+{
+    return amount < low ? low : (amount > high ? high : amount);
+}
+
+static float SC_degrees(float radians)
+{
+    return radians * (180.f / M_PI);
+}
+
+static float SC_max_f32(float v, float v2)
+{
+    return rsMax(v, v2);
+}
+
+static float SC_min_f32(float v, float v2)
+{
+    return rsMin(v, v2);
+}
+
+static float SC_mix_f32(float start, float stop, float amount)
+{
+    //LOGE("lerpf %f  %f  %f", start, stop, amount);
+    return start + (stop - start) * amount;
+}
+
+static float SC_radians(float degrees)
+{
+    return degrees * (M_PI / 180.f);
+}
+
+static float SC_step_f32(float edge, float v)
+{
+    if (v < edge) return 0.f;
+    return 1.f;
+}
+
+static float SC_sign_f32(float value)
+{
+    if (value > 0) return 1.f;
+    if (value < 0) return -1.f;
+    return value;
+}
+
+
+
+
+
+//////////////////////////////////////////////////////////////////////////////
+// Class implementation
+//////////////////////////////////////////////////////////////////////////////
+
+// llvm name mangling ref
+//  <builtin-type> ::= v  # void
+//                 ::= b  # bool
+//                 ::= c  # char
+//                 ::= a  # signed char
+//                 ::= h  # unsigned char
+//                 ::= s  # short
+//                 ::= t  # unsigned short
+//                 ::= i  # int
+//                 ::= j  # unsigned int
+//                 ::= l  # long
+//                 ::= m  # unsigned long
+//                 ::= x  # long long, __int64
+//                 ::= y  # unsigned long long, __int64
+//                 ::= f  # float
+//                 ::= d  # double
+
+static ScriptCState::SymbolTable_t gSyms[] = {
+    // OpenCL math
+    { "_Z4acosf", (void *)&acosf },
+    { "_Z5acoshf", (void *)&acoshf },
+    { "_Z6acospif", (void *)&SC_acospi },
+    { "_Z4asinf", (void *)&asinf },
+    { "_Z5asinhf", (void *)&asinhf },
+    { "_Z6asinpif", (void *)&SC_asinpi },
+    { "_Z4atanf", (void *)&atanf },
+    { "_Z5atan2f", (void *)&atan2f },
+    { "_Z6atanpif", (void *)&SC_atanpi },
+    { "_Z7atan2pif", (void *)&SC_atan2pi },
+    { "_Z4cbrtf", (void *)&cbrtf },
+    { "_Z4ceilf", (void *)&ceilf },
+    { "_Z8copysignff", (void *)&copysignf },
+    { "_Z3cosf", (void *)&cosf },
+    { "_Z4coshf", (void *)&coshf },
+    { "_Z5cospif", (void *)&SC_cospi },
+    { "_Z4erfcf", (void *)&erfcf },
+    { "_Z3erff", (void *)&erff },
+    { "_Z3expf", (void *)&expf },
+    { "_Z4exp2f", (void *)&exp2f },
+    { "_Z5exp10f", (void *)&SC_exp10 },
+    { "_Z5expm1f", (void *)&expm1f },
+    { "_Z4fabsf", (void *)&fabsf },
+    { "_Z4fdimff", (void *)&fdimf },
+    { "_Z5floorf", (void *)&floorf },
+    { "_Z3fmafff", (void *)&fmaf },
+    { "_Z4fmaxff", (void *)&fmaxf },
+    { "_Z4fminff", (void *)&fminf },  // float fmin(float, float)
+    { "_Z4fmodff", (void *)&fmodf },
+    { "_Z5fractfPf", (void *)&SC_fract },
+    { "_Z5frexpfPi", (void *)&frexpf },
+    { "_Z5hypotff", (void *)&hypotf },
+    { "_Z5ilogbf", (void *)&ilogbf },
+    { "_Z5ldexpfi", (void *)&ldexpf },
+    { "_Z6lgammaf", (void *)&lgammaf },
+    { "_Z3logf", (void *)&logf },
+    { "_Z4log2f", (void *)&SC_log2 },
+    { "_Z5log10f", (void *)&log10f },
+    { "_Z5log1pf", (void *)&log1pf },
+    //{ "logb", (void *)& },
+    //{ "mad", (void *)& },
+    { "modf", (void *)&modff },
+    //{ "nan", (void *)& },
+    { "_Z9nextafterff", (void *)&nextafterf },
+    { "_Z3powff", (void *)&powf },
+    { "_Z4pownfi", (void *)&SC_pown },
+    { "_Z4powrff", (void *)&SC_powr },
+    { "_Z9remainderff", (void *)&remainderf },
+    { "remquo", (void *)&remquof },
+    { "_Z4rintf", (void *)&rintf },
+    { "_Z5rootnfi", (void *)&SC_rootn },
+    { "_Z5roundf", (void *)&roundf },
+    { "_Z5rsqrtf", (void *)&SC_rsqrt },
+    { "_Z3sinf", (void *)&sinf },
+    { "sincos", (void *)&SC_sincos },
+    { "_Z4sinhf", (void *)&sinhf },
+    { "_Z5sinpif", (void *)&SC_sinpi },
+    { "_Z4sqrtf", (void *)&sqrtf },
+    { "_Z3tanf", (void *)&tanf },
+    { "_Z4tanhf", (void *)&tanhf },
+    { "_Z5tanpif", (void *)&SC_tanpi },
+    //{ "tgamma", (void *)& },
+    { "_Z5truncf", (void *)&truncf },
+
+    // OpenCL Int
+    { "_Z3absi", (void *)&SC_abs_i32 },
+    { "_Z3abss", (void *)&SC_abs_i16 },
+    { "_Z3absc", (void *)&SC_abs_i8 },
+    { "_Z3clzj", (void *)&SC_clz_u32 },
+    { "_Z3clzt", (void *)&SC_clz_u16 },
+    { "_Z3clzh", (void *)&SC_clz_u8 },
+    { "_Z3clzi", (void *)&SC_clz_i32 },
+    { "_Z3clzs", (void *)&SC_clz_i16 },
+    { "_Z3clzc", (void *)&SC_clz_i8 },
+    { "_Z3maxjj", (void *)&SC_max_u32 },
+    { "_Z3maxtt", (void *)&SC_max_u16 },
+    { "_Z3maxhh", (void *)&SC_max_u8 },
+    { "_Z3maxii", (void *)&SC_max_i32 },
+    { "_Z3maxss", (void *)&SC_max_i16 },
+    { "_Z3maxcc", (void *)&SC_max_i8 },
+    { "_Z3minjj", (void *)&SC_min_u32 },
+    { "_Z3mintt", (void *)&SC_min_u16 },
+    { "_Z3minhh", (void *)&SC_min_u8 },
+    { "_Z3minii", (void *)&SC_min_i32 },
+    { "_Z3minss", (void *)&SC_min_i16 },
+    { "_Z3mincc", (void *)&SC_min_i8 },
+
+    // OpenCL 6.11.4
+    { "_Z5clampfff", (void *)&SC_clamp_f32 },
+    { "_Z7degreesf", (void *)&SC_degrees },
+    { "_Z3maxff", (void *)&SC_max_f32 },
+    { "_Z3minff", (void *)&SC_min_f32 },
+    { "_Z3mixfff", (void *)&SC_mix_f32 },
+    { "_Z7radiansf", (void *)&SC_radians },
+    { "_Z4stepff", (void *)&SC_step_f32 },
+    //{ "smoothstep", (void *)& },
+    { "_Z4signf", (void *)&SC_sign_f32 },
+
+    { NULL, NULL }
+};
+
+const ScriptCState::SymbolTable_t * ScriptCState::lookupSymbolCL(const char *sym)
+{
+    ScriptCState::SymbolTable_t *syms = gSyms;
+
+    while (syms->mPtr) {
+        if (!strcmp(syms->mName, sym)) {
+            return syms;
+        }
+        syms++;
+    }
+    return NULL;
+}
+
diff --git a/libs/rs/rsScriptC_LibGL.cpp b/libs/rs/rsScriptC_LibGL.cpp
new file mode 100644
index 0000000..f5e59534
--- /dev/null
+++ b/libs/rs/rsScriptC_LibGL.cpp
@@ -0,0 +1,421 @@
+/*
+ * 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 "rsScriptC.h"
+#include "rsMatrix.h"
+
+#include "acc/acc.h"
+#include "utils/Timers.h"
+
+#define GL_GLEXT_PROTOTYPES
+
+#include <GLES/gl.h>
+#include <GLES/glext.h>
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
+
+#include <time.h>
+
+using namespace android;
+using namespace android::renderscript;
+
+#define GET_TLS()  Context::ScriptTLSStruct * tls = \
+    (Context::ScriptTLSStruct *)pthread_getspecific(Context::gThreadTLSKey); \
+    Context * rsc = tls->mContext; \
+    ScriptC * sc = (ScriptC *) tls->mScript
+
+
+//////////////////////////////////////////////////////////////////////////////
+// Context
+//////////////////////////////////////////////////////////////////////////////
+
+static void SC_bindTexture(RsProgramFragment vpf, uint32_t slot, RsAllocation va)
+{
+    GET_TLS();
+    rsi_ProgramBindTexture(rsc,
+                           static_cast<ProgramFragment *>(vpf),
+                           slot,
+                           static_cast<Allocation *>(va));
+
+}
+
+static void SC_bindSampler(RsProgramFragment vpf, uint32_t slot, RsSampler vs)
+{
+    GET_TLS();
+    rsi_ProgramBindSampler(rsc,
+                           static_cast<ProgramFragment *>(vpf),
+                           slot,
+                           static_cast<Sampler *>(vs));
+
+}
+
+static void SC_bindProgramStore(RsProgramStore pfs)
+{
+    GET_TLS();
+    rsi_ContextBindProgramStore(rsc, pfs);
+}
+
+static void SC_bindProgramFragment(RsProgramFragment pf)
+{
+    GET_TLS();
+    rsi_ContextBindProgramFragment(rsc, pf);
+}
+
+static void SC_bindProgramVertex(RsProgramVertex pv)
+{
+    GET_TLS();
+    rsi_ContextBindProgramVertex(rsc, pv);
+}
+
+static void SC_bindProgramRaster(RsProgramRaster pv)
+{
+    GET_TLS();
+    rsi_ContextBindProgramRaster(rsc, pv);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+// VP
+//////////////////////////////////////////////////////////////////////////////
+
+static void SC_vpLoadProjectionMatrix(const rsc_Matrix *m)
+{
+    GET_TLS();
+    rsc->getVertex()->setProjectionMatrix(m);
+}
+
+static void SC_vpLoadModelMatrix(const rsc_Matrix *m)
+{
+    GET_TLS();
+    rsc->getVertex()->setModelviewMatrix(m);
+}
+
+static void SC_vpLoadTextureMatrix(const rsc_Matrix *m)
+{
+    GET_TLS();
+    rsc->getVertex()->setTextureMatrix(m);
+}
+
+
+static void SC_pfConstantColor(RsProgramFragment vpf, float r, float g, float b, float a)
+{
+    //GET_TLS();
+    ProgramFragment *pf = static_cast<ProgramFragment *>(vpf);
+    pf->setConstantColor(r, g, b, a);
+}
+
+
+//////////////////////////////////////////////////////////////////////////////
+// Drawing
+//////////////////////////////////////////////////////////////////////////////
+
+static void SC_drawQuadTexCoords(float x1, float y1, float z1,
+                                 float u1, float v1,
+                                 float x2, float y2, float z2,
+                                 float u2, float v2,
+                                 float x3, float y3, float z3,
+                                 float u3, float v3,
+                                 float x4, float y4, float z4,
+                                 float u4, float v4)
+{
+    GET_TLS();
+    if (!rsc->setupCheck()) {
+        return;
+    }
+
+    //LOGE("Quad");
+    //LOGE("%4.2f, %4.2f, %4.2f", x1, y1, z1);
+    //LOGE("%4.2f, %4.2f, %4.2f", x2, y2, z2);
+    //LOGE("%4.2f, %4.2f, %4.2f", x3, y3, z3);
+    //LOGE("%4.2f, %4.2f, %4.2f", x4, y4, z4);
+
+    float vtx[] = {x1,y1,z1, x2,y2,z2, x3,y3,z3, x4,y4,z4};
+    const float tex[] = {u1,v1, u2,v2, u3,v3, u4,v4};
+
+    VertexArray va;
+    va.add(GL_FLOAT, 3, 12, false, (uint32_t)vtx, "position");
+    va.add(GL_FLOAT, 2, 8, false, (uint32_t)tex, "texture0");
+    va.setupGL2(rsc, &rsc->mStateVertexArray, &rsc->mShaderCache);
+
+    glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
+}
+
+static void SC_drawQuad(float x1, float y1, float z1,
+                        float x2, float y2, float z2,
+                        float x3, float y3, float z3,
+                        float x4, float y4, float z4)
+{
+    SC_drawQuadTexCoords(x1, y1, z1, 0, 1,
+                         x2, y2, z2, 1, 1,
+                         x3, y3, z3, 1, 0,
+                         x4, y4, z4, 0, 0);
+}
+
+static void SC_drawSpriteScreenspace(float x, float y, float z, float w, float h)
+{
+    GET_TLS();
+    ObjectBaseRef<const ProgramVertex> tmp(rsc->getVertex());
+    rsc->setVertex(rsc->getDefaultProgramVertex());
+    //rsc->setupCheck();
+
+    //GLint crop[4] = {0, h, w, -h};
+
+    float sh = rsc->getHeight();
+
+    SC_drawQuad(x,   sh - y,     z,
+                x+w, sh - y,     z,
+                x+w, sh - (y+h), z,
+                x,   sh - (y+h), z);
+    rsc->setVertex((ProgramVertex *)tmp.get());
+}
+/*
+static void SC_drawSprite(float x, float y, float z, float w, float h)
+{
+    GET_TLS();
+    float vin[3] = {x, y, z};
+    float vout[4];
+
+    //LOGE("ds  in %f %f %f", x, y, z);
+    rsc->getVertex()->transformToScreen(rsc, vout, vin);
+    //LOGE("ds  out %f %f %f %f", vout[0], vout[1], vout[2], vout[3]);
+    vout[0] /= vout[3];
+    vout[1] /= vout[3];
+    vout[2] /= vout[3];
+
+    vout[0] *= rsc->getWidth() / 2;
+    vout[1] *= rsc->getHeight() / 2;
+    vout[0] += rsc->getWidth() / 2;
+    vout[1] += rsc->getHeight() / 2;
+
+    vout[0] -= w/2;
+    vout[1] -= h/2;
+
+    //LOGE("ds  out2 %f %f %f", vout[0], vout[1], vout[2]);
+
+    // U, V, W, H
+    SC_drawSpriteScreenspace(vout[0], vout[1], z, h, w);
+    //rsc->setupCheck();
+}
+*/
+
+static void SC_drawRect(float x1, float y1,
+                        float x2, float y2, float z)
+{
+    //LOGE("SC_drawRect %f,%f  %f,%f  %f", x1, y1, x2, y2, z);
+    SC_drawQuad(x1, y2, z,
+                x2, y2, z,
+                x2, y1, z,
+                x1, y1, z);
+}
+
+static void SC_drawMesh(RsMesh vsm)
+{
+    GET_TLS();
+    Mesh *sm = static_cast<Mesh *>(vsm);
+    if (!rsc->setupCheck()) {
+        return;
+    }
+    sm->render(rsc);
+}
+
+static void SC_drawMeshPrimitive(RsMesh vsm, uint32_t primIndex)
+{
+    GET_TLS();
+    Mesh *sm = static_cast<Mesh *>(vsm);
+    if (!rsc->setupCheck()) {
+        return;
+    }
+    sm->renderPrimitive(rsc, primIndex);
+}
+
+static void SC_drawMeshPrimitiveRange(RsMesh vsm, uint32_t primIndex, uint32_t start, uint32_t len)
+{
+    GET_TLS();
+    Mesh *sm = static_cast<Mesh *>(vsm);
+    if (!rsc->setupCheck()) {
+        return;
+    }
+    sm->renderPrimitiveRange(rsc, primIndex, start, len);
+}
+
+
+//////////////////////////////////////////////////////////////////////////////
+//
+//////////////////////////////////////////////////////////////////////////////
+
+
+static void SC_color(float r, float g, float b, float a)
+{
+    GET_TLS();
+    ProgramFragment *pf = (ProgramFragment *)rsc->getFragment();
+    pf->setConstantColor(r, g, b, a);
+}
+
+static void SC_uploadToTexture2(RsAllocation va, uint32_t baseMipLevel)
+{
+    GET_TLS();
+    rsi_AllocationUploadToTexture(rsc, va, false, baseMipLevel);
+}
+static void SC_uploadToTexture(RsAllocation va)
+{
+    GET_TLS();
+    rsi_AllocationUploadToTexture(rsc, va, false, 0);
+}
+
+static void SC_uploadToBufferObject(RsAllocation va)
+{
+    GET_TLS();
+    rsi_AllocationUploadToBufferObject(rsc, va);
+}
+
+static void SC_ClearColor(float r, float g, float b, float a)
+{
+    GET_TLS();
+    if (!rsc->setupCheck()) {
+        return;
+    }
+
+    glClearColor(r, g, b, a);
+    glClear(GL_COLOR_BUFFER_BIT);
+}
+
+static void SC_ClearDepth(float v)
+{
+    GET_TLS();
+    if (!rsc->setupCheck()) {
+        return;
+    }
+
+    glClearDepthf(v);
+    glClear(GL_DEPTH_BUFFER_BIT);
+}
+
+static uint32_t SC_getWidth()
+{
+    GET_TLS();
+    return rsc->getWidth();
+}
+
+static uint32_t SC_getHeight()
+{
+    GET_TLS();
+    return rsc->getHeight();
+}
+
+static void SC_DrawTextAlloc(RsAllocation va, int x, int y)
+{
+    GET_TLS();
+    Allocation *alloc = static_cast<Allocation *>(va);
+    rsc->mStateFont.renderText(alloc, x, y);
+}
+
+static void SC_DrawText(const char *text, int x, int y)
+{
+    GET_TLS();
+    rsc->mStateFont.renderText(text, x, y);
+}
+
+static void SC_BindFont(RsFont font)
+{
+    GET_TLS();
+    rsi_ContextBindFont(rsc, font);
+}
+
+static void SC_FontColor(float r, float g, float b, float a)
+{
+    GET_TLS();
+    rsc->mStateFont.setFontColor(r, g, b, a);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+// Class implementation
+//////////////////////////////////////////////////////////////////////////////
+
+// llvm name mangling ref
+//  <builtin-type> ::= v  # void
+//                 ::= b  # bool
+//                 ::= c  # char
+//                 ::= a  # signed char
+//                 ::= h  # unsigned char
+//                 ::= s  # short
+//                 ::= t  # unsigned short
+//                 ::= i  # int
+//                 ::= j  # unsigned int
+//                 ::= l  # long
+//                 ::= m  # unsigned long
+//                 ::= x  # long long, __int64
+//                 ::= y  # unsigned long long, __int64
+//                 ::= f  # float
+//                 ::= d  # double
+
+static ScriptCState::SymbolTable_t gSyms[] = {
+    { "_Z22rsgBindProgramFragment19rs_program_fragment", (void *)&SC_bindProgramFragment },
+    { "_Z19rsgBindProgramStore16rs_program_store", (void *)&SC_bindProgramStore },
+    { "_Z20rsgBindProgramVertex17rs_program_vertex", (void *)&SC_bindProgramVertex },
+    { "_Z20rsgBindProgramRaster17rs_program_raster", (void *)&SC_bindProgramRaster },
+    { "_Z14rsgBindSampler19rs_program_fragmentj10rs_sampler", (void *)&SC_bindSampler },
+    { "_Z14rsgBindTexture19rs_program_fragmentj13rs_allocation", (void *)&SC_bindTexture },
+
+    { "_Z36rsgProgramVertexLoadProjectionMatrixPK12rs_matrix4x4", (void *)&SC_vpLoadProjectionMatrix },
+    { "_Z31rsgProgramVertexLoadModelMatrixPK12rs_matrix4x4", (void *)&SC_vpLoadModelMatrix },
+    { "_Z33rsgProgramVertexLoadTextureMatrixPK12rs_matrix4x4", (void *)&SC_vpLoadTextureMatrix },
+
+    { "_Z31rsgProgramFragmentConstantColor19rs_program_fragmentffff", (void *)&SC_pfConstantColor },
+
+    { "_Z11rsgGetWidthv", (void *)&SC_getWidth },
+    { "_Z12rsgGetHeightv", (void *)&SC_getHeight },
+
+    { "_Z18rsgUploadToTexture13rs_allocationj", (void *)&SC_uploadToTexture2 },
+    { "_Z18rsgUploadToTexture13rs_allocation", (void *)&SC_uploadToTexture },
+    { "_Z23rsgUploadToBufferObject13rs_allocation", (void *)&SC_uploadToBufferObject },
+
+    { "_Z11rsgDrawRectfffff", (void *)&SC_drawRect },
+    { "_Z11rsgDrawQuadffffffffffff", (void *)&SC_drawQuad },
+    { "_Z20rsgDrawQuadTexCoordsffffffffffffffffffff", (void *)&SC_drawQuadTexCoords },
+    { "_Z24rsgDrawSpriteScreenspacefffff", (void *)&SC_drawSpriteScreenspace },
+
+    { "_Z11rsgDrawMesh7rs_mesh", (void *)&SC_drawMesh },
+    { "_Z11rsgDrawMesh7rs_meshj", (void *)&SC_drawMeshPrimitive },
+    { "_Z11rsgDrawMesh7rs_meshjjj", (void *)&SC_drawMeshPrimitiveRange },
+
+    { "_Z13rsgClearColorffff", (void *)&SC_ClearColor },
+    { "_Z13rsgClearDepthf", (void *)&SC_ClearDepth },
+
+    { "_Z11rsgDrawTextPKcii", (void *)&SC_DrawText },
+    { "_Z11rsgDrawText13rs_allocationii", (void *)&SC_DrawTextAlloc },
+
+    { "_Z11rsgBindFont7rs_font", (void *)&SC_BindFont },
+    { "_Z12rsgFontColorffff", (void *)&SC_FontColor },
+
+    // misc
+    { "_Z5colorffff", (void *)&SC_color },
+
+    { NULL, NULL }
+};
+
+const ScriptCState::SymbolTable_t * ScriptCState::lookupSymbolGL(const char *sym)
+{
+    ScriptCState::SymbolTable_t *syms = gSyms;
+
+    while (syms->mPtr) {
+        if (!strcmp(syms->mName, sym)) {
+            return syms;
+        }
+        syms++;
+    }
+    return NULL;
+}
+
diff --git a/libs/rs/rsShaderCache.cpp b/libs/rs/rsShaderCache.cpp
index 4711d1b..5c073b3 100644
--- a/libs/rs/rsShaderCache.cpp
+++ b/libs/rs/rsShaderCache.cpp
@@ -14,10 +14,14 @@
  * limitations under the License.
  */
 
+#ifndef ANDROID_RS_BUILD_FOR_HOST
 #include "rsContext.h"
-
 #include <GLES/gl.h>
 #include <GLES2/gl2.h>
+#else
+#include "rsContextHostStub.h"
+#include <OpenGL/gl.h>
+#endif //ANDROID_RS_BUILD_FOR_HOST
 
 using namespace android;
 using namespace android::renderscript;
@@ -94,16 +98,10 @@
         glAttachShader(pgm, frag->getShaderID());
 
         if (!vtx->isUserProgram()) {
-            glBindAttribLocation(pgm, 0, "ATTRIB_LegacyPosition");
-            glBindAttribLocation(pgm, 1, "ATTRIB_LegacyColor");
-            glBindAttribLocation(pgm, 2, "ATTRIB_LegacyNormal");
-            glBindAttribLocation(pgm, 3, "ATTRIB_LegacyPointSize");
-            glBindAttribLocation(pgm, 4, "ATTRIB_LegacyTexture");
-            e->mVtxAttribSlots[RS_KIND_POSITION] = 0;
-            e->mVtxAttribSlots[RS_KIND_COLOR] = 1;
-            e->mVtxAttribSlots[RS_KIND_NORMAL] = 2;
-            e->mVtxAttribSlots[RS_KIND_POINT_SIZE] = 3;
-            e->mVtxAttribSlots[RS_KIND_TEXTURE] = 4;
+            glBindAttribLocation(pgm, 0, "ATTRIB_position");
+            glBindAttribLocation(pgm, 1, "ATTRIB_color");
+            glBindAttribLocation(pgm, 2, "ATTRIB_normal");
+            glBindAttribLocation(pgm, 3, "ATTRIB_texture0");
         }
 
         //LOGE("e2 %x", glGetError());
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/libs/rs/rsSignal.h b/libs/rs/rsSignal.h
new file mode 100644
index 0000000..2e760f1
--- /dev/null
+++ b/libs/rs/rsSignal.h
@@ -0,0 +1,46 @@
+/*
+ * 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_SIGNAL_H
+#define ANDROID_RS_SIGNAL_H
+
+
+#include "rsUtils.h"
+
+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/rsSimpleMesh.cpp b/libs/rs/rsSimpleMesh.cpp
deleted file mode 100644
index 53ce5cd..0000000
--- a/libs/rs/rsSimpleMesh.cpp
+++ /dev/null
@@ -1,169 +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.
- */
-
-#include "rsContext.h"
-
-using namespace android;
-using namespace android::renderscript;
-
-#include <GLES/gl.h>
-#include <GLES/glext.h>
-
-SimpleMesh::SimpleMesh(Context *rsc) : ObjectBase(rsc)
-{
-    mAllocFile = __FILE__;
-    mAllocLine = __LINE__;
-}
-
-SimpleMesh::~SimpleMesh()
-{
-    delete[] mVertexTypes;
-    delete[] mVertexBuffers;
-}
-
-void SimpleMesh::render(Context *rsc) const
-{
-    if (mPrimitiveType.get()) {
-        renderRange(rsc, 0, mPrimitiveType->getDimX());
-        return;
-    }
-
-    if (mIndexType.get()) {
-        renderRange(rsc, 0, mIndexType->getDimX());
-        return;
-    }
-
-    renderRange(rsc, 0, mVertexTypes[0]->getDimX());
-}
-
-void SimpleMesh::renderRange(Context *rsc, uint32_t start, uint32_t len) const
-{
-    if (len < 1) {
-        return;
-    }
-
-    rsc->checkError("SimpleMesh::renderRange 1");
-    VertexArray va;
-    if (rsc->checkVersion2_0()) {
-        for (uint32_t ct=0; ct < mVertexTypeCount; ct++) {
-            mVertexBuffers[ct]->uploadCheck(rsc);
-            va.setActiveBuffer(mVertexBuffers[ct]->getBufferObjectID());
-            mVertexTypes[ct]->enableGLVertexBuffer2(&va);
-        }
-        va.setupGL2(rsc, &rsc->mStateVertexArray, &rsc->mShaderCache);
-    } else {
-        for (uint32_t ct=0; ct < mVertexTypeCount; ct++) {
-            mVertexBuffers[ct]->uploadCheck(rsc);
-            va.setActiveBuffer(mVertexBuffers[ct]->getBufferObjectID());
-            mVertexTypes[ct]->enableGLVertexBuffer(&va);
-        }
-        va.setupGL(rsc, 0);
-    }
-
-    rsc->checkError("SimpleMesh::renderRange 2");
-    if (mIndexType.get()) {
-        mIndexBuffer->uploadCheck(rsc);
-        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mIndexBuffer->getBufferObjectID());
-        glDrawElements(mGLPrimitive, len, GL_UNSIGNED_SHORT, (uint16_t *)(start * 2));
-    } else {
-        glDrawArrays(mGLPrimitive, start, len);
-    }
-
-    rsc->checkError("SimpleMesh::renderRange");
-}
-
-void SimpleMesh::uploadAll(Context *rsc)
-{
-    for (uint32_t ct=0; ct < mVertexTypeCount; ct++) {
-        if (mVertexBuffers[ct].get()) {
-            mVertexBuffers[ct]->deferedUploadToBufferObject(rsc);
-        }
-    }
-    if (mIndexBuffer.get()) {
-        mIndexBuffer->deferedUploadToBufferObject(rsc);
-    }
-    if (mPrimitiveBuffer.get()) {
-        mPrimitiveBuffer->deferedUploadToBufferObject(rsc);
-    }
-    rsc->checkError("SimpleMesh::uploadAll");
-}
-
-
-SimpleMeshContext::SimpleMeshContext()
-{
-}
-
-SimpleMeshContext::~SimpleMeshContext()
-{
-}
-
-
-namespace android {
-namespace renderscript {
-
-
-RsSimpleMesh rsi_SimpleMeshCreate(Context *rsc, RsType prim, RsType idx, RsType *vtx, uint32_t vtxCount, uint32_t primType)
-{
-    SimpleMesh *sm = new SimpleMesh(rsc);
-    sm->incUserRef();
-
-    sm->mIndexType.set((const Type *)idx);
-    sm->mPrimitiveType.set((const Type *)prim);
-
-    sm->mVertexTypeCount = vtxCount;
-    sm->mVertexTypes = new ObjectBaseRef<const Type>[vtxCount];
-    sm->mVertexBuffers = new ObjectBaseRef<Allocation>[vtxCount];
-    for (uint32_t ct=0; ct < vtxCount; ct++) {
-        sm->mVertexTypes[ct].set((const Type *)vtx[ct]);
-    }
-
-    sm->mPrimitive = (RsPrimitive)primType;
-    switch(sm->mPrimitive) {
-    case RS_PRIMITIVE_POINT:          sm->mGLPrimitive = GL_POINTS; break;
-    case RS_PRIMITIVE_LINE:           sm->mGLPrimitive = GL_LINES; break;
-    case RS_PRIMITIVE_LINE_STRIP:     sm->mGLPrimitive = GL_LINE_STRIP; break;
-    case RS_PRIMITIVE_TRIANGLE:       sm->mGLPrimitive = GL_TRIANGLES; break;
-    case RS_PRIMITIVE_TRIANGLE_STRIP: sm->mGLPrimitive = GL_TRIANGLE_STRIP; break;
-    case RS_PRIMITIVE_TRIANGLE_FAN:   sm->mGLPrimitive = GL_TRIANGLE_FAN; break;
-    }
-    return sm;
-}
-
-void rsi_SimpleMeshBindVertex(Context *rsc, RsSimpleMesh mv, RsAllocation va, uint32_t slot)
-{
-    SimpleMesh *sm = static_cast<SimpleMesh *>(mv);
-    rsAssert(slot < sm->mVertexTypeCount);
-
-    sm->mVertexBuffers[slot].set((Allocation *)va);
-}
-
-void rsi_SimpleMeshBindIndex(Context *rsc, RsSimpleMesh mv, RsAllocation va)
-{
-    SimpleMesh *sm = static_cast<SimpleMesh *>(mv);
-    sm->mIndexBuffer.set((Allocation *)va);
-}
-
-void rsi_SimpleMeshBindPrimitive(Context *rsc, RsSimpleMesh mv, RsAllocation va)
-{
-    SimpleMesh *sm = static_cast<SimpleMesh *>(mv);
-    sm->mPrimitiveBuffer.set((Allocation *)va);
-}
-
-
-
-
-}}
-
diff --git a/libs/rs/rsSimpleMesh.h b/libs/rs/rsSimpleMesh.h
deleted file mode 100644
index 6defbda..0000000
--- a/libs/rs/rsSimpleMesh.h
+++ /dev/null
@@ -1,69 +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.
- */
-
-#ifndef ANDROID_RS_SIMPLE_MESH_H
-#define ANDROID_RS_SIMPLE_MESH_H
-
-
-#include "RenderScript.h"
-
-// ---------------------------------------------------------------------------
-namespace android {
-namespace renderscript {
-
-
-// An element is a group of Components that occupies one cell in a structure.
-class SimpleMesh : public ObjectBase
-{
-public:
-    SimpleMesh(Context *);
-    ~SimpleMesh();
-
-    ObjectBaseRef<const Type> mIndexType;
-    ObjectBaseRef<const Type> mPrimitiveType;
-    ObjectBaseRef<const Type> *mVertexTypes;
-    uint32_t mVertexTypeCount;
-
-    ObjectBaseRef<Allocation> mIndexBuffer;
-    ObjectBaseRef<Allocation> mPrimitiveBuffer;
-    ObjectBaseRef<Allocation> *mVertexBuffers;
-
-    RsPrimitive mPrimitive;
-    uint32_t mGLPrimitive;
-
-
-    void render(Context *) const;
-    void renderRange(Context *, uint32_t start, uint32_t len) const;
-    void uploadAll(Context *);
-
-
-protected:
-};
-
-class SimpleMeshContext
-{
-public:
-    SimpleMeshContext();
-    ~SimpleMeshContext();
-
-
-};
-
-
-}
-}
-#endif //ANDROID_RS_SIMPLE_MESH_H
-
diff --git a/libs/rs/rsStream.cpp b/libs/rs/rsStream.cpp
new file mode 100644
index 0000000..68241fa
--- /dev/null
+++ b/libs/rs/rsStream.cpp
@@ -0,0 +1,131 @@
+
+/*
+ * 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_BUILD_FOR_HOST
+#include "rsContext.h"
+#else
+#include "rsContextHostStub.h"
+#endif
+
+#include "rsStream.h"
+
+using namespace android;
+using namespace android::renderscript;
+
+IStream::IStream(const uint8_t *buf, bool use64)
+{
+    mData = buf;
+    mPos = 0;
+    mUse64 = use64;
+}
+
+void IStream::loadByteArray(void *dest, size_t numBytes)
+{
+    memcpy(dest, mData + mPos, numBytes);
+    mPos += numBytes;
+}
+
+uint64_t IStream::loadOffset()
+{
+    uint64_t tmp;
+    if (mUse64) {
+        mPos = (mPos + 7) & (~7);
+        tmp = reinterpret_cast<const uint64_t *>(&mData[mPos])[0];
+        mPos += sizeof(uint64_t);
+        return tmp;
+    }
+    return loadU32();
+}
+
+void IStream::loadString(String8 *s)
+{
+    uint32_t len = loadU32();
+    s->setTo((const char *)&mData[mPos], len);
+    mPos += len;
+}
+
+
+// Output stream implementation
+
+OStream::OStream(uint64_t len, bool use64)
+{
+    mData = (uint8_t*)malloc(len);
+    mLength = len;
+    mPos = 0;
+    mUse64 = use64;
+}
+
+OStream::~OStream()
+{
+    free(mData);
+}
+
+void OStream::addByteArray(const void *src, size_t numBytes)
+{
+    // We need to potentially grow more than once if the number of byes we write is substantial
+    while(mPos + numBytes >= mLength) {
+        growSize();
+    }
+    memcpy(mData + mPos, src, numBytes);
+    mPos += numBytes;
+}
+
+void OStream::addOffset(uint64_t v)
+{
+    if (mUse64) {
+        mPos = (mPos + 7) & (~7);
+        if(mPos + sizeof(v) >= mLength) {
+            growSize();
+        }
+        mData[mPos++] = (uint8_t)(v & 0xff);
+        mData[mPos++] = (uint8_t)((v >> 8) & 0xff);
+        mData[mPos++] = (uint8_t)((v >> 16) & 0xff);
+        mData[mPos++] = (uint8_t)((v >> 24) & 0xff);
+        mData[mPos++] = (uint8_t)((v >> 32) & 0xff);
+        mData[mPos++] = (uint8_t)((v >> 40) & 0xff);
+        mData[mPos++] = (uint8_t)((v >> 48) & 0xff);
+        mData[mPos++] = (uint8_t)((v >> 56) & 0xff);
+    }
+    else {
+        addU32(v);
+    }
+}
+
+void OStream::addString(String8 *s)
+{
+    uint32_t len = s->size();
+    addU32(len);
+    if(mPos + len*sizeof(char) >= mLength) {
+        growSize();
+    }
+    char *stringData = reinterpret_cast<char *>(&mData[mPos]);
+    for(uint32_t i = 0; i < len; i ++) {
+        stringData[i] = s->string()[i];
+    }
+    mPos += len*sizeof(char);
+}
+
+void OStream::growSize()
+{
+    uint8_t *newData = (uint8_t*)malloc(mLength*2);
+    memcpy(newData, mData, mLength*sizeof(uint8_t));
+    mLength = mLength * 2;
+    free(mData);
+    mData = newData;
+}
+
+
diff --git a/libs/rs/rsStream.h b/libs/rs/rsStream.h
new file mode 100644
index 0000000..d401cd12
--- /dev/null
+++ b/libs/rs/rsStream.h
@@ -0,0 +1,163 @@
+/*
+ * 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_STREAM_H
+#define ANDROID_RS_STREAM_H
+
+#include <utils/String8.h>
+#include <stdio.h>
+
+// ---------------------------------------------------------------------------
+namespace android {
+namespace renderscript {
+
+class IStream
+{
+public:
+    IStream(const uint8_t *, bool use64);
+
+    float loadF() {
+        mPos = (mPos + 3) & (~3);
+        float tmp = reinterpret_cast<const float *>(&mData[mPos])[0];
+        mPos += sizeof(float);
+        return tmp;
+    }
+    int32_t loadI32() {
+        mPos = (mPos + 3) & (~3);
+        int32_t tmp = reinterpret_cast<const int32_t *>(&mData[mPos])[0];
+        mPos += sizeof(int32_t);
+        return tmp;
+    }
+    uint32_t loadU32() {
+        mPos = (mPos + 3) & (~3);
+        uint32_t tmp = reinterpret_cast<const uint32_t *>(&mData[mPos])[0];
+        mPos += sizeof(uint32_t);
+        return tmp;
+    }
+    uint16_t loadU16() {
+        mPos = (mPos + 1) & (~1);
+        uint16_t tmp = reinterpret_cast<const uint16_t *>(&mData[mPos])[0];
+        mPos += sizeof(uint16_t);
+        return tmp;
+    }
+    inline uint8_t loadU8() {
+        uint8_t tmp = reinterpret_cast<const uint8_t *>(&mData[mPos])[0];
+        mPos += sizeof(uint8_t);
+        return tmp;
+    }
+    void loadByteArray(void *dest, size_t numBytes);
+    uint64_t loadOffset();
+    void loadString(String8 *s);
+    uint64_t getPos() const {
+        return mPos;
+    }
+    void reset(uint64_t pos) {
+        mPos = pos;
+    }
+    void reset() {
+        mPos = 0;
+    }
+    
+    const uint8_t * getPtr() const {
+        return mData;
+    }
+protected:
+    const uint8_t * mData;
+    uint64_t mPos;
+    bool mUse64;
+};
+
+class OStream
+{
+public:
+    OStream(uint64_t length, bool use64);
+    ~OStream();
+    
+    void align(uint32_t bytes) {
+        mPos = (mPos + (bytes - 1)) & (~(bytes - 1));
+        if(mPos >= mLength) {
+            growSize();
+        }
+    }
+    
+    void addF(float v) {
+        uint32_t uintV = *reinterpret_cast<uint32_t*> (&v);
+        addU32(uintV);
+    }
+    void addI32(int32_t v) {
+        mPos = (mPos + 3) & (~3);
+        if(mPos + sizeof(v) >= mLength) {
+            growSize();
+        }
+        mData[mPos++] = (uint8_t)(v & 0xff);
+        mData[mPos++] = (uint8_t)((v >> 8) & 0xff);
+        mData[mPos++] = (uint8_t)((v >> 16) & 0xff);
+        mData[mPos++] = (uint8_t)((v >> 24) & 0xff);
+    }
+    void addU32(uint32_t v) {
+        mPos = (mPos + 3) & (~3);
+        if(mPos + sizeof(v) >= mLength) {
+            growSize();
+        }
+        mData[mPos++] = (uint8_t)(v & 0xff);
+        mData[mPos++] = (uint8_t)((v >> 8) & 0xff);
+        mData[mPos++] = (uint8_t)((v >> 16) & 0xff);
+        mData[mPos++] = (uint8_t)((v >> 24) & 0xff);
+    }
+    void addU16(uint16_t v) {
+        mPos = (mPos + 1) & (~1);
+        if(mPos + sizeof(v) >= mLength) {
+            growSize();
+        }
+        mData[mPos++] = (uint8_t)(v & 0xff);
+        mData[mPos++] = (uint8_t)(v >> 8);
+    }
+    inline void addU8(uint8_t v) {
+        if(mPos + 1 >= mLength) {
+            growSize();
+        }
+        reinterpret_cast<uint8_t *>(&mData[mPos])[0] = v;
+        mPos ++;
+    }
+    void addByteArray(const void *src, size_t numBytes);
+    void addOffset(uint64_t v);
+    void addString(String8 *s);
+    uint64_t getPos() const {
+        return mPos;
+    }
+    void reset(uint64_t pos) {
+        mPos = pos;
+    }
+    void reset() {
+        mPos = 0;
+    }
+    const uint8_t * getPtr() const {
+        return mData;
+    }
+protected:
+    void growSize();
+    uint8_t * mData;
+    uint64_t mLength;
+    uint64_t mPos;
+    bool mUse64;
+};
+    
+
+} // renderscript
+} // android
+#endif //ANDROID_RS_STREAM_H
+
+
diff --git a/libs/rs/rsType.cpp b/libs/rs/rsType.cpp
index c09e979..79cfd41 100644
--- a/libs/rs/rsType.cpp
+++ b/libs/rs/rsType.cpp
@@ -14,8 +14,13 @@
  * limitations under the License.
  */
 
+#ifndef ANDROID_RS_BUILD_FOR_HOST
 #include "rsContext.h"
 #include <GLES/gl.h>
+#else
+#include "rsContextHostStub.h"
+#include <OpenGL/gl.h>
+#endif
 
 using namespace android;
 using namespace android::renderscript;
@@ -84,7 +89,9 @@
         mLODCount = 1;
     }
     if (mLODCount != oldLODCount) {
-        delete [] mLODs;
+        if(mLODs){
+            delete [] mLODs;
+        }
         mLODs = new LOD[mLODCount];
     }
 
@@ -138,136 +145,31 @@
 
 void Type::makeGLComponents()
 {
+    if(getElement()->getFieldCount() >= RS_MAX_ATTRIBS) {
+        return;
+    }
+
     uint32_t userNum = 0;
 
     for (uint32_t ct=0; ct < getElement()->getFieldCount(); ct++) {
         const Component &c = getElement()->getField(ct)->getComponent();
 
-        switch(c.getKind()) {
-        case RS_KIND_USER:
-            mGL.mUser[userNum].size = c.getVectorSize();
-            mGL.mUser[userNum].offset = mElement->getFieldOffsetBytes(ct);
-            mGL.mUser[userNum].type = c.getGLType();
-            mGL.mUser[userNum].normalized = c.getType() != RS_TYPE_FLOAT_32;//c.getIsNormalized();
-            mGL.mUser[userNum].name.setTo(getElement()->getFieldName(ct));
-            userNum ++;
-            break;
-
-        case RS_KIND_POSITION:
-            rsAssert(mGL.mVtx.size == 0);
-            mGL.mVtx.size = c.getVectorSize();
-            mGL.mVtx.offset = mElement->getFieldOffsetBytes(ct);
-            mGL.mVtx.type = c.getGLType();
-            mGL.mVtx.normalized = false;
-            mGL.mVtx.name.setTo("Position");
-            break;
-
-        case RS_KIND_COLOR:
-            rsAssert(mGL.mColor.size == 0);
-            mGL.mColor.size = c.getVectorSize();
-            mGL.mColor.offset = mElement->getFieldOffsetBytes(ct);
-            mGL.mColor.type = c.getGLType();
-            mGL.mColor.normalized = c.getType() != RS_TYPE_FLOAT_32;
-            mGL.mColor.name.setTo("Color");
-            break;
-
-        case RS_KIND_NORMAL:
-            rsAssert(mGL.mNorm.size == 0);
-            mGL.mNorm.size = c.getVectorSize();
-            mGL.mNorm.offset = mElement->getFieldOffsetBytes(ct);
-            mGL.mNorm.type = c.getGLType();
-            mGL.mNorm.normalized = false;
-            mGL.mNorm.name.setTo("Normal");
-            break;
-
-        case RS_KIND_TEXTURE:
-            rsAssert(mGL.mTex.size == 0);
-            mGL.mTex.size = c.getVectorSize();
-            mGL.mTex.offset = mElement->getFieldOffsetBytes(ct);
-            mGL.mTex.type = c.getGLType();
-            mGL.mTex.normalized = false;
-            mGL.mTex.name.setTo("Texture");
-            break;
-
-        case RS_KIND_POINT_SIZE:
-            rsAssert(!mGL.mPointSize.size);
-            mGL.mPointSize.size = c.getVectorSize();
-            mGL.mPointSize.offset = mElement->getFieldOffsetBytes(ct);
-            mGL.mPointSize.type = c.getGLType();
-            mGL.mPointSize.normalized = false;
-            mGL.mPointSize.name.setTo("PointSize");
-        break;
-
-        default:
-            break;
-        }
+        mAttribs[userNum].size = c.getVectorSize();
+        mAttribs[userNum].offset = mElement->getFieldOffsetBytes(ct);
+        mAttribs[userNum].type = c.getGLType();
+        mAttribs[userNum].normalized = c.getType() != RS_TYPE_FLOAT_32;//c.getIsNormalized();
+        mAttribs[userNum].name.setTo(getElement()->getFieldName(ct));
+        userNum ++;
     }
 }
 
+
 void Type::enableGLVertexBuffer(VertexArray *va) const
 {
-    // Note: We are only going to enable buffers and never disable them
-    // here.  The reason is more than one Allocation may be used as a vertex
-    // source.  So we cannot disable arrays that may have been in use by
-    // another allocation.
-
-    uint32_t stride = mElement->getSizeBytes();
-    if (mGL.mVtx.size) {
-        va->addLegacy(mGL.mVtx.type,
-                      mGL.mVtx.size,
-                      stride,
-                      RS_KIND_POSITION,
-                      false,
-                      mGL.mVtx.offset);
-    }
-
-    if (mGL.mNorm.size) {
-        va->addLegacy(mGL.mNorm.type,
-                     3,
-                     stride,
-                     RS_KIND_NORMAL,
-                     false,
-                     mGL.mNorm.offset);
-    }
-
-    if (mGL.mColor.size) {
-        va->addLegacy(mGL.mColor.type,
-                     mGL.mColor.size,
-                     stride,
-                     RS_KIND_COLOR,
-                     true,
-                     mGL.mColor.offset);
-    }
-
-    if (mGL.mTex.size) {
-        va->addLegacy(mGL.mTex.type,
-                     mGL.mTex.size,
-                     stride,
-                     RS_KIND_TEXTURE,
-                     false,
-                     mGL.mTex.offset);
-    }
-
-    if (mGL.mPointSize.size) {
-        va->addLegacy(mGL.mPointSize.type,
-                     1,
-                     stride,
-                     RS_KIND_POINT_SIZE,
-                     false,
-                     mGL.mPointSize.offset);
-    }
-
-}
-
-void Type::enableGLVertexBuffer2(VertexArray *va) const
-{
-    // Do legacy buffers
-    enableGLVertexBuffer(va);
-
     uint32_t stride = mElement->getSizeBytes();
     for (uint32_t ct=0; ct < RS_MAX_ATTRIBS; ct++) {
-        if (mGL.mUser[ct].size) {
-            va->addUser(mGL.mUser[ct], stride);
+        if (mAttribs[ct].size) {
+            va->add(mAttribs[ct], stride);
         }
     }
 }
@@ -283,6 +185,57 @@
     mElement->dumpLOGV(buf);
 }
 
+void Type::serialize(OStream *stream) const
+{
+    // Need to identify ourselves
+    stream->addU32((uint32_t)getClassId());
+
+    String8 name(getName());
+    stream->addString(&name);
+
+    mElement->serialize(stream);
+
+    stream->addU32(mDimX);
+    stream->addU32(mDimY);
+    stream->addU32(mDimZ);
+
+    stream->addU8((uint8_t)(mDimLOD ? 1 : 0));
+    stream->addU8((uint8_t)(mFaces ? 1 : 0));
+}
+
+Type *Type::createFromStream(Context *rsc, IStream *stream)
+{
+    // First make sure we are reading the correct object
+    RsA3DClassID classID = (RsA3DClassID)stream->loadU32();
+    if(classID != RS_A3D_CLASS_ID_TYPE) {
+        LOGE("type loading skipped due to invalid class id\n");
+        return NULL;
+    }
+
+    String8 name;
+    stream->loadString(&name);
+
+    Element *elem = Element::createFromStream(rsc, stream);
+    if(!elem) {
+        return NULL;
+    }
+
+    Type *type = new Type(rsc);
+    type->mDimX = stream->loadU32();
+    type->mDimY = stream->loadU32();
+    type->mDimZ = stream->loadU32();
+
+    uint8_t temp = stream->loadU8();
+    type->mDimLOD = temp != 0;
+
+    temp = stream->loadU8();
+    type->mFaces = temp != 0;
+
+    type->setElement(elem);
+
+    return type;
+}
+
 bool Type::getIsNp2() const
 {
     uint32_t x = getDimX();
@@ -391,6 +344,22 @@
     return st;
 }
 
+void rsi_TypeGetNativeData(Context *rsc, RsType type, uint32_t *typeData, uint32_t typeDataSize)
+{
+    rsAssert(typeDataSize == 6);
+    // Pack the data in the follofing way mDimX; mDimY; mDimZ;
+    // mDimLOD; mDimFaces; mElement; into typeData
+    Type *t = static_cast<Type *>(type);
+
+    (*typeData++) = t->getDimX();
+    (*typeData++) = t->getDimY();
+    (*typeData++) = t->getDimZ();
+    (*typeData++) = t->getDimLOD();
+    (*typeData++) = t->getDimFaces() ? 1 : 0;
+    (*typeData++) = (uint32_t)t->getElement();
+
+}
+
 
 }
 }
diff --git a/libs/rs/rsType.h b/libs/rs/rsType.h
index c25577c..5b51e20 100644
--- a/libs/rs/rsType.h
+++ b/libs/rs/rsType.h
@@ -71,9 +71,11 @@
     void compute();
 
     void enableGLVertexBuffer(class VertexArray *) const;
-    void enableGLVertexBuffer2(class VertexArray *) const;
 
     void dumpLOGV(const char *prefix) const;
+    virtual void serialize(OStream *stream) const;
+    virtual RsA3DClassID getClassId() const { return RS_A3D_CLASS_ID_TYPE; }
+    static Type *createFromStream(Context *rsc, IStream *stream);
 
 protected:
     struct LOD {
@@ -112,15 +114,7 @@
     LOD *mLODs;
     uint32_t mLODCount;
 
-    struct GLState_t {
-        VertexArray::Attrib mUser[RS_MAX_ATTRIBS];
-        VertexArray::Attrib mVtx;
-        VertexArray::Attrib mNorm;
-        VertexArray::Attrib mColor;
-        VertexArray::Attrib mTex;
-        VertexArray::Attrib mPointSize;
-    };
-    GLState_t mGL;
+    VertexArray::Attrib mAttribs[RS_MAX_ATTRIBS];
     void makeGLComponents();
 
 private:
diff --git a/libs/rs/rsUtils.h b/libs/rs/rsUtils.h
index 07f8933..17feb22 100644
--- a/libs/rs/rsUtils.h
+++ b/libs/rs/rsUtils.h
@@ -19,15 +19,23 @@
 
 #define LOG_NDEBUG 0
 #define LOG_TAG "RenderScript"
+
 #include <utils/Log.h>
-#include <utils/Vector.h>
-#include <utils/KeyedVector.h>
+
+#include "rsStream.h"
+
 #include <utils/String8.h>
+#include <utils/Vector.h>
+
 #include <stdlib.h>
 #include <pthread.h>
 #include <time.h>
+#include <cutils/atomic.h>
 
+#ifndef ANDROID_RS_BUILD_FOR_HOST
 #include <EGL/egl.h>
+#endif
+
 #include <math.h>
 
 #include "RenderScript.h"
@@ -41,6 +49,26 @@
 #define rsAssert(v) while(0)
 #endif
 
+typedef float rsvF_2 __attribute__ ((vector_size (8)));
+typedef float rsvF_4 __attribute__ ((vector_size (16)));
+typedef uint8_t rsvU8_4 __attribute__ ((vector_size (4)));
+
+union float2 {
+    rsvF_2 v;
+    float f[2];
+};
+
+union float4 {
+    rsvF_4 v;
+    float f[4];
+};
+
+union uchar4 {
+    rsvU8_4 v;
+    uint8_t f[4];
+    uint32_t packed;
+};
+
 template<typename T>
 T rsMin(T in1, T in2)
 {
diff --git a/libs/rs/rsVertexArray.cpp b/libs/rs/rsVertexArray.cpp
index 6c2002d..001927c 100644
--- a/libs/rs/rsVertexArray.cpp
+++ b/libs/rs/rsVertexArray.cpp
@@ -14,10 +14,15 @@
  * limitations under the License.
  */
 
+#ifndef ANDROID_RS_BUILD_FOR_HOST
 #include "rsContext.h"
-
 #include <GLES/gl.h>
 #include <GLES2/gl2.h>
+#else
+#include "rsContextHostStub.h"
+#include <OpenGL/gl.h>
+#endif
+
 
 using namespace android;
 using namespace android::renderscript;
@@ -39,6 +44,7 @@
         mAttribs[ct].clear();
     }
     mActiveBuffer = 0;
+    mActivePointer = NULL;
     mCount = 0;
 }
 
@@ -50,12 +56,12 @@
 void VertexArray::Attrib::set(const Attrib &a)
 {
     buffer = a.buffer;
+    ptr = a.ptr;
     offset = a.offset;
     type = a.type;
     size = a.size;
     stride = a.stride;
     normalized = a.normalized;
-    kind = RS_KIND_USER;
     name.setTo(a.name);
 }
 
@@ -66,6 +72,7 @@
     type = 0;
     size = 0;
     stride = 0;
+    ptr = NULL;
     normalized = false;
     name.setTo("");
 }
@@ -75,138 +82,84 @@
     mAttribs[n].clear();
 }
 
-void VertexArray::addUser(const Attrib &a, uint32_t stride)
+void VertexArray::add(const Attrib &a, uint32_t stride)
 {
-    assert(mCount < RS_MAX_ATTRIBS);
+    rsAssert(mCount < RS_MAX_ATTRIBS);
     mAttribs[mCount].set(a);
     mAttribs[mCount].buffer = mActiveBuffer;
+    mAttribs[mCount].ptr = mActivePointer;
     mAttribs[mCount].stride = stride;
-    mAttribs[mCount].kind = RS_KIND_USER;
     mCount ++;
 }
 
-void VertexArray::addLegacy(uint32_t type, uint32_t size, uint32_t stride, RsDataKind kind, bool normalized, uint32_t offset)
+void VertexArray::add(uint32_t type, uint32_t size, uint32_t stride, bool normalized, uint32_t offset, const char *name)
 {
-    assert(mCount < RS_MAX_ATTRIBS);
+    rsAssert(mCount < RS_MAX_ATTRIBS);
     mAttribs[mCount].clear();
     mAttribs[mCount].type = type;
     mAttribs[mCount].size = size;
     mAttribs[mCount].offset = offset;
     mAttribs[mCount].normalized = normalized;
-    mAttribs[mCount].buffer = mActiveBuffer;
     mAttribs[mCount].stride = stride;
-    mAttribs[mCount].kind = kind;
+    mAttribs[mCount].name.setTo(name);
+
+    mAttribs[mCount].buffer = mActiveBuffer;
+    mAttribs[mCount].ptr = mActivePointer;
     mCount ++;
 }
 
 void VertexArray::logAttrib(uint32_t idx, uint32_t slot) const {
-    LOGE("va %i: slot=%i name=%s buf=%i  size=%i  type=0x%x  kind=%i  stride=0x%x  norm=%i  offset=0x%x", idx, slot,
+    LOGE("va %i: slot=%i name=%s buf=%i ptr=%p size=%i  type=0x%x  stride=0x%x  norm=%i  offset=0x%x", idx, slot,
          mAttribs[idx].name.string(),
          mAttribs[idx].buffer,
+         mAttribs[idx].ptr,
          mAttribs[idx].size,
          mAttribs[idx].type,
-         mAttribs[idx].kind,
          mAttribs[idx].stride,
          mAttribs[idx].normalized,
          mAttribs[idx].offset);
 }
 
-void VertexArray::setupGL(const Context *rsc, class VertexArrayState *state) const
-{
-    glClientActiveTexture(GL_TEXTURE0);
-    glDisableClientState(GL_NORMAL_ARRAY);
-    glDisableClientState(GL_COLOR_ARRAY);
-    glDisableClientState(GL_TEXTURE_COORD_ARRAY);
-    glDisableClientState(GL_POINT_SIZE_ARRAY_OES);
-
-    for (uint32_t ct=0; ct < mCount; ct++) {
-        switch(mAttribs[ct].kind) {
-        case RS_KIND_POSITION:
-            //logAttrib(POSITION);
-            glEnableClientState(GL_VERTEX_ARRAY);
-            glBindBuffer(GL_ARRAY_BUFFER, mAttribs[ct].buffer);
-            glVertexPointer(mAttribs[ct].size,
-                            mAttribs[ct].type,
-                            mAttribs[ct].stride,
-                            (void *)mAttribs[ct].offset);
-            break;
-
-        case RS_KIND_NORMAL:
-            //logAttrib(NORMAL);
-            glEnableClientState(GL_NORMAL_ARRAY);
-            rsAssert(mAttribs[ct].size == 3);
-            glBindBuffer(GL_ARRAY_BUFFER, mAttribs[ct].buffer);
-            glNormalPointer(mAttribs[ct].type,
-                            mAttribs[ct].stride,
-                            (void *)mAttribs[ct].offset);
-            break;
-
-        case RS_KIND_COLOR:
-            //logAttrib(COLOR);
-            glEnableClientState(GL_COLOR_ARRAY);
-            glBindBuffer(GL_ARRAY_BUFFER, mAttribs[ct].buffer);
-            glColorPointer(mAttribs[ct].size,
-                           mAttribs[ct].type,
-                           mAttribs[ct].stride,
-                           (void *)mAttribs[ct].offset);
-            break;
-
-        case RS_KIND_TEXTURE:
-            //logAttrib(TEXTURE);
-            glEnableClientState(GL_TEXTURE_COORD_ARRAY);
-            glBindBuffer(GL_ARRAY_BUFFER, mAttribs[ct].buffer);
-            glTexCoordPointer(mAttribs[ct].size,
-                              mAttribs[ct].type,
-                              mAttribs[ct].stride,
-                              (void *)mAttribs[ct].offset);
-            break;
-
-        case RS_KIND_POINT_SIZE:
-            //logAttrib(POINT_SIZE);
-            glEnableClientState(GL_POINT_SIZE_ARRAY_OES);
-            glBindBuffer(GL_ARRAY_BUFFER, mAttribs[ct].buffer);
-            glPointSizePointerOES(mAttribs[ct].type,
-                                  mAttribs[ct].stride,
-                                  (void *)mAttribs[ct].offset);
-            break;
-
-        default:
-            rsAssert(0);
-        }
-    }
-
-    rsc->checkError("VertexArray::setupGL");
-}
-
 void VertexArray::setupGL2(const Context *rsc, class VertexArrayState *state, ShaderCache *sc) const
 {
     rsc->checkError("VertexArray::setupGL2 start");
-    for (uint32_t ct=1; ct <= state->mLastEnableCount; ct++) {
+    for (uint32_t ct=1; ct <= 0xf/*state->mLastEnableCount*/; ct++) {
         glDisableVertexAttribArray(ct);
     }
 
     rsc->checkError("VertexArray::setupGL2 disabled");
     for (uint32_t ct=0; ct < mCount; ct++) {
         uint32_t slot = 0;
+
+        if (mAttribs[ct].name[0] == '#') {
+            continue;
+        }
+
         if (sc->isUserVertexProgram()) {
             slot = sc->vtxAttribSlot(ct);
         } else {
-            if (mAttribs[ct].kind == RS_KIND_USER) {
+            if (mAttribs[ct].name == "position") {
+                slot = 0;
+            } else if (mAttribs[ct].name == "color") {
+                slot = 1;
+            } else if (mAttribs[ct].name == "normal") {
+                slot = 2;
+            } else if (mAttribs[ct].name == "texture0") {
+                slot = 3;
+            } else {
                 continue;
             }
-            slot = sc->vtxAttribSlot(mAttribs[ct].kind);
         }
 
         //logAttrib(ct, slot);
         glEnableVertexAttribArray(slot);
         glBindBuffer(GL_ARRAY_BUFFER, mAttribs[ct].buffer);
-
         glVertexAttribPointer(slot,
                               mAttribs[ct].size,
                               mAttribs[ct].type,
                               mAttribs[ct].normalized,
                               mAttribs[ct].stride,
-                              (void *)mAttribs[ct].offset);
+                              mAttribs[ct].ptr + mAttribs[ct].offset);
     }
     state->mLastEnableCount = mCount;
     rsc->checkError("VertexArray::setupGL2 done");
diff --git a/libs/rs/rsVertexArray.h b/libs/rs/rsVertexArray.h
index 3904cb6..7c609b2 100644
--- a/libs/rs/rsVertexArray.h
+++ b/libs/rs/rsVertexArray.h
@@ -37,13 +37,13 @@
     class Attrib {
     public:
         uint32_t buffer;
+        const uint8_t * ptr;
         uint32_t offset;
         uint32_t type;
         uint32_t size;
         uint32_t stride;
         bool normalized;
         String8 name;
-        RsDataKind kind;
 
         Attrib();
         void set(const Attrib &);
@@ -52,9 +52,18 @@
 
 
     void clearAll();
-    void setActiveBuffer(uint32_t id) {mActiveBuffer = id;}
-    void addUser(const Attrib &, uint32_t stride);
-    void addLegacy(uint32_t type, uint32_t size, uint32_t stride, RsDataKind kind, bool normalized, uint32_t offset);
+    void setActiveBuffer(uint32_t id) {
+        mActiveBuffer = id;
+        mActivePointer = NULL;
+    }
+    void setActiveBuffer(const void *ptr) {
+        mActiveBuffer = 0;
+        mActivePointer = (const uint8_t *)ptr;
+    }
+
+    void add(const Attrib &, uint32_t stride);
+    //void addLegacy(uint32_t type, uint32_t size, uint32_t stride, bool normalized, uint32_t offset);
+    void add(uint32_t type, uint32_t size, uint32_t stride, bool normalized, uint32_t offset, const char *name);
 
     void setupGL(const Context *rsc, class VertexArrayState *) const;
     void setupGL2(const Context *rsc, class VertexArrayState *, ShaderCache *) const;
@@ -63,6 +72,7 @@
 protected:
     void clear(uint32_t index);
     uint32_t mActiveBuffer;
+    const uint8_t * mActivePointer;
     uint32_t mCount;
 
     Attrib mAttribs[RS_MAX_ATTRIBS];
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_cl.rsh b/libs/rs/scriptc/rs_cl.rsh
new file mode 100644
index 0000000..64844a4
--- /dev/null
+++ b/libs/rs/scriptc/rs_cl.rsh
@@ -0,0 +1,785 @@
+#ifndef __RS_CL_RSH__
+#define __RS_CL_RSH__
+
+#define M_PI        3.14159265358979323846264338327950288f   /* pi */
+
+
+// Conversions
+#define CVT_FUNC_2(typeout, typein) \
+static typeout##2 __attribute__((overloadable)) convert_##typeout##2(typein##2 v) { \
+    typeout##2 r = {(typeout)v.x, (typeout)v.y}; \
+    return r; \
+} \
+static typeout##3 __attribute__((overloadable)) convert_##typeout##3(typein##3 v) { \
+    typeout##3 r = {(typeout)v.x, (typeout)v.y, (typeout)v.z}; \
+    return r; \
+} \
+static typeout##4 __attribute__((overloadable)) convert_##typeout##4(typein##4 v) { \
+    typeout##4 r = {(typeout)v.x, (typeout)v.y, (typeout)v.z, (typeout)v.w}; \
+    return r; \
+}
+
+#define CVT_FUNC(type)      CVT_FUNC_2(type, uchar) \
+                            CVT_FUNC_2(type, char) \
+                            CVT_FUNC_2(type, ushort) \
+                            CVT_FUNC_2(type, short) \
+                            CVT_FUNC_2(type, int) \
+                            CVT_FUNC_2(type, uint) \
+                            CVT_FUNC_2(type, float)
+
+CVT_FUNC(char)
+CVT_FUNC(uchar)
+CVT_FUNC(short)
+CVT_FUNC(ushort)
+CVT_FUNC(int)
+CVT_FUNC(uint)
+CVT_FUNC(float)
+
+
+
+// Float ops, 6.11.2
+
+#define DEF_FUNC_1(fnc) \
+static float2 __attribute__((overloadable)) fnc(float2 v) { \
+    float2 r; \
+    r.x = fnc(v.x); \
+    r.y = fnc(v.y); \
+    return r; \
+} \
+static float3 __attribute__((overloadable)) fnc(float3 v) { \
+    float3 r; \
+    r.x = fnc(v.x); \
+    r.y = fnc(v.y); \
+    r.z = fnc(v.z); \
+    return r; \
+} \
+static float4 __attribute__((overloadable)) fnc(float4 v) { \
+    float4 r; \
+    r.x = fnc(v.x); \
+    r.y = fnc(v.y); \
+    r.z = fnc(v.z); \
+    r.w = fnc(v.w); \
+    return r; \
+}
+
+#define DEF_FUNC_2(fnc) \
+static float2 __attribute__((overloadable)) fnc(float2 v1, float2 v2) { \
+    float2 r; \
+    r.x = fnc(v1.x, v2.x); \
+    r.y = fnc(v1.y, v2.y); \
+    return r; \
+} \
+static float3 __attribute__((overloadable)) fnc(float3 v1, float3 v2) { \
+    float3 r; \
+    r.x = fnc(v1.x, v2.x); \
+    r.y = fnc(v1.y, v2.y); \
+    r.z = fnc(v1.z, v2.z); \
+    return r; \
+} \
+static float4 __attribute__((overloadable)) fnc(float4 v1, float4 v2) { \
+    float4 r; \
+    r.x = fnc(v1.x, v2.x); \
+    r.y = fnc(v1.y, v2.y); \
+    r.z = fnc(v1.z, v2.z); \
+    r.w = fnc(v1.w, v2.z); \
+    return r; \
+}
+
+#define DEF_FUNC_2F(fnc) \
+static float2 __attribute__((overloadable)) fnc(float2 v1, float v2) { \
+    float2 r; \
+    r.x = fnc(v1.x, v2); \
+    r.y = fnc(v1.y, v2); \
+    return r; \
+} \
+static float3 __attribute__((overloadable)) fnc(float3 v1, float v2) { \
+    float3 r; \
+    r.x = fnc(v1.x, v2); \
+    r.y = fnc(v1.y, v2); \
+    r.z = fnc(v1.z, v2); \
+    return r; \
+} \
+static float4 __attribute__((overloadable)) fnc(float4 v1, float v2) { \
+    float4 r; \
+    r.x = fnc(v1.x, v2); \
+    r.y = fnc(v1.y, v2); \
+    r.z = fnc(v1.z, v2); \
+    r.w = fnc(v1.w, v2); \
+    return r; \
+}
+
+
+extern float __attribute__((overloadable)) acos(float);
+DEF_FUNC_1(acos)
+
+extern float __attribute__((overloadable)) acosh(float);
+DEF_FUNC_1(acosh)
+
+static float __attribute__((overloadable)) acospi(float v) {
+    return acos(v) / M_PI;
+}
+DEF_FUNC_1(acospi)
+
+extern float __attribute__((overloadable)) asin(float);
+DEF_FUNC_1(asin)
+
+extern float __attribute__((overloadable)) asinh(float);
+DEF_FUNC_1(asinh)
+
+static float __attribute__((overloadable)) asinpi(float v) {
+    return asin(v) / M_PI;
+}
+DEF_FUNC_1(asinpi)
+
+extern float __attribute__((overloadable)) atan(float);
+DEF_FUNC_1(atan)
+
+extern float __attribute__((overloadable)) atan2(float, float);
+DEF_FUNC_2(atan2)
+
+extern float __attribute__((overloadable)) atanh(float);
+DEF_FUNC_1(atanh)
+
+static float __attribute__((overloadable)) atanpi(float v) {
+    return atan(v) / M_PI;
+}
+DEF_FUNC_1(atanpi)
+
+static float __attribute__((overloadable)) atan2pi(float y, float x) {
+    return atan2(y, x) / M_PI;
+}
+DEF_FUNC_2(atan2pi)
+
+extern float __attribute__((overloadable)) cbrt(float);
+DEF_FUNC_1(cbrt)
+
+extern float __attribute__((overloadable)) ceil(float);
+DEF_FUNC_1(ceil)
+
+extern float __attribute__((overloadable)) copysign(float, float);
+DEF_FUNC_2(copysign)
+
+extern float __attribute__((overloadable)) cos(float);
+DEF_FUNC_1(cos)
+
+extern float __attribute__((overloadable)) cosh(float);
+DEF_FUNC_1(cosh)
+
+static float __attribute__((overloadable)) cospi(float v) {
+    return cos(v * M_PI);
+}
+DEF_FUNC_1(cospi)
+
+extern float __attribute__((overloadable)) erfc(float);
+DEF_FUNC_1(erfc)
+
+extern float __attribute__((overloadable)) erf(float);
+DEF_FUNC_1(erf)
+
+extern float __attribute__((overloadable)) exp(float);
+DEF_FUNC_1(exp)
+
+extern float __attribute__((overloadable)) exp2(float);
+DEF_FUNC_1(exp2)
+
+extern float __attribute__((overloadable)) pow(float, float);
+static float __attribute__((overloadable)) exp10(float v) {
+    return pow(10.f, v);
+}
+DEF_FUNC_1(exp10)
+
+extern float __attribute__((overloadable)) expm1(float);
+DEF_FUNC_1(expm1)
+
+extern float __attribute__((overloadable)) fabs(float);
+DEF_FUNC_1(fabs)
+
+extern float __attribute__((overloadable)) fdim(float, float);
+DEF_FUNC_2(fdim)
+
+extern float __attribute__((overloadable)) floor(float);
+DEF_FUNC_1(floor)
+
+extern float __attribute__((overloadable)) fma(float, float, float);
+extern float2 __attribute__((overloadable)) fma(float2, float2, float2);
+extern float3 __attribute__((overloadable)) fma(float3, float3, float3);
+extern float4 __attribute__((overloadable)) fma(float4, float4, float4);
+
+extern float __attribute__((overloadable)) fmax(float, float);
+DEF_FUNC_2(fmax);
+DEF_FUNC_2F(fmax);
+
+extern float __attribute__((overloadable)) fmin(float, float);
+DEF_FUNC_2(fmin);
+DEF_FUNC_2F(fmin);
+
+extern float __attribute__((overloadable)) fmod(float, float);
+DEF_FUNC_2(fmod)
+
+static float __attribute__((overloadable)) fract(float v, float *iptr) {
+    int i = (int)floor(v);
+    iptr[0] = i;
+    return fmin(v - i, 0x1.fffffep-1f);
+}
+static float2 __attribute__((overloadable)) fract(float2 v, float2 *iptr) {
+    float t[2];
+    float2 r;
+    r.x = fract(v.x, &t[0]);
+    r.y = fract(v.y, &t[1]);
+    iptr[0] = t[0];
+    iptr[1] = t[1];
+    return r;
+}
+static float3 __attribute__((overloadable)) fract(float3 v, float3 *iptr) {
+    float t[3];
+    float3 r;
+    r.x = fract(v.x, &t[0]);
+    r.y = fract(v.y, &t[1]);
+    r.z = fract(v.z, &t[2]);
+    iptr[0] = t[0];
+    iptr[1] = t[1];
+    iptr[2] = t[2];
+    return r;
+}
+static float4 __attribute__((overloadable)) fract(float4 v, float4 *iptr) {
+    float t[4];
+    float4 r;
+    r.x = fract(v.x, &t[0]);
+    r.y = fract(v.y, &t[1]);
+    r.z = fract(v.z, &t[2]);
+    r.w = fract(v.w, &t[3]);
+    iptr[0] = t[0];
+    iptr[1] = t[1];
+    iptr[2] = t[2];
+    iptr[3] = t[3];
+    return r;
+}
+
+extern float __attribute__((overloadable)) frexp(float, float *);
+extern float2 __attribute__((overloadable)) frexp(float2, float2 *);
+extern float3 __attribute__((overloadable)) frexp(float3, float3 *);
+extern float4 __attribute__((overloadable)) frexp(float4, float4 *);
+
+extern float __attribute__((overloadable)) hypot(float, float);
+DEF_FUNC_2(hypot)
+
+extern int __attribute__((overloadable)) ilogb(float);
+DEF_FUNC_1(ilogb)
+
+extern float __attribute__((overloadable)) ldexp(float, int);
+extern float2 __attribute__((overloadable)) ldexp(float2, int2);
+extern float3 __attribute__((overloadable)) ldexp(float3, int3);
+extern float4 __attribute__((overloadable)) ldexp(float4, int4);
+extern float2 __attribute__((overloadable)) ldexp(float2, int);
+extern float3 __attribute__((overloadable)) ldexp(float3, int);
+extern float4 __attribute__((overloadable)) ldexp(float4, int);
+
+extern float __attribute__((overloadable)) lgamma(float);
+DEF_FUNC_1(lgamma)
+extern float __attribute__((overloadable)) lgamma(float, float *);
+extern float2 __attribute__((overloadable)) lgamma(float2, float2 *);
+extern float3 __attribute__((overloadable)) lgamma(float3, float3 *);
+extern float4 __attribute__((overloadable)) lgamma(float4, float4 *);
+
+extern float __attribute__((overloadable)) log(float);
+DEF_FUNC_1(log)
+
+
+extern float __attribute__((overloadable)) log10(float);
+DEF_FUNC_1(log10)
+
+static float __attribute__((overloadable)) log2(float v) {
+    return log10(v) / log10(2.f);
+}
+DEF_FUNC_1(log2)
+
+extern float __attribute__((overloadable)) log1p(float);
+DEF_FUNC_1(log1p)
+
+extern float __attribute__((overloadable)) logb(float);
+DEF_FUNC_1(logb)
+
+extern float __attribute__((overloadable)) mad(float, float, float);
+extern float2 __attribute__((overloadable)) mad(float2, float2, float2);
+extern float3 __attribute__((overloadable)) mad(float3, float3, float3);
+extern float4 __attribute__((overloadable)) mad(float4, float4, float4);
+
+extern float __attribute__((overloadable)) modf(float, float *);
+extern float2 __attribute__((overloadable)) modf(float2, float2 *);
+extern float3 __attribute__((overloadable)) modf(float3, float3 *);
+extern float4 __attribute__((overloadable)) modf(float4, float4 *);
+
+//extern float __attribute__((overloadable)) nan(uint);
+
+extern float __attribute__((overloadable)) nextafter(float, float);
+DEF_FUNC_2(nextafter)
+
+DEF_FUNC_2(pow)
+
+static float __attribute__((overloadable)) pown(float v, int p) {
+    return pow(v, (float)p);
+}
+static float2 __attribute__((overloadable)) pown(float2 v, int2 p) {
+    return pow(v, (float2)p);
+}
+static float3 __attribute__((overloadable)) pown(float3 v, int3 p) {
+    return pow(v, (float3)p);
+}
+static float4 __attribute__((overloadable)) pown(float4 v, int4 p) {
+    return pow(v, (float4)p);
+}
+
+static float __attribute__((overloadable)) powr(float v, float p) {
+    return pow(v, p);
+}
+static float2 __attribute__((overloadable)) powr(float2 v, float2 p) {
+    return pow(v, p);
+}
+static float3 __attribute__((overloadable)) powr(float3 v, float3 p) {
+    return pow(v, p);
+}
+static float4 __attribute__((overloadable)) powr(float4 v, float4 p) {
+    return pow(v, p);
+}
+
+extern float __attribute__((overloadable)) remainder(float, float);
+DEF_FUNC_2(remainder)
+
+extern float __attribute__((overloadable)) remquo(float, float, float *);
+extern float2 __attribute__((overloadable)) remquo(float2, float2, float2 *);
+extern float3 __attribute__((overloadable)) remquo(float3, float3, float3 *);
+extern float4 __attribute__((overloadable)) remquo(float4, float4, float4 *);
+
+extern float __attribute__((overloadable)) rint(float);
+DEF_FUNC_1(rint)
+
+static float __attribute__((overloadable)) rootn(float v, int r) {
+    return pow(v, 1.f / r);
+}
+static float2 __attribute__((overloadable)) rootn(float2 v, int2 r) {
+    float2 t = {1.f / r.x, 1.f / r.y};
+    return pow(v, t);
+}
+static float3 __attribute__((overloadable)) rootn(float3 v, int3 r) {
+    float3 t = {1.f / r.x, 1.f / r.y, 1.f / r.z};
+    return pow(v, t);
+}
+static float4 __attribute__((overloadable)) rootn(float4 v, int4 r) {
+    float4 t = {1.f / r.x, 1.f / r.y, 1.f / r.z, 1.f / r.w};
+    return pow(v, t);
+}
+
+extern float __attribute__((overloadable)) round(float);
+DEF_FUNC_1(round)
+
+extern float __attribute__((overloadable)) sqrt(float);
+/*static float __attribute__((overloadable)) rsqrt(float v) {
+    return 1.f / sqrt(v);
+}
+DEF_FUNC_1(rsqrt)*/
+
+extern float __attribute__((overloadable)) sin(float);
+DEF_FUNC_1(sin)
+
+static float __attribute__((overloadable)) sincos(float v, float *cosptr) {
+    *cosptr = cos(v);
+    return sin(v);
+}
+static float2 __attribute__((overloadable)) sincos(float2 v, float2 *cosptr) {
+    *cosptr = cos(v);
+    return sin(v);
+}
+static float3 __attribute__((overloadable)) sincos(float3 v, float3 *cosptr) {
+    *cosptr = cos(v);
+    return sin(v);
+}
+static float4 __attribute__((overloadable)) sincos(float4 v, float4 *cosptr) {
+    *cosptr = cos(v);
+    return sin(v);
+}
+
+extern float __attribute__((overloadable)) sinh(float);
+DEF_FUNC_1(sinh)
+
+static float __attribute__((overloadable)) sinpi(float v) {
+    return sin(v * M_PI);
+}
+DEF_FUNC_1(sinpi)
+
+DEF_FUNC_1(sqrt)
+
+extern float __attribute__((overloadable)) tan(float);
+DEF_FUNC_1(tan)
+
+extern float __attribute__((overloadable)) tanh(float);
+DEF_FUNC_1(tanh)
+
+static float __attribute__((overloadable)) tanpi(float v) {
+    return tan(v * M_PI);
+}
+DEF_FUNC_1(tanpi)
+
+extern float __attribute__((overloadable)) tgamma(float);
+DEF_FUNC_1(tgamma)
+
+extern float __attribute__((overloadable)) trunc(float);
+DEF_FUNC_1(trunc)
+
+// Int ops (partial), 6.11.3
+extern uint __attribute__((overloadable)) abs(int);
+extern ushort __attribute__((overloadable)) abs(short);
+extern uchar __attribute__((overloadable)) abs(char);
+
+extern uint __attribute__((overloadable)) clz(uint);
+extern int __attribute__((overloadable)) clz(int);
+extern ushort __attribute__((overloadable)) clz(ushort);
+extern short __attribute__((overloadable)) clz(short);
+extern uchar __attribute__((overloadable)) clz(uchar);
+extern char __attribute__((overloadable)) clz(char);
+
+static uint __attribute__((overloadable)) min(uint v1, uint v2) {
+    return v1 < v2 ? v1 : v2;
+}
+static int __attribute__((overloadable)) min(int v1, int v2) {
+    return v1 < v2 ? v1 : v2;
+}
+static ushort __attribute__((overloadable)) min(ushort v1, ushort v2) {
+    return v1 < v2 ? v1 : v2;
+}
+static short __attribute__((overloadable)) min(short v1, short v2) {
+    return v1 < v2 ? v1 : v2;
+}
+static uchar __attribute__((overloadable)) min(uchar v1, uchar v2) {
+    return v1 < v2 ? v1 : v2;
+}
+static char __attribute__((overloadable)) min(char v1, char v2) {
+    return v1 < v2 ? v1 : v2;
+}
+
+static uint __attribute__((overloadable)) max(uint v1, uint v2) {
+    return v1 > v2 ? v1 : v2;
+}
+static int __attribute__((overloadable)) max(int v1, int v2) {
+    return v1 > v2 ? v1 : v2;
+}
+static ushort __attribute__((overloadable)) max(ushort v1, ushort v2) {
+    return v1 > v2 ? v1 : v2;
+}
+static short __attribute__((overloadable)) max(short v1, short v2) {
+    return v1 > v2 ? v1 : v2;
+}
+static uchar __attribute__((overloadable)) max(uchar v1, uchar v2) {
+    return v1 > v2 ? v1 : v2;
+}
+static char __attribute__((overloadable)) max(char v1, char v2) {
+    return v1 > v2 ? v1 : v2;
+}
+
+
+
+
+// 6.11.4
+
+static float __attribute__((overloadable)) clamp(float amount, float low, float high) {
+    return amount < low ? low : (amount > high ? high : amount);
+}
+static float2 __attribute__((overloadable)) clamp(float2 amount, float2 low, float2 high) {
+    float2 r;
+    r.x = amount.x < low.x ? low.x : (amount.x > high.x ? high.x : amount.x);
+    r.y = amount.y < low.y ? low.y : (amount.y > high.y ? high.y : amount.y);
+    return r;
+}
+static float3 __attribute__((overloadable)) clamp(float3 amount, float3 low, float3 high) {
+    float3 r;
+    r.x = amount.x < low.x ? low.x : (amount.x > high.x ? high.x : amount.x);
+    r.y = amount.y < low.y ? low.y : (amount.y > high.y ? high.y : amount.y);
+    r.z = amount.z < low.z ? low.z : (amount.z > high.z ? high.z : amount.z);
+    return r;
+}
+static float4 __attribute__((overloadable)) clamp(float4 amount, float4 low, float4 high) {
+    float4 r;
+    r.x = amount.x < low.x ? low.x : (amount.x > high.x ? high.x : amount.x);
+    r.y = amount.y < low.y ? low.y : (amount.y > high.y ? high.y : amount.y);
+    r.z = amount.z < low.z ? low.z : (amount.z > high.z ? high.z : amount.z);
+    r.w = amount.w < low.w ? low.w : (amount.w > high.w ? high.w : amount.w);
+    return r;
+}
+static float2 __attribute__((overloadable)) clamp(float2 amount, float low, float high) {
+    float2 r;
+    r.x = amount.x < low ? low : (amount.x > high ? high : amount.x);
+    r.y = amount.y < low ? low : (amount.y > high ? high : amount.y);
+    return r;
+}
+static float3 __attribute__((overloadable)) clamp(float3 amount, float low, float high) {
+    float3 r;
+    r.x = amount.x < low ? low : (amount.x > high ? high : amount.x);
+    r.y = amount.y < low ? low : (amount.y > high ? high : amount.y);
+    r.z = amount.z < low ? low : (amount.z > high ? high : amount.z);
+    return r;
+}
+static float4 __attribute__((overloadable)) clamp(float4 amount, float low, float high) {
+    float4 r;
+    r.x = amount.x < low ? low : (amount.x > high ? high : amount.x);
+    r.y = amount.y < low ? low : (amount.y > high ? high : amount.y);
+    r.z = amount.z < low ? low : (amount.z > high ? high : amount.z);
+    r.w = amount.w < low ? low : (amount.w > high ? high : amount.w);
+    return r;
+}
+
+static float __attribute__((overloadable)) degrees(float radians) {
+    return radians * (180.f / M_PI);
+}
+DEF_FUNC_1(degrees)
+
+static float __attribute__((overloadable)) max(float v1, float v2) {
+    return v1 > v2 ? v1 : v2;
+}
+static float2 __attribute__((overloadable)) max(float2 v1, float2 v2) {
+    float2 r;
+    r.x = v1.x > v2.x ? v1.x : v2.x;
+    r.y = v1.y > v2.y ? v1.y : v2.y;
+    return r;
+}
+static float3 __attribute__((overloadable)) max(float3 v1, float3 v2) {
+    float3 r;
+    r.x = v1.x > v2.x ? v1.x : v2.x;
+    r.y = v1.y > v2.y ? v1.y : v2.y;
+    r.z = v1.z > v2.z ? v1.z : v2.z;
+    return r;
+}
+static float4 __attribute__((overloadable)) max(float4 v1, float4 v2) {
+    float4 r;
+    r.x = v1.x > v2.x ? v1.x : v2.x;
+    r.y = v1.y > v2.y ? v1.y : v2.y;
+    r.z = v1.z > v2.z ? v1.z : v2.z;
+    r.w = v1.w > v2.w ? v1.w : v2.w;
+    return r;
+}
+static float2 __attribute__((overloadable)) max(float2 v1, float v2) {
+    float2 r;
+    r.x = v1.x > v2 ? v1.x : v2;
+    r.y = v1.y > v2 ? v1.y : v2;
+    return r;
+}
+static float3 __attribute__((overloadable)) max(float3 v1, float v2) {
+    float3 r;
+    r.x = v1.x > v2 ? v1.x : v2;
+    r.y = v1.y > v2 ? v1.y : v2;
+    r.z = v1.z > v2 ? v1.z : v2;
+    return r;
+}
+static float4 __attribute__((overloadable)) max(float4 v1, float v2) {
+    float4 r;
+    r.x = v1.x > v2 ? v1.x : v2;
+    r.y = v1.y > v2 ? v1.y : v2;
+    r.z = v1.z > v2 ? v1.z : v2;
+    r.w = v1.w > v2 ? v1.w : v2;
+    return r;
+}
+
+static float __attribute__((overloadable)) min(float v1, float v2) {
+    return v1 < v2 ? v1 : v2;
+}
+static float2 __attribute__((overloadable)) min(float2 v1, float2 v2) {
+    float2 r;
+    r.x = v1.x < v2.x ? v1.x : v2.x;
+    r.y = v1.y < v2.y ? v1.y : v2.y;
+    return r;
+}
+static float3 __attribute__((overloadable)) min(float3 v1, float3 v2) {
+    float3 r;
+    r.x = v1.x < v2.x ? v1.x : v2.x;
+    r.y = v1.y < v2.y ? v1.y : v2.y;
+    r.z = v1.z < v2.z ? v1.z : v2.z;
+    return r;
+}
+static float4 __attribute__((overloadable)) min(float4 v1, float4 v2) {
+    float4 r;
+    r.x = v1.x < v2.x ? v1.x : v2.x;
+    r.y = v1.y < v2.y ? v1.y : v2.y;
+    r.z = v1.z < v2.z ? v1.z : v2.z;
+    r.w = v1.w < v2.w ? v1.w : v2.w;
+    return r;
+}
+static float2 __attribute__((overloadable)) min(float2 v1, float v2) {
+    float2 r;
+    r.x = v1.x < v2 ? v1.x : v2;
+    r.y = v1.y < v2 ? v1.y : v2;
+    return r;
+}
+static float3 __attribute__((overloadable)) min(float3 v1, float v2) {
+    float3 r;
+    r.x = v1.x < v2 ? v1.x : v2;
+    r.y = v1.y < v2 ? v1.y : v2;
+    r.z = v1.z < v2 ? v1.z : v2;
+    return r;
+}
+static float4 __attribute__((overloadable)) min(float4 v1, float v2) {
+    float4 r;
+    r.x = v1.x < v2 ? v1.x : v2;
+    r.y = v1.y < v2 ? v1.y : v2;
+    r.z = v1.z < v2 ? v1.z : v2;
+    r.w = v1.w < v2 ? v1.w : v2;
+    return r;
+}
+
+static float __attribute__((overloadable)) mix(float start, float stop, float amount) {
+    return start + (stop - start) * amount;
+}
+static float2 __attribute__((overloadable)) mix(float2 start, float2 stop, float2 amount) {
+    return start + (stop - start) * amount;
+}
+static float3 __attribute__((overloadable)) mix(float3 start, float3 stop, float3 amount) {
+    return start + (stop - start) * amount;
+}
+static float4 __attribute__((overloadable)) mix(float4 start, float4 stop, float4 amount) {
+    return start + (stop - start) * amount;
+}
+static float2 __attribute__((overloadable)) mix(float2 start, float2 stop, float amount) {
+    return start + (stop - start) * amount;
+}
+static float3 __attribute__((overloadable)) mix(float3 start, float3 stop, float amount) {
+    return start + (stop - start) * amount;
+}
+static float4 __attribute__((overloadable)) mix(float4 start, float4 stop, float amount) {
+    return start + (stop - start) * amount;
+}
+
+static float __attribute__((overloadable)) radians(float degrees) {
+    return degrees * (M_PI / 180.f);
+}
+DEF_FUNC_1(radians)
+
+static float __attribute__((overloadable)) step(float edge, float v) {
+    return (v < edge) ? 0.f : 1.f;
+}
+static float2 __attribute__((overloadable)) step(float2 edge, float2 v) {
+    float2 r;
+    r.x = (v.x < edge.x) ? 0.f : 1.f;
+    r.y = (v.y < edge.y) ? 0.f : 1.f;
+    return r;
+}
+static float3 __attribute__((overloadable)) step(float3 edge, float3 v) {
+    float3 r;
+    r.x = (v.x < edge.x) ? 0.f : 1.f;
+    r.y = (v.y < edge.y) ? 0.f : 1.f;
+    r.z = (v.z < edge.z) ? 0.f : 1.f;
+    return r;
+}
+static float4 __attribute__((overloadable)) step(float4 edge, float4 v) {
+    float4 r;
+    r.x = (v.x < edge.x) ? 0.f : 1.f;
+    r.y = (v.y < edge.y) ? 0.f : 1.f;
+    r.z = (v.z < edge.z) ? 0.f : 1.f;
+    r.w = (v.w < edge.w) ? 0.f : 1.f;
+    return r;
+}
+static float2 __attribute__((overloadable)) step(float2 edge, float v) {
+    float2 r;
+    r.x = (v < edge.x) ? 0.f : 1.f;
+    r.y = (v < edge.y) ? 0.f : 1.f;
+    return r;
+}
+static float3 __attribute__((overloadable)) step(float3 edge, float v) {
+    float3 r;
+    r.x = (v < edge.x) ? 0.f : 1.f;
+    r.y = (v < edge.y) ? 0.f : 1.f;
+    r.z = (v < edge.z) ? 0.f : 1.f;
+    return r;
+}
+static float4 __attribute__((overloadable)) step(float4 edge, float v) {
+    float4 r;
+    r.x = (v < edge.x) ? 0.f : 1.f;
+    r.y = (v < edge.y) ? 0.f : 1.f;
+    r.z = (v < edge.z) ? 0.f : 1.f;
+    r.w = (v < edge.w) ? 0.f : 1.f;
+    return r;
+}
+
+extern float __attribute__((overloadable)) smoothstep(float, float, float);
+extern float2 __attribute__((overloadable)) smoothstep(float2, float2, float2);
+extern float3 __attribute__((overloadable)) smoothstep(float3, float3, float3);
+extern float4 __attribute__((overloadable)) smoothstep(float4, float4, float4);
+extern float2 __attribute__((overloadable)) smoothstep(float, float, float2);
+extern float3 __attribute__((overloadable)) smoothstep(float, float, float3);
+extern float4 __attribute__((overloadable)) smoothstep(float, float, float4);
+
+static float __attribute__((overloadable)) sign(float v) {
+    if (v > 0) return 1.f;
+    if (v < 0) return -1.f;
+    return v;
+}
+DEF_FUNC_1(sign)
+
+// 6.11.5
+static float3 __attribute__((overloadable)) cross(float3 lhs, float3 rhs) {
+    float3 r;
+    r.x = lhs.y * rhs.z  - lhs.z * rhs.y;
+    r.y = lhs.z * rhs.x  - lhs.x * rhs.z;
+    r.z = lhs.x * rhs.y  - lhs.y * rhs.x;
+    return r;
+}
+
+static float4 __attribute__((overloadable)) cross(float4 lhs, float4 rhs) {
+    float4 r;
+    r.x = lhs.y * rhs.z  - lhs.z * rhs.y;
+    r.y = lhs.z * rhs.x  - lhs.x * rhs.z;
+    r.z = lhs.x * rhs.y  - lhs.y * rhs.x;
+    r.w = 0.f;
+    return r;
+}
+
+static float __attribute__((overloadable)) dot(float lhs, float rhs) {
+    return lhs * rhs;
+}
+static float __attribute__((overloadable)) dot(float2 lhs, float2 rhs) {
+    return lhs.x*rhs.x + lhs.y*rhs.y;
+}
+static float __attribute__((overloadable)) dot(float3 lhs, float3 rhs) {
+    return lhs.x*rhs.x + lhs.y*rhs.y + lhs.z*rhs.z;
+}
+static float __attribute__((overloadable)) dot(float4 lhs, float4 rhs) {
+    return lhs.x*rhs.x + lhs.y*rhs.y + lhs.z*rhs.z + lhs.w*rhs.w;
+}
+
+static float __attribute__((overloadable)) length(float v) {
+    return v;
+}
+static float __attribute__((overloadable)) length(float2 v) {
+    return sqrt(v.x*v.x + v.y*v.y);
+}
+static float __attribute__((overloadable)) length(float3 v) {
+    return sqrt(v.x*v.x + v.y*v.y + v.z*v.z);
+}
+static float __attribute__((overloadable)) length(float4 v) {
+    return sqrt(v.x*v.x + v.y*v.y + v.z*v.z + v.w*v.w);
+}
+
+static float __attribute__((overloadable)) distance(float lhs, float rhs) {
+    return length(lhs - rhs);
+}
+static float __attribute__((overloadable)) distance(float2 lhs, float2 rhs) {
+    return length(lhs - rhs);
+}
+static float __attribute__((overloadable)) distance(float3 lhs, float3 rhs) {
+    return length(lhs - rhs);
+}
+static float __attribute__((overloadable)) distance(float4 lhs, float4 rhs) {
+    return length(lhs - rhs);
+}
+
+static float __attribute__((overloadable)) normalize(float v) {
+    return 1.f;
+}
+static float2 __attribute__((overloadable)) normalize(float2 v) {
+    return v / length(v);
+}
+static float3 __attribute__((overloadable)) normalize(float3 v) {
+    return v / length(v);
+}
+static float4 __attribute__((overloadable)) normalize(float4 v) {
+    return v / length(v);
+}
+
+
+#endif
diff --git a/libs/rs/scriptc/rs_core.rsh b/libs/rs/scriptc/rs_core.rsh
new file mode 100644
index 0000000..01b039f
--- /dev/null
+++ b/libs/rs/scriptc/rs_core.rsh
@@ -0,0 +1,623 @@
+#ifndef __RS_CORE_RSH__
+#define __RS_CORE_RSH__
+
+
+static uchar4 __attribute__((overloadable)) rsPackColorTo8888(float r, float g, float b)
+{
+    uchar4 c;
+    c.x = (uchar)(r * 255.f);
+    c.y = (uchar)(g * 255.f);
+    c.z = (uchar)(b * 255.f);
+    c.w = 255;
+    return c;
+}
+
+static uchar4 __attribute__((overloadable)) rsPackColorTo8888(float r, float g, float b, float a)
+{
+    uchar4 c;
+    c.x = (uchar)(r * 255.f);
+    c.y = (uchar)(g * 255.f);
+    c.z = (uchar)(b * 255.f);
+    c.w = (uchar)(a * 255.f);
+    return c;
+}
+
+static uchar4 __attribute__((overloadable)) rsPackColorTo8888(float3 color)
+{
+    color *= 255.f;
+    uchar4 c = {color.x, color.y, color.z, 255};
+    return c;
+}
+
+static uchar4 __attribute__((overloadable)) rsPackColorTo8888(float4 color)
+{
+    color *= 255.f;
+    uchar4 c = {color.x, color.y, color.z, color.w};
+    return c;
+}
+
+static float4 rsUnpackColor8888(uchar4 c)
+{
+    float4 ret = (float4)0.0039156862745f;
+    ret *= convert_float4(c);
+    return ret;
+}
+
+//extern uchar4 __attribute__((overloadable)) rsPackColorTo565(float r, float g, float b);
+//extern uchar4 __attribute__((overloadable)) rsPackColorTo565(float3);
+//extern float4 rsUnpackColor565(uchar4);
+
+
+/////////////////////////////////////////////////////
+// Matrix ops
+/////////////////////////////////////////////////////
+
+static void __attribute__((overloadable))
+rsMatrixSet(rs_matrix4x4 *m, uint32_t row, uint32_t col, float v) {
+    m->m[row * 4 + col] = v;
+}
+
+static float __attribute__((overloadable))
+rsMatrixGet(const rs_matrix4x4 *m, uint32_t row, uint32_t col) {
+    return m->m[row * 4 + col];
+}
+
+static void __attribute__((overloadable))
+rsMatrixSet(rs_matrix3x3 *m, uint32_t row, uint32_t col, float v) {
+    m->m[row * 3 + col] = v;
+}
+
+static float __attribute__((overloadable))
+rsMatrixGet(const rs_matrix3x3 *m, uint32_t row, uint32_t col) {
+    return m->m[row * 3 + col];
+}
+
+static void __attribute__((overloadable))
+rsMatrixSet(rs_matrix2x2 *m, uint32_t row, uint32_t col, float v) {
+    m->m[row * 2 + col] = v;
+}
+
+static float __attribute__((overloadable))
+rsMatrixGet(const rs_matrix2x2 *m, uint32_t row, uint32_t col) {
+    return m->m[row * 2 + col];
+}
+
+static void __attribute__((overloadable))
+rsMatrixLoadIdentity(rs_matrix4x4 *m) {
+    m->m[0] = 1.f;
+    m->m[1] = 0.f;
+    m->m[2] = 0.f;
+    m->m[3] = 0.f;
+    m->m[4] = 0.f;
+    m->m[5] = 1.f;
+    m->m[6] = 0.f;
+    m->m[7] = 0.f;
+    m->m[8] = 0.f;
+    m->m[9] = 0.f;
+    m->m[10] = 1.f;
+    m->m[11] = 0.f;
+    m->m[12] = 0.f;
+    m->m[13] = 0.f;
+    m->m[14] = 0.f;
+    m->m[15] = 1.f;
+}
+
+static void __attribute__((overloadable))
+rsMatrixLoadIdentity(rs_matrix3x3 *m) {
+    m->m[0] = 1.f;
+    m->m[1] = 0.f;
+    m->m[2] = 0.f;
+    m->m[3] = 0.f;
+    m->m[4] = 1.f;
+    m->m[5] = 0.f;
+    m->m[6] = 0.f;
+    m->m[7] = 0.f;
+    m->m[8] = 1.f;
+}
+
+static void __attribute__((overloadable))
+rsMatrixLoadIdentity(rs_matrix2x2 *m) {
+    m->m[0] = 1.f;
+    m->m[1] = 0.f;
+    m->m[2] = 0.f;
+    m->m[3] = 1.f;
+}
+
+static void __attribute__((overloadable))
+rsMatrixLoad(rs_matrix4x4 *m, const float *v) {
+    m->m[0] = v[0];
+    m->m[1] = v[1];
+    m->m[2] = v[2];
+    m->m[3] = v[3];
+    m->m[4] = v[4];
+    m->m[5] = v[5];
+    m->m[6] = v[6];
+    m->m[7] = v[7];
+    m->m[8] = v[8];
+    m->m[9] = v[9];
+    m->m[10] = v[10];
+    m->m[11] = v[11];
+    m->m[12] = v[12];
+    m->m[13] = v[13];
+    m->m[14] = v[14];
+    m->m[15] = v[15];
+}
+
+static void __attribute__((overloadable))
+rsMatrixLoad(rs_matrix3x3 *m, const float *v) {
+    m->m[0] = v[0];
+    m->m[1] = v[1];
+    m->m[2] = v[2];
+    m->m[3] = v[3];
+    m->m[4] = v[4];
+    m->m[5] = v[5];
+    m->m[6] = v[6];
+    m->m[7] = v[7];
+    m->m[8] = v[8];
+}
+
+static void __attribute__((overloadable))
+rsMatrixLoad(rs_matrix2x2 *m, const float *v) {
+    m->m[0] = v[0];
+    m->m[1] = v[1];
+    m->m[2] = v[2];
+    m->m[3] = v[3];
+}
+
+static void __attribute__((overloadable))
+rsMatrixLoad(rs_matrix4x4 *m, const rs_matrix4x4 *v) {
+    m->m[0] = v->m[0];
+    m->m[1] = v->m[1];
+    m->m[2] = v->m[2];
+    m->m[3] = v->m[3];
+    m->m[4] = v->m[4];
+    m->m[5] = v->m[5];
+    m->m[6] = v->m[6];
+    m->m[7] = v->m[7];
+    m->m[8] = v->m[8];
+    m->m[9] = v->m[9];
+    m->m[10] = v->m[10];
+    m->m[11] = v->m[11];
+    m->m[12] = v->m[12];
+    m->m[13] = v->m[13];
+    m->m[14] = v->m[14];
+    m->m[15] = v->m[15];
+}
+
+static void __attribute__((overloadable))
+rsMatrixLoad(rs_matrix4x4 *m, const rs_matrix3x3 *v) {
+    m->m[0] = v->m[0];
+    m->m[1] = v->m[1];
+    m->m[2] = v->m[2];
+    m->m[3] = 0.f;
+    m->m[4] = v->m[3];
+    m->m[5] = v->m[4];
+    m->m[6] = v->m[5];
+    m->m[7] = 0.f;
+    m->m[8] = v->m[6];
+    m->m[9] = v->m[7];
+    m->m[10] = v->m[8];
+    m->m[11] = 0.f;
+    m->m[12] = 0.f;
+    m->m[13] = 0.f;
+    m->m[14] = 0.f;
+    m->m[15] = 1.f;
+}
+
+static void __attribute__((overloadable))
+rsMatrixLoad(rs_matrix4x4 *m, const rs_matrix2x2 *v) {
+    m->m[0] = v->m[0];
+    m->m[1] = v->m[1];
+    m->m[2] = 0.f;
+    m->m[3] = 0.f;
+    m->m[4] = v->m[3];
+    m->m[5] = v->m[4];
+    m->m[6] = 0.f;
+    m->m[7] = 0.f;
+    m->m[8] = v->m[6];
+    m->m[9] = v->m[7];
+    m->m[10] = 1.f;
+    m->m[11] = 0.f;
+    m->m[12] = 0.f;
+    m->m[13] = 0.f;
+    m->m[14] = 0.f;
+    m->m[15] = 1.f;
+}
+
+static void __attribute__((overloadable))
+rsMatrixLoad(rs_matrix3x3 *m, const rs_matrix3x3 *v) {
+    m->m[0] = v->m[0];
+    m->m[1] = v->m[1];
+    m->m[2] = v->m[2];
+    m->m[3] = v->m[3];
+    m->m[4] = v->m[4];
+    m->m[5] = v->m[5];
+    m->m[6] = v->m[6];
+    m->m[7] = v->m[7];
+    m->m[8] = v->m[8];
+}
+
+static void __attribute__((overloadable))
+rsMatrixLoad(rs_matrix2x2 *m, const rs_matrix2x2 *v) {
+    m->m[0] = v->m[0];
+    m->m[1] = v->m[1];
+    m->m[2] = v->m[2];
+    m->m[3] = v->m[3];
+}
+
+static void __attribute__((overloadable))
+rsMatrixLoadRotate(rs_matrix4x4 *m, float rot, float x, float y, float z) {
+    float c, s;
+    m->m[3] = 0;
+    m->m[7] = 0;
+    m->m[11]= 0;
+    m->m[12]= 0;
+    m->m[13]= 0;
+    m->m[14]= 0;
+    m->m[15]= 1;
+    rot *= (float)(M_PI / 180.0f);
+    c = cos(rot);
+    s = sin(rot);
+
+    const float len = x*x + y*y + z*z;
+    if (!(len != 1)) {
+        const float recipLen = 1.f / sqrt(len);
+        x *= recipLen;
+        y *= recipLen;
+        z *= recipLen;
+    }
+    const float nc = 1.0f - c;
+    const float xy = x * y;
+    const float yz = y * z;
+    const float zx = z * x;
+    const float xs = x * s;
+    const float ys = y * s;
+    const float zs = z * s;
+    m->m[ 0] = x*x*nc +  c;
+    m->m[ 4] =  xy*nc - zs;
+    m->m[ 8] =  zx*nc + ys;
+    m->m[ 1] =  xy*nc + zs;
+    m->m[ 5] = y*y*nc +  c;
+    m->m[ 9] =  yz*nc - xs;
+    m->m[ 2] =  zx*nc - ys;
+    m->m[ 6] =  yz*nc + xs;
+    m->m[10] = z*z*nc +  c;
+}
+
+static void __attribute__((overloadable))
+rsMatrixLoadScale(rs_matrix4x4 *m, float x, float y, float z) {
+    rsMatrixLoadIdentity(m);
+    m->m[0] = x;
+    m->m[5] = y;
+    m->m[10] = z;
+}
+
+static void __attribute__((overloadable))
+rsMatrixLoadTranslate(rs_matrix4x4 *m, float x, float y, float z) {
+    rsMatrixLoadIdentity(m);
+    m->m[12] = x;
+    m->m[13] = y;
+    m->m[14] = z;
+}
+
+static void __attribute__((overloadable))
+rsMatrixLoadMultiply(rs_matrix4x4 *m, const rs_matrix4x4 *lhs, const rs_matrix4x4 *rhs) {
+    for (int i=0 ; i<4 ; i++) {
+        float ri0 = 0;
+        float ri1 = 0;
+        float ri2 = 0;
+        float ri3 = 0;
+        for (int j=0 ; j<4 ; j++) {
+            const float rhs_ij = rsMatrixGet(rhs, i,j);
+            ri0 += rsMatrixGet(lhs, j, 0) * rhs_ij;
+            ri1 += rsMatrixGet(lhs, j, 1) * rhs_ij;
+            ri2 += rsMatrixGet(lhs, j, 2) * rhs_ij;
+            ri3 += rsMatrixGet(lhs, j, 3) * rhs_ij;
+        }
+        rsMatrixSet(m, i, 0, ri0);
+        rsMatrixSet(m, i, 1, ri1);
+        rsMatrixSet(m, i, 2, ri2);
+        rsMatrixSet(m, i, 3, ri3);
+    }
+}
+
+static void __attribute__((overloadable))
+rsMatrixMultiply(rs_matrix4x4 *m, const rs_matrix4x4 *rhs) {
+    rs_matrix4x4 mt;
+    rsMatrixLoadMultiply(&mt, m, rhs);
+    rsMatrixLoad(m, &mt);
+}
+
+static void __attribute__((overloadable))
+rsMatrixLoadMultiply(rs_matrix3x3 *m, const rs_matrix3x3 *lhs, const rs_matrix3x3 *rhs) {
+    for (int i=0 ; i<3 ; i++) {
+        float ri0 = 0;
+        float ri1 = 0;
+        float ri2 = 0;
+        for (int j=0 ; j<3 ; j++) {
+            const float rhs_ij = rsMatrixGet(rhs, i,j);
+            ri0 += rsMatrixGet(lhs, j, 0) * rhs_ij;
+            ri1 += rsMatrixGet(lhs, j, 1) * rhs_ij;
+            ri2 += rsMatrixGet(lhs, j, 2) * rhs_ij;
+        }
+        rsMatrixSet(m, i, 0, ri0);
+        rsMatrixSet(m, i, 1, ri1);
+        rsMatrixSet(m, i, 2, ri2);
+    }
+}
+
+static void __attribute__((overloadable))
+rsMatrixMultiply(rs_matrix3x3 *m, const rs_matrix3x3 *rhs) {
+    rs_matrix3x3 mt;
+    rsMatrixLoadMultiply(&mt, m, rhs);
+    rsMatrixLoad(m, &mt);
+}
+
+static void __attribute__((overloadable))
+rsMatrixLoadMultiply(rs_matrix2x2 *m, const rs_matrix2x2 *lhs, const rs_matrix2x2 *rhs) {
+    for (int i=0 ; i<2 ; i++) {
+        float ri0 = 0;
+        float ri1 = 0;
+        for (int j=0 ; j<2 ; j++) {
+            const float rhs_ij = rsMatrixGet(rhs, i,j);
+            ri0 += rsMatrixGet(lhs, j, 0) * rhs_ij;
+            ri1 += rsMatrixGet(lhs, j, 1) * rhs_ij;
+        }
+        rsMatrixSet(m, i, 0, ri0);
+        rsMatrixSet(m, i, 1, ri1);
+    }
+}
+
+static void __attribute__((overloadable))
+rsMatrixMultiply(rs_matrix2x2 *m, const rs_matrix2x2 *rhs) {
+    rs_matrix2x2 mt;
+    rsMatrixLoadMultiply(&mt, m, rhs);
+    rsMatrixLoad(m, &mt);
+}
+
+static void __attribute__((overloadable))
+rsMatrixRotate(rs_matrix4x4 *m, float rot, float x, float y, float z) {
+    rs_matrix4x4 m1;
+    rsMatrixLoadRotate(&m1, rot, x, y, z);
+    rsMatrixMultiply(m, &m1);
+}
+
+static void __attribute__((overloadable))
+rsMatrixScale(rs_matrix4x4 *m, float x, float y, float z) {
+    rs_matrix4x4 m1;
+    rsMatrixLoadScale(&m1, x, y, z);
+    rsMatrixMultiply(m, &m1);
+}
+
+static void __attribute__((overloadable))
+rsMatrixTranslate(rs_matrix4x4 *m, float x, float y, float z) {
+    rs_matrix4x4 m1;
+    rsMatrixLoadTranslate(&m1, x, y, z);
+    rsMatrixMultiply(m, &m1);
+}
+
+static void __attribute__((overloadable))
+rsMatrixLoadOrtho(rs_matrix4x4 *m, float left, float right, float bottom, float top, float near, float far) {
+    rsMatrixLoadIdentity(m);
+    m->m[0] = 2.f / (right - left);
+    m->m[5] = 2.f / (top - bottom);
+    m->m[10]= -2.f / (far - near);
+    m->m[12]= -(right + left) / (right - left);
+    m->m[13]= -(top + bottom) / (top - bottom);
+    m->m[14]= -(far + near) / (far - near);
+}
+
+static void __attribute__((overloadable))
+rsMatrixLoadFrustum(rs_matrix4x4 *m, float left, float right, float bottom, float top, float near, float far) {
+    rsMatrixLoadIdentity(m);
+    m->m[0] = 2.f * near / (right - left);
+    m->m[5] = 2.f * near / (top - bottom);
+    m->m[8] = (right + left) / (right - left);
+    m->m[9] = (top + bottom) / (top - bottom);
+    m->m[10]= -(far + near) / (far - near);
+    m->m[11]= -1.f;
+    m->m[14]= -2.f * far * near / (far - near);
+    m->m[15]= 0.f;
+}
+
+static float4 __attribute__((overloadable))
+rsMatrixMultiply(rs_matrix4x4 *m, float4 in) {
+    float4 ret;
+    ret.x = (m->m[0] * in.x) + (m->m[4] * in.y) + (m->m[8] * in.z) + (m->m[12] * in.w);
+    ret.y = (m->m[1] * in.x) + (m->m[5] * in.y) + (m->m[9] * in.z) + (m->m[13] * in.w);
+    ret.z = (m->m[2] * in.x) + (m->m[6] * in.y) + (m->m[10] * in.z) + (m->m[14] * in.w);
+    ret.w = (m->m[3] * in.x) + (m->m[7] * in.y) + (m->m[11] * in.z) + (m->m[15] * in.w);
+    return ret;
+}
+
+static float4 __attribute__((overloadable))
+rsMatrixMultiply(rs_matrix4x4 *m, float3 in) {
+    float4 ret;
+    ret.x = (m->m[0] * in.x) + (m->m[4] * in.y) + (m->m[8] * in.z) + m->m[12];
+    ret.y = (m->m[1] * in.x) + (m->m[5] * in.y) + (m->m[9] * in.z) + m->m[13];
+    ret.z = (m->m[2] * in.x) + (m->m[6] * in.y) + (m->m[10] * in.z) + m->m[14];
+    ret.w = (m->m[3] * in.x) + (m->m[7] * in.y) + (m->m[11] * in.z) + m->m[15];
+    return ret;
+}
+
+static float4 __attribute__((overloadable))
+rsMatrixMultiply(rs_matrix4x4 *m, float2 in) {
+    float4 ret;
+    ret.x = (m->m[0] * in.x) + (m->m[4] * in.y) + m->m[12];
+    ret.y = (m->m[1] * in.x) + (m->m[5] * in.y) + m->m[13];
+    ret.z = (m->m[2] * in.x) + (m->m[6] * in.y) + m->m[14];
+    ret.w = (m->m[3] * in.x) + (m->m[7] * in.y) + m->m[15];
+    return ret;
+}
+
+static float3 __attribute__((overloadable))
+rsMatrixMultiply(rs_matrix3x3 *m, float3 in) {
+    float3 ret;
+    ret.x = (m->m[0] * in.x) + (m->m[3] * in.y) + (m->m[6] * in.z);
+    ret.y = (m->m[1] * in.x) + (m->m[4] * in.y) + (m->m[7] * in.z);
+    ret.z = (m->m[2] * in.x) + (m->m[5] * in.y) + (m->m[8] * in.z);
+    return ret;
+}
+
+static float3 __attribute__((overloadable))
+rsMatrixMultiply(rs_matrix3x3 *m, float2 in) {
+    float3 ret;
+    ret.x = (m->m[0] * in.x) + (m->m[3] * in.y);
+    ret.y = (m->m[1] * in.x) + (m->m[4] * in.y);
+    ret.z = (m->m[2] * in.x) + (m->m[5] * in.y);
+    return ret;
+}
+
+static float2 __attribute__((overloadable))
+rsMatrixMultiply(rs_matrix2x2 *m, float2 in) {
+    float2 ret;
+    ret.x = (m->m[0] * in.x) + (m->m[2] * in.y);
+    ret.y = (m->m[1] * in.x) + (m->m[3] * in.y);
+    return ret;
+}
+
+// Returns true if the matrix was successfully inversed
+static bool __attribute__((overloadable))
+rsMatrixInverse(rs_matrix4x4 *m) {
+    rs_matrix4x4 result;
+
+    int i, j;
+    for (i = 0; i < 4; ++i) {
+        for (j = 0; j < 4; ++j) {
+            // computeCofactor for int i, int j
+            int c0 = (i+1) % 4;
+            int c1 = (i+2) % 4;
+            int c2 = (i+3) % 4;
+            int r0 = (j+1) % 4;
+            int r1 = (j+2) % 4;
+            int r2 = (j+3) % 4;
+
+            float minor = (m->m[c0 + 4*r0] * (m->m[c1 + 4*r1] * m->m[c2 + 4*r2] - m->m[c1 + 4*r2] * m->m[c2 + 4*r1]))
+                         - (m->m[c0 + 4*r1] * (m->m[c1 + 4*r0] * m->m[c2 + 4*r2] - m->m[c1 + 4*r2] * m->m[c2 + 4*r0]))
+                         + (m->m[c0 + 4*r2] * (m->m[c1 + 4*r0] * m->m[c2 + 4*r1] - m->m[c1 + 4*r1] * m->m[c2 + 4*r0]));
+
+            float cofactor = (i+j) & 1 ? -minor : minor;
+
+            result.m[4*i + j] = cofactor;
+        }
+    }
+
+    // Dot product of 0th column of source and 0th row of result
+    float det = m->m[0]*result.m[0] + m->m[4]*result.m[1] +
+                 m->m[8]*result.m[2] + m->m[12]*result.m[3];
+
+    if (fabs(det) < 1e-6) {
+        return false;
+    }
+
+    det = 1.0f / det;
+    for (i = 0; i < 16; ++i) {
+        m->m[i] = result.m[i] * det;
+    }
+
+    return true;
+}
+
+// Returns true if the matrix was successfully inversed
+static bool __attribute__((overloadable))
+rsMatrixInverseTranspose(rs_matrix4x4 *m) {
+    rs_matrix4x4 result;
+
+    int i, j;
+    for (i = 0; i < 4; ++i) {
+        for (j = 0; j < 4; ++j) {
+            // computeCofactor for int i, int j
+            int c0 = (i+1) % 4;
+            int c1 = (i+2) % 4;
+            int c2 = (i+3) % 4;
+            int r0 = (j+1) % 4;
+            int r1 = (j+2) % 4;
+            int r2 = (j+3) % 4;
+
+            float minor = (m->m[c0 + 4*r0] * (m->m[c1 + 4*r1] * m->m[c2 + 4*r2] - m->m[c1 + 4*r2] * m->m[c2 + 4*r1]))
+                         - (m->m[c0 + 4*r1] * (m->m[c1 + 4*r0] * m->m[c2 + 4*r2] - m->m[c1 + 4*r2] * m->m[c2 + 4*r0]))
+                         + (m->m[c0 + 4*r2] * (m->m[c1 + 4*r0] * m->m[c2 + 4*r1] - m->m[c1 + 4*r1] * m->m[c2 + 4*r0]));
+
+            float cofactor = (i+j) & 1 ? -minor : minor;
+
+            result.m[4*j + i] = cofactor;
+        }
+    }
+
+    // Dot product of 0th column of source and 0th column of result
+    float det = m->m[0]*result.m[0] + m->m[4]*result.m[4] +
+                 m->m[8]*result.m[8] + m->m[12]*result.m[12];
+
+    if (fabs(det) < 1e-6) {
+        return false;
+    }
+
+    det = 1.0f / det;
+    for (i = 0; i < 16; ++i) {
+        m->m[i] = result.m[i] * det;
+    }
+
+    return true;
+}
+
+static void __attribute__((overloadable))
+rsMatrixTranspose(rs_matrix4x4 *m) {
+    int i, j;
+    float temp;
+    for (i = 0; i < 3; ++i) {
+        for (j = i + 1; j < 4; ++j) {
+            temp = m->m[i*4 + j];
+            m->m[i*4 + j] = m->m[j*4 + i];
+            m->m[j*4 + i] = temp;
+        }
+    }
+}
+
+static void __attribute__((overloadable))
+rsMatrixTranspose(rs_matrix3x3 *m) {
+    int i, j;
+    float temp;
+    for (i = 0; i < 2; ++i) {
+        for (j = i + 1; j < 3; ++j) {
+            temp = m->m[i*3 + j];
+            m->m[i*3 + j] = m->m[j*4 + i];
+            m->m[j*3 + i] = temp;
+        }
+    }
+}
+
+static void __attribute__((overloadable))
+rsMatrixTranspose(rs_matrix2x2 *m) {
+    float temp = m->m[1];
+    m->m[1] = m->m[2];
+    m->m[2] = temp;
+}
+
+
+/////////////////////////////////////////////////////
+// int ops
+/////////////////////////////////////////////////////
+
+__inline__ static uint __attribute__((overloadable, always_inline)) rsClamp(uint amount, uint low, uint high) {
+    return amount < low ? low : (amount > high ? high : amount);
+}
+__inline__ static int __attribute__((overloadable, always_inline)) rsClamp(int amount, int low, int high) {
+    return amount < low ? low : (amount > high ? high : amount);
+}
+__inline__ static ushort __attribute__((overloadable, always_inline)) rsClamp(ushort amount, ushort low, ushort high) {
+    return amount < low ? low : (amount > high ? high : amount);
+}
+__inline__ static short __attribute__((overloadable, always_inline)) rsClamp(short amount, short low, short high) {
+    return amount < low ? low : (amount > high ? high : amount);
+}
+__inline__ static uchar __attribute__((overloadable, always_inline)) rsClamp(uchar amount, uchar low, uchar high) {
+    return amount < low ? low : (amount > high ? high : amount);
+}
+__inline__ static char __attribute__((overloadable, always_inline)) rsClamp(char amount, char low, char high) {
+    return amount < low ? low : (amount > high ? high : amount);
+}
+
+
+
+#endif
+
diff --git a/libs/rs/scriptc/rs_graphics.rsh b/libs/rs/scriptc/rs_graphics.rsh
index 70cd562..fd0491c 100644
--- a/libs/rs/scriptc/rs_graphics.rsh
+++ b/libs/rs/scriptc/rs_graphics.rsh
@@ -1,65 +1,88 @@
+#ifndef __RS_GRAPHICS_RSH__
+#define __RS_GRAPHICS_RSH__
+
+#include "rs_math.rsh"
 
 
-extern float2 vec2Rand(float len);
+// Bind a ProgramFragment to the RS context.
+extern void __attribute__((overloadable))
+    rsgBindProgramFragment(rs_program_fragment);
+extern void __attribute__((overloadable))
+    rsgBindProgramStore(rs_program_store);
+extern void __attribute__((overloadable))
+    rsgBindProgramVertex(rs_program_vertex);
+extern void __attribute__((overloadable))
+    rsgBindProgramRaster(rs_program_raster);
 
-extern float3 float3Norm(float3);
-extern float float3Length(float3);
-extern float3 float3Add(float3 lhs, float3 rhs);
-extern float3 float3Sub(float3 lhs, float3 rhs);
-extern float3 float3Cross(float3 lhs, float3 rhs);
-extern float float3Dot(float3 lhs, float3 rhs);
-extern float3 float3Scale(float3 v, float scale);
+extern void __attribute__((overloadable))
+    rsgBindSampler(rs_program_fragment, uint slot, rs_sampler);
+extern void __attribute__((overloadable))
+    rsgBindTexture(rs_program_fragment, uint slot, rs_allocation);
 
-extern float4 float4Add(float4 lhs, float4 rhs);
-extern float4 float4Sub(float4 lhs, float4 rhs);
-extern float4 float4Cross(float4 lhs, float4 rhs);
-extern float float4Dot(float4 lhs, float4 rhs);
-extern float4 float4Scale(float4 v, float scale);
+extern void __attribute__((overloadable))
+    rsgProgramVertexLoadProjectionMatrix(const rs_matrix4x4 *);
+extern void __attribute__((overloadable))
+    rsgProgramVertexLoadModelMatrix(const rs_matrix4x4 *);
+extern void __attribute__((overloadable))
+    rsgProgramVertexLoadTextureMatrix(const rs_matrix4x4 *);
 
-    // context
-extern void bindProgramFragment(rs_program_fragment);
-extern void bindProgramStore(rs_program_store);
-extern void bindProgramVertex(rs_program_vertex);
+extern void __attribute__((overloadable))
+    rsgProgramFragmentConstantColor(rs_program_fragment, float, float, float, float);
 
-extern void bindSampler(rs_program_fragment, int slot, rs_sampler);
-extern void bindSampler(rs_program_fragment, int slot, rs_allocation);
+extern uint __attribute__((overloadable))
+    rsgGetWidth(void);
+extern uint __attribute__((overloadable))
+    rsgGetHeight(void);
 
-extern void vpLoadModelMatrix(const float *);
-extern void vpLoadTextureMatrix(const float *);
+extern void __attribute__((overloadable))
+    rsgUploadToTexture(rs_allocation);
+extern void __attribute__((overloadable))
+    rsgUploadToTexture(rs_allocation, uint mipLevel);
+extern void __attribute__((overloadable))
+    rsgUploadToBufferObject(rs_allocation);
 
+extern void __attribute__((overloadable))
+    rsgDrawRect(float x1, float y1, float x2, float y2, float z);
+extern void __attribute__((overloadable))
+    rsgDrawQuad(float x1, float y1, float z1,
+                float x2, float y2, float z2,
+                float x3, float y3, float z3,
+                float x4, float y4, float z4);
+extern void __attribute__((overloadable))
+    rsgDrawQuadTexCoords(float x1, float y1, float z1, float u1, float v1,
+                         float x2, float y2, float z2, float u2, float v2,
+                         float x3, float y3, float z3, float u3, float v3,
+                         float x4, float y4, float z4, float u4, float v4);
+extern void __attribute__((overloadable))
+    rsgDrawSpriteScreenspace(float x, float y, float z, float w, float h);
 
-// drawing
-extern void drawRect(float x1, float y1, float x2, float y2, float z);
-extern void drawQuad(float x1, float y1, float z1, float x2, float y2, float z2, float x3, float y3, float z3, float x4, float y4, float z4);
-extern void drawQuadTexCoords(float x1, float y1, float z1, float u1, float v1, float x2, float y2, float z2, float u2, float v2, float x3, float y3, float z3, float u3, float v3, float x4, float y4, float z4, float u4, float v4);
-extern void drawSprite(float x, float y, float z, float w, float h);
-extern void drawSpriteScreenspace(float x, float y, float z, float w, float h);
-extern void drawLine(float x1, float y1, float z1, float x2, float y2, float z2);
-extern void drawPoint(float x1, float y1, float z1);
-extern void drawSimpleMesh(int ism);
-extern void drawSimpleMeshRange(int ism, int start, int len);
+extern void __attribute__((overloadable))
+    rsgDrawMesh(rs_mesh ism);
+extern void __attribute__((overloadable))
+    rsgDrawMesh(rs_mesh ism, uint primitiveIndex);
+extern void __attribute__((overloadable))
+    rsgDrawMesh(rs_mesh ism, uint primitiveIndex, uint start, uint len);
 
+extern void __attribute__((overloadable))
+    rsgClearColor(float, float, float, float);
+extern void __attribute__((overloadable))
+    rsgClearDepth(float);
+
+extern void __attribute__((overloadable))
+    rsgDrawText(const char *, int x, int y);
+extern void __attribute__((overloadable))
+    rsgDrawText(rs_allocation, int x, int y);
+extern void __attribute__((overloadable))
+    rsgBindFont(rs_font);
+extern void __attribute__((overloadable))
+    rsgFontColor(float, float, float, float);
+
+///////////////////////////////////////////////////////
 // misc
-extern void pfClearColor(float, float, float, float);
-extern void color(float, float, float, float);
-extern void hsb(float, float, float, float);
-extern void hsbToRgb(float, float, float, float*);
-extern int hsbToAbgr(float, float, float, float);
 
-extern void uploadToTexture(int, int);
-extern void uploadToBufferObject(int);
+// Depricated
+extern void __attribute__((overloadable))
+    color(float, float, float, float);
 
-extern int colorFloatRGBAtoUNorm8(float, float, float, float);
-extern int colorFloatRGBto565(float, float, float);
-
-extern int getWidth();
-extern int getHeight();
-
-extern int sendToClient(void *data, int cmdID, int len, int waitForSpace);
-
-extern void debugF(const char *, float);
-extern void debugI32(const char *, int);
-extern void debugHexI32(const char *, int);
-
-
+#endif
 
diff --git a/libs/rs/scriptc/rs_math.rsh b/libs/rs/scriptc/rs_math.rsh
index 613c7ca..66171d8 100644
--- a/libs/rs/scriptc/rs_math.rsh
+++ b/libs/rs/scriptc/rs_math.rsh
@@ -1,287 +1,131 @@
-// Float ops
+#ifndef __RS_MATH_RSH__
+#define __RS_MATH_RSH__
 
-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);
+#include "rs_cl.rsh"
+#include "rs_core.rsh"
 
-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);
+// Allocations
 
-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);
+// Return the rs_allocation associated with a bound data
+// pointer.
+extern rs_allocation __attribute__((overloadable))
+    rsGetAllocation(const void *);
 
-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);
+// Return the dimensions associated with an allocation.
+extern uint32_t __attribute__((overloadable))
+    rsAllocationGetDimX(rs_allocation);
+extern uint32_t __attribute__((overloadable))
+    rsAllocationGetDimY(rs_allocation);
+extern uint32_t __attribute__((overloadable))
+    rsAllocationGetDimZ(rs_allocation);
+extern uint32_t __attribute__((overloadable))
+    rsAllocationGetDimLOD(rs_allocation);
+extern uint32_t __attribute__((overloadable))
+    rsAllocationGetDimFaces(rs_allocation);
 
-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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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);
+// Extract a single element from an allocation.
+extern const void * __attribute__((overloadable))
+    rsGetElementAt(rs_allocation, uint32_t x);
+extern const void * __attribute__((overloadable))
+    rsGetElementAt(rs_allocation, uint32_t x, uint32_t y);
+extern const void * __attribute__((overloadable))
+    rsGetElementAt(rs_allocation, uint32_t x, uint32_t y, uint32_t z);
 
 
+// Debugging, print to the LOG a description string and a value.
+extern void __attribute__((overloadable))
+    rsDebug(const char *, float);
+extern void __attribute__((overloadable))
+    rsDebug(const char *, float2);
+extern void __attribute__((overloadable))
+    rsDebug(const char *, float3);
+extern void __attribute__((overloadable))
+    rsDebug(const char *, float4);
+extern void __attribute__((overloadable))
+    rsDebug(const char *, int);
+extern void __attribute__((overloadable))
+    rsDebug(const char *, uint);
+extern void __attribute__((overloadable))
+    rsDebug(const char *, const void *);
+#define RS_DEBUG(a) rsDebug(#a, a)
+#define RS_DEBUG_MARKER rsDebug(__FILE__, __LINE__)
 
+// Return a random value between 0 (or min_value) and max_malue.
+extern int __attribute__((overloadable))
+    rsRand(int max_value);
+extern int __attribute__((overloadable))
+    rsRand(int min_value, int max_value);
+extern float __attribute__((overloadable))
+    rsRand(float max_value);
+extern float __attribute__((overloadable))
+    rsRand(float min_value, float max_value);
 
+// return the fractional part of a float
+// min(v - ((int)floor(v)), 0x1.fffffep-1f);
+extern float __attribute__((overloadable))
+    rsFrac(float);
 
+// time
+extern int32_t __attribute__((overloadable))
+    rsSecond(void);
+extern int32_t __attribute__((overloadable))
+    rsMinute(void);
+extern int32_t __attribute__((overloadable))
+    rsHour(void);
+extern int32_t __attribute__((overloadable))
+    rsDay(void);
+extern int32_t __attribute__((overloadable))
+    rsMonth(void);
+extern int32_t __attribute__((overloadable))
+    rsYear(void);
 
-// Int ops
+// Return the current system clock in milliseconds
+extern int64_t __attribute__((overloadable))
+    rsUptimeMillis(void);
 
-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);
+// Return the current system clock in nanoseconds
+extern int64_t __attribute__((overloadable))
+    rsUptimeNanos(void);
 
+// Return the time in seconds since function was last called in this script.
+extern float __attribute__((overloadable))
+    rsGetDt(void);
 
+// Send a message back to the client.  Will not block and returns true
+// if the message was sendable and false if the fifo was full.
+// A message ID is required.  Data payload is optional.
+extern bool __attribute__((overloadable))
+    rsSendToClient(int cmdID);
+extern bool __attribute__((overloadable))
+    rsSendToClient(int cmdID, const void *data, uint len);
 
-/*
-extern float modf(float, float);
-extern float randf(float);
-extern float randf2(float, float);
-extern float fracf(float);
-extern float lerpf(float, float, float);
-extern float mapf(float, float, float, float, float);
-*/
+// Send a message back to the client, blocking until the message is queued.
+// A message ID is required.  Data payload is optional.
+extern void __attribute__((overloadable))
+    rsSendToClientBlocking(int cmdID);
+extern void __attribute__((overloadable))
+    rsSendToClientBlocking(int cmdID, const void *data, uint len);
 
+// Script to Script
+typedef struct rs_script_call {
+    uint32_t xStart;
+    uint32_t xEnd;
+    uint32_t yStart;
+    uint32_t yEnd;
+    uint32_t zStart;
+    uint32_t zEnd;
+    uint32_t arrayStart;
+    uint32_t arrayEnd;
+
+} rs_script_call_t;
+
+extern void __attribute__((overloadable))
+    rsForEach(rs_script script, rs_allocation input,
+              rs_allocation output, const void * usrData);
+
+extern void __attribute__((overloadable))
+    rsForEach(rs_script script, rs_allocation input,
+              rs_allocation output, const void * usrData,
+              const rs_script_call_t *);
+
+#endif
diff --git a/libs/rs/scriptc/rs_types.rsh b/libs/rs/scriptc/rs_types.rsh
index 4198a74..69e1aed 100644
--- a/libs/rs/scriptc/rs_types.rsh
+++ b/libs/rs/scriptc/rs_types.rsh
@@ -2,70 +2,71 @@
 typedef char int8_t;
 typedef short int16_t;
 typedef int int32_t;
-//typedef long int64_t;
+typedef long long int64_t;
 
 typedef unsigned char uint8_t;
 typedef unsigned short uint16_t;
 typedef unsigned int uint32_t;
-//typedef long uint64_t;
+typedef unsigned long long uint64_t;
 
 typedef uint8_t uchar;
 typedef uint16_t ushort;
 typedef uint32_t uint;
-//typedef uint64_t ulong;
+typedef uint64_t ulong;
 
-typedef int rs_element;
-typedef int rs_type;
-typedef int rs_allocation;
-typedef int rs_sampler;
-typedef int rs_script;
-typedef int rs_mesh;
-typedef int rs_program_fragment;
-typedef int rs_program_vertex;
-typedef int rs_program_raster;
-typedef int rs_program_store;
+typedef struct { int* p; } __attribute__((packed, aligned(4))) rs_element;
+typedef struct { int* p; } __attribute__((packed, aligned(4))) rs_type;
+typedef struct { int* p; } __attribute__((packed, aligned(4))) rs_allocation;
+typedef struct { int* p; } __attribute__((packed, aligned(4))) rs_sampler;
+typedef struct { int* p; } __attribute__((packed, aligned(4))) rs_script;
+typedef struct { int* p; } __attribute__((packed, aligned(4))) rs_mesh;
+typedef struct { int* p; } __attribute__((packed, aligned(4))) rs_program_fragment;
+typedef struct { int* p; } __attribute__((packed, aligned(4))) rs_program_vertex;
+typedef struct { int* p; } __attribute__((packed, aligned(4))) rs_program_raster;
+typedef struct { int* p; } __attribute__((packed, aligned(4))) rs_program_store;
+typedef struct { int* p; } __attribute__((packed, aligned(4))) rs_font;
 
 typedef float float2 __attribute__((ext_vector_type(2)));
 typedef float float3 __attribute__((ext_vector_type(3)));
 typedef float float4 __attribute__((ext_vector_type(4)));
-typedef float float8 __attribute__((ext_vector_type(8)));
-typedef float float16 __attribute__((ext_vector_type(16)));
 
 typedef uchar uchar2 __attribute__((ext_vector_type(2)));
 typedef uchar uchar3 __attribute__((ext_vector_type(3)));
 typedef uchar uchar4 __attribute__((ext_vector_type(4)));
-typedef uchar uchar8 __attribute__((ext_vector_type(8)));
-typedef uchar uchar16 __attribute__((ext_vector_type(16)));
 
 typedef ushort ushort2 __attribute__((ext_vector_type(2)));
 typedef ushort ushort3 __attribute__((ext_vector_type(3)));
 typedef ushort ushort4 __attribute__((ext_vector_type(4)));
-typedef ushort ushort8 __attribute__((ext_vector_type(8)));
-typedef ushort ushort16 __attribute__((ext_vector_type(16)));
 
 typedef uint uint2 __attribute__((ext_vector_type(2)));
 typedef uint uint3 __attribute__((ext_vector_type(3)));
 typedef uint uint4 __attribute__((ext_vector_type(4)));
-typedef uint uint8 __attribute__((ext_vector_type(8)));
-typedef uint uint16 __attribute__((ext_vector_type(16)));
 
 typedef char char2 __attribute__((ext_vector_type(2)));
 typedef char char3 __attribute__((ext_vector_type(3)));
 typedef char char4 __attribute__((ext_vector_type(4)));
-typedef char char8 __attribute__((ext_vector_type(8)));
-typedef char char16 __attribute__((ext_vector_type(16)));
 
 typedef short short2 __attribute__((ext_vector_type(2)));
 typedef short short3 __attribute__((ext_vector_type(3)));
 typedef short short4 __attribute__((ext_vector_type(4)));
-typedef short short8 __attribute__((ext_vector_type(8)));
-typedef short short16 __attribute__((ext_vector_type(16)));
 
 typedef int int2 __attribute__((ext_vector_type(2)));
 typedef int int3 __attribute__((ext_vector_type(3)));
 typedef int int4 __attribute__((ext_vector_type(4)));
-typedef int int8 __attribute__((ext_vector_type(8)));
-typedef int int16 __attribute__((ext_vector_type(16)));
 
 
+typedef struct {
+    float m[16];
+} rs_matrix4x4;
+
+typedef struct {
+    float m[9];
+} rs_matrix3x3;
+
+typedef struct {
+    float m[4];
+} rs_matrix2x2;
+
+
+#define RS_PACKED __attribute__((packed, aligned(4)))
 
diff --git a/libs/utils/ResourceTypes.cpp b/libs/utils/ResourceTypes.cpp
index a1401ad..4362d14 100644
--- a/libs/utils/ResourceTypes.cpp
+++ b/libs/utils/ResourceTypes.cpp
@@ -317,6 +317,12 @@
             mStringPoolSize =
                 (mHeader->header.size-mHeader->stringsStart)/charSize;
         } else {
+            // check invariant: styles starts before end of data
+            if (mHeader->stylesStart >= (mHeader->header.size-sizeof(uint16_t))) {
+                LOGW("Bad style block: style block starts at %d past data size of %d\n",
+                    (int)mHeader->stylesStart, (int)mHeader->header.size);
+                return (mError=BAD_TYPE);
+            }
             // check invariant: styles follow the strings
             if (mHeader->stylesStart <= mHeader->stringsStart) {
                 LOGW("Bad style block: style block starts at %d, before strings at %d\n",
@@ -1878,6 +1884,12 @@
         outName->type = grp->basePackage->typeStrings.stringAt(t, &outName->typeLen);
         outName->name = grp->basePackage->keyStrings.stringAt(
             dtohl(entry->key.index), &outName->nameLen);
+
+        // If we have a bad index for some reason, we should abort.
+        if (outName->type == NULL || outName->name == NULL) {
+            return false;
+        }
+
         return true;
     }
 
@@ -2609,6 +2621,24 @@
         *outType = *defType;
     }
     *outName = String16(p, end-p);
+    if(**outPackage == 0) {
+        if(outErrorMsg) {
+            *outErrorMsg = "Resource package cannot be an empty string";
+        }
+        return false;
+    }
+    if(**outType == 0) {
+        if(outErrorMsg) {
+            *outErrorMsg = "Resource type cannot be an empty string";
+        }
+        return false;
+    }
+    if(**outName == 0) {
+        if(outErrorMsg) {
+            *outErrorMsg = "Resource id cannot be an empty string";
+        }
+        return false;
+    }
     return true;
 }
 
@@ -4127,13 +4157,16 @@
                                     | (0x00ff0000 & ((typeIndex+1)<<16))
                                     | (0x0000ffff & (entryIndex));
                         resource_name resName;
-                        this->getResourceName(resID, &resName);
-                        printf("      spec resource 0x%08x %s:%s/%s: flags=0x%08x\n",
-                            resID,
-                            CHAR16_TO_CSTR(resName.package, resName.packageLen),
-                            CHAR16_TO_CSTR(resName.type, resName.typeLen),
-                            CHAR16_TO_CSTR(resName.name, resName.nameLen),
-                            dtohl(typeConfigs->typeSpecFlags[entryIndex]));
+                        if (this->getResourceName(resID, &resName)) {
+                            printf("      spec resource 0x%08x %s:%s/%s: flags=0x%08x\n",
+                                resID,
+                                CHAR16_TO_CSTR(resName.package, resName.packageLen),
+                                CHAR16_TO_CSTR(resName.type, resName.typeLen),
+                                CHAR16_TO_CSTR(resName.name, resName.nameLen),
+                                dtohl(typeConfigs->typeSpecFlags[entryIndex]));
+                        } else {
+                            printf("      INVALID TYPE CONFIG FOR RESOURCE 0x%08x\n", resID);
+                        }
                     }
                 }
                 for (size_t configIndex=0; configIndex<NTC; configIndex++) {
@@ -4340,11 +4373,14 @@
                                     | (0x00ff0000 & ((typeIndex+1)<<16))
                                     | (0x0000ffff & (entryIndex));
                         resource_name resName;
-                        this->getResourceName(resID, &resName);
-                        printf("        resource 0x%08x %s:%s/%s: ", resID,
-                                CHAR16_TO_CSTR(resName.package, resName.packageLen),
-                                CHAR16_TO_CSTR(resName.type, resName.typeLen),
-                                CHAR16_TO_CSTR(resName.name, resName.nameLen));
+                        if (this->getResourceName(resID, &resName)) {
+                            printf("        resource 0x%08x %s:%s/%s: ", resID,
+                                    CHAR16_TO_CSTR(resName.package, resName.packageLen),
+                                    CHAR16_TO_CSTR(resName.type, resName.typeLen),
+                                    CHAR16_TO_CSTR(resName.name, resName.nameLen));
+                        } else {
+                            printf("        INVALID RESOURCE 0x%08x: ", resID);
+                        }
                         if ((thisOffset&0x3) != 0) {
                             printf("NON-INTEGER OFFSET: %p\n", (void*)thisOffset);
                             continue;
@@ -4402,18 +4438,19 @@
                                 print_value(pkg, value);
                             } else if (bagPtr != NULL) {
                                 const int N = dtohl(bagPtr->count);
-                                const ResTable_map* mapPtr = (const ResTable_map*)
-                                        (((const uint8_t*)ent) + esize);
+                                const uint8_t* baseMapPtr = (const uint8_t*)ent;
+                                size_t mapOffset = esize;
+                                const ResTable_map* mapPtr = (ResTable_map*)(baseMapPtr+mapOffset);
                                 printf("          Parent=0x%08x, Count=%d\n",
                                     dtohl(bagPtr->parent.ident), N);
-                                for (int i=0; i<N; i++) {
+                                for (int i=0; i<N && mapOffset < (typeSize-sizeof(ResTable_map)); i++) {
                                     printf("          #%i (Key=0x%08x): ",
                                         i, dtohl(mapPtr->name.ident));
                                     value.copyFrom_dtoh(mapPtr->value);
                                     print_value(pkg, value);
                                     const size_t size = dtohs(mapPtr->value.size);
-                                    mapPtr = (ResTable_map*)(((const uint8_t*)mapPtr)
-                                            + size + sizeof(*mapPtr)-sizeof(mapPtr->value));
+                                    mapOffset += size + sizeof(*mapPtr)-sizeof(mapPtr->value);
+                                    mapPtr = (ResTable_map*)(baseMapPtr+mapOffset);
                                 }
                             }
                         }
diff --git a/location/java/android/location/Country.aidl b/location/java/android/location/Country.aidl
new file mode 100644
index 0000000..c83d645
--- /dev/null
+++ b/location/java/android/location/Country.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.location;
+
+parcelable Country;
\ No newline at end of file
diff --git a/location/java/android/location/Country.java b/location/java/android/location/Country.java
new file mode 100755
index 0000000..3c05403
--- /dev/null
+++ b/location/java/android/location/Country.java
@@ -0,0 +1,161 @@
+/*
+ * 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.location;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * This class wraps the country information.
+ *
+ * @hide
+ */
+public class Country implements Parcelable {
+    /**
+     * The country code came from the mobile network
+     */
+    public static final int COUNTRY_SOURCE_NETWORK = 0;
+
+    /**
+     * The country code came from the location service
+     */
+    public static final int COUNTRY_SOURCE_LOCATION = 1;
+
+    /**
+     * The country code was read from the SIM card
+     */
+    public static final int COUNTRY_SOURCE_SIM = 2;
+
+    /**
+     * The country code came from the system locale setting
+     */
+    public static final int COUNTRY_SOURCE_LOCALE = 3;
+
+    /**
+     * The ISO 3166-1 two letters country code.
+     */
+    private final String mCountryIso;
+
+    /**
+     * Where the country code came from.
+     */
+    private final int mSource;
+
+    private int mHashCode;
+    /**
+     *
+     * @param countryIso the ISO 3166-1 two letters country code.
+     * @param source where the countryIso came from, could be one of below
+     *        values
+     *        <p>
+     *        <ul>
+     *        <li>{@link #COUNTRY_SOURCE_NETWORK}</li>
+     *        <li>{@link #COUNTRY_SOURCE_LOCATION}</li>
+     *        <li>{@link #COUNTRY_SOURCE_SIM}</li>
+     *        <li>{@link #COUNTRY_SOURCE_LOCALE}</li>
+     *        </ul>
+     */
+    public Country(final String countryIso, final int source) {
+        if (countryIso == null || source < COUNTRY_SOURCE_NETWORK
+                || source > COUNTRY_SOURCE_LOCALE) {
+            throw new IllegalArgumentException();
+        }
+        mCountryIso = countryIso.toLowerCase();
+        mSource = source;
+    }
+
+    public Country(Country country) {
+        mCountryIso = country.mCountryIso;
+        mSource = country.mSource;
+    }
+
+    /**
+     * @return the ISO 3166-1 two letters country code
+     */
+    public final String getCountryIso() {
+        return mCountryIso;
+    }
+
+    /**
+     * @return where the country code came from, could be one of below values
+     *         <p>
+     *         <ul>
+     *         <li>{@link #COUNTRY_SOURCE_NETWORK}</li>
+     *         <li>{@link #COUNTRY_SOURCE_LOCATION}</li>
+     *         <li>{@link #COUNTRY_SOURCE_SIM}</li>
+     *         <li>{@link #COUNTRY_SOURCE_LOCALE}</li>
+     *         </ul>
+     */
+    public final int getSource() {
+        return mSource;
+    }
+
+    public static final Parcelable.Creator<Country> CREATOR = new Parcelable.Creator<Country>() {
+        public Country createFromParcel(Parcel in) {
+            return new Country(in.readString(), in.readInt());
+        }
+
+        public Country[] newArray(int size) {
+            return new Country[size];
+        }
+    };
+
+    public int describeContents() {
+        return 0;
+    }
+
+    public void writeToParcel(Parcel parcel, int flags) {
+        parcel.writeString(mCountryIso);
+        parcel.writeInt(mSource);
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        if (object == this) {
+            return true;
+        }
+        if (object instanceof Country) {
+            Country c = (Country) object;
+            return mCountryIso.equals(c.getCountryIso()) && mSource == c.getSource();
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        int hash = mHashCode;
+        if (hash == 0) {
+            hash = 17;
+            hash = hash * 13 + mCountryIso.hashCode();
+            hash = hash * 13 + mSource;
+            mHashCode = hash;
+        }
+        return mHashCode;
+    }
+
+    /**
+     * Compare the specified country to this country object ignoring the mSource
+     * field, return true if the countryIso fields are equal
+     *
+     * @param country the country to compare
+     * @return true if the specified country's countryIso field is equal to this
+     *         country's, false otherwise.
+     */
+    public boolean equalsIgnoreSource(Country country) {
+        return country != null && mCountryIso.equals(country.getCountryIso());
+    }
+}
diff --git a/location/java/android/location/CountryDetector.java b/location/java/android/location/CountryDetector.java
new file mode 100644
index 0000000..0b780ce
--- /dev/null
+++ b/location/java/android/location/CountryDetector.java
@@ -0,0 +1,152 @@
+/*
+ * 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.location;
+
+import java.util.HashMap;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.util.Log;
+
+/**
+ * This class provides access to the system country detector service. This
+ * service allows applications to obtain the country that the user is in.
+ * <p>
+ * The country will be detected in order of reliability, like
+ * <ul>
+ * <li>Mobile network</li>
+ * <li>Location</li>
+ * <li>SIM's country</li>
+ * <li>Phone's locale</li>
+ * </ul>
+ * <p>
+ * Call the {@link #detectCountry()} to get the available country immediately.
+ * <p>
+ * To be notified of the future country change, use the
+ * {@link #addCountryListener}
+ * <p>
+ * <p>
+ * You do not instantiate this class directly; instead, retrieve it through
+ * {@link android.content.Context#getSystemService
+ * Context.getSystemService(Context.COUNTRY_DETECTOR)}.
+ * <p>
+ * Both ACCESS_FINE_LOCATION and ACCESS_COARSE_LOCATION permissions are needed.
+ *
+ * @hide
+ */
+public class CountryDetector {
+
+    /**
+     * The class to wrap the ICountryListener.Stub and CountryListener objects
+     * together. The CountryListener will be notified through the specific
+     * looper once the country changed and detected.
+     */
+    private final static class ListenerTransport extends ICountryListener.Stub {
+
+        private final CountryListener mListener;
+
+        private final Handler mHandler;
+
+        public ListenerTransport(CountryListener listener, Looper looper) {
+            mListener = listener;
+            if (looper != null) {
+                mHandler = new Handler(looper);
+            } else {
+                mHandler = new Handler();
+            }
+        }
+
+        public void onCountryDetected(final Country country) {
+            mHandler.post(new Runnable() {
+                public void run() {
+                    mListener.onCountryDetected(country);
+                }
+            });
+        }
+    }
+
+    private final static String TAG = "CountryDetector";
+    private final ICountryDetector mService;
+    private final HashMap<CountryListener, ListenerTransport> mListeners;
+
+    /**
+     * @hide - hide this constructor because it has a parameter of type
+     *       ICountryDetector, which is a system private class. The right way to
+     *       create an instance of this class is using the factory
+     *       Context.getSystemService.
+     */
+    public CountryDetector(ICountryDetector service) {
+        mService = service;
+        mListeners = new HashMap<CountryListener, ListenerTransport>();
+    }
+
+    /**
+     * Start detecting the country that the user is in.
+     *
+     * @return the country if it is available immediately, otherwise null will
+     *         be returned.
+     */
+    public Country detectCountry() {
+        try {
+            return mService.detectCountry();
+        } catch (RemoteException e) {
+            Log.e(TAG, "detectCountry: RemoteException", e);
+            return null;
+        }
+    }
+
+    /**
+     * Add a listener to receive the notification when the country is detected
+     * or changed.
+     *
+     * @param listener will be called when the country is detected or changed.
+     * @param looper a Looper object whose message queue will be used to
+     *        implement the callback mechanism. If looper is null then the
+     *        callbacks will be called on the main thread.
+     */
+    public void addCountryListener(CountryListener listener, Looper looper) {
+        synchronized (mListeners) {
+            if (!mListeners.containsKey(listener)) {
+                ListenerTransport transport = new ListenerTransport(listener, looper);
+                try {
+                    mService.addCountryListener(transport);
+                    mListeners.put(listener, transport);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "addCountryListener: RemoteException", e);
+                }
+            }
+        }
+    }
+
+    /**
+     * Remove the listener
+     */
+    public void removeCountryListener(CountryListener listener) {
+        synchronized (mListeners) {
+            ListenerTransport transport = mListeners.get(listener);
+            if (transport != null) {
+                try {
+                    mListeners.remove(listener);
+                    mService.removeCountryListener(transport);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "removeCountryListener: RemoteException", e);
+                }
+            }
+        }
+    }
+}
diff --git a/location/java/android/location/CountryListener.java b/location/java/android/location/CountryListener.java
new file mode 100644
index 0000000..e36db41
--- /dev/null
+++ b/location/java/android/location/CountryListener.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.location;
+
+/**
+ * The listener for receiving the notification when the country is detected or
+ * changed
+ *
+ * @hide
+ */
+public interface CountryListener {
+    /**
+     * @param country the changed or detected country.
+     */
+    void onCountryDetected(Country country);
+}
diff --git a/location/java/android/location/ICountryDetector.aidl b/location/java/android/location/ICountryDetector.aidl
new file mode 100644
index 0000000..6eaf07c
--- /dev/null
+++ b/location/java/android/location/ICountryDetector.aidl
@@ -0,0 +1,44 @@
+/*
+ * 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.location;
+
+import android.location.Country;
+import android.location.ICountryListener;
+
+/**
+ * The API for detecting the country where the user is.
+ *
+ * {@hide}
+ */
+interface ICountryDetector
+{
+    /**
+     * Start detecting the country that the user is in.
+     * @return the country if it is available immediately, otherwise null will be returned.
+     */
+    Country detectCountry();
+
+    /**
+     * Add a listener to receive the notification when the country is detected or changed.
+     */
+    void addCountryListener(in ICountryListener listener);
+
+    /**
+     * Remove the listener
+     */
+    void removeCountryListener(in ICountryListener listener);
+}
\ No newline at end of file
diff --git a/location/java/android/location/ICountryListener.aidl b/location/java/android/location/ICountryListener.aidl
new file mode 100644
index 0000000..76ecb13
--- /dev/null
+++ b/location/java/android/location/ICountryListener.aidl
@@ -0,0 +1,26 @@
+/*
+ * 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.location;
+
+import android.location.Country;
+/**
+ * {@hide}
+ */
+oneway interface ICountryListener
+{
+    void onCountryDetected(in Country country);
+}
diff --git a/location/tests/locationtests/src/android/location/CountryTester.java b/location/tests/locationtests/src/android/location/CountryTester.java
new file mode 100644
index 0000000..9802d5a
--- /dev/null
+++ b/location/tests/locationtests/src/android/location/CountryTester.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.location;
+
+import android.test.AndroidTestCase;
+
+public class CountryTester extends AndroidTestCase {
+    public void testCountryEquals() {
+        Country countryA = new Country("US", Country.COUNTRY_SOURCE_NETWORK);
+        Country countryB = new Country("US", Country.COUNTRY_SOURCE_LOCALE);
+        Country countryC = new Country("CN", Country.COUNTRY_SOURCE_LOCALE);
+        Country countryD = new Country("us", Country.COUNTRY_SOURCE_NETWORK);
+        assertTrue(countryA.equalsIgnoreSource(countryB));
+        assertFalse(countryA.equalsIgnoreSource(countryC));
+        assertFalse(countryA.equals(countryC));
+        assertTrue(countryA.equals(countryD));
+        assertTrue(countryA.hashCode() == countryD.hashCode());
+    }
+}
diff --git a/media/java/android/media/AudioFormat.java b/media/java/android/media/AudioFormat.java
index f1fa1e8..31e4631 100644
--- a/media/java/android/media/AudioFormat.java
+++ b/media/java/android/media/AudioFormat.java
@@ -31,9 +31,9 @@
     public static final int ENCODING_INVALID = 0;
     /** Default audio data format */
     public static final int ENCODING_DEFAULT = 1;
-    /** Audio data format: PCM 16 bit per sample */
+    /** Audio data format: PCM 16 bit per sample. Guaranteed to be supported by devices. */
     public static final int ENCODING_PCM_16BIT = 2; // accessed by native code
-    /** Audio data format: PCM 8 bit per sample */
+    /** Audio data format: PCM 8 bit per sample. Not guaranteed to be supported by devices. */
     public static final int ENCODING_PCM_8BIT = 3;  // accessed by native code
 
     /** Invalid audio channel configuration */
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index bbbba74..b23dcde 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -1525,4 +1525,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/media/java/android/media/AudioRecord.java b/media/java/android/media/AudioRecord.java
index c48eaad..c567a6e 100644
--- a/media/java/android/media/AudioRecord.java
+++ b/media/java/android/media/AudioRecord.java
@@ -194,11 +194,13 @@
      * Class constructor.
      * @param audioSource the recording source. See {@link MediaRecorder.AudioSource} for
      *    recording source definitions.
-     * @param sampleRateInHz the sample rate expressed in Hertz. Examples of rates are (but
-     *   not limited to) 44100, 22050 and 11025.
+     * @param sampleRateInHz the sample rate expressed in Hertz. 44100Hz is currently the only
+     *   rate that is guaranteed to work on all devices, but other rates such as 22050,
+     *   16000, and 11025 may work on some devices.
      * @param channelConfig describes the configuration of the audio channels. 
      *   See {@link AudioFormat#CHANNEL_IN_MONO} and
-     *   {@link AudioFormat#CHANNEL_IN_STEREO}
+     *   {@link AudioFormat#CHANNEL_IN_STEREO}.  {@link AudioFormat#CHANNEL_IN_MONO} is guaranteed
+     *   to work on all devices.
      * @param audioFormat the format in which the audio data is represented. 
      *   See {@link AudioFormat#ENCODING_PCM_16BIT} and 
      *   {@link AudioFormat#ENCODING_PCM_8BIT}
@@ -444,6 +446,8 @@
      *  or {@link #ERROR} if the implementation was unable to query the hardware for its 
      *  output properties, 
      *   or the minimum buffer size expressed in bytes.
+     * @see #AudioRecord(int, int, int, int, int) for more information on valid
+     *   configuration values.
      */
     static public int getMinBufferSize(int sampleRateInHz, int channelConfig, int audioFormat) {
         int channelCount = 0;
diff --git a/media/java/android/media/MediaFile.java b/media/java/android/media/MediaFile.java
index 6e527d9..a346ae4 100644
--- a/media/java/android/media/MediaFile.java
+++ b/media/java/android/media/MediaFile.java
@@ -20,6 +20,7 @@
 import android.provider.MediaStore.Audio;
 import android.provider.MediaStore.Images;
 import android.provider.MediaStore.Video;
+import android.provider.Mtp;
 import android.media.DecoderCapabilities;
 import android.media.DecoderCapabilities.VideoDecoder;
 import android.media.DecoderCapabilities.AudioDecoder;
@@ -96,15 +97,32 @@
         }
     }
     
-    private static HashMap<String, MediaFileType> sFileTypeMap 
+    private static HashMap<String, MediaFileType> sFileTypeMap
             = new HashMap<String, MediaFileType>();
-    private static HashMap<String, Integer> sMimeTypeMap 
-            = new HashMap<String, Integer>();            
+    private static HashMap<String, Integer> sMimeTypeMap
+            = new HashMap<String, Integer>();
+    // maps file extension to MTP format code
+    private static HashMap<String, Integer> sFileTypeToFormatMap
+            = new HashMap<String, Integer>();
+    // maps mime type to MTP format code
+    private static HashMap<String, Integer> sMimeTypeToFormatMap
+            = new HashMap<String, Integer>();
+    // maps MTP format code to mime type
+    private static HashMap<Integer, String> sFormatToMimeTypeMap
+            = new HashMap<Integer, String>();
+
     static void addFileType(String extension, int fileType, String mimeType) {
         sFileTypeMap.put(extension, new MediaFileType(fileType, mimeType));
         sMimeTypeMap.put(mimeType, Integer.valueOf(fileType));
     }
 
+    static void addFileType(String extension, int fileType, String mimeType, int mtpFormatCode) {
+        addFileType(extension, fileType, mimeType);
+        sFileTypeToFormatMap.put(extension, Integer.valueOf(mtpFormatCode));
+        sMimeTypeToFormatMap.put(mimeType, Integer.valueOf(mtpFormatCode));
+        sFormatToMimeTypeMap.put(mtpFormatCode, mimeType);
+    }
+
     private static boolean isWMAEnabled() {
         List<AudioDecoder> decoders = DecoderCapabilities.getAudioDecoders();
         for (AudioDecoder decoder: decoders) {
@@ -126,17 +144,17 @@
     }
 
     static {
-        addFileType("MP3", FILE_TYPE_MP3, "audio/mpeg");
-        addFileType("M4A", FILE_TYPE_M4A, "audio/mp4");
-        addFileType("WAV", FILE_TYPE_WAV, "audio/x-wav");
+        addFileType("MP3", FILE_TYPE_MP3, "audio/mpeg", Mtp.Object.FORMAT_MP3);
+        addFileType("M4A", FILE_TYPE_M4A, "audio/mp4", Mtp.Object.FORMAT_MPEG);
+        addFileType("WAV", FILE_TYPE_WAV, "audio/x-wav", Mtp.Object.FORMAT_WAV);
         addFileType("AMR", FILE_TYPE_AMR, "audio/amr");
         addFileType("AWB", FILE_TYPE_AWB, "audio/amr-wb");
         if (isWMAEnabled()) {
-            addFileType("WMA", FILE_TYPE_WMA, "audio/x-ms-wma");
+            addFileType("WMA", FILE_TYPE_WMA, "audio/x-ms-wma", Mtp.Object.FORMAT_WMA);
         }
-        addFileType("OGG", FILE_TYPE_OGG, "application/ogg");
-        addFileType("OGA", FILE_TYPE_OGG, "application/ogg");
-        addFileType("AAC", FILE_TYPE_AAC, "audio/aac");
+        addFileType("OGG", FILE_TYPE_OGG, "application/ogg", Mtp.Object.FORMAT_OGG);
+        addFileType("OGA", FILE_TYPE_OGG, "application/ogg", Mtp.Object.FORMAT_OGG);
+        addFileType("AAC", FILE_TYPE_AAC, "audio/aac", Mtp.Object.FORMAT_AAC);
         addFileType("MKA", FILE_TYPE_MKA, "audio/x-matroska");
  
         addFileType("MID", FILE_TYPE_MID, "audio/midi");
@@ -148,32 +166,32 @@
         addFileType("RTX", FILE_TYPE_MID, "audio/midi");
         addFileType("OTA", FILE_TYPE_MID, "audio/midi");
         
-        addFileType("MPEG", FILE_TYPE_MP4, "video/mpeg");
-        addFileType("MP4", FILE_TYPE_MP4, "video/mp4");
-        addFileType("M4V", FILE_TYPE_M4V, "video/mp4");
-        addFileType("3GP", FILE_TYPE_3GPP, "video/3gpp");
-        addFileType("3GPP", FILE_TYPE_3GPP, "video/3gpp");
-        addFileType("3G2", FILE_TYPE_3GPP2, "video/3gpp2");
-        addFileType("3GPP2", FILE_TYPE_3GPP2, "video/3gpp2");
+        addFileType("MPEG", FILE_TYPE_MP4, "video/mpeg", Mtp.Object.FORMAT_MPEG);
+        addFileType("MP4", FILE_TYPE_MP4, "video/mp4", Mtp.Object.FORMAT_MPEG);
+        addFileType("M4V", FILE_TYPE_M4V, "video/mp4", Mtp.Object.FORMAT_MPEG);
+        addFileType("3GP", FILE_TYPE_3GPP, "video/3gpp",  Mtp.Object.FORMAT_3GP_CONTAINER);
+        addFileType("3GPP", FILE_TYPE_3GPP, "video/3gpp", Mtp.Object.FORMAT_3GP_CONTAINER);
+        addFileType("3G2", FILE_TYPE_3GPP2, "video/3gpp2", Mtp.Object.FORMAT_3GP_CONTAINER);
+        addFileType("3GPP2", FILE_TYPE_3GPP2, "video/3gpp2", Mtp.Object.FORMAT_3GP_CONTAINER);
         addFileType("MKV", FILE_TYPE_MKV, "video/x-matroska");
         addFileType("WEBM", FILE_TYPE_MKV, "video/x-matroska");
         addFileType("TS", FILE_TYPE_MP2TS, "video/mp2ts");
 
         if (isWMVEnabled()) {
-            addFileType("WMV", FILE_TYPE_WMV, "video/x-ms-wmv");
+            addFileType("WMV", FILE_TYPE_WMV, "video/x-ms-wmv", Mtp.Object.FORMAT_WMV);
             addFileType("ASF", FILE_TYPE_ASF, "video/x-ms-asf");
         }
 
-        addFileType("JPG", FILE_TYPE_JPEG, "image/jpeg");
-        addFileType("JPEG", FILE_TYPE_JPEG, "image/jpeg");
-        addFileType("GIF", FILE_TYPE_GIF, "image/gif");
-        addFileType("PNG", FILE_TYPE_PNG, "image/png");
-        addFileType("BMP", FILE_TYPE_BMP, "image/x-ms-bmp");
+        addFileType("JPG", FILE_TYPE_JPEG, "image/jpeg", Mtp.Object.FORMAT_EXIF_JPEG);
+        addFileType("JPEG", FILE_TYPE_JPEG, "image/jpeg", Mtp.Object.FORMAT_EXIF_JPEG);
+        addFileType("GIF", FILE_TYPE_GIF, "image/gif", Mtp.Object.FORMAT_GIF);
+        addFileType("PNG", FILE_TYPE_PNG, "image/png", Mtp.Object.FORMAT_PNG);
+        addFileType("BMP", FILE_TYPE_BMP, "image/x-ms-bmp", Mtp.Object.FORMAT_BMP);
         addFileType("WBMP", FILE_TYPE_WBMP, "image/vnd.wap.wbmp");
  
-        addFileType("M3U", FILE_TYPE_M3U, "audio/x-mpegurl");
-        addFileType("PLS", FILE_TYPE_PLS, "audio/x-scpls");
-        addFileType("WPL", FILE_TYPE_WPL, "application/vnd.ms-wpl");
+        addFileType("M3U", FILE_TYPE_M3U, "audio/x-mpegurl", Mtp.Object.FORMAT_M3U_PLAYLIST);
+        addFileType("PLS", FILE_TYPE_PLS, "audio/x-scpls", Mtp.Object.FORMAT_PLS_PLAYLIST);
+        addFileType("WPL", FILE_TYPE_WPL, "application/vnd.ms-wpl", Mtp.Object.FORMAT_WPL_PLAYLIST);
 
         // compute file extensions list for native Media Scanner
         StringBuilder builder = new StringBuilder();
@@ -222,4 +240,25 @@
         return (value == null ? 0 : value.intValue());
     }
 
+    public static int getFormatCode(String fileName, String mimeType) {
+        if (mimeType != null) {
+            Integer value = sMimeTypeToFormatMap.get(mimeType);
+            if (value != null) {
+                return value.intValue();
+            }
+        }
+        int lastDot = fileName.lastIndexOf('.');
+        if (lastDot > 0) {
+            String extension = fileName.substring(lastDot + 1);
+            Integer value = sFileTypeToFormatMap.get(extension);
+            if (value != null) {
+                return value.intValue();
+            }
+        }
+        return Mtp.Object.FORMAT_UNDEFINED;
+    }
+
+    public static String getMimeTypeForFormatCode(int formatCode) {
+        return sFormatToMimeTypeMap.get(formatCode);
+    }
 }
diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java
index 34a86ec..94f5c7a 100644
--- a/media/java/android/media/MediaRecorder.java
+++ b/media/java/android/media/MediaRecorder.java
@@ -282,6 +282,28 @@
     }
 
     /**
+     * Enables/Disables time lapse capture and sets its parameters. This method should
+     * be called after setProfile().
+     *
+     * @param enableTimeLapse Pass true to enable time lapse capture, false to disable it.
+     * @param useStillCameraForTimeLapse Pass true to use still camera for capturing time lapse
+     * frames, false to use the video camera.
+     * @param timeBetweenTimeLapseFrameCaptureMs time between two captures of time lapse frames.
+     * @param encoderLevel the video encoder level.
+     */
+    public void setTimeLapseParameters(boolean enableTimeLapse,
+            boolean useStillCameraForTimeLapse,
+            int timeBetweenTimeLapseFrameCaptureMs, int encoderLevel) {
+        setParameter(String.format("time-lapse-enable=%d",
+                    (enableTimeLapse) ? 1 : 0));
+        setParameter(String.format("use-still-camera-for-time-lapse=%d",
+                    (useStillCameraForTimeLapse) ? 1 : 0));
+        setParameter(String.format("time-between-time-lapse-frame-capture=%d",
+                    timeBetweenTimeLapseFrameCaptureMs));
+        setVideoEncoderLevel(encoderLevel);
+    }
+
+    /**
      * Sets the format of the output file produced during recording. Call this
      * after setAudioSource()/setVideoSource() but before prepare().
      *
@@ -448,6 +470,16 @@
     }
 
     /**
+     * Sets the level of the encoder. Call this before prepare().
+     *
+     * @param encoderLevel the video encoder level.
+     * @hide
+     */
+    public void setVideoEncoderLevel(int encoderLevel) {
+        setParameter(String.format("video-param-encoder-level=%d", encoderLevel));
+    }
+
+    /**
      * Pass in the file descriptor of the file to be written. Call this after
      * setOutputFormat() but before prepare().
      *
diff --git a/media/java/android/media/MediaScanner.java b/media/java/android/media/MediaScanner.java
index 3333268..7f91c22 100644
--- a/media/java/android/media/MediaScanner.java
+++ b/media/java/android/media/MediaScanner.java
@@ -305,6 +305,7 @@
     private Uri mGenresUri;
     private Uri mPlaylistsUri;
     private boolean mProcessPlaylists, mProcessGenres;
+    private int mMtpObjectHandle;
 
     // used when scanning the image database so we know whether we have to prune
     // old thumbnail files
@@ -625,6 +626,9 @@
             map.put(MediaStore.MediaColumns.DATE_MODIFIED, mLastModified);
             map.put(MediaStore.MediaColumns.SIZE, mFileSize);
             map.put(MediaStore.MediaColumns.MIME_TYPE, mMimeType);
+            if (mMtpObjectHandle != 0) {
+                map.put(MediaStore.MediaColumns.MEDIA_SCANNER_NEW_OBJECT_ID, mMtpObjectHandle);
+            }
 
             if (MediaFile.isVideoFileType(mFileType)) {
                 map.put(Video.Media.ARTIST, (mArtist != null && mArtist.length() > 0 ? mArtist : MediaStore.UNKNOWN_STRING));
@@ -1227,6 +1231,14 @@
         }
     }
 
+    public Uri scanMtpFile(String path, String volumeName, int objectHandle, int format) {
+        String mimeType = MediaFile.getMimeTypeForFormatCode(format);
+        mMtpObjectHandle = objectHandle;
+        Uri result = scanSingleFile(path, volumeName, mimeType);
+        mMtpObjectHandle = 0;
+        return result;
+    }
+
     // returns the number of matching file/directory names, starting from the right
     private int matchPaths(String path1, String path2) {
         int result = 0;
diff --git a/media/java/android/media/MtpClient.java b/media/java/android/media/MtpClient.java
new file mode 100644
index 0000000..1aebcb8
--- /dev/null
+++ b/media/java/android/media/MtpClient.java
@@ -0,0 +1,104 @@
+/*
+ * 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.media;
+
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
+/**
+ * {@hide}
+ */
+public class MtpClient {
+
+    private static final String TAG = "MtpClient";
+
+    private final Listener mListener;
+
+    static {
+        System.loadLibrary("media_jni");
+    }
+
+    public MtpClient(Listener listener) {
+        native_setup();
+        if (listener == null) {
+            throw new NullPointerException("MtpClient: listener is null");
+        }
+        mListener = listener;
+    }
+
+    @Override
+    protected void finalize() {
+        native_finalize();
+    }
+
+    public boolean start() {
+        return native_start();
+    }
+
+    public void stop() {
+        native_stop();
+    }
+
+    public boolean deleteObject(int deviceID, int objectID) {
+        return native_delete_object(deviceID, objectID);
+    }
+
+    public int getParent(int deviceID, int objectID) {
+        return native_get_parent(deviceID, objectID);
+    }
+
+    public int getStorageID(int deviceID, int objectID) {
+        return native_get_storage_id(deviceID, objectID);
+    }
+
+    // create a file descriptor for reading the contents of an object over MTP
+    public ParcelFileDescriptor openFile(int deviceID, int objectID) {
+        return native_open_file(deviceID, objectID);
+    }
+
+    public interface Listener {
+        // called when a new MTP device has been discovered
+        void deviceAdded(int id);
+
+        // called when an MTP device has been removed
+        void deviceRemoved(int id);
+    }
+
+    // called from native code
+    private void deviceAdded(int id) {
+        Log.d(TAG, "deviceAdded " + id);
+        mListener.deviceAdded(id);
+    }
+
+    // called from native code
+    private void deviceRemoved(int id) {
+        Log.d(TAG, "deviceRemoved " + id);
+        mListener.deviceRemoved(id);
+    }
+
+    // used by the JNI code
+    private int mNativeContext;
+
+    private native final void native_setup();
+    private native final void native_finalize();
+    private native boolean native_start();
+    private native void native_stop();
+    private native boolean native_delete_object(int deviceID, int objectID);
+    private native int native_get_parent(int deviceID, int objectID);
+    private native int native_get_storage_id(int deviceID, int objectID);
+    private native ParcelFileDescriptor native_open_file(int deviceID, int objectID);
+}
diff --git a/media/java/android/media/MtpCursor.java b/media/java/android/media/MtpCursor.java
new file mode 100644
index 0000000..6ecfd0d
--- /dev/null
+++ b/media/java/android/media/MtpCursor.java
@@ -0,0 +1,223 @@
+/*
+ * 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.media;
+
+import android.database.AbstractWindowedCursor;
+import android.database.CursorWindow;
+import android.provider.Mtp;
+import android.util.Log;
+
+import java.util.HashMap;
+
+/**
+  * Cursor class for MTP content provider
+  * @hide
+  */
+public final class MtpCursor extends AbstractWindowedCursor {
+    static final String TAG = "MtpCursor";
+    static final int NO_COUNT = -1;
+
+    /* constants for mQueryType */
+    public static final int DEVICE              = 1;
+    public static final int DEVICE_ID           = 2;
+    public static final int STORAGE             = 3;
+    public static final int STORAGE_ID          = 4;
+    public static final int OBJECT              = 5;
+    public static final int OBJECT_ID           = 6;
+    public static final int STORAGE_CHILDREN    = 7;
+    public static final int OBJECT_CHILDREN     = 8;
+
+    private int mQueryType;
+    private int mDeviceID;
+    private int mStorageID;
+    private int mQbjectID;
+
+    /** The names of the columns in the projection */
+    private String[] mColumns;
+
+    /** The number of rows in the cursor */
+    private int mCount = NO_COUNT;
+
+    private final MtpClient mClient;
+
+    public MtpCursor(MtpClient client, int queryType, int deviceID, int storageID, int objectID,
+            String[] projection) {
+
+        mClient = client;
+        mQueryType = queryType;
+        mDeviceID = deviceID;
+        mStorageID = storageID;
+        mQbjectID = objectID;
+        mColumns = projection;
+
+        HashMap<String, Integer> map;
+        switch (queryType) {
+            case DEVICE:
+            case DEVICE_ID:
+                map = sDeviceProjectionMap;
+                break;
+            case STORAGE:
+            case STORAGE_ID:
+                map = sStorageProjectionMap;
+                break;
+            case OBJECT:
+            case OBJECT_ID:
+            case STORAGE_CHILDREN:
+            case OBJECT_CHILDREN:
+                map = sObjectProjectionMap;
+                break;
+            default:
+                throw new IllegalArgumentException("unknown query type "  + queryType);
+        }
+
+        int[] columns = new int[projection.length];
+        for (int i = 0; i < projection.length; i++) {
+            Integer id = map.get(projection[i]);
+            if (id == null) {
+                throw new IllegalArgumentException("unknown column "  + projection[i]);
+            }
+            columns[i] = id.intValue();
+        }
+        native_setup(client, queryType, deviceID, storageID, objectID, columns);
+    }
+
+    @Override
+    protected void finalize() {
+        native_finalize();
+    }
+
+    @Override
+    public int getCount() {
+        if (mCount == NO_COUNT) {
+            fillWindow(0);
+        }
+        return mCount;
+    }
+
+    @Override
+    public boolean requery() {
+        Log.d(TAG, "requery");
+        mCount = NO_COUNT;
+        if (mWindow != null) {
+            mWindow.clear();
+        }
+        return super.requery();
+    }
+
+    private void fillWindow(int startPos) {
+        if (mWindow == null) {
+            // If there isn't a window set already it will only be accessed locally
+            mWindow = new CursorWindow(true /* the window is local only */);
+        } else {
+                mWindow.clear();
+        }
+        mWindow.setStartPosition(startPos);
+        mCount = native_fill_window(mWindow, startPos);
+    }
+
+    @Override
+    public String[] getColumnNames() {
+        Log.d(TAG, "getColumnNames returning " + mColumns);
+        return mColumns;
+    }
+
+    /* Device Column IDs */
+     /* These must match the values in MtpCursor.cpp */
+   private static final int DEVICE_ROW_ID          = 1;
+    private static final int DEVICE_MANUFACTURER    = 2;
+    private static final int DEVICE_MODEL           = 3;
+
+    /* Storage Column IDs */
+    /* These must match the values in MtpCursor.cpp */
+    private static final int STORAGE_ROW_ID         = 101;
+    private static final int STORAGE_IDENTIFIER     = 102;
+    private static final int STORAGE_DESCRIPTION    = 103;
+
+    /* Object Column IDs */
+    /* These must match the values in MtpCursor.cpp */
+    private static final int OBJECT_ROW_ID              = 201;
+    private static final int OBJECT_STORAGE_ID          = 202;
+    private static final int OBJECT_FORMAT              = 203;
+    private static final int OBJECT_PROTECTION_STATUS   = 204;
+    private static final int OBJECT_SIZE                = 205;
+    private static final int OBJECT_THUMB_FORMAT        = 206;
+    private static final int OBJECT_THUMB_SIZE          = 207;
+    private static final int OBJECT_THUMB_WIDTH         = 208;
+    private static final int OBJECT_THUMB_HEIGHT        = 209;
+    private static final int OBJECT_IMAGE_WIDTH         = 210;
+    private static final int OBJECT_IMAGE_HEIGHT        = 211;
+    private static final int OBJECT_IMAGE_DEPTH         = 212;
+    private static final int OBJECT_PARENT              = 213;
+    private static final int OBJECT_ASSOCIATION_TYPE    = 214;
+    private static final int OBJECT_ASSOCIATION_DESC    = 215;
+    private static final int OBJECT_SEQUENCE_NUMBER     = 216;
+    private static final int OBJECT_NAME                = 217;
+    private static final int OBJECT_DATE_CREATED        = 218;
+    private static final int OBJECT_DATE_MODIFIED       = 219;
+    private static final int OBJECT_KEYWORDS            = 220;
+    private static final int OBJECT_THUMB               = 221;
+
+    private static HashMap<String, Integer> sDeviceProjectionMap;
+    private static HashMap<String, Integer> sStorageProjectionMap;
+    private static HashMap<String, Integer> sObjectProjectionMap;
+
+    static {
+        sDeviceProjectionMap = new HashMap<String, Integer>();
+        sDeviceProjectionMap.put(Mtp.Device._ID, new Integer(DEVICE_ROW_ID));
+        sDeviceProjectionMap.put(Mtp.Device.MANUFACTURER, new Integer(DEVICE_MANUFACTURER));
+        sDeviceProjectionMap.put(Mtp.Device.MODEL, new Integer(DEVICE_MODEL));
+
+        sStorageProjectionMap = new HashMap<String, Integer>();
+        sStorageProjectionMap.put(Mtp.Storage._ID, new Integer(STORAGE_ROW_ID));
+        sStorageProjectionMap.put(Mtp.Storage.IDENTIFIER, new Integer(STORAGE_IDENTIFIER));
+        sStorageProjectionMap.put(Mtp.Storage.DESCRIPTION, new Integer(STORAGE_DESCRIPTION));
+
+        sObjectProjectionMap = new HashMap<String, Integer>();
+        sObjectProjectionMap.put(Mtp.Object._ID, new Integer(OBJECT_ROW_ID));
+        sObjectProjectionMap.put(Mtp.Object.STORAGE_ID, new Integer(OBJECT_STORAGE_ID));
+        sObjectProjectionMap.put(Mtp.Object.FORMAT, new Integer(OBJECT_FORMAT));
+        sObjectProjectionMap.put(Mtp.Object.PROTECTION_STATUS, new Integer(OBJECT_PROTECTION_STATUS));
+        sObjectProjectionMap.put(Mtp.Object.SIZE, new Integer(OBJECT_SIZE));
+        sObjectProjectionMap.put(Mtp.Object.THUMB_FORMAT, new Integer(OBJECT_THUMB_FORMAT));
+        sObjectProjectionMap.put(Mtp.Object.THUMB_SIZE, new Integer(OBJECT_THUMB_SIZE));
+        sObjectProjectionMap.put(Mtp.Object.THUMB_WIDTH, new Integer(OBJECT_THUMB_WIDTH));
+        sObjectProjectionMap.put(Mtp.Object.THUMB_HEIGHT, new Integer(OBJECT_THUMB_HEIGHT));
+        sObjectProjectionMap.put(Mtp.Object.IMAGE_WIDTH, new Integer(OBJECT_IMAGE_WIDTH));
+        sObjectProjectionMap.put(Mtp.Object.IMAGE_HEIGHT, new Integer(OBJECT_IMAGE_HEIGHT));
+        sObjectProjectionMap.put(Mtp.Object.IMAGE_DEPTH, new Integer(OBJECT_IMAGE_DEPTH));
+        sObjectProjectionMap.put(Mtp.Object.PARENT, new Integer(OBJECT_PARENT));
+        sObjectProjectionMap.put(Mtp.Object.ASSOCIATION_TYPE, new Integer(OBJECT_ASSOCIATION_TYPE));
+        sObjectProjectionMap.put(Mtp.Object.ASSOCIATION_DESC, new Integer(OBJECT_ASSOCIATION_DESC));
+        sObjectProjectionMap.put(Mtp.Object.SEQUENCE_NUMBER, new Integer(OBJECT_SEQUENCE_NUMBER));
+        sObjectProjectionMap.put(Mtp.Object.NAME, new Integer(OBJECT_NAME));
+        sObjectProjectionMap.put(Mtp.Object.DATE_CREATED, new Integer(OBJECT_DATE_CREATED));
+        sObjectProjectionMap.put(Mtp.Object.DATE_MODIFIED, new Integer(OBJECT_DATE_MODIFIED));
+        sObjectProjectionMap.put(Mtp.Object.KEYWORDS, new Integer(OBJECT_KEYWORDS));
+        sObjectProjectionMap.put(Mtp.Object.THUMB, new Integer(OBJECT_THUMB));
+
+        sObjectProjectionMap.put(Mtp.Object.NAME, new Integer(OBJECT_NAME));
+    }
+
+    // used by the JNI code
+    private int mNativeContext;
+
+    private native final void native_setup(MtpClient client, int queryType,
+            int deviceID, int storageID, int objectID, int[] columns);
+    private native final void native_finalize();
+    private native void native_wait_for_event();
+    private native int native_fill_window(CursorWindow window, int startPos);
+}
diff --git a/media/java/android/media/MtpDatabase.java b/media/java/android/media/MtpDatabase.java
new file mode 100644
index 0000000..37f9f2c
--- /dev/null
+++ b/media/java/android/media/MtpDatabase.java
@@ -0,0 +1,422 @@
+/*
+ * 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.media;
+
+import android.content.Context;
+import android.content.ContentValues;
+import android.content.IContentProvider;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.RemoteException;
+import android.provider.MediaStore.Audio;
+import android.provider.MediaStore.MediaColumns;
+import android.provider.MediaStore.MtpObjects;
+import android.provider.Mtp;
+import android.util.Log;
+
+/**
+ * {@hide}
+ */
+public class MtpDatabase {
+
+    private static final String TAG = "MtpDatabase";
+
+    private final IContentProvider mMediaProvider;
+    private final String mVolumeName;
+    private final Uri mObjectsUri;
+
+    // FIXME - this should be passed in via the constructor
+    private final int mStorageID = 0x00010001;
+
+    private static final String[] ID_PROJECTION = new String[] {
+            MtpObjects.ObjectColumns._ID, // 0
+    };
+    private static final String[] PATH_SIZE_PROJECTION = new String[] {
+            MtpObjects.ObjectColumns._ID, // 0
+            MtpObjects.ObjectColumns.DATA, // 1
+            MtpObjects.ObjectColumns.SIZE, // 2
+    };
+    private static final String[] OBJECT_INFO_PROJECTION = new String[] {
+            MtpObjects.ObjectColumns._ID, // 0
+            MtpObjects.ObjectColumns.DATA, // 1
+            MtpObjects.ObjectColumns.FORMAT, // 2
+            MtpObjects.ObjectColumns.PARENT, // 3
+            MtpObjects.ObjectColumns.SIZE, // 4
+            MtpObjects.ObjectColumns.DATE_MODIFIED, // 5
+    };
+    private static final String ID_WHERE = MtpObjects.ObjectColumns._ID + "=?";
+    private static final String PATH_WHERE = MtpObjects.ObjectColumns.DATA + "=?";
+    private static final String PARENT_WHERE = MtpObjects.ObjectColumns.PARENT + "=?";
+    private static final String PARENT_FORMAT_WHERE = PARENT_WHERE + " AND "
+                                            + MtpObjects.ObjectColumns.FORMAT + "=?";
+
+    private final MediaScanner mMediaScanner;
+
+    // MTP property codes
+    private static final int MTP_PROPERTY_STORAGE_ID = 0xDC01;
+    private static final int MTP_PROPERTY_OBJECT_FORMAT = 0xDC02;
+    private static final int MTP_PROPERTY_OBJECT_SIZE = 0xDC04;
+    private static final int MTP_PROPERTY_OBJECT_FILE_NAME = 0xDC07;
+    private static final int MTP_PROPERTY_DATE_MODIFIED = 0xDC09;
+    private static final int MTP_PROPERTY_PARENT_OBJECT = 0xDC0B;
+
+    // MTP response codes
+    private static final int MTP_RESPONSE_OK = 0x2001;
+    private static final int MTP_RESPONSE_GENERAL_ERROR = 0x2002;
+    private static final int MTP_RESPONSE_INVALID_OBJECT_HANDLE = 0x2009;
+    private static final int MTP_RESPONSE_OBJECT_PROP_NOT_SUPPORTED = 0xA80A;
+
+    static {
+        System.loadLibrary("media_jni");
+    }
+
+    public MtpDatabase(Context context, String volumeName) {
+        native_setup();
+
+        mMediaProvider = context.getContentResolver().acquireProvider("media");
+        mVolumeName = volumeName;
+        mObjectsUri = MtpObjects.getContentUri(volumeName);
+        mMediaScanner = new MediaScanner(context);
+    }
+
+    @Override
+    protected void finalize() {
+        native_finalize();
+    }
+
+    private int beginSendObject(String path, int format, int parent,
+                         int storage, long size, long modified) {
+        ContentValues values = new ContentValues();
+        values.put(MtpObjects.ObjectColumns.DATA, path);
+        values.put(MtpObjects.ObjectColumns.FORMAT, format);
+        values.put(MtpObjects.ObjectColumns.PARENT, parent);
+        // storage is ignored for now
+        values.put(MtpObjects.ObjectColumns.SIZE, size);
+        values.put(MtpObjects.ObjectColumns.DATE_MODIFIED, modified);
+
+        try {
+            Uri uri = mMediaProvider.insert(mObjectsUri, values);
+            if (uri != null) {
+                return Integer.parseInt(uri.getPathSegments().get(2));
+            } else {
+                return -1;
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "RemoteException in beginSendObject", e);
+            return -1;
+        }
+    }
+
+    private void endSendObject(String path, int handle, int format, boolean succeeded) {
+        if (succeeded) {
+            // handle abstract playlists separately
+            // they do not exist in the file system so don't use the media scanner here
+            if (format == Mtp.Object.FORMAT_ABSTRACT_AV_PLAYLIST) {
+                // Strip Windows Media Player file extension
+                if (path.endsWith(".pla")) {
+                    path = path.substring(0, path.length() - 4);
+                }
+
+                // extract name from path
+                String name = path;
+                int lastSlash = name.lastIndexOf('/');
+                if (lastSlash >= 0) {
+                    name = name.substring(lastSlash + 1);
+                }
+
+                ContentValues values = new ContentValues(1);
+                values.put(Audio.Playlists.DATA, path);
+                values.put(Audio.Playlists.NAME, name);
+                values.put(MediaColumns.MEDIA_SCANNER_NEW_OBJECT_ID, handle);
+                try {
+                    Uri uri = mMediaProvider.insert(Audio.Playlists.EXTERNAL_CONTENT_URI, values);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "RemoteException in endSendObject", e);
+                }
+            } else {
+                Uri uri = mMediaScanner.scanMtpFile(path, mVolumeName, handle, format);
+            }
+        } else {
+            deleteFile(handle);
+        }
+    }
+
+    private int[] getObjectList(int storageID, int format, int parent) {
+        // we can ignore storageID until we support multiple storages
+        Log.d(TAG, "getObjectList parent: " + parent);
+        Cursor c = null;
+        try {
+            if (format != 0) {
+                c = mMediaProvider.query(mObjectsUri, ID_PROJECTION,
+                            PARENT_FORMAT_WHERE,
+                            new String[] { Integer.toString(parent), Integer.toString(format) },
+                             null);
+            } else {
+                c = mMediaProvider.query(mObjectsUri, ID_PROJECTION,
+                            PARENT_WHERE, new String[] { Integer.toString(parent) }, null);
+            }
+            if (c == null) {
+                Log.d(TAG, "null cursor");
+                return null;
+            }
+            int count = c.getCount();
+            if (count > 0) {
+                int[] result = new int[count];
+                for (int i = 0; i < count; i++) {
+                    c.moveToNext();
+                    result[i] = c.getInt(0);
+                }
+                Log.d(TAG, "returning " + result);
+                return result;
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "RemoteException in getObjectList", e);
+        } finally {
+            if (c != null) {
+                c.close();
+            }
+        }
+        return null;
+    }
+
+    private int getNumObjects(int storageID, int format, int parent) {
+        // we can ignore storageID until we support multiple storages
+        Log.d(TAG, "getObjectList parent: " + parent);
+        Cursor c = null;
+        try {
+            if (format != 0) {
+                c = mMediaProvider.query(mObjectsUri, ID_PROJECTION,
+                            PARENT_FORMAT_WHERE,
+                            new String[] { Integer.toString(parent), Integer.toString(format) },
+                             null);
+            } else {
+                c = mMediaProvider.query(mObjectsUri, ID_PROJECTION,
+                            PARENT_WHERE, new String[] { Integer.toString(parent) }, null);
+            }
+            if (c != null) {
+                return c.getCount();
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "RemoteException in getNumObjects", e);
+        } finally {
+            if (c != null) {
+                c.close();
+            }
+        }
+        return -1;
+    }
+
+    private int getObjectProperty(int handle, int property,
+                            long[] outIntValue, char[] outStringValue) {
+        Log.d(TAG, "getObjectProperty: " + property);
+        String column = null;
+        boolean isString = false;
+
+        switch (property) {
+            case MTP_PROPERTY_STORAGE_ID:
+                outIntValue[0] = mStorageID;
+                return MTP_RESPONSE_OK;
+            case MTP_PROPERTY_OBJECT_FORMAT:
+                column = MtpObjects.ObjectColumns.FORMAT;
+                break;
+            case MTP_PROPERTY_OBJECT_SIZE:
+                column = MtpObjects.ObjectColumns.SIZE;
+                break;
+            case MTP_PROPERTY_OBJECT_FILE_NAME:
+                column = MtpObjects.ObjectColumns.DATA;
+                isString = true;
+                break;
+            case MTP_PROPERTY_DATE_MODIFIED:
+                column = MtpObjects.ObjectColumns.DATE_MODIFIED;
+                break;
+            case MTP_PROPERTY_PARENT_OBJECT:
+                column = MtpObjects.ObjectColumns.PARENT;
+                break;
+            default:
+                return MTP_RESPONSE_OBJECT_PROP_NOT_SUPPORTED;
+        }
+
+        Cursor c = null;
+        try {
+            // for now we are only reading properties from the "objects" table
+            c = mMediaProvider.query(mObjectsUri,
+                            new String [] { MtpObjects.ObjectColumns._ID, column },
+                            ID_WHERE, new String[] { Integer.toString(handle) }, null);
+            if (c != null && c.moveToNext()) {
+                if (isString) {
+                    String value = c.getString(1);
+                    int start = 0;
+
+                    if (property == MTP_PROPERTY_OBJECT_FILE_NAME) {
+                        // extract name from full path
+                        int lastSlash = value.lastIndexOf('/');
+                        if (lastSlash >= 0) {
+                            start = lastSlash + 1;
+                        }
+                    }
+                    int end = value.length();
+                    if (end - start > 255) {
+                        end = start + 255;
+                    }
+                    value.getChars(start, end, outStringValue, 0);
+                    outStringValue[end - start] = 0;
+                } else {
+                    outIntValue[0] = c.getLong(1);
+                }
+                return MTP_RESPONSE_OK;
+            }
+        } catch (Exception e) {
+            return MTP_RESPONSE_GENERAL_ERROR;
+        } finally {
+            if (c != null) {
+                c.close();
+            }
+        }
+        // query failed if we get here
+        return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
+    }
+
+    private boolean getObjectInfo(int handle, int[] outStorageFormatParent,
+                        char[] outName, long[] outSizeModified) {
+        Log.d(TAG, "getObjectInfo: " + handle);
+        Cursor c = null;
+        try {
+            c = mMediaProvider.query(mObjectsUri, OBJECT_INFO_PROJECTION,
+                            ID_WHERE, new String[] {  Integer.toString(handle) }, null);
+            if (c != null && c.moveToNext()) {
+                outStorageFormatParent[0] = mStorageID;
+                outStorageFormatParent[1] = c.getInt(2);
+                outStorageFormatParent[2] = c.getInt(3);
+
+                // extract name from path
+                String path = c.getString(1);
+                int lastSlash = path.lastIndexOf('/');
+                int start = (lastSlash >= 0 ? lastSlash + 1 : 0);
+                int end = path.length();
+                if (end - start > 255) {
+                    end = start + 255;
+                }
+                path.getChars(start, end, outName, 0);
+                outName[end - start] = 0;
+
+                outSizeModified[0] = c.getLong(4);
+                outSizeModified[1] = c.getLong(5);
+                return true;
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "RemoteException in getObjectProperty", e);
+        } finally {
+            if (c != null) {
+                c.close();
+            }
+        }
+        return false;
+    }
+
+    private int getObjectFilePath(int handle, char[] outFilePath, long[] outFileLength) {
+        Log.d(TAG, "getObjectFilePath: " + handle);
+        Cursor c = null;
+        try {
+            c = mMediaProvider.query(mObjectsUri, PATH_SIZE_PROJECTION,
+                            ID_WHERE, new String[] {  Integer.toString(handle) }, null);
+            if (c != null && c.moveToNext()) {
+                String path = c.getString(1);
+                path.getChars(0, path.length(), outFilePath, 0);
+                outFilePath[path.length()] = 0;
+                outFileLength[0] = c.getLong(2);
+                return MTP_RESPONSE_OK;
+            } else {
+                return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "RemoteException in getObjectFilePath", e);
+            return MTP_RESPONSE_GENERAL_ERROR;
+        } finally {
+            if (c != null) {
+                c.close();
+            }
+        }
+    }
+
+    private int deleteFile(int handle) {
+        Log.d(TAG, "deleteFile: " + handle);
+        Uri uri = MtpObjects.getContentUri(mVolumeName, handle);
+        try {
+            if (mMediaProvider.delete(uri, null, null) == 1) {
+                return MTP_RESPONSE_OK;
+            } else {
+                return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "RemoteException in deleteFile", e);
+            return MTP_RESPONSE_GENERAL_ERROR;
+        }
+    }
+
+    private int[] getObjectReferences(int handle) {
+        Log.d(TAG, "getObjectReferences for: " + handle);
+        Uri uri = MtpObjects.getReferencesUri(mVolumeName, handle);
+        Cursor c = null;
+        try {
+            c = mMediaProvider.query(uri, ID_PROJECTION, null, null, null);
+            if (c == null) {
+                return null;
+            }
+            int count = c.getCount();
+            if (count > 0) {
+                int[] result = new int[count];
+                for (int i = 0; i < count; i++) {
+                    c.moveToNext();
+                    result[i] = c.getInt(0);
+                }
+                return result;
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "RemoteException in getObjectList", e);
+        } finally {
+            if (c != null) {
+                c.close();
+            }
+        }
+        return null;
+    }
+
+    private int setObjectReferences(int handle, int[] references) {
+        Uri uri = MtpObjects.getReferencesUri(mVolumeName, handle);
+        int count = references.length;
+        ContentValues[] valuesList = new ContentValues[count];
+        for (int i = 0; i < count; i++) {
+            ContentValues values = new ContentValues();
+            values.put(MtpObjects.ObjectColumns._ID, references[i]);
+            valuesList[i] = values;
+        }
+        try {
+            if (count == mMediaProvider.bulkInsert(uri, valuesList)) {
+                return MTP_RESPONSE_OK;
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "RemoteException in setObjectReferences", e);
+        }
+        return MTP_RESPONSE_GENERAL_ERROR;
+    }
+
+    // used by the JNI code
+    private int mNativeContext;
+
+    private native final void native_setup();
+    private native final void native_finalize();
+}
diff --git a/media/java/android/media/MtpServer.java b/media/java/android/media/MtpServer.java
new file mode 100644
index 0000000..b0945a5
--- /dev/null
+++ b/media/java/android/media/MtpServer.java
@@ -0,0 +1,67 @@
+/*
+ * 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.media;
+
+import android.util.Log;
+
+/**
+ * Java wrapper for MTP/PTP support as USB responder.
+ * {@hide}
+ */
+public class MtpServer {
+
+    private static final String TAG = "MtpServer";
+
+    static {
+        System.loadLibrary("media_jni");
+    }
+
+    public MtpServer(MtpDatabase database, String storagePath) {
+        native_setup(database, storagePath);
+    }
+
+    @Override
+    protected void finalize() {
+        native_finalize();
+    }
+
+    public void start() {
+        native_start();
+    }
+
+    public void stop() {
+        native_stop();
+    }
+
+    public void sendObjectAdded(int handle) {
+        native_send_object_added(handle);
+    }
+
+    public void sendObjectRemoved(int handle) {
+        native_send_object_removed(handle);
+    }
+
+    // used by the JNI code
+    private int mNativeContext;
+
+    private native final void native_setup(MtpDatabase database, String storagePath);
+    private native final void native_finalize();
+    private native final void native_start();
+    private native final void native_stop();
+    private native final void native_send_object_added(int handle);
+    private native final void native_send_object_removed(int handle);
+}
diff --git a/media/jni/Android.mk b/media/jni/Android.mk
index 6eec215..653532c 100644
--- a/media/jni/Android.mk
+++ b/media/jni/Android.mk
@@ -12,7 +12,11 @@
     android_media_MediaMetadataRetriever.cpp \
     android_media_ResampleInputStream.cpp \
     android_media_MediaProfiles.cpp \
-    android_media_AmrInputStream.cpp
+    android_media_AmrInputStream.cpp \
+	android_media_MtpClient.cpp \
+	android_media_MtpCursor.cpp \
+	android_media_MtpDatabase.cpp \
+	android_media_MtpServer.cpp \
 
 LOCAL_SHARED_LIBRARIES := \
     libandroid_runtime \
@@ -25,7 +29,8 @@
     libcutils \
     libsurfaceflinger_client \
     libstagefright \
-    libcamera_client
+    libcamera_client \
+	libsqlite
 
 ifneq ($(BUILD_WITHOUT_PV),true)
 
@@ -35,7 +40,9 @@
     LOCAL_CFLAGS += -DNO_OPENCORE
 endif
 
-LOCAL_STATIC_LIBRARIES :=
+ifneq ($(TARGET_SIMULATOR),true)
+LOCAL_STATIC_LIBRARIES := libmtp libusbhost
+endif
 
 LOCAL_C_INCLUDES += \
     external/tremor/Tremor \
@@ -44,6 +51,7 @@
     frameworks/base/media/libstagefright/codecs/amrnb/enc/src \
     frameworks/base/media/libstagefright/codecs/amrnb/common \
     frameworks/base/media/libstagefright/codecs/amrnb/common/include \
+    frameworks/base/media/mtp \
     $(PV_INCLUDES) \
     $(JNI_H_INCLUDE) \
     $(call include-path-for, corecg graphics)
diff --git a/media/jni/android_media_MediaPlayer.cpp b/media/jni/android_media_MediaPlayer.cpp
index 474a174..6710db0 100644
--- a/media/jni/android_media_MediaPlayer.cpp
+++ b/media/jni/android_media_MediaPlayer.cpp
@@ -788,6 +788,10 @@
 extern int register_android_media_MediaScanner(JNIEnv *env);
 extern int register_android_media_ResampleInputStream(JNIEnv *env);
 extern int register_android_media_MediaProfiles(JNIEnv *env);
+extern int register_android_media_MtpClient(JNIEnv *env);
+extern int register_android_media_MtpCursor(JNIEnv *env);
+extern int register_android_media_MtpDatabase(JNIEnv *env);
+extern int register_android_media_MtpServer(JNIEnv *env);
 
 #ifndef NO_OPENCORE
 extern int register_android_media_AmrInputStream(JNIEnv *env);
@@ -841,6 +845,26 @@
         goto bail;
     }
 
+    if (register_android_media_MtpClient(env) < 0) {
+        LOGE("ERROR: MtpClient native registration failed");
+        goto bail;
+    }
+
+    if (register_android_media_MtpCursor(env) < 0) {
+        LOGE("ERROR: MtpCursor native registration failed");
+        goto bail;
+    }
+
+    if (register_android_media_MtpDatabase(env) < 0) {
+        LOGE("ERROR: MtpDatabase native registration failed");
+        goto bail;
+    }
+
+    if (register_android_media_MtpServer(env) < 0) {
+        LOGE("ERROR: MtpServer native registration failed");
+        goto bail;
+    }
+
     /* success -- return valid version number */
     result = JNI_VERSION_1_4;
 
diff --git a/media/jni/android_media_MtpClient.cpp b/media/jni/android_media_MtpClient.cpp
new file mode 100644
index 0000000..67740bc
--- /dev/null
+++ b/media/jni/android_media_MtpClient.cpp
@@ -0,0 +1,296 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "MtpClientJNI"
+#include "utils/Log.h"
+
+#include <stdio.h>
+#include <assert.h>
+#include <limits.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#include "jni.h"
+#include "JNIHelp.h"
+#include "android_runtime/AndroidRuntime.h"
+
+#include "MtpClient.h"
+#include "MtpDevice.h"
+#include "MtpObjectInfo.h"
+
+using namespace android;
+
+// ----------------------------------------------------------------------------
+
+static jmethodID method_deviceAdded;
+static jmethodID method_deviceRemoved;
+static jfieldID field_context;
+
+static struct file_descriptor_offsets_t
+{
+    jclass mClass;
+    jmethodID mConstructor;
+    jfieldID mDescriptor;
+} gFileDescriptorOffsets;
+
+static struct parcel_file_descriptor_offsets_t
+{
+    jclass mClass;
+    jmethodID mConstructor;
+} gParcelFileDescriptorOffsets;
+
+#ifdef HAVE_ANDROID_OS
+
+static void checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName) {
+    if (env->ExceptionCheck()) {
+        LOGE("An exception was thrown by callback '%s'.", methodName);
+        LOGE_EX(env);
+        env->ExceptionClear();
+    }
+}
+
+class MyClient : public MtpClient {
+private:
+    virtual void    deviceAdded(MtpDevice *device);
+    virtual void    deviceRemoved(MtpDevice *device);
+
+    jobject         mClient;
+    MtpDevice*      mEventDevice;
+
+public:
+                    MyClient(JNIEnv *env, jobject client);
+    void            cleanup(JNIEnv *env);
+};
+
+MtpClient* get_client_from_object(JNIEnv* env, jobject javaClient)
+{
+    return (MtpClient*)env->GetIntField(javaClient, field_context);
+}
+
+
+MyClient::MyClient(JNIEnv *env, jobject client)
+    :   mClient(env->NewGlobalRef(client))
+{
+}
+
+void MyClient::cleanup(JNIEnv *env) {
+    env->DeleteGlobalRef(mClient);
+}
+
+void MyClient::deviceAdded(MtpDevice *device) {
+    JNIEnv* env = AndroidRuntime::getJNIEnv();
+    const char* name = device->getDeviceName();
+    LOGD("MyClient::deviceAdded %s\n", name);
+
+    env->CallVoidMethod(mClient, method_deviceAdded, device->getID());
+
+    checkAndClearExceptionFromCallback(env, __FUNCTION__);
+}
+
+void MyClient::deviceRemoved(MtpDevice *device) {
+    JNIEnv* env = AndroidRuntime::getJNIEnv();
+    const char* name = device->getDeviceName();
+    LOGD("MyClient::deviceRemoved %s\n", name);
+
+    env->CallVoidMethod(mClient, method_deviceRemoved, device->getID());
+
+    checkAndClearExceptionFromCallback(env, __FUNCTION__);
+}
+
+#endif // HAVE_ANDROID_OS
+
+// ----------------------------------------------------------------------------
+
+static void
+android_media_MtpClient_setup(JNIEnv *env, jobject thiz)
+{
+#ifdef HAVE_ANDROID_OS
+    LOGD("setup\n");
+    MyClient* client = new MyClient(env, thiz);
+    client->start();
+    env->SetIntField(thiz, field_context, (int)client);
+#endif
+}
+
+static void
+android_media_MtpClient_finalize(JNIEnv *env, jobject thiz)
+{
+#ifdef HAVE_ANDROID_OS
+    LOGD("finalize\n");
+    MyClient *client = (MyClient *)env->GetIntField(thiz, field_context);
+    client->cleanup(env);
+    delete client;
+    env->SetIntField(thiz, field_context, 0);
+#endif
+}
+
+static jboolean
+android_media_MtpClient_start(JNIEnv *env, jobject thiz)
+{
+#ifdef HAVE_ANDROID_OS
+    LOGD("start\n");
+    MyClient *client = (MyClient *)env->GetIntField(thiz, field_context);
+    return client->start();
+#else
+    return false;
+#endif
+}
+
+static void
+android_media_MtpClient_stop(JNIEnv *env, jobject thiz)
+{
+#ifdef HAVE_ANDROID_OS
+    LOGD("stop\n");
+    MyClient *client = (MyClient *)env->GetIntField(thiz, field_context);
+    client->stop();
+#endif
+}
+
+static jboolean
+android_media_MtpClient_delete_object(JNIEnv *env, jobject thiz,
+        jint device_id, jint object_id)
+{
+#ifdef HAVE_ANDROID_OS
+    MyClient *client = (MyClient *)env->GetIntField(thiz, field_context);
+    MtpDevice* device = client->getDevice(device_id);
+    if (device)
+        return device->deleteObject(object_id);
+    else
+ #endif
+        return NULL;
+}
+
+static jint
+android_media_MtpClient_get_parent(JNIEnv *env, jobject thiz,
+        jint device_id, jint object_id)
+{
+#ifdef HAVE_ANDROID_OS
+    MyClient *client = (MyClient *)env->GetIntField(thiz, field_context);
+    MtpDevice* device = client->getDevice(device_id);
+    if (device)
+        return device->getParent(object_id);
+    else
+#endif
+        return -1;
+}
+
+static jint
+android_media_MtpClient_get_storage_id(JNIEnv *env, jobject thiz,
+        jint device_id, jint object_id)
+{
+ #ifdef HAVE_ANDROID_OS
+    MyClient *client = (MyClient *)env->GetIntField(thiz, field_context);
+    MtpDevice* device = client->getDevice(device_id);
+    if (device)
+        return device->getStorageID(object_id);
+    else
+#endif
+        return -1;
+}
+
+static jobject
+android_media_MtpClient_open_file(JNIEnv *env, jobject thiz,
+        jint device_id, jint object_id)
+{
+#ifdef HAVE_ANDROID_OS
+    MyClient *client = (MyClient *)env->GetIntField(thiz, field_context);
+    MtpDevice* device = client->getDevice(device_id);
+    if (!device)
+        return NULL;
+
+    MtpObjectInfo* info = device->getObjectInfo(object_id);
+    if (!info)
+        return NULL;
+    int object_size = info->mCompressedSize;
+    delete info;
+    int fd = device->readObject(object_id, object_size);
+    if (fd < 0)
+        return NULL;
+
+    jobject fileDescriptor = env->NewObject(gFileDescriptorOffsets.mClass,
+        gFileDescriptorOffsets.mConstructor);
+    if (fileDescriptor != NULL) {
+        env->SetIntField(fileDescriptor, gFileDescriptorOffsets.mDescriptor, fd);
+    } else {
+        return NULL;
+    }
+    return env->NewObject(gParcelFileDescriptorOffsets.mClass,
+        gParcelFileDescriptorOffsets.mConstructor, fileDescriptor);
+#endif
+    return NULL;
+}
+
+// ----------------------------------------------------------------------------
+
+static JNINativeMethod gMethods[] = {
+    {"native_setup",            "()V",  (void *)android_media_MtpClient_setup},
+    {"native_finalize",         "()V",  (void *)android_media_MtpClient_finalize},
+    {"native_start",            "()Z",  (void *)android_media_MtpClient_start},
+    {"native_stop",             "()V",  (void *)android_media_MtpClient_stop},
+    {"native_delete_object",   "(II)Z", (void *)android_media_MtpClient_delete_object},
+    {"native_get_parent",      "(II)I", (void *)android_media_MtpClient_get_parent},
+    {"native_get_storage_id",  "(II)I", (void *)android_media_MtpClient_get_storage_id},
+    {"native_open_file",       "(II)Landroid/os/ParcelFileDescriptor;",
+                                        (void *)android_media_MtpClient_open_file},
+};
+
+static const char* const kClassPathName = "android/media/MtpClient";
+
+int register_android_media_MtpClient(JNIEnv *env)
+{
+    jclass clazz;
+
+    LOGD("register_android_media_MtpClient\n");
+
+    clazz = env->FindClass("android/media/MtpClient");
+    if (clazz == NULL) {
+        LOGE("Can't find android/media/MtpClient");
+        return -1;
+    }
+    method_deviceAdded = env->GetMethodID(clazz, "deviceAdded", "(I)V");
+    if (method_deviceAdded == NULL) {
+        LOGE("Can't find deviceAdded");
+        return -1;
+    }
+    method_deviceRemoved = env->GetMethodID(clazz, "deviceRemoved", "(I)V");
+    if (method_deviceRemoved == NULL) {
+        LOGE("Can't find deviceRemoved");
+        return -1;
+    }
+    field_context = env->GetFieldID(clazz, "mNativeContext", "I");
+    if (field_context == NULL) {
+        LOGE("Can't find MtpClient.mNativeContext");
+        return -1;
+    }
+
+   clazz = env->FindClass("java/io/FileDescriptor");
+    LOG_FATAL_IF(clazz == NULL, "Unable to find class java.io.FileDescriptor");
+    gFileDescriptorOffsets.mClass = (jclass) env->NewGlobalRef(clazz);
+    gFileDescriptorOffsets.mConstructor = env->GetMethodID(clazz, "<init>", "()V");
+    gFileDescriptorOffsets.mDescriptor = env->GetFieldID(clazz, "descriptor", "I");
+    LOG_FATAL_IF(gFileDescriptorOffsets.mDescriptor == NULL,
+                 "Unable to find descriptor field in java.io.FileDescriptor");
+
+   clazz = env->FindClass("android/os/ParcelFileDescriptor");
+    LOG_FATAL_IF(clazz == NULL, "Unable to find class android.os.ParcelFileDescriptor");
+    gParcelFileDescriptorOffsets.mClass = (jclass) env->NewGlobalRef(clazz);
+    gParcelFileDescriptorOffsets.mConstructor = env->GetMethodID(clazz, "<init>", "(Ljava/io/FileDescriptor;)V");
+    LOG_FATAL_IF(gParcelFileDescriptorOffsets.mConstructor == NULL,
+                 "Unable to find constructor for android.os.ParcelFileDescriptor");
+
+    return AndroidRuntime::registerNativeMethods(env,
+                "android/media/MtpClient", gMethods, NELEM(gMethods));
+}
diff --git a/media/jni/android_media_MtpCursor.cpp b/media/jni/android_media_MtpCursor.cpp
new file mode 100644
index 0000000..6228b5d
--- /dev/null
+++ b/media/jni/android_media_MtpCursor.cpp
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "MtpCursorJNI"
+#include "utils/Log.h"
+
+#include <stdio.h>
+#include <assert.h>
+#include <limits.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#include "jni.h"
+#include "JNIHelp.h"
+#include "android_runtime/AndroidRuntime.h"
+#include "binder/CursorWindow.h"
+
+#include "MtpClient.h"
+#include "MtpCursor.h"
+
+using namespace android;
+
+// ----------------------------------------------------------------------------
+
+static jfieldID field_context;
+
+// From android_media_MtpClient.cpp
+MtpClient * get_client_from_object(JNIEnv * env, jobject javaClient);
+
+// ----------------------------------------------------------------------------
+
+static bool ExceptionCheck(void* env)
+{
+    return ((JNIEnv *)env)->ExceptionCheck();
+}
+
+static void
+android_media_MtpCursor_setup(JNIEnv *env, jobject thiz, jobject javaClient,
+        jint queryType, jint deviceID, jint storageID, jint objectID, jintArray javaColumns)
+{
+#ifdef HAVE_ANDROID_OS
+    LOGD("android_media_MtpCursor_setup queryType: %d deviceID: %d storageID: %d objectID: %d\n",
+                queryType, deviceID, storageID, objectID);
+
+    int* columns = NULL;
+    int columnCount = 0;
+    if (javaColumns) {
+        columns = env->GetIntArrayElements(javaColumns, 0);
+        columnCount = env->GetArrayLength(javaColumns);
+    }
+
+    MtpClient* client = get_client_from_object(env, javaClient);
+    MtpCursor* cursor = new MtpCursor(client, queryType,
+            deviceID, storageID, objectID, columnCount, columns);
+
+    if (columns)
+        env->ReleaseIntArrayElements(javaColumns, columns, 0);
+    env->SetIntField(thiz, field_context, (int)cursor);
+#endif
+}
+
+static void
+android_media_MtpCursor_finalize(JNIEnv *env, jobject thiz)
+{
+#ifdef HAVE_ANDROID_OS
+    LOGD("finalize\n");
+    MtpCursor *cursor = (MtpCursor *)env->GetIntField(thiz, field_context);
+    delete cursor;
+#endif
+}
+
+static jint
+android_media_MtpCursor_fill_window(JNIEnv *env, jobject thiz, jobject javaWindow, jint startPos)
+{
+#ifdef HAVE_ANDROID_OS
+    CursorWindow* window = get_window_from_object(env, javaWindow);
+    if (!window) {
+        LOGE("Invalid CursorWindow");
+        jniThrowException(env, "java/lang/IllegalArgumentException",
+                          "Bad CursorWindow");
+        return 0;
+    }
+    MtpCursor *cursor = (MtpCursor *)env->GetIntField(thiz, field_context);
+
+    return cursor->fillWindow(window, startPos);
+#else
+    return 0;
+#endif
+}
+
+// ----------------------------------------------------------------------------
+
+static JNINativeMethod gMethods[] = {
+    {"native_setup",            "(Landroid/media/MtpClient;IIII[I)V",
+                                        (void *)android_media_MtpCursor_setup},
+    {"native_finalize",         "()V",  (void *)android_media_MtpCursor_finalize},
+    {"native_fill_window",      "(Landroid/database/CursorWindow;I)I",
+                                        (void *)android_media_MtpCursor_fill_window},
+
+};
+
+static const char* const kClassPathName = "android/media/MtpCursor";
+
+int register_android_media_MtpCursor(JNIEnv *env)
+{
+    jclass clazz;
+
+    LOGD("register_android_media_MtpCursor\n");
+
+    clazz = env->FindClass("android/media/MtpCursor");
+    if (clazz == NULL) {
+        LOGE("Can't find android/media/MtpCursor");
+        return -1;
+    }
+    field_context = env->GetFieldID(clazz, "mNativeContext", "I");
+    if (field_context == NULL) {
+        LOGE("Can't find MtpCursor.mNativeContext");
+        return -1;
+    }
+
+    return AndroidRuntime::registerNativeMethods(env,
+                "android/media/MtpCursor", gMethods, NELEM(gMethods));
+}
diff --git a/media/jni/android_media_MtpDatabase.cpp b/media/jni/android_media_MtpDatabase.cpp
new file mode 100644
index 0000000..d4539fe
--- /dev/null
+++ b/media/jni/android_media_MtpDatabase.cpp
@@ -0,0 +1,501 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "MtpDatabaseJNI"
+#include "utils/Log.h"
+
+#include <stdio.h>
+#include <assert.h>
+#include <limits.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#include "jni.h"
+#include "JNIHelp.h"
+#include "android_runtime/AndroidRuntime.h"
+
+#include "MtpDatabase.h"
+#include "MtpDataPacket.h"
+#include "MtpUtils.h"
+#include "mtp.h"
+
+using namespace android;
+
+// ----------------------------------------------------------------------------
+
+static jmethodID method_beginSendObject;
+static jmethodID method_endSendObject;
+static jmethodID method_getObjectList;
+static jmethodID method_getNumObjects;
+static jmethodID method_getObjectProperty;
+static jmethodID method_getObjectInfo;
+static jmethodID method_getObjectFilePath;
+static jmethodID method_deleteFile;
+static jmethodID method_getObjectReferences;
+static jmethodID method_setObjectReferences;
+static jfieldID field_context;
+
+MtpDatabase* getMtpDatabase(JNIEnv *env, jobject database) {
+    return (MtpDatabase *)env->GetIntField(database, field_context);
+}
+
+#ifdef HAVE_ANDROID_OS
+// ----------------------------------------------------------------------------
+
+class MyMtpDatabase : public MtpDatabase {
+private:
+    jobject         mDatabase;
+    jintArray       mIntBuffer;
+    jlongArray      mLongBuffer;
+    jcharArray      mStringBuffer;
+
+public:
+                                    MyMtpDatabase(JNIEnv *env, jobject client);
+    virtual                         ~MyMtpDatabase();
+    void                            cleanup(JNIEnv *env);
+
+    virtual MtpObjectHandle         beginSendObject(const char* path,
+                                            MtpObjectFormat format,
+                                            MtpObjectHandle parent,
+                                            MtpStorageID storage,
+                                            uint64_t size,
+                                            time_t modified);
+
+    virtual void                    endSendObject(const char* path,
+                                            MtpObjectHandle handle,
+                                            MtpObjectFormat format,
+                                            bool succeeded);
+
+    virtual MtpObjectHandleList*    getObjectList(MtpStorageID storageID,
+                                    MtpObjectFormat format,
+                                    MtpObjectHandle parent);
+
+    virtual int                     getNumObjects(MtpStorageID storageID,
+                                            MtpObjectFormat format,
+                                            MtpObjectHandle parent);
+
+    virtual MtpResponseCode         getObjectProperty(MtpObjectHandle handle,
+                                            MtpObjectProperty property,
+                                            MtpDataPacket& packet);
+
+    virtual MtpResponseCode         getObjectInfo(MtpObjectHandle handle,
+                                            MtpDataPacket& packet);
+
+    virtual MtpResponseCode         getObjectFilePath(MtpObjectHandle handle,
+                                            MtpString& filePath,
+                                            int64_t& fileLength);
+    virtual MtpResponseCode         deleteFile(MtpObjectHandle handle);
+
+    bool                            getPropertyInfo(MtpObjectProperty property, int& type);
+
+    virtual MtpObjectHandleList*    getObjectReferences(MtpObjectHandle handle);
+
+    virtual MtpResponseCode         setObjectReferences(MtpObjectHandle handle,
+                                            MtpObjectHandleList* references);
+};
+
+MyMtpDatabase::MyMtpDatabase(JNIEnv *env, jobject client)
+    :   mDatabase(env->NewGlobalRef(client)),
+        mIntBuffer(NULL),
+        mLongBuffer(NULL),
+        mStringBuffer(NULL)
+{
+    jintArray intArray;
+    jlongArray longArray;
+    jcharArray charArray;
+
+    // create buffers for out arguments
+    // we don't need to be thread-safe so this is OK
+    intArray = env->NewIntArray(3);
+    if (!intArray)
+        goto out_of_memory;
+    mIntBuffer = (jintArray)env->NewGlobalRef(intArray);
+    longArray = env->NewLongArray(2);
+    if (!longArray)
+        goto out_of_memory;
+    mLongBuffer = (jlongArray)env->NewGlobalRef(longArray);
+    charArray = env->NewCharArray(256);
+    if (!charArray)
+        goto out_of_memory;
+    mStringBuffer = (jcharArray)env->NewGlobalRef(charArray);
+    return;
+
+out_of_memory:
+    env->ThrowNew(env->FindClass("java/lang/OutOfMemoryError"), NULL);
+}
+
+void MyMtpDatabase::cleanup(JNIEnv *env) {
+    env->DeleteGlobalRef(mDatabase);
+    env->DeleteGlobalRef(mIntBuffer);
+    env->DeleteGlobalRef(mLongBuffer);
+    env->DeleteGlobalRef(mStringBuffer);
+}
+
+MyMtpDatabase::~MyMtpDatabase() {
+}
+
+MtpObjectHandle MyMtpDatabase::beginSendObject(const char* path,
+                                            MtpObjectFormat format,
+                                            MtpObjectHandle parent,
+                                            MtpStorageID storage,
+                                            uint64_t size,
+                                            time_t modified) {
+    JNIEnv* env = AndroidRuntime::getJNIEnv();
+    return env->CallIntMethod(mDatabase, method_beginSendObject, env->NewStringUTF(path),
+                (jint)format, (jint)parent, (jint)storage, (jlong)size, (jlong)modified);
+}
+
+void MyMtpDatabase::endSendObject(const char* path, MtpObjectHandle handle,
+                                MtpObjectFormat format, bool succeeded) {
+    JNIEnv* env = AndroidRuntime::getJNIEnv();
+    env->CallVoidMethod(mDatabase, method_endSendObject, env->NewStringUTF(path),
+                        (jint)handle, (jint)format, (jboolean)succeeded);
+}
+
+MtpObjectHandleList* MyMtpDatabase::getObjectList(MtpStorageID storageID,
+                                    MtpObjectFormat format,
+                                    MtpObjectHandle parent) {
+    JNIEnv* env = AndroidRuntime::getJNIEnv();
+    jintArray array = (jintArray)env->CallObjectMethod(mDatabase, method_getObjectList,
+                (jint)storageID, (jint)format, (jint)parent);
+    if (!array)
+        return NULL;
+    MtpObjectHandleList* list = new MtpObjectHandleList();
+    jint* handles = env->GetIntArrayElements(array, 0);
+    jsize length = env->GetArrayLength(array);
+    for (int i = 0; i < length; i++)
+        list->push(handles[i]);
+   env->ReleaseIntArrayElements(array, handles, 0);
+   return list;
+}
+
+int MyMtpDatabase::getNumObjects(MtpStorageID storageID,
+                                MtpObjectFormat format,
+                                MtpObjectHandle parent) {
+    JNIEnv* env = AndroidRuntime::getJNIEnv();
+    return env->CallIntMethod(mDatabase, method_getNumObjects,
+                (jint)storageID, (jint)format, (jint)parent);
+}
+
+MtpResponseCode MyMtpDatabase::getObjectProperty(MtpObjectHandle handle,
+                                            MtpObjectProperty property,
+                                            MtpDataPacket& packet) {
+    int         type;
+
+    if (!getPropertyInfo(property, type))
+        return MTP_RESPONSE_INVALID_OBJECT_PROP_CODE;
+
+    JNIEnv* env = AndroidRuntime::getJNIEnv();
+    jint result = env->CallIntMethod(mDatabase, method_getObjectProperty,
+                (jint)handle, (jint)property, mLongBuffer, mStringBuffer);
+    if (result != MTP_RESPONSE_OK)
+        return result;
+
+    jlong* longValues = env->GetLongArrayElements(mLongBuffer, 0);
+    jlong longValue = longValues[0];
+    env->ReleaseLongArrayElements(mLongBuffer, longValues, 0);
+
+    switch (type) {
+        case MTP_TYPE_INT8:
+            packet.putInt8(longValue);
+            break;
+        case MTP_TYPE_UINT8:
+            packet.putUInt8(longValue);
+            break;
+        case MTP_TYPE_INT16:
+            packet.putInt16(longValue);
+            break;
+        case MTP_TYPE_UINT16:
+            packet.putUInt16(longValue);
+            break;
+        case MTP_TYPE_INT32:
+            packet.putInt32(longValue);
+            break;
+        case MTP_TYPE_UINT32:
+            packet.putUInt32(longValue);
+            break;
+        case MTP_TYPE_INT64:
+            packet.putInt64(longValue);
+            break;
+        case MTP_TYPE_UINT64:
+            packet.putUInt64(longValue);
+            break;
+        case MTP_TYPE_STR:
+        {
+            jchar* str = env->GetCharArrayElements(mStringBuffer, 0);
+            packet.putString(str);
+            env->ReleaseCharArrayElements(mStringBuffer, str, 0);
+            break;
+         }
+        default:
+            LOGE("unsupported object type\n");
+            return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
+    }
+    return MTP_RESPONSE_OK;
+}
+
+MtpResponseCode MyMtpDatabase::getObjectInfo(MtpObjectHandle handle,
+                                            MtpDataPacket& packet) {
+    char    date[20];
+
+    JNIEnv* env = AndroidRuntime::getJNIEnv();
+    jboolean result = env->CallBooleanMethod(mDatabase, method_getObjectInfo,
+                (jint)handle, mIntBuffer, mStringBuffer, mLongBuffer);
+    if (!result)
+        return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
+
+    jint* intValues = env->GetIntArrayElements(mIntBuffer, 0);
+    MtpStorageID storageID = intValues[0];
+    MtpObjectFormat format = intValues[1];
+    MtpObjectHandle parent = intValues[2];
+    env->ReleaseIntArrayElements(mIntBuffer, intValues, 0);
+
+    jlong* longValues = env->GetLongArrayElements(mLongBuffer, 0);
+    uint64_t size = longValues[0];
+    uint64_t modified = longValues[1];
+    env->ReleaseLongArrayElements(mLongBuffer, longValues, 0);
+
+    int associationType = (format == MTP_FORMAT_ASSOCIATION ?
+                            MTP_ASSOCIATION_TYPE_GENERIC_FOLDER :
+                            MTP_ASSOCIATION_TYPE_UNDEFINED);
+
+    packet.putUInt32(storageID);
+    packet.putUInt16(format);
+    packet.putUInt16(0);   // protection status
+    packet.putUInt32((size > 0xFFFFFFFFLL ? 0xFFFFFFFF : size));
+    packet.putUInt16(0);   // thumb format
+    packet.putUInt32(0);   // thumb compressed size
+    packet.putUInt32(0);   // thumb pix width
+    packet.putUInt32(0);   // thumb pix height
+    packet.putUInt32(0);   // image pix width
+    packet.putUInt32(0);   // image pix height
+    packet.putUInt32(0);   // image bit depth
+    packet.putUInt32(parent);
+    packet.putUInt16(associationType);
+    packet.putUInt32(0);   // association desc
+    packet.putUInt32(0);   // sequence number
+
+    jchar* str = env->GetCharArrayElements(mStringBuffer, 0);
+    packet.putString(str);   // file name
+    env->ReleaseCharArrayElements(mStringBuffer, str, 0);
+
+    packet.putEmptyString();
+    formatDateTime(modified, date, sizeof(date));
+    packet.putString(date);   // date modified
+    packet.putEmptyString();   // keywords
+
+    return MTP_RESPONSE_OK;
+}
+
+MtpResponseCode MyMtpDatabase::getObjectFilePath(MtpObjectHandle handle,
+                                            MtpString& filePath,
+                                            int64_t& fileLength) {
+    JNIEnv* env = AndroidRuntime::getJNIEnv();
+    jint result = env->CallIntMethod(mDatabase, method_getObjectFilePath,
+                (jint)handle, mStringBuffer, mLongBuffer);
+    if (result != MTP_RESPONSE_OK)
+        return result;
+
+    jchar* str = env->GetCharArrayElements(mStringBuffer, 0);
+    filePath.setTo(str, strlen16(str));
+    env->ReleaseCharArrayElements(mStringBuffer, str, 0);
+
+    jlong* longValues = env->GetLongArrayElements(mLongBuffer, 0);
+    fileLength = longValues[0];
+    env->ReleaseLongArrayElements(mLongBuffer, longValues, 0);
+    
+    return result;
+}
+
+MtpResponseCode MyMtpDatabase::deleteFile(MtpObjectHandle handle) {
+    JNIEnv* env = AndroidRuntime::getJNIEnv();
+    return env->CallIntMethod(mDatabase, method_deleteFile, (jint)handle);
+}
+
+struct PropertyTableEntry {
+    MtpObjectProperty   property;
+    int                 type;
+};
+
+static const PropertyTableEntry   kPropertyTable[] = {
+    {   MTP_PROPERTY_PARENT_OBJECT,     MTP_TYPE_UINT32 },
+    {   MTP_PROPERTY_STORAGE_ID,        MTP_TYPE_UINT32 },
+    {   MTP_PROPERTY_OBJECT_FORMAT,     MTP_TYPE_UINT32 },
+    {   MTP_PROPERTY_OBJECT_FILE_NAME,  MTP_TYPE_STR    },
+    {   MTP_PROPERTY_OBJECT_SIZE,       MTP_TYPE_UINT64 },
+    {   MTP_PROPERTY_DATE_MODIFIED,     MTP_TYPE_STR    },
+};
+
+bool MyMtpDatabase::getPropertyInfo(MtpObjectProperty property, int& type) {
+    int count = sizeof(kPropertyTable) / sizeof(kPropertyTable[0]);
+    const PropertyTableEntry* entry = kPropertyTable;
+    for (int i = 0; i < count; i++, entry++) {
+        if (entry->property == property) {
+            type = entry->type;
+            return true;
+        }
+    }
+    return false;
+}
+
+MtpObjectHandleList* MyMtpDatabase::getObjectReferences(MtpObjectHandle handle) {
+    JNIEnv* env = AndroidRuntime::getJNIEnv();
+    jintArray array = (jintArray)env->CallObjectMethod(mDatabase, method_getObjectReferences,
+                (jint)handle);
+    if (!array)
+        return NULL;
+    MtpObjectHandleList* list = new MtpObjectHandleList();
+    jint* handles = env->GetIntArrayElements(array, 0);
+    jsize length = env->GetArrayLength(array);
+    for (int i = 0; i < length; i++)
+        list->push(handles[i]);
+   env->ReleaseIntArrayElements(array, handles, 0);
+   return list;
+}
+
+MtpResponseCode MyMtpDatabase::setObjectReferences(MtpObjectHandle handle, MtpObjectHandleList* references) {
+    JNIEnv* env = AndroidRuntime::getJNIEnv();
+    int count = references->size();
+    jintArray array = env->NewIntArray(count);
+    if (!array) {
+        LOGE("out of memory in setObjectReferences");
+        return false;
+    }
+    jint* handles = env->GetIntArrayElements(array, 0);
+     for (int i = 0; i < count; i++)
+        handles[i] = (*references)[i];
+    env->ReleaseIntArrayElements(array, handles, 0);
+    return env->CallIntMethod(mDatabase, method_setObjectReferences,
+                (jint)handle, array);
+}
+
+// ----------------------------------------------------------------------------
+
+static void checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName) {
+    if (env->ExceptionCheck()) {
+        LOGE("An exception was thrown by callback '%s'.", methodName);
+        LOGE_EX(env);
+        env->ExceptionClear();
+    }
+}
+
+#endif // HAVE_ANDROID_OS
+
+// ----------------------------------------------------------------------------
+
+static void
+android_media_MtpDatabase_setup(JNIEnv *env, jobject thiz)
+{
+#ifdef HAVE_ANDROID_OS
+    LOGD("setup\n");
+    MyMtpDatabase* database = new MyMtpDatabase(env, thiz);
+    env->SetIntField(thiz, field_context, (int)database);
+    checkAndClearExceptionFromCallback(env, __FUNCTION__);
+#endif
+}
+
+static void
+android_media_MtpDatabase_finalize(JNIEnv *env, jobject thiz)
+{
+#ifdef HAVE_ANDROID_OS
+    LOGD("finalize\n");
+    MyMtpDatabase* database = (MyMtpDatabase *)env->GetIntField(thiz, field_context);
+    database->cleanup(env);
+    delete database;
+    env->SetIntField(thiz, field_context, 0);
+    checkAndClearExceptionFromCallback(env, __FUNCTION__);
+#endif
+}
+
+// ----------------------------------------------------------------------------
+
+static JNINativeMethod gMethods[] = {
+    {"native_setup",            "()V",  (void *)android_media_MtpDatabase_setup},
+    {"native_finalize",         "()V",  (void *)android_media_MtpDatabase_finalize},
+};
+
+static const char* const kClassPathName = "android/media/MtpDatabase";
+
+int register_android_media_MtpDatabase(JNIEnv *env)
+{
+    jclass clazz;
+
+    LOGD("register_android_media_MtpDatabase\n");
+
+    clazz = env->FindClass("android/media/MtpDatabase");
+    if (clazz == NULL) {
+        LOGE("Can't find android/media/MtpDatabase");
+        return -1;
+    }
+    method_beginSendObject = env->GetMethodID(clazz, "beginSendObject", "(Ljava/lang/String;IIIJJ)I");
+    if (method_beginSendObject == NULL) {
+        LOGE("Can't find beginSendObject");
+        return -1;
+    }
+    method_endSendObject = env->GetMethodID(clazz, "endSendObject", "(Ljava/lang/String;IIZ)V");
+    if (method_endSendObject == NULL) {
+        LOGE("Can't find endSendObject");
+        return -1;
+    }
+    method_getObjectList = env->GetMethodID(clazz, "getObjectList", "(III)[I");
+    if (method_getObjectList == NULL) {
+        LOGE("Can't find getObjectList");
+        return -1;
+    }
+    method_getNumObjects = env->GetMethodID(clazz, "getNumObjects", "(III)I");
+    if (method_getNumObjects == NULL) {
+        LOGE("Can't find getNumObjects");
+        return -1;
+    }
+    method_getObjectProperty = env->GetMethodID(clazz, "getObjectProperty", "(II[J[C)I");
+    if (method_getObjectProperty == NULL) {
+        LOGE("Can't find getObjectProperty");
+        return -1;
+    }
+    method_getObjectInfo = env->GetMethodID(clazz, "getObjectInfo", "(I[I[C[J)Z");
+    if (method_getObjectInfo == NULL) {
+        LOGE("Can't find getObjectInfo");
+        return -1;
+    }
+    method_getObjectFilePath = env->GetMethodID(clazz, "getObjectFilePath", "(I[C[J)I");
+    if (method_getObjectFilePath == NULL) {
+        LOGE("Can't find getObjectFilePath");
+        return -1;
+    }
+    method_deleteFile = env->GetMethodID(clazz, "deleteFile", "(I)I");
+    if (method_deleteFile == NULL) {
+        LOGE("Can't find deleteFile");
+        return -1;
+    }
+    method_getObjectReferences = env->GetMethodID(clazz, "getObjectReferences", "(I)[I");
+    if (method_getObjectReferences == NULL) {
+        LOGE("Can't find getObjectReferences");
+        return -1;
+    }
+    method_setObjectReferences = env->GetMethodID(clazz, "setObjectReferences", "(I[I)I");
+    if (method_setObjectReferences == NULL) {
+        LOGE("Can't find setObjectReferences");
+        return -1;
+    }
+    field_context = env->GetFieldID(clazz, "mNativeContext", "I");
+    if (field_context == NULL) {
+        LOGE("Can't find MtpDatabase.mNativeContext");
+        return -1;
+    }
+
+    return AndroidRuntime::registerNativeMethods(env,
+                "android/media/MtpDatabase", gMethods, NELEM(gMethods));
+}
diff --git a/media/jni/android_media_MtpServer.cpp b/media/jni/android_media_MtpServer.cpp
new file mode 100644
index 0000000..1ef2c58
--- /dev/null
+++ b/media/jni/android_media_MtpServer.cpp
@@ -0,0 +1,241 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "MtpServerJNI"
+#include "utils/Log.h"
+
+#include <stdio.h>
+#include <assert.h>
+#include <limits.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <utils/threads.h>
+
+#include "jni.h"
+#include "JNIHelp.h"
+#include "android_runtime/AndroidRuntime.h"
+#include "private/android_filesystem_config.h"
+
+#include "MtpServer.h"
+
+using namespace android;
+
+// ----------------------------------------------------------------------------
+
+static jfieldID field_context;
+static Mutex    sMutex;
+
+// in android_media_MtpDatabase.cpp
+extern MtpDatabase* getMtpDatabase(JNIEnv *env, jobject database);
+
+// ----------------------------------------------------------------------------
+
+#ifdef HAVE_ANDROID_OS
+
+static bool ExceptionCheck(void* env)
+{
+    return ((JNIEnv *)env)->ExceptionCheck();
+}
+
+class MtpThread : public Thread {
+private:
+    MtpDatabase*    mDatabase;
+    MtpServer*      mServer;
+    String8         mStoragePath;
+    bool            mDone;
+    jobject         mJavaServer;
+
+public:
+    MtpThread(MtpDatabase* database, const char* storagePath, jobject javaServer)
+        : mDatabase(database),
+            mServer(NULL),
+            mStoragePath(storagePath),
+            mDone(false),
+            mJavaServer(javaServer)
+    {
+    }
+
+    virtual bool threadLoop() {
+        while (1) {
+            int fd = open("/dev/mtp_usb", O_RDWR);
+            printf("open returned %d\n", fd);
+            if (fd < 0) {
+                LOGE("could not open MTP driver\n");
+                break;
+            }
+
+            sMutex.lock();
+            mServer = new MtpServer(fd, mDatabase, AID_SDCARD_RW, 0664, 0775);
+            mServer->addStorage(mStoragePath);
+            sMutex.unlock();
+
+            LOGD("MtpThread mServer->run");
+            mServer->run();
+            close(fd);
+
+            sMutex.lock();
+            delete mServer;
+            mServer = NULL;
+            if (mDone)
+                goto done;
+            sMutex.unlock();
+            // wait a bit before retrying
+            sleep(1);
+        }
+
+        sMutex.lock();
+done:
+        JNIEnv* env = AndroidRuntime::getJNIEnv();
+        env->SetIntField(mJavaServer, field_context, 0);
+        env->DeleteGlobalRef(mJavaServer);
+        sMutex.unlock();
+
+        LOGD("threadLoop returning");
+        return false;
+    }
+
+    void setDone() {
+        LOGD("setDone");
+        mDone = true; 
+    }
+
+    void sendObjectAdded(MtpObjectHandle handle) {
+        sMutex.lock();
+        if (mServer)
+            mServer->sendObjectAdded(handle);
+        else
+            LOGE("sendObjectAdded called while disconnected\n");
+        sMutex.unlock();
+    }
+
+    void sendObjectRemoved(MtpObjectHandle handle) {
+        sMutex.lock();
+        if (mServer)
+            mServer->sendObjectRemoved(handle);
+        else
+            LOGE("sendObjectRemoved called while disconnected\n");
+        sMutex.unlock();
+    }
+};
+
+#endif // HAVE_ANDROID_OS
+
+static void
+android_media_MtpServer_setup(JNIEnv *env, jobject thiz, jobject javaDatabase, jstring storagePath)
+{
+#ifdef HAVE_ANDROID_OS
+    LOGD("setup\n");
+
+    MtpDatabase* database = getMtpDatabase(env, javaDatabase);
+    const char *storagePathStr = env->GetStringUTFChars(storagePath, NULL);
+
+    MtpThread* thread = new MtpThread(database, storagePathStr, env->NewGlobalRef(thiz));
+    env->SetIntField(thiz, field_context, (int)thread);
+
+    env->ReleaseStringUTFChars(storagePath, storagePathStr);
+#endif
+}
+
+static void
+android_media_MtpServer_finalize(JNIEnv *env, jobject thiz)
+{
+    LOGD("finalize\n");
+}
+
+
+static void
+android_media_MtpServer_start(JNIEnv *env, jobject thiz)
+{
+#ifdef HAVE_ANDROID_OS
+    LOGD("start\n");
+    MtpThread *thread = (MtpThread *)env->GetIntField(thiz, field_context);
+    thread->run("MtpThread");
+#endif // HAVE_ANDROID_OS
+}
+
+static void
+android_media_MtpServer_stop(JNIEnv *env, jobject thiz)
+{
+#ifdef HAVE_ANDROID_OS
+    LOGD("stop\n");
+    sMutex.lock();
+    MtpThread *thread = (MtpThread *)env->GetIntField(thiz, field_context);
+    if (thread)
+        thread->setDone();
+    sMutex.unlock();
+#endif
+}
+
+static void
+android_media_MtpServer_send_object_added(JNIEnv *env, jobject thiz, jint handle)
+{
+#ifdef HAVE_ANDROID_OS
+    LOGD("send_object_added %d\n", handle);
+    MtpThread *thread = (MtpThread *)env->GetIntField(thiz, field_context);
+    if (thread)
+        thread->sendObjectAdded(handle);
+    else
+        LOGE("sendObjectAdded called while disconnected\n");
+#endif
+}
+
+static void
+android_media_MtpServer_send_object_removed(JNIEnv *env, jobject thiz, jint handle)
+{
+#ifdef HAVE_ANDROID_OS
+    LOGD("send_object_removed %d\n", handle);
+    MtpThread *thread = (MtpThread *)env->GetIntField(thiz, field_context);
+    if (thread)
+        thread->sendObjectRemoved(handle);
+    else
+        LOGE("sendObjectRemoved called while disconnected\n");
+#endif
+}
+
+// ----------------------------------------------------------------------------
+
+static JNINativeMethod gMethods[] = {
+    {"native_setup",                "(Landroid/media/MtpDatabase;Ljava/lang/String;)V",
+                                            (void *)android_media_MtpServer_setup},
+    {"native_finalize",             "()V",  (void *)android_media_MtpServer_finalize},
+    {"native_start",                "()V",  (void *)android_media_MtpServer_start},
+    {"native_stop",                 "()V",  (void *)android_media_MtpServer_stop},
+    {"native_send_object_added",    "(I)V", (void *)android_media_MtpServer_send_object_added},
+    {"native_send_object_removed",  "(I)V", (void *)android_media_MtpServer_send_object_removed},
+};
+
+static const char* const kClassPathName = "android/media/MtpServer";
+
+int register_android_media_MtpServer(JNIEnv *env)
+{
+    jclass clazz;
+
+    LOGD("register_android_media_MtpServer\n");
+
+    clazz = env->FindClass("android/media/MtpServer");
+    if (clazz == NULL) {
+        LOGE("Can't find android/media/MtpServer");
+        return -1;
+    }
+    field_context = env->GetFieldID(clazz, "mNativeContext", "I");
+    if (field_context == NULL) {
+        LOGE("Can't find MtpServer.mNativeContext");
+        return -1;
+    }
+
+    return AndroidRuntime::registerNativeMethods(env,
+                "android/media/MtpServer", gMethods, NELEM(gMethods));
+}
diff --git a/media/libeffects/visualizer/Android.mk b/media/libeffects/visualizer/Android.mk
index 48b45ff..e6ff654 100644
--- a/media/libeffects/visualizer/Android.mk
+++ b/media/libeffects/visualizer/Android.mk
@@ -27,4 +27,4 @@
 
 LOCAL_PRELINK_MODULE := false
 
-include $(BUILD_SHARED_LIBRARY)
\ No newline at end of file
+include $(BUILD_SHARED_LIBRARY)
diff --git a/media/libmedia/AudioSystem.cpp b/media/libmedia/AudioSystem.cpp
index 7e3b743..9c2a8ba 100644
--- a/media/libmedia/AudioSystem.cpp
+++ b/media/libmedia/AudioSystem.cpp
@@ -763,7 +763,8 @@
     if ((popCount(device) == 1 ) &&
         (device & (AudioSystem::DEVICE_OUT_BLUETOOTH_SCO |
                    AudioSystem::DEVICE_OUT_BLUETOOTH_SCO_HEADSET |
-                   AudioSystem::DEVICE_OUT_BLUETOOTH_SCO_CARKIT))) {
+                   AudioSystem::DEVICE_OUT_BLUETOOTH_SCO_CARKIT |
+                   AudioSystem::DEVICE_IN_BLUETOOTH_SCO_HEADSET))) {
         return true;
     } else {
         return false;
diff --git a/media/libmedia/MediaScanner.cpp b/media/libmedia/MediaScanner.cpp
index 6f581d3..ba98f04 100644
--- a/media/libmedia/MediaScanner.cpp
+++ b/media/libmedia/MediaScanner.cpp
@@ -81,13 +81,13 @@
 }
 
 static bool fileMatchesExtension(const char* path, const char* extensions) {
-    char* extension = strrchr(path, '.');
+    const char* extension = strrchr(path, '.');
     if (!extension) return false;
     ++extension;    // skip the dot
     if (extension[0] == 0) return false;
 
     while (extensions[0]) {
-        char* comma = strchr(extensions, ',');
+        const char* comma = strchr(extensions, ',');
         size_t length = (comma ? comma - extensions : strlen(extensions));
         if (length == strlen(extension) && strncasecmp(extension, extensions, length) == 0) return true;
         extensions += length;
@@ -133,6 +133,13 @@
             continue;
         }
 
+        int nameLength = strlen(name);
+        if (nameLength + 1 > pathRemaining) {
+            // path too long!
+            continue;
+        }
+        strcpy(fileSpot, name);
+
         int type = entry->d_type;
         if (type == DT_UNKNOWN) {
             // If the type is unknown, stat() the file instead.
@@ -150,16 +157,7 @@
             }
         }
         if (type == DT_REG || type == DT_DIR) {
-            int nameLength = strlen(name);
-            bool isDirectory = (type == DT_DIR);
-
-            if (nameLength > pathRemaining || (isDirectory && nameLength + 1 > pathRemaining)) {
-                // path too long!
-                continue;
-            }
-
-            strcpy(fileSpot, name);
-            if (isDirectory) {
+            if (type == DT_DIR) {
                 // ignore directories with a name that starts with '.'
                 // for example, the Mac ".Trashes" directory
                 if (name[0] == '.') continue;
diff --git a/media/libmediaplayerservice/StagefrightRecorder.cpp b/media/libmediaplayerservice/StagefrightRecorder.cpp
index f6f89c7..805f86b 100644
--- a/media/libmediaplayerservice/StagefrightRecorder.cpp
+++ b/media/libmediaplayerservice/StagefrightRecorder.cpp
@@ -24,6 +24,7 @@
 #include <media/stagefright/AudioSource.h>
 #include <media/stagefright/AMRWriter.h>
 #include <media/stagefright/CameraSource.h>
+#include <media/stagefright/CameraSourceTimeLapse.h>
 #include <media/stagefright/MPEG4Writer.h>
 #include <media/stagefright/MediaDebug.h>
 #include <media/stagefright/MediaDefs.h>
@@ -474,6 +475,45 @@
     return OK;
 }
 
+status_t StagefrightRecorder::setParamTimeLapseEnable(int32_t timeLapseEnable) {
+    LOGV("setParamTimeLapseEnable: %d", timeLapseEnable);
+
+    if(timeLapseEnable == 0) {
+        mCaptureTimeLapse = false;
+    } else if (timeLapseEnable == 1) {
+        mCaptureTimeLapse = true;
+    } else {
+        return BAD_VALUE;
+    }
+    return OK;
+}
+
+status_t StagefrightRecorder::setParamUseStillCameraForTimeLapse(int32_t useStillCamera) {
+    LOGV("setParamUseStillCameraForTimeLapse: %d", useStillCamera);
+
+    if(useStillCamera == 0) {
+        mUseStillCameraForTimeLapse= false;
+    } else if (useStillCamera == 1) {
+        mUseStillCameraForTimeLapse= true;
+    } else {
+        return BAD_VALUE;
+    }
+    return OK;
+}
+
+status_t StagefrightRecorder::setParamTimeBetweenTimeLapseFrameCapture(int64_t timeUs) {
+    LOGV("setParamTimeBetweenTimeLapseFrameCapture: %lld us", timeUs);
+
+    // Not allowing time more than a day
+    if (timeUs <= 0 || timeUs > 86400*1E6) {
+        LOGE("Time between time lapse frame capture (%lld) is out of range [0, 1 Day]", timeUs);
+        return BAD_VALUE;
+    }
+
+    mTimeBetweenTimeLapseFrameCaptureUs = timeUs;
+    return OK;
+}
+
 status_t StagefrightRecorder::setParameter(
         const String8 &key, const String8 &value) {
     LOGV("setParameter: key (%s) => value (%s)", key.string(), value.string());
@@ -557,6 +597,22 @@
         if (safe_strtoi32(value.string(), &timeScale)) {
             return setParamVideoTimeScale(timeScale);
         }
+    } else if (key == "time-lapse-enable") {
+        int32_t timeLapseEnable;
+        if (safe_strtoi32(value.string(), &timeLapseEnable)) {
+            return setParamTimeLapseEnable(timeLapseEnable);
+        }
+    } else if (key == "use-still-camera-for-time-lapse") {
+        int32_t useStillCamera;
+        if (safe_strtoi32(value.string(), &useStillCamera)) {
+            return setParamUseStillCameraForTimeLapse(useStillCamera);
+        }
+    } else if (key == "time-between-time-lapse-frame-capture") {
+        int64_t timeBetweenTimeLapseFrameCaptureMs;
+        if (safe_strtoi64(value.string(), &timeBetweenTimeLapseFrameCaptureMs)) {
+            return setParamTimeBetweenTimeLapseFrameCapture(
+                    1000LL * timeBetweenTimeLapseFrameCaptureMs);
+        }
     } else {
         LOGE("setParameter: failed to find key %s", key.string());
     }
@@ -850,10 +906,14 @@
 }
 
 status_t StagefrightRecorder::setupCameraSource() {
-    clipVideoBitRate();
-    clipVideoFrameRate();
-    clipVideoFrameWidth();
-    clipVideoFrameHeight();
+    if(!mCaptureTimeLapse) {
+        // Dont clip for time lapse capture as encoder will have enough
+        // time to encode because of slow capture rate of time lapse.
+        clipVideoBitRate();
+        clipVideoFrameRate();
+        clipVideoFrameWidth();
+        clipVideoFrameHeight();
+    }
 
     int64_t token = IPCThreadState::self()->clearCallingIdentity();
     if (mCamera == 0) {
@@ -868,7 +928,13 @@
 
     // Set the actual video recording frame size
     CameraParameters params(mCamera->getParameters());
-    params.setPreviewSize(mVideoWidth, mVideoHeight);
+
+    // dont change the preview size for time lapse as mVideoWidth, mVideoHeight
+    // may correspond to HD resolution not supported by video camera.
+    if (!mCaptureTimeLapse) {
+        params.setPreviewSize(mVideoWidth, mVideoHeight);
+    }
+
     params.setPreviewFrameRate(mFrameRate);
     String8 s = params.flatten();
     if (OK != mCamera->setParameters(s)) {
@@ -881,8 +947,9 @@
     // Check on video frame size
     int frameWidth = 0, frameHeight = 0;
     newCameraParams.getPreviewSize(&frameWidth, &frameHeight);
-    if (frameWidth  < 0 || frameWidth  != mVideoWidth ||
-        frameHeight < 0 || frameHeight != mVideoHeight) {
+    if (!mCaptureTimeLapse &&
+        (frameWidth  < 0 || frameWidth  != mVideoWidth ||
+        frameHeight < 0 || frameHeight != mVideoHeight)) {
         LOGE("Failed to set the video frame size to %dx%d",
                 mVideoWidth, mVideoHeight);
         IPCThreadState::self()->restoreCallingIdentity(token);
@@ -926,7 +993,10 @@
     status_t err = setupCameraSource();
     if (err != OK) return err;
 
-    sp<CameraSource> cameraSource = CameraSource::CreateFromCamera(mCamera);
+    sp<CameraSource> cameraSource = (mCaptureTimeLapse) ?
+        CameraSourceTimeLapse::CreateFromCamera(mCamera, mUseStillCameraForTimeLapse,
+                mTimeBetweenTimeLapseFrameCaptureUs, mVideoWidth, mVideoHeight, mFrameRate):
+        CameraSource::CreateFromCamera(mCamera);
     CHECK(cameraSource != NULL);
 
     sp<MetaData> enc_meta = new MetaData;
@@ -977,9 +1047,12 @@
     OMXClient client;
     CHECK_EQ(client.connect(), OK);
 
+    // Use software codec for time lapse
+    uint32_t encoder_flags = (mCaptureTimeLapse) ? OMXCodec::kPreferSoftwareCodecs : 0;
     sp<MediaSource> encoder = OMXCodec::Create(
             client.interface(), enc_meta,
-            true /* createEncoder */, cameraSource);
+            true /* createEncoder */, cameraSource,
+            NULL, encoder_flags);
     if (encoder == NULL) {
         return UNKNOWN_ERROR;
     }
@@ -1016,7 +1089,7 @@
     sp<MediaWriter> writer = new MPEG4Writer(dup(mOutputFd));
 
     // Add audio source first if it exists
-    if (mAudioSource != AUDIO_SOURCE_LIST_END) {
+    if (!mCaptureTimeLapse && (mAudioSource != AUDIO_SOURCE_LIST_END)) {
         err = setupAudioEncoder(writer);
         if (err != OK) return err;
         totalBitRate += mAudioBitRate;
@@ -1126,6 +1199,9 @@
     mMaxFileDurationUs = 0;
     mMaxFileSizeBytes = 0;
     mTrackEveryTimeDurationUs = 0;
+    mCaptureTimeLapse = false;
+    mUseStillCameraForTimeLapse = true;
+    mTimeBetweenTimeLapseFrameCaptureUs = -1;
     mEncoderProfiles = MediaProfiles::getInstance();
 
     mOutputFd = -1;
diff --git a/media/libmediaplayerservice/StagefrightRecorder.h b/media/libmediaplayerservice/StagefrightRecorder.h
index 216f6bc..a8be27d 100644
--- a/media/libmediaplayerservice/StagefrightRecorder.h
+++ b/media/libmediaplayerservice/StagefrightRecorder.h
@@ -92,6 +92,10 @@
     int64_t mMaxFileDurationUs;
     int64_t mTrackEveryTimeDurationUs;
 
+    bool mCaptureTimeLapse;
+    bool mUseStillCameraForTimeLapse;
+    int64_t mTimeBetweenTimeLapseFrameCaptureUs;
+
     String8 mParams;
     int mOutputFd;
     int32_t mFlags;
@@ -113,6 +117,9 @@
     status_t setParamAudioNumberOfChannels(int32_t channles);
     status_t setParamAudioSamplingRate(int32_t sampleRate);
     status_t setParamAudioTimeScale(int32_t timeScale);
+    status_t setParamTimeLapseEnable(int32_t timeLapseEnable);
+    status_t setParamUseStillCameraForTimeLapse(int32_t useStillCamera);
+    status_t setParamTimeBetweenTimeLapseFrameCapture(int64_t timeUs);
     status_t setParamVideoEncodingBitRate(int32_t bitRate);
     status_t setParamVideoIFramesInterval(int32_t seconds);
     status_t setParamVideoEncoderProfile(int32_t profile);
diff --git a/media/libstagefright/Android.mk b/media/libstagefright/Android.mk
index fb85287..77a1476 100644
--- a/media/libstagefright/Android.mk
+++ b/media/libstagefright/Android.mk
@@ -10,6 +10,7 @@
         AudioSource.cpp                   \
         AwesomePlayer.cpp                 \
         CameraSource.cpp                  \
+        CameraSourceTimeLapse.cpp                  \
         DataSource.cpp                    \
         ESDS.cpp                          \
         FileSource.cpp                    \
@@ -57,6 +58,7 @@
         libsonivox        \
         libvorbisidec     \
         libsurfaceflinger_client \
+        libstagefright_yuv \
         libcamera_client
 
 LOCAL_STATIC_LIBRARIES := \
diff --git a/media/libstagefright/CameraSource.cpp b/media/libstagefright/CameraSource.cpp
index 9c48daf..5e7dd5c 100644
--- a/media/libstagefright/CameraSource.cpp
+++ b/media/libstagefright/CameraSource.cpp
@@ -65,6 +65,11 @@
 void CameraSourceListener::postData(int32_t msgType, const sp<IMemory> &dataPtr) {
     LOGV("postData(%d, ptr:%p, size:%d)",
          msgType, dataPtr->pointer(), dataPtr->size());
+
+    sp<CameraSource> source = mSource.promote();
+    if (source.get() != NULL) {
+        source->dataCallback(msgType, dataPtr);
+    }
 }
 
 void CameraSourceListener::postDataTimestamp(
@@ -118,15 +123,15 @@
 
 CameraSource::CameraSource(const sp<Camera> &camera)
     : mCamera(camera),
-      mFirstFrameTimeUs(0),
-      mLastFrameTimestampUs(0),
       mNumFramesReceived(0),
+      mLastFrameTimestampUs(0),
+      mStarted(false),
+      mFirstFrameTimeUs(0),
       mNumFramesEncoded(0),
       mNumFramesDropped(0),
       mNumGlitches(0),
       mGlitchDurationThresholdUs(200000),
-      mCollectStats(false),
-      mStarted(false) {
+      mCollectStats(false) {
 
     int64_t token = IPCThreadState::self()->clearCallingIdentity();
     String8 s = mCamera->getParameters();
@@ -161,7 +166,6 @@
     mMeta->setInt32(kKeyHeight, height);
     mMeta->setInt32(kKeyStride, stride);
     mMeta->setInt32(kKeySliceHeight, sliceHeight);
-
 }
 
 CameraSource::~CameraSource() {
@@ -170,6 +174,10 @@
     }
 }
 
+void CameraSource::startCameraRecording() {
+    CHECK_EQ(OK, mCamera->startRecording());
+}
+
 status_t CameraSource::start(MetaData *meta) {
     CHECK(!mStarted);
 
@@ -187,13 +195,17 @@
 
     int64_t token = IPCThreadState::self()->clearCallingIdentity();
     mCamera->setListener(new CameraSourceListener(this));
-    CHECK_EQ(OK, mCamera->startRecording());
+    startCameraRecording();
     IPCThreadState::self()->restoreCallingIdentity(token);
 
     mStarted = true;
     return OK;
 }
 
+void CameraSource::stopCameraRecording() {
+    mCamera->stopRecording();
+}
+
 status_t CameraSource::stop() {
     LOGV("stop");
     Mutex::Autolock autoLock(mLock);
@@ -202,7 +214,7 @@
 
     int64_t token = IPCThreadState::self()->clearCallingIdentity();
     mCamera->setListener(NULL);
-    mCamera->stopRecording();
+    stopCameraRecording();
     releaseQueuedFrames();
     while (!mFramesBeingEncoded.empty()) {
         LOGI("Waiting for outstanding frames being encoded: %d",
@@ -222,11 +234,15 @@
     return OK;
 }
 
+void CameraSource::releaseRecordingFrame(const sp<IMemory>& frame) {
+    mCamera->releaseRecordingFrame(frame);
+}
+
 void CameraSource::releaseQueuedFrames() {
     List<sp<IMemory> >::iterator it;
     while (!mFramesReceived.empty()) {
         it = mFramesReceived.begin();
-        mCamera->releaseRecordingFrame(*it);
+        releaseRecordingFrame(*it);
         mFramesReceived.erase(it);
         ++mNumFramesDropped;
     }
@@ -238,7 +254,7 @@
 
 void CameraSource::releaseOneRecordingFrame(const sp<IMemory>& frame) {
     int64_t token = IPCThreadState::self()->clearCallingIdentity();
-    mCamera->releaseRecordingFrame(frame);
+    releaseRecordingFrame(frame);
     IPCThreadState::self()->restoreCallingIdentity(token);
 }
 
@@ -247,7 +263,6 @@
     for (List<sp<IMemory> >::iterator it = mFramesBeingEncoded.begin();
          it != mFramesBeingEncoded.end(); ++it) {
         if ((*it)->pointer() ==  buffer->data()) {
-
             releaseOneRecordingFrame((*it));
             mFramesBeingEncoded.erase(it);
             ++mNumFramesEncoded;
@@ -338,6 +353,13 @@
         ++mNumGlitches;
     }
 
+    // May need to skip frame or modify timestamp. Currently implemented
+    // by the subclass CameraSourceTimeLapse.
+    if(skipCurrentFrame(timestampUs)) {
+        releaseOneRecordingFrame(data);
+        return;
+    }
+
     mLastFrameTimestampUs = timestampUs;
     if (mNumFramesReceived == 0) {
         mFirstFrameTimeUs = timestampUs;
diff --git a/media/libstagefright/CameraSourceTimeLapse.cpp b/media/libstagefright/CameraSourceTimeLapse.cpp
new file mode 100644
index 0000000..23d8f56
--- /dev/null
+++ b/media/libstagefright/CameraSourceTimeLapse.cpp
@@ -0,0 +1,349 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "CameraSourceTimeLapse"
+
+#include <binder/IPCThreadState.h>
+#include <binder/MemoryBase.h>
+#include <binder/MemoryHeapBase.h>
+#include <media/stagefright/CameraSource.h>
+#include <media/stagefright/CameraSourceTimeLapse.h>
+#include <media/stagefright/MediaDebug.h>
+#include <media/stagefright/MetaData.h>
+#include <media/stagefright/YUVImage.h>
+#include <media/stagefright/YUVCanvas.h>
+#include <camera/Camera.h>
+#include <camera/CameraParameters.h>
+#include <ui/Rect.h>
+#include <utils/String8.h>
+#include <utils/Vector.h>
+#include "OMX_Video.h"
+#include <limits.h>
+
+namespace android {
+
+// static
+CameraSourceTimeLapse *CameraSourceTimeLapse::Create(bool useStillCameraForTimeLapse,
+        int64_t timeBetweenTimeLapseFrameCaptureUs,
+        int32_t width, int32_t height,
+        int32_t videoFrameRate) {
+    sp<Camera> camera = Camera::connect(0);
+
+    if (camera.get() == NULL) {
+        return NULL;
+    }
+
+    return new CameraSourceTimeLapse(camera, useStillCameraForTimeLapse,
+            timeBetweenTimeLapseFrameCaptureUs, width, height, videoFrameRate);
+}
+
+// static
+CameraSourceTimeLapse *CameraSourceTimeLapse::CreateFromCamera(const sp<Camera> &camera,
+        bool useStillCameraForTimeLapse,
+        int64_t timeBetweenTimeLapseFrameCaptureUs,
+        int32_t width, int32_t height,
+        int32_t videoFrameRate) {
+    if (camera.get() == NULL) {
+        return NULL;
+    }
+
+    return new CameraSourceTimeLapse(camera, useStillCameraForTimeLapse,
+            timeBetweenTimeLapseFrameCaptureUs, width, height, videoFrameRate);
+}
+
+CameraSourceTimeLapse::CameraSourceTimeLapse(const sp<Camera> &camera,
+        bool useStillCameraForTimeLapse,
+        int64_t timeBetweenTimeLapseFrameCaptureUs,
+        int32_t width, int32_t height,
+        int32_t videoFrameRate)
+    : CameraSource(camera),
+      mUseStillCameraForTimeLapse(useStillCameraForTimeLapse),
+      mTimeBetweenTimeLapseFrameCaptureUs(timeBetweenTimeLapseFrameCaptureUs),
+      mTimeBetweenTimeLapseVideoFramesUs(1E6/videoFrameRate),
+      mLastTimeLapseFrameRealTimestampUs(0),
+      mSkipCurrentFrame(false) {
+
+    LOGV("starting time lapse mode");
+    mVideoWidth = width;
+    mVideoHeight = height;
+    if (mUseStillCameraForTimeLapse) {
+        CHECK(setPictureSizeToClosestSupported(width, height));
+        mNeedCropping = computeCropRectangleOffset();
+        mMeta->setInt32(kKeyWidth, width);
+        mMeta->setInt32(kKeyHeight, height);
+    }
+}
+
+CameraSourceTimeLapse::~CameraSourceTimeLapse() {
+}
+
+bool CameraSourceTimeLapse::setPictureSizeToClosestSupported(int32_t width, int32_t height) {
+    int64_t token = IPCThreadState::self()->clearCallingIdentity();
+    String8 s = mCamera->getParameters();
+    IPCThreadState::self()->restoreCallingIdentity(token);
+
+    CameraParameters params(s);
+    Vector<Size> supportedSizes;
+    params.getSupportedPictureSizes(supportedSizes);
+
+    int32_t minPictureSize = INT_MAX;
+    for (uint32_t i = 0; i < supportedSizes.size(); ++i) {
+        int32_t pictureWidth = supportedSizes[i].width;
+        int32_t pictureHeight = supportedSizes[i].height;
+
+        if ((pictureWidth >= width) && (pictureHeight >= height)) {
+            int32_t pictureSize = pictureWidth*pictureHeight;
+            if (pictureSize < minPictureSize) {
+                minPictureSize = pictureSize;
+                mPictureWidth = pictureWidth;
+                mPictureHeight = pictureHeight;
+            }
+        }
+    }
+    LOGV("Picture size = (%d, %d)", mPictureWidth, mPictureHeight);
+    return (minPictureSize != INT_MAX);
+}
+
+bool CameraSourceTimeLapse::computeCropRectangleOffset() {
+    if ((mPictureWidth == mVideoWidth) && (mPictureHeight == mVideoHeight)) {
+        return false;
+    }
+
+    CHECK((mPictureWidth > mVideoWidth) && (mPictureHeight > mVideoHeight));
+
+    int32_t widthDifference = mPictureWidth - mVideoWidth;
+    int32_t heightDifference = mPictureHeight - mVideoHeight;
+
+    mCropRectStartX = widthDifference/2;
+    mCropRectStartY = heightDifference/2;
+
+    LOGV("setting crop rectangle offset to (%d, %d)", mCropRectStartX, mCropRectStartY);
+
+    return true;
+}
+
+// static
+void *CameraSourceTimeLapse::ThreadTimeLapseWrapper(void *me) {
+    CameraSourceTimeLapse *source = static_cast<CameraSourceTimeLapse *>(me);
+    source->threadTimeLapseEntry();
+    return NULL;
+}
+
+void CameraSourceTimeLapse::threadTimeLapseEntry() {
+    while(mStarted) {
+        if (mCameraIdle) {
+            LOGV("threadTimeLapseEntry: taking picture");
+            CHECK_EQ(OK, mCamera->takePicture());
+            mCameraIdle = false;
+            sleep(mTimeBetweenTimeLapseFrameCaptureUs/1E6);
+        } else {
+            LOGV("threadTimeLapseEntry: camera busy with old takePicture. Sleeping a little.");
+            sleep(.01);
+        }
+    }
+}
+
+void CameraSourceTimeLapse::startCameraRecording() {
+    if (mUseStillCameraForTimeLapse) {
+        LOGV("start time lapse recording using still camera");
+
+        int64_t token = IPCThreadState::self()->clearCallingIdentity();
+        String8 s = mCamera->getParameters();
+        IPCThreadState::self()->restoreCallingIdentity(token);
+
+        CameraParameters params(s);
+        params.setPictureSize(mPictureWidth, mPictureHeight);
+        mCamera->setParameters(params.flatten());
+        mCameraIdle = true;
+
+        // create a thread which takes pictures in a loop
+        pthread_attr_t attr;
+        pthread_attr_init(&attr);
+        pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
+
+        pthread_create(&mThreadTimeLapse, &attr, ThreadTimeLapseWrapper, this);
+        pthread_attr_destroy(&attr);
+    } else {
+        LOGV("start time lapse recording using video camera");
+        CHECK_EQ(OK, mCamera->startRecording());
+    }
+}
+
+void CameraSourceTimeLapse::stopCameraRecording() {
+    if (mUseStillCameraForTimeLapse) {
+        void *dummy;
+        pthread_join(mThreadTimeLapse, &dummy);
+    } else {
+        mCamera->stopRecording();
+    }
+}
+
+void CameraSourceTimeLapse::releaseRecordingFrame(const sp<IMemory>& frame) {
+    if (!mUseStillCameraForTimeLapse) {
+        mCamera->releaseRecordingFrame(frame);
+    }
+}
+
+sp<IMemory> CameraSourceTimeLapse::createIMemoryCopy(const sp<IMemory> &source_data) {
+    size_t source_size = source_data->size();
+    void* source_pointer = source_data->pointer();
+
+    sp<MemoryHeapBase> newMemoryHeap = new MemoryHeapBase(source_size);
+    sp<MemoryBase> newMemory = new MemoryBase(newMemoryHeap, 0, source_size);
+    memcpy(newMemory->pointer(), source_pointer, source_size);
+    return newMemory;
+}
+
+// Allocates IMemory of final type MemoryBase with the given size.
+sp<IMemory> allocateIMemory(size_t size) {
+    sp<MemoryHeapBase> newMemoryHeap = new MemoryHeapBase(size);
+    sp<MemoryBase> newMemory = new MemoryBase(newMemoryHeap, 0, size);
+    return newMemory;
+}
+
+// static
+void *CameraSourceTimeLapse::ThreadStartPreviewWrapper(void *me) {
+    CameraSourceTimeLapse *source = static_cast<CameraSourceTimeLapse *>(me);
+    source->threadStartPreview();
+    return NULL;
+}
+
+void CameraSourceTimeLapse::threadStartPreview() {
+    CHECK_EQ(OK, mCamera->startPreview());
+    mCameraIdle = true;
+}
+
+void CameraSourceTimeLapse::restartPreview() {
+    // Start this in a different thread, so that the dataCallback can return
+    LOGV("restartPreview");
+    pthread_attr_t attr;
+    pthread_attr_init(&attr);
+    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
+
+    pthread_t threadPreview;
+    pthread_create(&threadPreview, &attr, ThreadStartPreviewWrapper, this);
+    pthread_attr_destroy(&attr);
+}
+
+sp<IMemory> CameraSourceTimeLapse::cropYUVImage(const sp<IMemory> &source_data) {
+    // find the YUV format
+    int32_t srcFormat;
+    CHECK(mMeta->findInt32(kKeyColorFormat, &srcFormat));
+    YUVImage::YUVFormat yuvFormat;
+    if (srcFormat == OMX_COLOR_FormatYUV420SemiPlanar) {
+        yuvFormat = YUVImage::YUV420SemiPlanar;
+    } else if (srcFormat == OMX_COLOR_FormatYUV420Planar) {
+        yuvFormat = YUVImage::YUV420Planar;
+    }
+
+    // allocate memory for cropped image and setup a canvas using it.
+    sp<IMemory> croppedImageMemory = allocateIMemory(
+            YUVImage::bufferSize(yuvFormat, mVideoWidth, mVideoHeight));
+    YUVImage yuvImageCropped(yuvFormat,
+            mVideoWidth, mVideoHeight,
+            (uint8_t *)croppedImageMemory->pointer());
+    YUVCanvas yuvCanvasCrop(yuvImageCropped);
+
+    YUVImage yuvImageSource(yuvFormat,
+            mPictureWidth, mPictureHeight,
+            (uint8_t *)source_data->pointer());
+    yuvCanvasCrop.CopyImageRect(
+            Rect(mCropRectStartX, mCropRectStartY,
+                mCropRectStartX + mVideoWidth,
+                mCropRectStartY + mVideoHeight),
+            0, 0,
+            yuvImageSource);
+
+    return croppedImageMemory;
+}
+
+void CameraSourceTimeLapse::dataCallback(int32_t msgType, const sp<IMemory> &data) {
+    if (msgType == CAMERA_MSG_COMPRESSED_IMAGE) {
+        // takePicture will complete after this callback, so restart preview.
+        restartPreview();
+        return;
+    }
+    if (msgType != CAMERA_MSG_RAW_IMAGE) {
+        return;
+    }
+
+    LOGV("dataCallback for timelapse still frame");
+    CHECK_EQ(true, mUseStillCameraForTimeLapse);
+
+    int64_t timestampUs;
+    if (mNumFramesReceived == 0) {
+        timestampUs = mStartTimeUs;
+    } else {
+        timestampUs = mLastFrameTimestampUs + mTimeBetweenTimeLapseVideoFramesUs;
+    }
+
+    if (mNeedCropping) {
+        sp<IMemory> croppedImageData = cropYUVImage(data);
+        dataCallbackTimestamp(timestampUs, msgType, croppedImageData);
+    } else {
+        sp<IMemory> dataCopy = createIMemoryCopy(data);
+        dataCallbackTimestamp(timestampUs, msgType, dataCopy);
+    }
+}
+
+bool CameraSourceTimeLapse::skipCurrentFrame(int64_t timestampUs) {
+    if (mSkipCurrentFrame) {
+        mSkipCurrentFrame = false;
+        return true;
+    } else {
+        return false;
+    }
+}
+
+bool CameraSourceTimeLapse::skipFrameAndModifyTimeStamp(int64_t *timestampUs) {
+    if (!mUseStillCameraForTimeLapse) {
+        if (mLastTimeLapseFrameRealTimestampUs == 0) {
+            // First time lapse frame. Initialize mLastTimeLapseFrameRealTimestampUs
+            // to current time (timestampUs) and save frame data.
+            LOGV("dataCallbackTimestamp timelapse: initial frame");
+
+            mLastTimeLapseFrameRealTimestampUs = *timestampUs;
+        } else if (*timestampUs <
+                (mLastTimeLapseFrameRealTimestampUs + mTimeBetweenTimeLapseFrameCaptureUs)) {
+            // Skip all frames from last encoded frame until
+            // sufficient time (mTimeBetweenTimeLapseFrameCaptureUs) has passed.
+            // Tell the camera to release its recording frame and return.
+            LOGV("dataCallbackTimestamp timelapse: skipping intermediate frame");
+            return true;
+        } else {
+            // Desired frame has arrived after mTimeBetweenTimeLapseFrameCaptureUs time:
+            // - Reset mLastTimeLapseFrameRealTimestampUs to current time.
+            // - Artificially modify timestampUs to be one frame time (1/framerate) ahead
+            // of the last encoded frame's time stamp.
+            LOGV("dataCallbackTimestamp timelapse: got timelapse frame");
+
+            mLastTimeLapseFrameRealTimestampUs = *timestampUs;
+            *timestampUs = mLastFrameTimestampUs + mTimeBetweenTimeLapseVideoFramesUs;
+        }
+    }
+    return false;
+}
+
+void CameraSourceTimeLapse::dataCallbackTimestamp(int64_t timestampUs, int32_t msgType,
+            const sp<IMemory> &data) {
+    if (!mUseStillCameraForTimeLapse) {
+        mSkipCurrentFrame = skipFrameAndModifyTimeStamp(&timestampUs);
+    }
+    CameraSource::dataCallbackTimestamp(timestampUs, msgType, data);
+}
+
+}  // namespace android
diff --git a/media/libstagefright/NuHTTPDataSource.cpp b/media/libstagefright/NuHTTPDataSource.cpp
index ab9285d..332bab3 100644
--- a/media/libstagefright/NuHTTPDataSource.cpp
+++ b/media/libstagefright/NuHTTPDataSource.cpp
@@ -42,7 +42,7 @@
         path->setTo(slashPos);
     }
 
-    char *colonPos = strchr(host->string(), ':');
+    const char *colonPos = strchr(host->string(), ':');
 
     if (colonPos != NULL) {
         unsigned long x;
diff --git a/media/libstagefright/httplive/M3UParser.cpp b/media/libstagefright/httplive/M3UParser.cpp
index edd8648..17771c4 100644
--- a/media/libstagefright/httplive/M3UParser.cpp
+++ b/media/libstagefright/httplive/M3UParser.cpp
@@ -90,7 +90,7 @@
         out->setTo(baseURL);
         out->append(url);
     } else {
-        char *slashPos = strrchr(baseURL, '/');
+        const char *slashPos = strrchr(baseURL, '/');
 
         if (slashPos > &baseURL[6]) {
             out->setTo(baseURL, slashPos - baseURL);
diff --git a/media/libstagefright/rtsp/ARTSPConnection.cpp b/media/libstagefright/rtsp/ARTSPConnection.cpp
index e9162c0..9826990 100644
--- a/media/libstagefright/rtsp/ARTSPConnection.cpp
+++ b/media/libstagefright/rtsp/ARTSPConnection.cpp
@@ -116,7 +116,7 @@
         path->setTo(slashPos);
     }
 
-    char *colonPos = strchr(host->c_str(), ':');
+    const char *colonPos = strchr(host->c_str(), ':');
 
     if (colonPos != NULL) {
         unsigned long x;
diff --git a/media/libstagefright/rtsp/ASessionDescription.cpp b/media/libstagefright/rtsp/ASessionDescription.cpp
index ad813cd..8187e0c 100644
--- a/media/libstagefright/rtsp/ASessionDescription.cpp
+++ b/media/libstagefright/rtsp/ASessionDescription.cpp
@@ -182,7 +182,7 @@
     AString format;
     getFormat(index, &format);
 
-    char *lastSpacePos = strrchr(format.c_str(), ' ');
+    const char *lastSpacePos = strrchr(format.c_str(), ' ');
     CHECK(lastSpacePos != NULL);
 
     char *end;
diff --git a/media/libstagefright/rtsp/MyHandler.h b/media/libstagefright/rtsp/MyHandler.h
index f21c8dc..8be8914 100644
--- a/media/libstagefright/rtsp/MyHandler.h
+++ b/media/libstagefright/rtsp/MyHandler.h
@@ -441,7 +441,7 @@
             out->setTo(baseURL);
             out->append(url);
         } else {
-            char *slashPos = strrchr(baseURL, '/');
+            const char *slashPos = strrchr(baseURL, '/');
 
             if (slashPos > &baseURL[6]) {
                 out->setTo(baseURL, slashPos - baseURL);
diff --git a/media/libstagefright/yuv/Android.mk b/media/libstagefright/yuv/Android.mk
new file mode 100644
index 0000000..0794ad1
--- /dev/null
+++ b/media/libstagefright/yuv/Android.mk
@@ -0,0 +1,13 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES:=               \
+        YUVImage.cpp            \
+        YUVCanvas.cpp
+
+LOCAL_SHARED_LIBRARIES :=       \
+        libcutils
+
+LOCAL_MODULE:= libstagefright_yuv
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/media/libstagefright/yuv/YUVCanvas.cpp b/media/libstagefright/yuv/YUVCanvas.cpp
new file mode 100644
index 0000000..7ef652d
--- /dev/null
+++ b/media/libstagefright/yuv/YUVCanvas.cpp
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_NDEBUG 0
+#define LOG_TAG "YUVCanvas"
+
+#include <media/stagefright/YUVCanvas.h>
+#include <media/stagefright/YUVImage.h>
+#include <ui/Rect.h>
+
+namespace android {
+
+YUVCanvas::YUVCanvas(YUVImage &yuvImage)
+    : mYUVImage(yuvImage) {
+}
+
+YUVCanvas::~YUVCanvas() {
+}
+
+void YUVCanvas::FillYUV(uint8_t yValue, uint8_t uValue, uint8_t vValue) {
+    for (int32_t y = 0; y < mYUVImage.height(); ++y) {
+        for (int32_t x = 0; x < mYUVImage.width(); ++x) {
+            mYUVImage.setPixelValue(x, y, yValue, uValue, vValue);
+        }
+    }
+}
+
+void YUVCanvas::FillYUVRectangle(const Rect& rect,
+        uint8_t yValue, uint8_t uValue, uint8_t vValue) {
+    for (int32_t y = rect.top; y < rect.bottom; ++y) {
+        for (int32_t x = rect.left; x < rect.right; ++x) {
+            mYUVImage.setPixelValue(x, y, yValue, uValue, vValue);
+        }
+    }
+}
+
+void YUVCanvas::CopyImageRect(
+        const Rect& srcRect,
+        int32_t destStartX, int32_t destStartY,
+        const YUVImage &srcImage) {
+
+    // Try fast copy first
+    if (YUVImage::fastCopyRectangle(
+                srcRect,
+                destStartX, destStartY,
+                srcImage, mYUVImage)) {
+        return;
+    }
+
+    int32_t srcStartX = srcRect.left;
+    int32_t srcStartY = srcRect.top;
+    for (int32_t offsetY = 0; offsetY < srcRect.height(); ++offsetY) {
+        for (int32_t offsetX = 0; offsetX < srcRect.width(); ++offsetX) {
+            int32_t srcX = srcStartX + offsetX;
+            int32_t srcY = srcStartY + offsetY;
+
+            int32_t destX = destStartX + offsetX;
+            int32_t destY = destStartY + offsetY;
+
+            uint8_t yValue;
+            uint8_t uValue;
+            uint8_t vValue;
+
+            srcImage.getPixelValue(srcX, srcY, &yValue, &uValue, & vValue);
+            mYUVImage.setPixelValue(destX, destY, yValue, uValue, vValue);
+        }
+    }
+}
+
+}  // namespace android
diff --git a/media/libstagefright/yuv/YUVImage.cpp b/media/libstagefright/yuv/YUVImage.cpp
new file mode 100644
index 0000000..73e3297
--- /dev/null
+++ b/media/libstagefright/yuv/YUVImage.cpp
@@ -0,0 +1,404 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_NDEBUG 0
+#define LOG_TAG "YUVImage"
+
+#include <media/stagefright/YUVImage.h>
+#include <ui/Rect.h>
+#include <media/stagefright/MediaDebug.h>
+
+namespace android {
+
+YUVImage::YUVImage(YUVFormat yuvFormat, int32_t width, int32_t height) {
+    mYUVFormat = yuvFormat;
+    mWidth = width;
+    mHeight = height;
+
+    size_t numberOfBytes = bufferSize(yuvFormat, width, height);
+    uint8_t *buffer = new uint8_t[numberOfBytes];
+    mBuffer = buffer;
+    mOwnBuffer = true;
+
+    initializeYUVPointers();
+}
+
+YUVImage::YUVImage(YUVFormat yuvFormat, int32_t width, int32_t height, uint8_t *buffer) {
+    mYUVFormat = yuvFormat;
+    mWidth = width;
+    mHeight = height;
+    mBuffer = buffer;
+    mOwnBuffer = false;
+
+    initializeYUVPointers();
+}
+
+//static
+size_t YUVImage::bufferSize(YUVFormat yuvFormat, int32_t width, int32_t height) {
+    int32_t numberOfPixels = width*height;
+    size_t numberOfBytes = 0;
+    if (yuvFormat == YUV420Planar || yuvFormat == YUV420SemiPlanar) {
+        // Y takes numberOfPixels bytes and U/V take numberOfPixels/4 bytes each.
+        numberOfBytes = (size_t)(numberOfPixels + (numberOfPixels >> 1));
+    } else {
+        LOGE("Format not supported");
+    }
+    return numberOfBytes;
+}
+
+bool YUVImage::initializeYUVPointers() {
+    int32_t numberOfPixels = mWidth * mHeight;
+
+    if (mYUVFormat == YUV420Planar) {
+        mYdata = (uint8_t *)mBuffer;
+        mUdata = mYdata + numberOfPixels;
+        mVdata = mUdata + (numberOfPixels >> 2);
+    } else if (mYUVFormat == YUV420SemiPlanar) {
+        // U and V channels are interleaved as VUVUVU.
+        // So V data starts at the end of Y channel and
+        // U data starts right after V's start.
+        mYdata = (uint8_t *)mBuffer;
+        mVdata = mYdata + numberOfPixels;
+        mUdata = mVdata + 1;
+    } else {
+        LOGE("Format not supported");
+        return false;
+    }
+    return true;
+}
+
+YUVImage::~YUVImage() {
+    if (mOwnBuffer) delete[] mBuffer;
+}
+
+bool YUVImage::getOffsets(int32_t x, int32_t y,
+        int32_t *yOffset, int32_t *uOffset, int32_t *vOffset) const {
+    *yOffset = y*mWidth + x;
+
+    int32_t uvOffset = (y >> 1) * (mWidth >> 1) + (x >> 1);
+    if (mYUVFormat == YUV420Planar) {
+        *uOffset = uvOffset;
+        *vOffset = uvOffset;
+    } else if (mYUVFormat == YUV420SemiPlanar) {
+        // Since U and V channels are interleaved, offsets need
+        // to be doubled.
+        *uOffset = 2*uvOffset;
+        *vOffset = 2*uvOffset;
+    } else {
+        LOGE("Format not supported");
+        return false;
+    }
+
+    return true;
+}
+
+bool YUVImage::getOffsetIncrementsPerDataRow(
+        int32_t *yDataOffsetIncrement,
+        int32_t *uDataOffsetIncrement,
+        int32_t *vDataOffsetIncrement) const {
+    *yDataOffsetIncrement = mWidth;
+
+    int32_t uvDataOffsetIncrement = mWidth >> 1;
+
+    if (mYUVFormat == YUV420Planar) {
+        *uDataOffsetIncrement = uvDataOffsetIncrement;
+        *vDataOffsetIncrement = uvDataOffsetIncrement;
+    } else if (mYUVFormat == YUV420SemiPlanar) {
+        // Since U and V channels are interleaved, offsets need
+        // to be doubled.
+        *uDataOffsetIncrement = 2*uvDataOffsetIncrement;
+        *vDataOffsetIncrement = 2*uvDataOffsetIncrement;
+    } else {
+        LOGE("Format not supported");
+        return false;
+    }
+
+    return true;
+}
+
+uint8_t* YUVImage::getYAddress(int32_t offset) const {
+    return mYdata + offset;
+}
+
+uint8_t* YUVImage::getUAddress(int32_t offset) const {
+    return mUdata + offset;
+}
+
+uint8_t* YUVImage::getVAddress(int32_t offset) const {
+    return mVdata + offset;
+}
+
+bool YUVImage::getYUVAddresses(int32_t x, int32_t y,
+        uint8_t **yAddr, uint8_t **uAddr, uint8_t **vAddr) const {
+    int32_t yOffset;
+    int32_t uOffset;
+    int32_t vOffset;
+    if (!getOffsets(x, y, &yOffset, &uOffset, &vOffset)) return false;
+
+    *yAddr = getYAddress(yOffset);
+    *uAddr = getUAddress(uOffset);
+    *vAddr = getVAddress(vOffset);
+
+    return true;
+}
+
+bool YUVImage::getPixelValue(int32_t x, int32_t y,
+        uint8_t *yPtr, uint8_t *uPtr, uint8_t *vPtr) const {
+    uint8_t *yAddr;
+    uint8_t *uAddr;
+    uint8_t *vAddr;
+    if (!getYUVAddresses(x, y, &yAddr, &uAddr, &vAddr)) return false;
+
+    *yPtr = *yAddr;
+    *uPtr = *uAddr;
+    *vPtr = *vAddr;
+
+    return true;
+}
+
+bool YUVImage::setPixelValue(int32_t x, int32_t y,
+        uint8_t yValue, uint8_t uValue, uint8_t vValue) {
+    uint8_t *yAddr;
+    uint8_t *uAddr;
+    uint8_t *vAddr;
+    if (!getYUVAddresses(x, y, &yAddr, &uAddr, &vAddr)) return false;
+
+    *yAddr = yValue;
+    *uAddr = uValue;
+    *vAddr = vValue;
+
+    return true;
+}
+
+void YUVImage::fastCopyRectangle420Planar(
+        const Rect& srcRect,
+        int32_t destStartX, int32_t destStartY,
+        const YUVImage &srcImage, YUVImage &destImage) {
+    CHECK(srcImage.mYUVFormat == YUV420Planar);
+    CHECK(destImage.mYUVFormat == YUV420Planar);
+
+    int32_t srcStartX = srcRect.left;
+    int32_t srcStartY = srcRect.top;
+    int32_t width = srcRect.width();
+    int32_t height = srcRect.height();
+
+    // Get source and destination start addresses
+    uint8_t *ySrcAddrBase;
+    uint8_t *uSrcAddrBase;
+    uint8_t *vSrcAddrBase;
+    srcImage.getYUVAddresses(srcStartX, srcStartY,
+            &ySrcAddrBase, &uSrcAddrBase, &vSrcAddrBase);
+
+    uint8_t *yDestAddrBase;
+    uint8_t *uDestAddrBase;
+    uint8_t *vDestAddrBase;
+    destImage.getYUVAddresses(destStartX, destStartY,
+            &yDestAddrBase, &uDestAddrBase, &vDestAddrBase);
+
+    // Get source and destination offset increments incurred in going
+    // from one data row to next.
+    int32_t ySrcOffsetIncrement;
+    int32_t uSrcOffsetIncrement;
+    int32_t vSrcOffsetIncrement;
+    srcImage.getOffsetIncrementsPerDataRow(
+            &ySrcOffsetIncrement, &uSrcOffsetIncrement, &vSrcOffsetIncrement);
+
+    int32_t yDestOffsetIncrement;
+    int32_t uDestOffsetIncrement;
+    int32_t vDestOffsetIncrement;
+    destImage.getOffsetIncrementsPerDataRow(
+            &yDestOffsetIncrement, &uDestOffsetIncrement, &vDestOffsetIncrement);
+
+    // Copy Y
+    {
+        size_t numberOfYBytesPerRow = (size_t) width;
+        uint8_t *ySrcAddr = ySrcAddrBase;
+        uint8_t *yDestAddr = yDestAddrBase;
+        for (int32_t offsetY = 0; offsetY < height; ++offsetY) {
+            memcpy(yDestAddr, ySrcAddr, numberOfYBytesPerRow);
+
+            ySrcAddr += ySrcOffsetIncrement;
+            yDestAddr += yDestOffsetIncrement;
+        }
+    }
+
+    // Copy U
+    {
+        size_t numberOfUBytesPerRow = (size_t) (width >> 1);
+        uint8_t *uSrcAddr = uSrcAddrBase;
+        uint8_t *uDestAddr = uDestAddrBase;
+        // Every other row has an entry for U/V channel values. Hence only
+        // go half the height.
+        for (int32_t offsetY = 0; offsetY < (height >> 1); ++offsetY) {
+            memcpy(uDestAddr, uSrcAddr, numberOfUBytesPerRow);
+
+            uSrcAddr += uSrcOffsetIncrement;
+            uDestAddr += uDestOffsetIncrement;
+        }
+    }
+
+    // Copy V
+    {
+        size_t numberOfVBytesPerRow = (size_t) (width >> 1);
+        uint8_t *vSrcAddr = vSrcAddrBase;
+        uint8_t *vDestAddr = vDestAddrBase;
+        // Every other pixel row has a U/V data row. Hence only go half the height.
+        for (int32_t offsetY = 0; offsetY < (height >> 1); ++offsetY) {
+            memcpy(vDestAddr, vSrcAddr, numberOfVBytesPerRow);
+
+            vSrcAddr += vSrcOffsetIncrement;
+            vDestAddr += vDestOffsetIncrement;
+        }
+    }
+}
+
+void YUVImage::fastCopyRectangle420SemiPlanar(
+        const Rect& srcRect,
+        int32_t destStartX, int32_t destStartY,
+        const YUVImage &srcImage, YUVImage &destImage) {
+    CHECK(srcImage.mYUVFormat == YUV420SemiPlanar);
+    CHECK(destImage.mYUVFormat == YUV420SemiPlanar);
+
+    int32_t srcStartX = srcRect.left;
+    int32_t srcStartY = srcRect.top;
+    int32_t width = srcRect.width();
+    int32_t height = srcRect.height();
+
+    // Get source and destination start addresses
+    uint8_t *ySrcAddrBase;
+    uint8_t *uSrcAddrBase;
+    uint8_t *vSrcAddrBase;
+    srcImage.getYUVAddresses(srcStartX, srcStartY,
+            &ySrcAddrBase, &uSrcAddrBase, &vSrcAddrBase);
+
+    uint8_t *yDestAddrBase;
+    uint8_t *uDestAddrBase;
+    uint8_t *vDestAddrBase;
+    destImage.getYUVAddresses(destStartX, destStartY,
+            &yDestAddrBase, &uDestAddrBase, &vDestAddrBase);
+
+    // Get source and destination offset increments incurred in going
+    // from one data row to next.
+    int32_t ySrcOffsetIncrement;
+    int32_t uSrcOffsetIncrement;
+    int32_t vSrcOffsetIncrement;
+    srcImage.getOffsetIncrementsPerDataRow(
+            &ySrcOffsetIncrement, &uSrcOffsetIncrement, &vSrcOffsetIncrement);
+
+    int32_t yDestOffsetIncrement;
+    int32_t uDestOffsetIncrement;
+    int32_t vDestOffsetIncrement;
+    destImage.getOffsetIncrementsPerDataRow(
+            &yDestOffsetIncrement, &uDestOffsetIncrement, &vDestOffsetIncrement);
+
+    // Copy Y
+    {
+        size_t numberOfYBytesPerRow = (size_t) width;
+        uint8_t *ySrcAddr = ySrcAddrBase;
+        uint8_t *yDestAddr = yDestAddrBase;
+        for (int32_t offsetY = 0; offsetY < height; ++offsetY) {
+            memcpy(yDestAddr, ySrcAddr, numberOfYBytesPerRow);
+
+            ySrcAddr = ySrcAddr + ySrcOffsetIncrement;
+            yDestAddr = yDestAddr + yDestOffsetIncrement;
+        }
+    }
+
+    // Copy UV
+    {
+        // UV are interleaved. So number of UV bytes per row is 2*(width/2).
+        size_t numberOfUVBytesPerRow = (size_t) width;
+        uint8_t *vSrcAddr = vSrcAddrBase;
+        uint8_t *vDestAddr = vDestAddrBase;
+        // Every other pixel row has a U/V data row. Hence only go half the height.
+        for (int32_t offsetY = 0; offsetY < (height >> 1); ++offsetY) {
+            memcpy(vDestAddr, vSrcAddr, numberOfUVBytesPerRow);
+
+            vSrcAddr += vSrcOffsetIncrement;
+            vDestAddr += vDestOffsetIncrement;
+        }
+    }
+}
+
+// static
+bool YUVImage::fastCopyRectangle(
+        const Rect& srcRect,
+        int32_t destStartX, int32_t destStartY,
+        const YUVImage &srcImage, YUVImage &destImage) {
+    if (srcImage.mYUVFormat == destImage.mYUVFormat) {
+        if (srcImage.mYUVFormat == YUV420Planar) {
+            fastCopyRectangle420Planar(
+                    srcRect,
+                    destStartX, destStartY,
+                    srcImage, destImage);
+        } else if (srcImage.mYUVFormat == YUV420SemiPlanar) {
+            fastCopyRectangle420SemiPlanar(
+                    srcRect,
+                    destStartX, destStartY,
+                    srcImage, destImage);
+        }
+        return true;
+    }
+    return false;
+}
+
+uint8_t clamp(uint8_t v, uint8_t minValue, uint8_t maxValue) {
+    CHECK(maxValue >= minValue);
+
+    if (v < minValue) return minValue;
+    else if (v > maxValue) return maxValue;
+    else return v;
+}
+
+void YUVImage::yuv2rgb(uint8_t yValue, uint8_t uValue, uint8_t vValue,
+        uint8_t *r, uint8_t *g, uint8_t *b) const {
+    *r = yValue + (1.370705 * (vValue-128));
+    *g = yValue - (0.698001 * (vValue-128)) - (0.337633 * (uValue-128));
+    *b = yValue + (1.732446 * (uValue-128));
+
+    *r = clamp(*r, 0, 255);
+    *g = clamp(*g, 0, 255);
+    *b = clamp(*b, 0, 255);
+}
+
+bool YUVImage::writeToPPM(const char *filename) const {
+    FILE *fp = fopen(filename, "w");
+    if (fp == NULL) {
+        return false;
+    }
+    fprintf(fp, "P3\n");
+    fprintf(fp, "%d %d\n", mWidth, mHeight);
+    fprintf(fp, "255\n");
+    for (int32_t y = 0; y < mHeight; ++y) {
+        for (int32_t x = 0; x < mWidth; ++x) {
+            uint8_t yValue;
+            uint8_t uValue;
+            uint8_t vValue;
+            getPixelValue(x, y, &yValue, &uValue, & vValue);
+
+            uint8_t rValue;
+            uint8_t gValue;
+            uint8_t bValue;
+            yuv2rgb(yValue, uValue, vValue, &rValue, &gValue, &bValue);
+
+            fprintf(fp, "%d %d %d\n", (int32_t)rValue, (int32_t)gValue, (int32_t)bValue);
+        }
+    }
+    fclose(fp);
+    return true;
+}
+
+}  // namespace android
diff --git a/media/mtp/Android.mk b/media/mtp/Android.mk
new file mode 100644
index 0000000..7502f6e
--- /dev/null
+++ b/media/mtp/Android.mk
@@ -0,0 +1,78 @@
+#
+# 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.
+#
+
+LOCAL_PATH:= $(call my-dir)
+
+ifneq ($(TARGET_SIMULATOR),true)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES:=                                       \
+                  MtpClient.cpp                         \
+                  MtpCursor.cpp                         \
+                  MtpDataPacket.cpp                     \
+                  MtpDebug.cpp                          \
+                  MtpDevice.cpp                         \
+                  MtpEventPacket.cpp                    \
+                  MtpDeviceInfo.cpp                     \
+                  MtpObjectInfo.cpp                     \
+                  MtpPacket.cpp                         \
+                  MtpProperty.cpp                       \
+                  MtpRequestPacket.cpp                  \
+                  MtpResponsePacket.cpp                 \
+                  MtpServer.cpp                         \
+                  MtpStorageInfo.cpp                    \
+                  MtpStringBuffer.cpp                   \
+                  MtpStorage.cpp                        \
+                  MtpUtils.cpp                          \
+
+LOCAL_MODULE:= libmtp
+
+LOCAL_CFLAGS := -DMTP_DEVICE -DMTP_HOST
+
+include $(BUILD_STATIC_LIBRARY)
+
+endif
+
+ifeq ($(HOST_OS),linux)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES:=                                       \
+                  MtpClient.cpp                         \
+                  MtpCursor.cpp                         \
+                  MtpDataPacket.cpp                     \
+                  MtpDebug.cpp                          \
+                  MtpDevice.cpp                         \
+                  MtpEventPacket.cpp                    \
+                  MtpDeviceInfo.cpp                     \
+                  MtpObjectInfo.cpp                     \
+                  MtpPacket.cpp                         \
+                  MtpProperty.cpp                       \
+                  MtpRequestPacket.cpp                  \
+                  MtpResponsePacket.cpp                 \
+                  MtpStorageInfo.cpp                    \
+                  MtpStringBuffer.cpp                   \
+                  MtpStorage.cpp                        \
+                  MtpUtils.cpp                          \
+
+LOCAL_MODULE:= libmtp
+
+LOCAL_CFLAGS := -DMTP_HOST
+
+include $(BUILD_HOST_STATIC_LIBRARY)
+
+endif
diff --git a/media/mtp/MtpClient.cpp b/media/mtp/MtpClient.cpp
new file mode 100644
index 0000000..ceb6a43
--- /dev/null
+++ b/media/mtp/MtpClient.cpp
@@ -0,0 +1,262 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "MtpClient"
+
+#include "MtpDebug.h"
+#include "MtpClient.h"
+#include "MtpDevice.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#include <usbhost/usbhost.h>
+#include <linux/version.h>
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 20)
+#include <linux/usb/ch9.h>
+#else
+#include <linux/usb_ch9.h>
+#endif
+
+namespace android {
+
+static bool isMtpDevice(uint16_t vendor, uint16_t product) {
+    // Sandisk Sansa Fuze
+    if (vendor == 0x0781 && product == 0x74c2)
+        return true;
+    // Samsung YP-Z5
+    if (vendor == 0x04e8 && product == 0x503c)
+        return true;
+    return false;
+}
+
+class MtpClientThread : public Thread {
+private:
+    MtpClient*   mClient;
+
+public:
+    MtpClientThread(MtpClient* client)
+        : mClient(client)
+    {
+    }
+
+    virtual bool threadLoop() {
+        return mClient->threadLoop();
+    }
+};
+
+
+MtpClient::MtpClient()
+    :   mThread(NULL),
+        mUsbHostContext(NULL),
+        mDone(false)
+{
+}
+
+MtpClient::~MtpClient() {
+    usb_host_cleanup(mUsbHostContext);
+}
+
+bool MtpClient::start() {
+    Mutex::Autolock autoLock(mMutex);
+
+    if (mThread)
+        return true;
+
+    mUsbHostContext = usb_host_init();
+    if (!mUsbHostContext)
+        return false;
+
+    mThread = new MtpClientThread(this);
+    mThread->run("MtpClientThread");
+    // wait for the thread to do initial device discovery before returning
+    mThreadStartCondition.wait(mMutex);
+
+    return true;
+}
+
+void MtpClient::stop() {
+    mDone = true;
+}
+
+MtpDevice* MtpClient::getDevice(int id) {
+    for (int i = 0; i < mDeviceList.size(); i++) {
+        MtpDevice* device = mDeviceList[i];
+        if (device->getID() == id)
+            return device;
+    }
+    return NULL;
+}
+
+bool MtpClient::usbDeviceAdded(const char *devname) {
+    struct usb_descriptor_header* desc;
+    struct usb_descriptor_iter iter;
+
+    struct usb_device *device = usb_device_open(devname);
+    if (!device) {
+        LOGE("usb_device_open failed\n");
+        return mDone;
+    }
+
+    usb_descriptor_iter_init(device, &iter);
+
+    while ((desc = usb_descriptor_iter_next(&iter)) != NULL) {
+        if (desc->bDescriptorType == USB_DT_INTERFACE) {
+            struct usb_interface_descriptor *interface = (struct usb_interface_descriptor *)desc;
+
+            if (interface->bInterfaceClass == USB_CLASS_STILL_IMAGE &&
+                interface->bInterfaceSubClass == 1 && // Still Image Capture
+                interface->bInterfaceProtocol == 1)     // Picture Transfer Protocol (PIMA 15470)
+            {
+                LOGD("Found camera: \"%s\" \"%s\"\n", usb_device_get_manufacturer_name(device),
+                        usb_device_get_product_name(device));
+            } else if (interface->bInterfaceClass == 0xFF &&
+                    interface->bInterfaceSubClass == 0xFF &&
+                    interface->bInterfaceProtocol == 0) {
+                char* interfaceName = usb_device_get_string(device, interface->iInterface);
+                if (!interfaceName || strcmp(interfaceName, "MTP"))
+                    continue;
+                // Looks like an android style MTP device
+                LOGD("Found MTP device: \"%s\" \"%s\"\n", usb_device_get_manufacturer_name(device),
+                        usb_device_get_product_name(device));
+            } else {
+                // look for special cased devices based on vendor/product ID
+                // we are doing this mainly for testing purposes
+                uint16_t vendor = usb_device_get_vendor_id(device);
+                uint16_t product = usb_device_get_product_id(device);
+                if (!isMtpDevice(vendor, product)) {
+                    // not an MTP or PTP device
+                    continue;
+                }
+                // request MTP OS string and descriptor
+                // some music players need to see this before entering MTP mode.
+                char buffer[256];
+                memset(buffer, 0, sizeof(buffer));
+                int ret = usb_device_send_control(device,
+                        USB_DIR_IN|USB_RECIP_DEVICE|USB_TYPE_STANDARD,
+                        USB_REQ_GET_DESCRIPTOR, (USB_DT_STRING << 8) | 0xEE,
+                        0, sizeof(buffer), buffer);
+                printf("usb_device_send_control returned %d errno: %d\n", ret, errno);
+                if (ret > 0) {
+                    printf("got MTP string %s\n", buffer);
+                    ret = usb_device_send_control(device,
+                            USB_DIR_IN|USB_RECIP_DEVICE|USB_TYPE_VENDOR, 1,
+                            0, 4, sizeof(buffer), buffer);
+                    printf("OS descriptor got %d\n", ret);
+                } else {
+                    printf("no MTP string\n");
+                }
+            }
+
+            // if we got here, then we have a likely MTP or PTP device
+
+            // interface should be followed by three endpoints
+            struct usb_endpoint_descriptor *ep;
+            struct usb_endpoint_descriptor *ep_in_desc = NULL;
+            struct usb_endpoint_descriptor *ep_out_desc = NULL;
+            struct usb_endpoint_descriptor *ep_intr_desc = NULL;
+            for (int i = 0; i < 3; i++) {
+                ep = (struct usb_endpoint_descriptor *)usb_descriptor_iter_next(&iter);
+                if (!ep || ep->bDescriptorType != USB_DT_ENDPOINT) {
+                    LOGE("endpoints not found\n");
+                    return mDone;
+                }
+                if (ep->bmAttributes == USB_ENDPOINT_XFER_BULK) {
+                    if (ep->bEndpointAddress & USB_ENDPOINT_DIR_MASK)
+                        ep_in_desc = ep;
+                    else
+                        ep_out_desc = ep;
+                } else if (ep->bmAttributes == USB_ENDPOINT_XFER_INT &&
+                    ep->bEndpointAddress & USB_ENDPOINT_DIR_MASK) {
+                    ep_intr_desc = ep;
+                }
+            }
+            if (!ep_in_desc || !ep_out_desc || !ep_intr_desc) {
+                LOGE("endpoints not found\n");
+                return mDone;
+            }
+
+            struct usb_endpoint *ep_in = usb_endpoint_open(device, ep_in_desc);
+            struct usb_endpoint *ep_out = usb_endpoint_open(device, ep_out_desc);
+            struct usb_endpoint *ep_intr = usb_endpoint_open(device, ep_intr_desc);
+
+            if (usb_device_claim_interface(device, interface->bInterfaceNumber)) {
+                LOGE("usb_device_claim_interface failed errno: %d\n", errno);
+                usb_endpoint_close(ep_in);
+                usb_endpoint_close(ep_out);
+                usb_endpoint_close(ep_intr);
+                return mDone;
+            }
+
+            MtpDevice* mtpDevice = new MtpDevice(device, interface->bInterfaceNumber,
+                        ep_in, ep_out, ep_intr);
+            mDeviceList.add(mtpDevice);
+            mtpDevice->initialize();
+            deviceAdded(mtpDevice);
+            return mDone;
+        }
+    }
+
+    usb_device_close(device);
+    return mDone;
+}
+
+bool MtpClient::usbDeviceRemoved(const char *devname) {
+    for (int i = 0; i < mDeviceList.size(); i++) {
+        MtpDevice* device = mDeviceList[i];
+        if (!strcmp(devname, device->getDeviceName())) {
+            deviceRemoved(device);
+            mDeviceList.removeAt(i);
+            delete device;
+            LOGD("Camera removed!\n");
+            break;
+        }
+    }
+    return mDone;
+}
+
+bool MtpClient::usbDiscoveryDone() {
+    Mutex::Autolock autoLock(mMutex);
+    mThreadStartCondition.signal();
+    return mDone;
+}
+
+bool MtpClient::threadLoop() {
+    usb_host_run(mUsbHostContext, usb_device_added, usb_device_removed, usb_discovery_done, this);
+    return false;
+}
+
+int MtpClient::usb_device_added(const char *devname, void* client_data) {
+    LOGD("usb_device_added %s\n", devname);
+    return ((MtpClient *)client_data)->usbDeviceAdded(devname);
+}
+
+int MtpClient::usb_device_removed(const char *devname, void* client_data) {
+    LOGD("usb_device_removed %s\n", devname);
+    return ((MtpClient *)client_data)->usbDeviceRemoved(devname);
+}
+
+int MtpClient::usb_discovery_done(void* client_data) {
+    LOGD("usb_discovery_done\n");
+    return ((MtpClient *)client_data)->usbDiscoveryDone();
+}
+
+}  // namespace android
diff --git a/media/mtp/MtpClient.h b/media/mtp/MtpClient.h
new file mode 100644
index 0000000..fa5c527
--- /dev/null
+++ b/media/mtp/MtpClient.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _MTP_CLIENT_H
+#define _MTP_CLIENT_H
+
+#include "MtpTypes.h"
+
+#include <utils/threads.h>
+
+struct usb_host_context;
+
+namespace android {
+
+class MtpClientThread;
+
+class MtpClient {
+private:
+    MtpDeviceList               mDeviceList;
+    MtpClientThread*            mThread;
+    Condition                   mThreadStartCondition;
+    Mutex                       mMutex;
+    struct usb_host_context*    mUsbHostContext;
+    bool                        mDone;
+
+public:
+                            MtpClient();
+    virtual                 ~MtpClient();
+
+    bool                    start();
+    void                    stop();
+
+    inline MtpDeviceList&   getDeviceList() { return mDeviceList; }
+    MtpDevice*              getDevice(int id);
+
+
+    virtual void            deviceAdded(MtpDevice *device) = 0;
+    virtual void            deviceRemoved(MtpDevice *device) = 0;
+
+private:
+    // these return true if we should stop monitoring USB and clean up
+    bool                    usbDeviceAdded(const char *devname);
+    bool                    usbDeviceRemoved(const char *devname);
+    bool                    usbDiscoveryDone();
+
+    friend class MtpClientThread;
+    bool                    threadLoop();
+    static int              usb_device_added(const char *devname, void* client_data);
+    static int              usb_device_removed(const char *devname, void* client_data);
+    static int              usb_discovery_done(void* client_data);
+};
+
+}; // namespace android
+
+#endif // _MTP_CLIENT_H
diff --git a/media/mtp/MtpCursor.cpp b/media/mtp/MtpCursor.cpp
new file mode 100644
index 0000000..3ba07cc
--- /dev/null
+++ b/media/mtp/MtpCursor.cpp
@@ -0,0 +1,460 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "MtpCursor"
+
+#include "MtpDebug.h"
+#include "MtpClient.h"
+#include "MtpCursor.h"
+#include "MtpDevice.h"
+#include "MtpDeviceInfo.h"
+#include "MtpObjectInfo.h"
+#include "MtpStorageInfo.h"
+
+
+#include "binder/CursorWindow.h"
+
+namespace android {
+
+/* Device Column IDs */
+/* These must match the values in MtpCursor.java */
+#define DEVICE_ROW_ID           1
+#define DEVICE_MANUFACTURER     2
+#define DEVICE_MODEL            3
+
+/* Storage Column IDs */
+/* These must match the values in MtpCursor.java */
+#define STORAGE_ROW_ID          101
+#define STORAGE_IDENTIFIER      102
+#define STORAGE_DESCRIPTION     103
+
+/* Object Column IDs */
+/* These must match the values in MtpCursor.java */
+#define OBJECT_ROW_ID               201
+#define OBJECT_STORAGE_ID           202
+#define OBJECT_FORMAT               203
+#define OBJECT_PROTECTION_STATUS    204
+#define OBJECT_SIZE                 205
+#define OBJECT_THUMB_FORMAT         206
+#define OBJECT_THUMB_SIZE           207
+#define OBJECT_THUMB_WIDTH          208
+#define OBJECT_THUMB_HEIGHT         209
+#define OBJECT_IMAGE_WIDTH          210
+#define OBJECT_IMAGE_HEIGHT         211
+#define OBJECT_IMAGE_DEPTH          212
+#define OBJECT_PARENT               213
+#define OBJECT_ASSOCIATION_TYPE     214
+#define OBJECT_ASSOCIATION_DESC     215
+#define OBJECT_SEQUENCE_NUMBER      216
+#define OBJECT_NAME                 217
+#define OBJECT_DATE_CREATED         218
+#define OBJECT_DATE_MODIFIED        219
+#define OBJECT_KEYWORDS             220
+#define OBJECT_THUMB                221
+
+MtpCursor::MtpCursor(MtpClient* client, int queryType, int deviceID,
+                int storageID, int objectID, int columnCount, int* columns)
+        :   mClient(client),
+            mQueryType(queryType),
+            mDeviceID(deviceID),
+            mStorageID(storageID),
+            mQbjectID(objectID),
+            mColumnCount(columnCount),
+            mColumns(NULL)
+{
+    if (columns) {
+        mColumns = new int[columnCount];
+        memcpy(mColumns, columns, columnCount * sizeof(int));
+    }
+}
+
+MtpCursor::~MtpCursor() {
+    delete[] mColumns;
+}
+
+int MtpCursor::fillWindow(CursorWindow* window, int startPos) {
+    LOGD("MtpCursor::fillWindow mQueryType: %d\n", mQueryType);
+
+    switch (mQueryType) {
+        case DEVICE:
+            return fillDevices(window, startPos);
+        case DEVICE_ID:
+            return fillDevice(window, startPos);
+        case STORAGE:
+            return fillStorages(window, startPos);
+        case STORAGE_ID:
+            return fillStorage(window, startPos);
+        case OBJECT:
+            return fillObjects(window, 0, startPos);
+        case OBJECT_ID:
+            return fillObject(window, startPos);
+        case STORAGE_CHILDREN:
+            return fillObjects(window, -1, startPos);
+        case OBJECT_CHILDREN:
+            return fillObjects(window, mQbjectID, startPos);
+        default:
+            LOGE("MtpCursor::fillWindow: unknown query type %d\n", mQueryType);
+            return 0;
+    }
+}
+
+int MtpCursor::fillDevices(CursorWindow* window, int startPos) {
+    int count = 0;
+    MtpDeviceList& deviceList = mClient->getDeviceList();
+    for (int i = 0; i < deviceList.size(); i++) {
+        MtpDevice* device = deviceList[i];
+        if (fillDevice(window, device, startPos)) {
+            count++;
+            startPos++;
+        } else {
+            break;
+        }
+    }
+    return count;
+}
+
+int MtpCursor::fillDevice(CursorWindow* window, int startPos) {
+    MtpDevice* device = mClient->getDevice(mDeviceID);
+    if (device && fillDevice(window, device, startPos))
+        return 1;
+    else
+        return 0;
+}
+
+int MtpCursor::fillStorages(CursorWindow* window, int startPos) {
+    int count = 0;
+    MtpDevice* device = mClient->getDevice(mDeviceID);
+    if (!device)
+        return 0;
+    MtpStorageIDList* storageIDs = device->getStorageIDs();
+    if (!storageIDs)
+        return 0;
+
+    for (int i = 0; i < storageIDs->size(); i++) {
+        MtpStorageID storageID = (*storageIDs)[i];
+        if (fillStorage(window, device, storageID, startPos)) {
+            count++;
+            startPos++;
+        } else {
+            break;
+        }
+    }
+    delete storageIDs;
+    return count;
+}
+
+int MtpCursor::fillStorage(CursorWindow* window, int startPos) {
+    MtpDevice* device = mClient->getDevice(mDeviceID);
+    if (device && fillStorage(window, device, mStorageID, startPos))
+        return 1;
+    else
+        return 0;
+}
+
+int MtpCursor::fillObjects(CursorWindow* window, int parent, int startPos) {
+    int count = 0;
+    MtpDevice* device = mClient->getDevice(mDeviceID);
+    if (!device)
+        return 0;
+    MtpObjectHandleList* handles = device->getObjectHandles(mStorageID, 0, parent);
+    if (!handles)
+        return 0;
+
+    for (int i = 0; i < handles->size(); i++) {
+        MtpObjectHandle handle = (*handles)[i];
+        if (fillObject(window, device, handle, startPos)) {
+            count++;
+            startPos++;
+        } else {
+            break;
+        }
+    }
+    delete handles;
+    return count;
+}
+
+int MtpCursor::fillObject(CursorWindow* window, int startPos) {
+    MtpDevice* device = mClient->getDevice(mDeviceID);
+    if (device && fillObject(window, device, mQbjectID, startPos))
+        return 1;
+    else
+        return 0;
+}
+
+bool MtpCursor::fillDevice(CursorWindow* window, MtpDevice* device, int row) {
+    MtpDeviceInfo* deviceInfo = device->getDeviceInfo();
+    if (!deviceInfo)
+        return false;
+    if (!prepareRow(window))
+        return false;
+
+    for (int i = 0; i < mColumnCount; i++) {
+        switch (mColumns[i]) {
+            case DEVICE_ROW_ID:
+                if (!putLong(window, device->getID(), row, i))
+                    return false;
+                 break;
+            case DEVICE_MANUFACTURER:
+                if (!putString(window, deviceInfo->mManufacturer, row, i))
+                    return false;
+                 break;
+            case DEVICE_MODEL:
+                if (!putString(window, deviceInfo->mModel, row, i))
+                    return false;
+                 break;
+            default:
+                LOGE("fillDevice: unknown column %d\n", mColumns[i]);
+                return false;
+        }
+    }
+
+    return true;
+}
+
+bool MtpCursor::fillStorage(CursorWindow* window, MtpDevice* device,
+        MtpStorageID storageID, int row) {
+
+LOGD("fillStorage %d\n", storageID);
+
+    MtpStorageInfo* storageInfo = device->getStorageInfo(storageID);
+    if (!storageInfo)
+        return false;
+    if (!prepareRow(window)) {
+        delete storageInfo;
+        return false;
+    }
+
+    const char* text;
+    for (int i = 0; i < mColumnCount; i++) {
+        switch (mColumns[i]) {
+            case STORAGE_ROW_ID:
+                if (!putLong(window, storageID, row, i))
+                    goto fail;
+                 break;
+            case STORAGE_IDENTIFIER:
+                text = storageInfo->mVolumeIdentifier;
+                if (!text || !text[0])
+                    text = "Camera Storage";
+                if (!putString(window, text, row, i))
+                    goto fail;
+                 break;
+            case STORAGE_DESCRIPTION:
+                text = storageInfo->mStorageDescription;
+                if (!text || !text[0])
+                    text = "Storage Description";
+                if (!putString(window, text, row, i))
+                    goto fail;
+                 break;
+            default:
+                LOGE("fillStorage: unknown column %d\n", mColumns[i]);
+                goto fail;
+        }
+    }
+
+    delete storageInfo;
+    return true;
+
+fail:
+    delete storageInfo;
+    return false;
+}
+
+bool MtpCursor::fillObject(CursorWindow* window, MtpDevice* device,
+        MtpObjectHandle objectID, int row) {
+
+    MtpObjectInfo* objectInfo = device->getObjectInfo(objectID);
+    if (!objectInfo)
+        return false;
+    // objectInfo->print();
+    if (!prepareRow(window)) {
+        delete objectInfo;
+        return false;
+    }
+
+    for (int i = 0; i < mColumnCount; i++) {
+        switch (mColumns[i]) {
+            case OBJECT_ROW_ID:
+                if (!putLong(window, objectID, row, i))
+                    goto fail;
+                 break;
+            case OBJECT_STORAGE_ID:
+                if (!putLong(window, objectInfo->mStorageID, row, i))
+                    goto fail;
+                 break;
+            case OBJECT_FORMAT:
+                if (!putLong(window, objectInfo->mFormat, row, i))
+                    goto fail;
+                 break;
+            case OBJECT_PROTECTION_STATUS:
+                if (!putLong(window, objectInfo->mProtectionStatus, row, i))
+                    goto fail;
+                 break;
+            case OBJECT_SIZE:
+                if (!putLong(window, objectInfo->mCompressedSize, row, i))
+                    goto fail;
+                 break;
+            case OBJECT_THUMB_FORMAT:
+                if (!putLong(window, objectInfo->mThumbFormat, row, i))
+                    goto fail;
+                 break;
+            case OBJECT_THUMB_SIZE:
+                if (!putLong(window, objectInfo->mThumbCompressedSize, row, i))
+                    goto fail;
+                 break;
+            case OBJECT_THUMB_WIDTH:
+                if (!putLong(window, objectInfo->mThumbPixWidth, row, i))
+                    goto fail;
+                 break;
+            case OBJECT_THUMB_HEIGHT:
+                if (!putLong(window, objectInfo->mThumbPixHeight, row, i))
+                    goto fail;
+                 break;
+            case OBJECT_IMAGE_WIDTH:
+                if (!putLong(window, objectInfo->mImagePixWidth, row, i))
+                    goto fail;
+                 break;
+            case OBJECT_IMAGE_HEIGHT:
+                if (!putLong(window, objectInfo->mImagePixHeight, row, i))
+                    goto fail;
+                 break;
+            case OBJECT_IMAGE_DEPTH:
+                if (!putLong(window, objectInfo->mImagePixDepth, row, i))
+                    goto fail;
+                 break;
+            case OBJECT_PARENT:
+                if (!putLong(window, objectInfo->mParent, row, i))
+                    goto fail;
+                 break;
+            case OBJECT_ASSOCIATION_TYPE:
+                if (!putLong(window, objectInfo->mAssociationType, row, i))
+                    goto fail;
+                 break;
+            case OBJECT_ASSOCIATION_DESC:
+                if (!putLong(window, objectInfo->mAssociationDesc, row, i))
+                    goto fail;
+                 break;
+            case OBJECT_SEQUENCE_NUMBER:
+                if (!putLong(window, objectInfo->mSequenceNumber, row, i))
+                    goto fail;
+                 break;
+            case OBJECT_NAME:
+                if (!putString(window, objectInfo->mName, row, i))
+                    goto fail;
+                 break;
+            case OBJECT_DATE_CREATED:
+                if (!putLong(window, objectInfo->mDateCreated, row, i))
+                    goto fail;
+                 break;
+            case OBJECT_DATE_MODIFIED:
+                if (!putLong(window, objectInfo->mDateModified, row, i))
+                    goto fail;
+                 break;
+            case OBJECT_KEYWORDS:
+                if (!putString(window, objectInfo->mKeywords, row, i))
+                    goto fail;
+                 break;
+            case OBJECT_THUMB:
+                if (!putThumbnail(window, objectID, objectInfo->mFormat, row, i))
+                    goto fail;
+                break;
+            default:
+                LOGE("fillStorage: unknown column %d\n", mColumns[i]);
+                goto fail;
+        }
+    }
+
+    delete objectInfo;
+    return true;
+
+fail:
+    delete objectInfo;
+    return false;
+}
+
+bool MtpCursor::prepareRow(CursorWindow* window) {
+    if (!window->setNumColumns(mColumnCount)) {
+        LOGE("Failed to change column count from %d to %d", window->getNumColumns(), mColumnCount);
+        return false;
+    }
+    field_slot_t * fieldDir = window->allocRow();
+    if (!fieldDir) {
+        LOGE("Failed allocating fieldDir");
+        return false;
+    }
+    return true;
+}
+
+
+bool MtpCursor::putLong(CursorWindow* window, int value, int row, int column) {
+
+    if (!window->putLong(row, column, value)) {
+        window->freeLastRow();
+        LOGE("Failed allocating space for a long in column %d", column);
+        return false;
+    }
+    return true;
+}
+
+bool MtpCursor::putString(CursorWindow* window, const char* text, int row, int column) {
+    int size = strlen(text) + 1;
+    int offset = window->alloc(size);
+    if (!offset) {
+        window->freeLastRow();
+        LOGE("Failed allocating %u bytes for text/blob %s", size, text);
+        return false;
+    }
+    window->copyIn(offset, (const uint8_t*)text, size);
+
+    // This must be updated after the call to alloc(), since that
+    // may move the field around in the window
+    field_slot_t * fieldSlot = window->getFieldSlot(row, column);
+    fieldSlot->type = FIELD_TYPE_STRING;
+    fieldSlot->data.buffer.offset = offset;
+    fieldSlot->data.buffer.size = size;
+    return true;
+}
+
+bool MtpCursor::putThumbnail(CursorWindow* window, int objectID, int format, int row, int column) {
+    MtpDevice* device = mClient->getDevice(mDeviceID);
+    void* thumbnail;
+    int size, offset;
+    if (format == MTP_FORMAT_ASSOCIATION) {
+        thumbnail = NULL;
+        size = offset = 0;
+    } else {
+        thumbnail = device->getThumbnail(objectID, size);
+
+        LOGV("putThumbnail: %p, size: %d\n", thumbnail, size);
+        offset = window->alloc(size);
+        if (!offset) {
+            window->freeLastRow();
+            LOGE("Failed allocating %u bytes for thumbnail", size);
+            return false;
+        }
+    }
+    if (thumbnail)
+        window->copyIn(offset, (const uint8_t*)thumbnail, size);
+
+    // This must be updated after the call to alloc(), since that
+    // may move the field around in the window
+    field_slot_t * fieldSlot = window->getFieldSlot(row, column);
+    fieldSlot->type = FIELD_TYPE_BLOB;
+    fieldSlot->data.buffer.offset = offset;
+    fieldSlot->data.buffer.size = size;
+    return true;
+}
+
+} // namespace android
diff --git a/media/mtp/MtpCursor.h b/media/mtp/MtpCursor.h
new file mode 100644
index 0000000..3f84753
--- /dev/null
+++ b/media/mtp/MtpCursor.h
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _MTP_CURSOR_H
+#define _MTP_CURSOR_H
+
+#include "MtpTypes.h"
+
+namespace android {
+
+class CursorWindow;
+
+class MtpCursor {
+private:
+    enum {
+        DEVICE              = 1,
+        DEVICE_ID           = 2,
+        STORAGE             = 3,
+        STORAGE_ID          = 4,
+        OBJECT              = 5,
+        OBJECT_ID           = 6,
+        STORAGE_CHILDREN    = 7,
+        OBJECT_CHILDREN     = 8,
+    };
+
+    MtpClient*  mClient;
+    int         mQueryType;
+    int         mDeviceID;
+    int         mStorageID;
+    int         mQbjectID;
+    int         mColumnCount;
+    int*        mColumns;
+
+public:
+                MtpCursor(MtpClient* client, int queryType, int deviceID,
+                        int storageID, int objectID, int columnCount, int* columns);
+    virtual     ~MtpCursor();
+
+    int         fillWindow(CursorWindow* window, int startPos);
+
+private:
+    int         fillDevices(CursorWindow* window, int startPos);
+    int         fillDevice(CursorWindow* window, int startPos);
+    int         fillStorages(CursorWindow* window, int startPos);
+    int         fillStorage(CursorWindow* window, int startPos);
+    int         fillObjects(CursorWindow* window, int parent, int startPos);
+    int         fillObject(CursorWindow* window, int startPos);
+
+    bool        fillDevice(CursorWindow* window, MtpDevice* device, int startPos);
+    bool        fillStorage(CursorWindow* window, MtpDevice* device,
+                        MtpStorageID storageID, int row);
+    bool        fillObject(CursorWindow* window, MtpDevice* device,
+                        MtpObjectHandle objectID, int row);
+
+    bool        prepareRow(CursorWindow* window);
+    bool        putLong(CursorWindow* window, int value, int row, int column);
+    bool        putString(CursorWindow* window, const char* text, int row, int column);
+    bool        putThumbnail(CursorWindow* window, int objectID, int format, int row, int column);
+};
+
+}; // namespace android
+
+#endif // _MTP_CURSOR_H
diff --git a/media/mtp/MtpDataPacket.cpp b/media/mtp/MtpDataPacket.cpp
new file mode 100644
index 0000000..c159e20
--- /dev/null
+++ b/media/mtp/MtpDataPacket.cpp
@@ -0,0 +1,466 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "MtpDataPacket"
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <fcntl.h>
+
+#include <usbhost/usbhost.h>
+
+#include "MtpDataPacket.h"
+#include "MtpStringBuffer.h"
+
+namespace android {
+
+MtpDataPacket::MtpDataPacket()
+    :   MtpPacket(512),
+        mOffset(MTP_CONTAINER_HEADER_SIZE)
+{
+}
+
+MtpDataPacket::~MtpDataPacket() {
+}
+
+void MtpDataPacket::reset() {
+    MtpPacket::reset();
+    mOffset = MTP_CONTAINER_HEADER_SIZE;
+}
+
+void MtpDataPacket::setOperationCode(MtpOperationCode code) {
+    MtpPacket::putUInt16(MTP_CONTAINER_CODE_OFFSET, code);
+}
+
+void MtpDataPacket::setTransactionID(MtpTransactionID id) {
+    MtpPacket::putUInt32(MTP_CONTAINER_TRANSACTION_ID_OFFSET, id);
+}
+
+uint16_t MtpDataPacket::getUInt16() {
+    int offset = mOffset;
+    uint16_t result = (uint16_t)mBuffer[offset] | ((uint16_t)mBuffer[offset + 1] << 8);
+    mOffset += 2;
+    return result;
+}
+
+uint32_t MtpDataPacket::getUInt32() {
+    int offset = mOffset;
+    uint32_t result = (uint32_t)mBuffer[offset] | ((uint32_t)mBuffer[offset + 1] << 8) |
+           ((uint32_t)mBuffer[offset + 2] << 16)  | ((uint32_t)mBuffer[offset + 3] << 24);
+    mOffset += 4;
+    return result;
+}
+
+uint64_t MtpDataPacket::getUInt64() {
+    int offset = mOffset;
+    uint64_t result = (uint64_t)mBuffer[offset] | ((uint64_t)mBuffer[offset + 1] << 8) |
+           ((uint64_t)mBuffer[offset + 2] << 16) | ((uint64_t)mBuffer[offset + 3] << 24) |
+           ((uint64_t)mBuffer[offset + 4] << 32) | ((uint64_t)mBuffer[offset + 5] << 40) |
+           ((uint64_t)mBuffer[offset + 6] << 48)  | ((uint64_t)mBuffer[offset + 7] << 56);
+    mOffset += 8;
+    return result;
+}
+
+void MtpDataPacket::getUInt128(uint128_t& value) {
+    value[0] = getUInt32();
+    value[1] = getUInt32();
+    value[2] = getUInt32();
+    value[3] = getUInt32();
+}
+
+void MtpDataPacket::getString(MtpStringBuffer& string)
+{
+    string.readFromPacket(this);
+}
+
+Int8List* MtpDataPacket::getAInt8() {
+    Int8List* result = new Int8List;
+    int count = getUInt32();
+    for (int i = 0; i < count; i++)
+        result->push(getInt8());
+    return result;
+}
+
+UInt8List* MtpDataPacket::getAUInt8() {
+    UInt8List* result = new UInt8List;
+    int count = getUInt32();
+    for (int i = 0; i < count; i++)
+        result->push(getUInt8());
+    return result;
+}
+
+Int16List* MtpDataPacket::getAInt16() {
+    Int16List* result = new Int16List;
+    int count = getUInt32();
+    for (int i = 0; i < count; i++)
+        result->push(getInt16());
+    return result;
+}
+
+UInt16List* MtpDataPacket::getAUInt16() {
+    UInt16List* result = new UInt16List;
+    int count = getUInt32();
+    for (int i = 0; i < count; i++)
+        result->push(getUInt16());
+    return result;
+}
+
+Int32List* MtpDataPacket::getAInt32() {
+    Int32List* result = new Int32List;
+    int count = getUInt32();
+    for (int i = 0; i < count; i++)
+        result->push(getInt32());
+    return result;
+}
+
+UInt32List* MtpDataPacket::getAUInt32() {
+    UInt32List* result = new UInt32List;
+    int count = getUInt32();
+    for (int i = 0; i < count; i++)
+        result->push(getUInt32());
+    return result;
+}
+
+Int64List* MtpDataPacket::getAInt64() {
+    Int64List* result = new Int64List;
+    int count = getUInt32();
+    for (int i = 0; i < count; i++)
+        result->push(getInt64());
+    return result;
+}
+
+UInt64List* MtpDataPacket::getAUInt64() {
+    UInt64List* result = new UInt64List;
+    int count = getUInt32();
+    for (int i = 0; i < count; i++)
+        result->push(getUInt64());
+    return result;
+}
+
+void MtpDataPacket::putInt8(int8_t value) {
+    allocate(mOffset + 1);
+    mBuffer[mOffset++] = (uint8_t)value;
+    if (mPacketSize < mOffset)
+        mPacketSize = mOffset;
+}
+
+void MtpDataPacket::putUInt8(uint8_t value) {
+    allocate(mOffset + 1);
+    mBuffer[mOffset++] = (uint8_t)value;
+    if (mPacketSize < mOffset)
+        mPacketSize = mOffset;
+}
+
+void MtpDataPacket::putInt16(int16_t value) {
+    allocate(mOffset + 2);
+    mBuffer[mOffset++] = (uint8_t)(value & 0xFF);
+    mBuffer[mOffset++] = (uint8_t)((value >> 8) & 0xFF);
+    if (mPacketSize < mOffset)
+        mPacketSize = mOffset;
+}
+
+void MtpDataPacket::putUInt16(uint16_t value) {
+    allocate(mOffset + 2);
+    mBuffer[mOffset++] = (uint8_t)(value & 0xFF);
+    mBuffer[mOffset++] = (uint8_t)((value >> 8) & 0xFF);
+    if (mPacketSize < mOffset)
+        mPacketSize = mOffset;
+}
+
+void MtpDataPacket::putInt32(int32_t value) {
+    allocate(mOffset + 4);
+    mBuffer[mOffset++] = (uint8_t)(value & 0xFF);
+    mBuffer[mOffset++] = (uint8_t)((value >> 8) & 0xFF);
+    mBuffer[mOffset++] = (uint8_t)((value >> 16) & 0xFF);
+    mBuffer[mOffset++] = (uint8_t)((value >> 24) & 0xFF);
+    if (mPacketSize < mOffset)
+        mPacketSize = mOffset;
+}
+
+void MtpDataPacket::putUInt32(uint32_t value) {
+    allocate(mOffset + 4);
+    mBuffer[mOffset++] = (uint8_t)(value & 0xFF);
+    mBuffer[mOffset++] = (uint8_t)((value >> 8) & 0xFF);
+    mBuffer[mOffset++] = (uint8_t)((value >> 16) & 0xFF);
+    mBuffer[mOffset++] = (uint8_t)((value >> 24) & 0xFF);
+    if (mPacketSize < mOffset)
+        mPacketSize = mOffset;
+}
+
+void MtpDataPacket::putInt64(int64_t value) {
+    allocate(mOffset + 8);
+    mBuffer[mOffset++] = (uint8_t)(value & 0xFF);
+    mBuffer[mOffset++] = (uint8_t)((value >> 8) & 0xFF);
+    mBuffer[mOffset++] = (uint8_t)((value >> 16) & 0xFF);
+    mBuffer[mOffset++] = (uint8_t)((value >> 24) & 0xFF);
+    mBuffer[mOffset++] = (uint8_t)((value >> 32) & 0xFF);
+    mBuffer[mOffset++] = (uint8_t)((value >> 40) & 0xFF);
+    mBuffer[mOffset++] = (uint8_t)((value >> 48) & 0xFF);
+    mBuffer[mOffset++] = (uint8_t)((value >> 56) & 0xFF);
+    if (mPacketSize < mOffset)
+        mPacketSize = mOffset;
+}
+
+void MtpDataPacket::putUInt64(uint64_t value) {
+    allocate(mOffset + 8);
+    mBuffer[mOffset++] = (uint8_t)(value & 0xFF);
+    mBuffer[mOffset++] = (uint8_t)((value >> 8) & 0xFF);
+    mBuffer[mOffset++] = (uint8_t)((value >> 16) & 0xFF);
+    mBuffer[mOffset++] = (uint8_t)((value >> 24) & 0xFF);
+    mBuffer[mOffset++] = (uint8_t)((value >> 32) & 0xFF);
+    mBuffer[mOffset++] = (uint8_t)((value >> 40) & 0xFF);
+    mBuffer[mOffset++] = (uint8_t)((value >> 48) & 0xFF);
+    mBuffer[mOffset++] = (uint8_t)((value >> 56) & 0xFF);
+    if (mPacketSize < mOffset)
+        mPacketSize = mOffset;
+}
+
+void MtpDataPacket::putInt128(const int128_t& value) {
+    putInt32(value[0]);
+    putInt32(value[1]);
+    putInt32(value[2]);
+    putInt32(value[3]);
+}
+
+void MtpDataPacket::putUInt128(const uint128_t& value) {
+    putUInt32(value[0]);
+    putUInt32(value[1]);
+    putUInt32(value[2]);
+    putUInt32(value[3]);
+}
+
+void MtpDataPacket::putAInt8(const int8_t* values, int count) {
+    putUInt32(count);
+    for (int i = 0; i < count; i++)
+        putInt8(*values++);
+}
+
+void MtpDataPacket::putAUInt8(const uint8_t* values, int count) {
+    putUInt32(count);
+    for (int i = 0; i < count; i++)
+        putUInt8(*values++);
+}
+
+void MtpDataPacket::putAInt16(const int16_t* values, int count) {
+    putUInt32(count);
+    for (int i = 0; i < count; i++)
+        putInt16(*values++);
+}
+
+void MtpDataPacket::putAUInt16(const uint16_t* values, int count) {
+    putUInt32(count);
+    for (int i = 0; i < count; i++)
+        putUInt16(*values++);
+}
+
+void MtpDataPacket::putAInt32(const int32_t* values, int count) {
+    putUInt32(count);
+    for (int i = 0; i < count; i++)
+        putInt32(*values++);
+}
+
+void MtpDataPacket::putAUInt32(const uint32_t* values, int count) {
+    putUInt32(count);
+    for (int i = 0; i < count; i++)
+        putUInt32(*values++);
+}
+
+void MtpDataPacket::putAUInt32(const UInt32List* list) {
+    if (!list) {
+        putEmptyArray();
+    } else {
+        size_t size = list->size();
+        putUInt32(size);
+        for (size_t i = 0; i < size; i++)
+            putUInt32((*list)[i]);
+    }
+}
+
+void MtpDataPacket::putAInt64(const int64_t* values, int count) {
+    putUInt32(count);
+    for (int i = 0; i < count; i++)
+        putInt64(*values++);
+}
+
+void MtpDataPacket::putAUInt64(const uint64_t* values, int count) {
+    putUInt32(count);
+    for (int i = 0; i < count; i++)
+        putUInt64(*values++);
+}
+
+void MtpDataPacket::putString(const MtpStringBuffer& string) {
+    string.writeToPacket(this);
+}
+
+void MtpDataPacket::putString(const char* s) {
+    MtpStringBuffer string(s);
+    string.writeToPacket(this);
+}
+
+void MtpDataPacket::putString(const uint16_t* string) {
+    int count = 0;
+    for (int i = 0; i < 256; i++) {
+        if (string[i])
+            count++;
+        else
+            break;
+    }
+    putUInt8(count);
+    for (int i = 0; i < count; i++)
+        putUInt16(string[i]);
+}
+
+#ifdef MTP_DEVICE 
+int MtpDataPacket::read(int fd) {
+    // first read the header
+    int ret = ::read(fd, mBuffer, MTP_CONTAINER_HEADER_SIZE);
+    if (ret != MTP_CONTAINER_HEADER_SIZE)
+        return -1;
+    // then the following data
+    int total = MtpPacket::getUInt32(MTP_CONTAINER_LENGTH_OFFSET);
+    int remaining = total - MTP_CONTAINER_HEADER_SIZE;
+    ret = ::read(fd, &mBuffer[0] + MTP_CONTAINER_HEADER_SIZE, remaining);
+    if (ret != remaining)
+        return -1;
+
+    mPacketSize = total;
+    mOffset = MTP_CONTAINER_HEADER_SIZE;
+    return total;
+}
+
+int MtpDataPacket::readDataHeader(int fd) {
+    int ret = ::read(fd, mBuffer, MTP_CONTAINER_HEADER_SIZE);
+    if (ret > 0)
+        mPacketSize = ret;
+    else
+        mPacketSize = 0;
+    return ret;
+}
+
+int MtpDataPacket::write(int fd) {
+    MtpPacket::putUInt32(MTP_CONTAINER_LENGTH_OFFSET, mPacketSize);
+    MtpPacket::putUInt16(MTP_CONTAINER_TYPE_OFFSET, MTP_CONTAINER_TYPE_DATA);
+
+    // send header separately from data
+    int ret = ::write(fd, mBuffer, MTP_CONTAINER_HEADER_SIZE);
+    if (ret == MTP_CONTAINER_HEADER_SIZE)
+        ret = ::write(fd, mBuffer + MTP_CONTAINER_HEADER_SIZE,
+                        mPacketSize - MTP_CONTAINER_HEADER_SIZE);
+    return (ret < 0 ? ret : 0);
+}
+
+int MtpDataPacket::writeDataHeader(int fd, uint32_t length) {
+    MtpPacket::putUInt32(MTP_CONTAINER_LENGTH_OFFSET, length);
+    MtpPacket::putUInt16(MTP_CONTAINER_TYPE_OFFSET, MTP_CONTAINER_TYPE_DATA);
+    int ret = ::write(fd, mBuffer, MTP_CONTAINER_HEADER_SIZE);
+    return (ret < 0 ? ret : 0);
+}
+#endif // MTP_DEVICE
+
+#ifdef MTP_HOST
+int MtpDataPacket::read(struct usb_endpoint *ep) {
+    // first read the header
+    int length = transfer(ep, mBuffer, mBufferSize);
+    if (length >= MTP_CONTAINER_HEADER_SIZE) {
+        // look at the length field to see if the data spans multiple packets
+        uint32_t totalLength = MtpPacket::getUInt32(MTP_CONTAINER_LENGTH_OFFSET);
+        while (totalLength > length) {
+            allocate(length + mAllocationIncrement);
+            int ret = transfer(ep, mBuffer + length, mAllocationIncrement);
+            if (ret >= 0)
+                length += ret;
+            else {
+                length = ret;
+                break;
+            }
+        }
+    }
+    if (length >= 0)
+        mPacketSize = length;
+    return length;
+}
+
+int MtpDataPacket::readData(struct usb_endpoint *ep, void* buffer, int length) {
+    int packetSize = usb_endpoint_max_packet(ep);
+    int read = 0;
+    while (read < length) {
+        int ret = transfer(ep, (char *)buffer + read, packetSize);
+        if (ret < 0) {
+printf("MtpDataPacket::readData returning %d\n", ret);
+            return ret;
+        }
+        read += ret;
+    }
+printf("MtpDataPacket::readData returning %d\n", read);
+    return read;
+}
+
+int MtpDataPacket::readDataHeader(struct usb_endpoint *ep) {
+    int length = transfer(ep, mBuffer, usb_endpoint_max_packet(ep));
+    if (length >= 0)
+        mPacketSize = length;
+    return length;
+}
+
+int MtpDataPacket::writeDataHeader(struct usb_endpoint *ep, uint32_t length) {
+    MtpPacket::putUInt32(MTP_CONTAINER_LENGTH_OFFSET, length);
+    MtpPacket::putUInt16(MTP_CONTAINER_TYPE_OFFSET, MTP_CONTAINER_TYPE_DATA);
+    int ret = transfer(ep, mBuffer, MTP_CONTAINER_HEADER_SIZE);
+    return (ret < 0 ? ret : 0);
+}
+
+int MtpDataPacket::write(struct usb_endpoint *ep) {
+    MtpPacket::putUInt32(MTP_CONTAINER_LENGTH_OFFSET, mPacketSize);
+    MtpPacket::putUInt16(MTP_CONTAINER_TYPE_OFFSET, MTP_CONTAINER_TYPE_DATA);
+
+    // send header separately from data
+    int ret = transfer(ep, mBuffer, MTP_CONTAINER_HEADER_SIZE);
+    if (ret == MTP_CONTAINER_HEADER_SIZE)
+        ret = transfer(ep, mBuffer + MTP_CONTAINER_HEADER_SIZE,
+                        mPacketSize - MTP_CONTAINER_HEADER_SIZE);
+    return (ret < 0 ? ret : 0);
+}
+
+int MtpDataPacket::write(struct usb_endpoint *ep, void* buffer, uint32_t length) {
+    int ret = 0;
+    int packetSize = usb_endpoint_max_packet(ep);
+    while (length > 0) {
+        int write = (length > packetSize ? packetSize : length);
+        int ret = transfer(ep, buffer, write);
+        if (ret < 0)
+            break;
+        length -= ret;
+    }
+    return (ret < 0 ? ret : 0);
+}
+
+#endif // MTP_HOST
+
+void* MtpDataPacket::getData(int& outLength) const {
+    int length = mPacketSize - MTP_CONTAINER_HEADER_SIZE;
+    if (length > 0) {
+        void* result = malloc(length);
+        if (result) {
+            memcpy(result, mBuffer + MTP_CONTAINER_HEADER_SIZE, length);
+            outLength = length;
+            return result;
+        }
+    }
+    outLength = 0;
+    return NULL;
+}
+
+}  // namespace android
diff --git a/media/mtp/MtpDataPacket.h b/media/mtp/MtpDataPacket.h
new file mode 100644
index 0000000..e8314d7
--- /dev/null
+++ b/media/mtp/MtpDataPacket.h
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _MTP_DATA_PACKET_H
+#define _MTP_DATA_PACKET_H
+
+#include "MtpPacket.h"
+#include "mtp.h"
+
+namespace android {
+
+class MtpStringBuffer;
+
+class MtpDataPacket : public MtpPacket {
+private:
+    // current offset for get/put methods
+    int                 mOffset;
+
+public:
+                        MtpDataPacket();
+    virtual             ~MtpDataPacket();
+
+    virtual void        reset();
+
+    void                setOperationCode(MtpOperationCode code);
+    void                setTransactionID(MtpTransactionID id);
+
+    inline uint8_t      getUInt8() { return (uint8_t)mBuffer[mOffset++]; }
+    inline int8_t       getInt8() { return (int8_t)mBuffer[mOffset++]; }
+    uint16_t            getUInt16();
+    inline int16_t      getInt16() { return (int16_t)getUInt16(); }
+    uint32_t            getUInt32();
+    inline int32_t      getInt32() { return (int32_t)getUInt32(); }
+    uint64_t            getUInt64();
+    inline int64_t      getInt64() { return (int64_t)getUInt64(); }
+    void                getUInt128(uint128_t& value);
+    inline void         getInt128(int128_t& value) { getUInt128((uint128_t&)value); }
+    void                getString(MtpStringBuffer& string);
+
+    Int8List*           getAInt8();
+    UInt8List*          getAUInt8();
+    Int16List*          getAInt16();
+    UInt16List*         getAUInt16();
+    Int32List*          getAInt32();
+    UInt32List*         getAUInt32();
+    Int64List*          getAInt64();
+    UInt64List*         getAUInt64();
+
+    void                putInt8(int8_t value);
+    void                putUInt8(uint8_t value);
+    void                putInt16(int16_t value);
+    void                putUInt16(uint16_t value);
+    void                putInt32(int32_t value);
+    void                putUInt32(uint32_t value);
+    void                putInt64(int64_t value);
+    void                putUInt64(uint64_t value);
+    void                putInt128(const int128_t& value);
+    void                putUInt128(const uint128_t& value);
+
+    void                putAInt8(const int8_t* values, int count);
+    void                putAUInt8(const uint8_t* values, int count);
+    void                putAInt16(const int16_t* values, int count);
+    void                putAUInt16(const uint16_t* values, int count);
+    void                putAInt32(const int32_t* values, int count);
+    void                putAUInt32(const uint32_t* values, int count);
+    void                putAUInt32(const UInt32List* list);
+    void                putAInt64(const int64_t* values, int count);
+    void                putAUInt64(const uint64_t* values, int count);
+    void                putString(const MtpStringBuffer& string);
+    void                putString(const char* string);
+    void                putString(const uint16_t* string);
+    inline void         putEmptyString() { putUInt16(0); }
+    inline void         putEmptyArray() { putUInt32(0); }
+
+
+#ifdef MTP_DEVICE
+    // fill our buffer with data from the given file descriptor
+    int                 read(int fd);
+    int                 readDataHeader(int fd);
+
+    // write our data to the given file descriptor
+    int                 write(int fd);
+    int                 writeDataHeader(int fd, uint32_t length);
+#endif
+
+#ifdef MTP_HOST
+    int                 read(struct usb_endpoint *ep);
+    int                 readData(struct usb_endpoint *ep, void* buffer, int length);
+    int                 readDataHeader(struct usb_endpoint *ep);
+
+    int                 writeDataHeader(struct usb_endpoint *ep, uint32_t length);
+    int                 write(struct usb_endpoint *ep);
+    int                 write(struct usb_endpoint *ep, void* buffer, uint32_t length);
+#endif
+
+    inline bool         hasData() const { return mPacketSize > MTP_CONTAINER_HEADER_SIZE; }
+    void*               getData(int& outLength) const;
+};
+
+}; // namespace android
+
+#endif // _MTP_DATA_PACKET_H
diff --git a/media/mtp/MtpDatabase.h b/media/mtp/MtpDatabase.h
new file mode 100644
index 0000000..02bb0d9
--- /dev/null
+++ b/media/mtp/MtpDatabase.h
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _MTP_DATABASE_H
+#define _MTP_DATABASE_H
+
+#include "MtpTypes.h"
+
+namespace android {
+
+class MtpDataPacket;
+
+class MtpDatabase {
+public:
+    virtual ~MtpDatabase() {}
+
+    // called from SendObjectInfo to reserve a database entry for the incoming file
+    virtual MtpObjectHandle         beginSendObject(const char* path,
+                                            MtpObjectFormat format,
+                                            MtpObjectHandle parent,
+                                            MtpStorageID storage,
+                                            uint64_t size,
+                                            time_t modified) = 0;
+
+    // called to report success or failure of the SendObject file transfer
+    // success should signal a notification of the new object's creation,
+    // failure should remove the database entry created in beginSendObject
+    virtual void                    endSendObject(const char* path,
+                                            MtpObjectHandle handle,
+                                            MtpObjectFormat format,
+                                            bool succeeded) = 0;
+
+    virtual MtpObjectHandleList*    getObjectList(MtpStorageID storageID,
+                                            MtpObjectFormat format,
+                                            MtpObjectHandle parent) = 0;
+
+    virtual int                     getNumObjects(MtpStorageID storageID,
+                                            MtpObjectFormat format,
+                                            MtpObjectHandle parent) = 0;
+
+    virtual MtpResponseCode         getObjectProperty(MtpObjectHandle handle,
+                                            MtpObjectProperty property,
+                                            MtpDataPacket& packet) = 0;
+
+    virtual MtpResponseCode         getObjectInfo(MtpObjectHandle handle,
+                                            MtpDataPacket& packet) = 0;
+
+    virtual MtpResponseCode         getObjectFilePath(MtpObjectHandle handle,
+                                            MtpString& filePath,
+                                            int64_t& fileLength) = 0;
+
+    virtual MtpResponseCode         deleteFile(MtpObjectHandle handle) = 0;
+
+    virtual MtpObjectHandleList*    getObjectReferences(MtpObjectHandle handle) = 0;
+
+    virtual MtpResponseCode         setObjectReferences(MtpObjectHandle handle,
+                                            MtpObjectHandleList* references) = 0;
+
+};
+
+}; // namespace android
+
+#endif // _MTP_DATABASE_H
diff --git a/media/mtp/MtpDebug.cpp b/media/mtp/MtpDebug.cpp
new file mode 100644
index 0000000..9ded6e2
--- /dev/null
+++ b/media/mtp/MtpDebug.cpp
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "MtpDebug.h"
+
+namespace android {
+
+struct OperationCodeEntry {
+    const char* name;
+    MtpOperationCode code;
+};
+
+static const OperationCodeEntry sOperationCodes[] = {
+    { "MTP_OPERATION_GET_DEVICE_INFO",              0x1001 },
+    { "MTP_OPERATION_OPEN_SESSION",                 0x1002 },
+    { "MTP_OPERATION_CLOSE_SESSION",                0x1003 },
+    { "MTP_OPERATION_GET_STORAGE_IDS",              0x1004 },
+    { "MTP_OPERATION_GET_STORAGE_INFO",             0x1005 },
+    { "MTP_OPERATION_GET_NUM_OBJECTS",              0x1006 },
+    { "MTP_OPERATION_GET_OBJECT_HANDLES",           0x1007 },
+    { "MTP_OPERATION_GET_OBJECT_INFO",              0x1008 },
+    { "MTP_OPERATION_GET_OBJECT",                   0x1009 },
+    { "MTP_OPERATION_GET_THUMB",                    0x100A },
+    { "MTP_OPERATION_DELETE_OBJECT",                0x100B },
+    { "MTP_OPERATION_SEND_OBJECT_INFO",             0x100C },
+    { "MTP_OPERATION_SEND_OBJECT",                  0x100D },
+    { "MTP_OPERATION_INITIATE_CAPTURE",             0x100E },
+    { "MTP_OPERATION_FORMAT_STORE",                 0x100F },
+    { "MTP_OPERATION_RESET_DEVICE",                 0x1010 },
+    { "MTP_OPERATION_SELF_TEST",                    0x1011 },
+    { "MTP_OPERATION_SET_OBJECT_PROTECTION",        0x1012 },
+    { "MTP_OPERATION_POWER_DOWN",                   0x1013 },
+    { "MTP_OPERATION_GET_DEVICE_PROP_DESC",         0x1014 },
+    { "MTP_OPERATION_GET_DEVICE_PROP_VALUE",        0x1015 },
+    { "MTP_OPERATION_SET_DEVICE_PROP_VALUE",        0x1016 },
+    { "MTP_OPERATION_RESET_DEVICE_PROP_VALUE",      0x1017 },
+    { "MTP_OPERATION_TERMINATE_OPEN_CAPTURE",       0x1018 },
+    { "MTP_OPERATION_MOVE_OBJECT",                  0x1019 },
+    { "MTP_OPERATION_COPY_OBJECT",                  0x101A },
+    { "MTP_OPERATION_GET_PARTIAL_OBJECT",           0x101B },
+    { "MTP_OPERATION_INITIATE_OPEN_CAPTURE",        0x101C },
+    { "MTP_OPERATION_GET_OBJECT_PROPS_SUPPORTED",   0x9801 },
+    { "MTP_OPERATION_GET_OBJECT_PROP_DESC",         0x9802 },
+    { "MTP_OPERATION_GET_OBJECT_PROP_VALUE",        0x9803 },
+    { "MTP_OPERATION_SET_OBJECT_PROP_VALUE",        0x9804 },
+    { "MTP_OPERATION_GET_OBJECT_REFERENCES",        0x9810 },
+    { "MTP_OPERATION_SET_OBJECT_REFERENCES",        0x9811 },
+    { "MTP_OPERATION_SKIP",                         0x9820 },
+    { 0,                                            0      },
+};
+
+
+const char* MtpDebug::getOperationCodeName(MtpOperationCode code) {
+    const OperationCodeEntry* entry = sOperationCodes;
+    while (entry->name) {
+        if (entry->code == code)
+            return entry->name;
+        entry++;
+    }
+    return "*** UNKNOWN OPERATION ***";
+}
+
+}  // namespace android
diff --git a/media/mtp/MtpDebug.h b/media/mtp/MtpDebug.h
new file mode 100644
index 0000000..87eff74
--- /dev/null
+++ b/media/mtp/MtpDebug.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _MTP_DEBUG_H
+#define _MTP_DEBUG_H
+
+// #define LOG_NDEBUG 0
+#include <utils/Log.h>
+
+#include "MtpTypes.h"
+
+namespace android {
+
+class MtpDebug {
+public:
+    static const char* getOperationCodeName(MtpOperationCode code);
+};
+
+}; // namespace android
+
+#endif // _MTP_DEBUG_H
diff --git a/media/mtp/MtpDevice.cpp b/media/mtp/MtpDevice.cpp
new file mode 100644
index 0000000..bd68f75
--- /dev/null
+++ b/media/mtp/MtpDevice.cpp
@@ -0,0 +1,496 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "MtpDevice"
+
+#include "MtpDebug.h"
+#include "MtpDevice.h"
+#include "MtpDeviceInfo.h"
+#include "MtpObjectInfo.h"
+#include "MtpProperty.h"
+#include "MtpStorageInfo.h"
+#include "MtpStringBuffer.h"
+#include "MtpUtils.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <endian.h>
+
+#include <usbhost/usbhost.h>
+
+namespace android {
+
+MtpDevice::MtpDevice(struct usb_device* device, int interface,
+            struct usb_endpoint *ep_in, struct usb_endpoint *ep_out,
+            struct usb_endpoint *ep_intr)
+    :   mDevice(device),
+        mInterface(interface),
+        mEndpointIn(ep_in),
+        mEndpointOut(ep_out),
+        mEndpointIntr(ep_intr),
+        mDeviceInfo(NULL),
+        mID(usb_device_get_unique_id(device)),
+        mSessionID(0),
+        mTransactionID(0)
+{
+}
+
+MtpDevice::~MtpDevice() {
+    close();
+    for (int i = 0; i < mDeviceProperties.size(); i++)
+        delete mDeviceProperties[i];
+}
+
+void MtpDevice::initialize() {
+    openSession();
+    mDeviceInfo = getDeviceInfo();
+    if (mDeviceInfo) {
+        mDeviceInfo->print();
+
+        if (mDeviceInfo->mDeviceProperties) {
+            int count = mDeviceInfo->mDeviceProperties->size();
+            for (int i = 0; i < count; i++) {
+                MtpDeviceProperty propCode = (*mDeviceInfo->mDeviceProperties)[i];
+                MtpProperty* property = getDevicePropDesc(propCode);
+                if (property) {
+                    property->print();
+                    mDeviceProperties.push(property);
+                }
+            }
+        }
+    }
+}
+
+void MtpDevice::close() {
+    if (mDevice) {
+        usb_device_release_interface(mDevice, mInterface);
+        usb_device_close(mDevice);
+        mDevice = NULL;
+    }
+}
+
+const char* MtpDevice::getDeviceName() {
+    if (mDevice)
+        return usb_device_get_name(mDevice);
+    else
+        return "???";
+}
+
+bool MtpDevice::openSession() {
+    Mutex::Autolock autoLock(mMutex);
+
+    mSessionID = 0;
+    mTransactionID = 0;
+    MtpSessionID newSession = 1;
+    mRequest.reset();
+    mRequest.setParameter(1, newSession);
+    if (!sendRequest(MTP_OPERATION_OPEN_SESSION))
+        return false;
+    MtpResponseCode ret = readResponse();
+    if (ret == MTP_RESPONSE_SESSION_ALREADY_OPEN)
+        newSession = mResponse.getParameter(1);
+    else if (ret != MTP_RESPONSE_OK)
+        return false;
+
+    mSessionID = newSession;
+    mTransactionID = 1;
+    return true;
+}
+
+bool MtpDevice::closeSession() {
+    // FIXME
+    return true;
+}
+
+MtpDeviceInfo* MtpDevice::getDeviceInfo() {
+    Mutex::Autolock autoLock(mMutex);
+
+    mRequest.reset();
+    if (!sendRequest(MTP_OPERATION_GET_DEVICE_INFO))
+        return NULL;
+    if (!readData())
+        return NULL;
+    MtpResponseCode ret = readResponse();
+    if (ret == MTP_RESPONSE_OK) {
+        MtpDeviceInfo* info = new MtpDeviceInfo;
+        info->read(mData);
+        return info;
+    }
+    return NULL;
+}
+
+MtpStorageIDList* MtpDevice::getStorageIDs() {
+    Mutex::Autolock autoLock(mMutex);
+
+    mRequest.reset();
+    if (!sendRequest(MTP_OPERATION_GET_STORAGE_IDS))
+        return NULL;
+    if (!readData())
+        return NULL;
+    MtpResponseCode ret = readResponse();
+    if (ret == MTP_RESPONSE_OK) {
+        return mData.getAUInt32();
+    }
+    return NULL;
+}
+
+MtpStorageInfo* MtpDevice::getStorageInfo(MtpStorageID storageID) {
+    Mutex::Autolock autoLock(mMutex);
+
+    mRequest.reset();
+    mRequest.setParameter(1, storageID);
+    if (!sendRequest(MTP_OPERATION_GET_STORAGE_INFO))
+        return NULL;
+    if (!readData())
+        return NULL;
+    MtpResponseCode ret = readResponse();
+    if (ret == MTP_RESPONSE_OK) {
+        MtpStorageInfo* info = new MtpStorageInfo(storageID);
+        info->read(mData);
+        return info;
+    }
+    return NULL;
+}
+
+MtpObjectHandleList* MtpDevice::getObjectHandles(MtpStorageID storageID,
+            MtpObjectFormat format, MtpObjectHandle parent) {
+    Mutex::Autolock autoLock(mMutex);
+
+    mRequest.reset();
+    mRequest.setParameter(1, storageID);
+    mRequest.setParameter(2, format);
+    mRequest.setParameter(3, parent);
+    if (!sendRequest(MTP_OPERATION_GET_OBJECT_HANDLES))
+        return NULL;
+    if (!readData())
+        return NULL;
+    MtpResponseCode ret = readResponse();
+    if (ret == MTP_RESPONSE_OK) {
+        return mData.getAUInt32();
+    }
+    return NULL;
+}
+
+MtpObjectInfo* MtpDevice::getObjectInfo(MtpObjectHandle handle) {
+    Mutex::Autolock autoLock(mMutex);
+
+    // FIXME - we might want to add some caching here
+
+    mRequest.reset();
+    mRequest.setParameter(1, handle);
+    if (!sendRequest(MTP_OPERATION_GET_OBJECT_INFO))
+        return NULL;
+    if (!readData())
+        return NULL;
+    MtpResponseCode ret = readResponse();
+    if (ret == MTP_RESPONSE_OK) {
+        MtpObjectInfo* info = new MtpObjectInfo(handle);
+        info->read(mData);
+        return info;
+    }
+    return NULL;
+}
+
+void* MtpDevice::getThumbnail(MtpObjectHandle handle, int& outLength) {
+    Mutex::Autolock autoLock(mMutex);
+
+    mRequest.reset();
+    mRequest.setParameter(1, handle);
+    if (sendRequest(MTP_OPERATION_GET_THUMB) && readData()) {
+        MtpResponseCode ret = readResponse();
+        if (ret == MTP_RESPONSE_OK) {
+            return mData.getData(outLength);
+        }
+    }
+    outLength = 0;
+    return NULL;
+}
+
+MtpObjectHandle MtpDevice::sendObjectInfo(MtpObjectInfo* info) {
+    Mutex::Autolock autoLock(mMutex);
+
+    mRequest.reset();
+    MtpObjectHandle parent = info->mParent;
+    if (parent == 0)
+        parent = MTP_PARENT_ROOT;
+
+    mRequest.setParameter(1, info->mStorageID);
+    mRequest.setParameter(2, info->mParent);
+
+    mData.putUInt32(info->mStorageID);
+    mData.putUInt16(info->mFormat);
+    mData.putUInt16(info->mProtectionStatus);
+    mData.putUInt32(info->mCompressedSize);
+    mData.putUInt16(info->mThumbFormat);
+    mData.putUInt32(info->mThumbCompressedSize);
+    mData.putUInt32(info->mThumbPixWidth);
+    mData.putUInt32(info->mThumbPixHeight);
+    mData.putUInt32(info->mImagePixWidth);
+    mData.putUInt32(info->mImagePixHeight);
+    mData.putUInt32(info->mImagePixDepth);
+    mData.putUInt32(info->mParent);
+    mData.putUInt16(info->mAssociationType);
+    mData.putUInt32(info->mAssociationDesc);
+    mData.putUInt32(info->mSequenceNumber);
+    mData.putString(info->mName);
+
+    char created[100], modified[100];
+    formatDateTime(info->mDateCreated, created, sizeof(created));
+    formatDateTime(info->mDateModified, modified, sizeof(modified));
+
+    mData.putString(created);
+    mData.putString(modified);
+    if (info->mKeywords)
+        mData.putString(info->mKeywords);
+    else
+        mData.putEmptyString();
+
+   if (sendRequest(MTP_OPERATION_SEND_OBJECT_INFO) && sendData()) {
+        MtpResponseCode ret = readResponse();
+        if (ret == MTP_RESPONSE_OK) {
+            info->mStorageID = mResponse.getParameter(1);
+            info->mParent = mResponse.getParameter(2);
+            info->mHandle = mResponse.getParameter(3);
+            return info->mHandle;
+        }
+    }
+    return (MtpObjectHandle)-1;
+}
+
+bool MtpDevice::sendObject(MtpObjectInfo* info, int srcFD) {
+    Mutex::Autolock autoLock(mMutex);
+
+    int remaining = info->mCompressedSize;
+    mRequest.reset();
+    mRequest.setParameter(1, info->mHandle);
+    if (sendRequest(MTP_OPERATION_SEND_OBJECT)) {
+        // send data header
+        writeDataHeader(MTP_OPERATION_SEND_OBJECT, remaining);
+
+        char buffer[65536];
+        while (remaining > 0) {
+            int count = read(srcFD, buffer, sizeof(buffer));
+            if (count > 0) {
+                int written = mData.write(mEndpointOut, buffer, count);
+                // FIXME check error
+                remaining -= count;
+            } else {
+                break;
+            }
+        }
+    }
+    MtpResponseCode ret = readResponse();
+    return (remaining == 0 && ret == MTP_RESPONSE_OK);
+}
+
+bool MtpDevice::deleteObject(MtpObjectHandle handle) {
+    Mutex::Autolock autoLock(mMutex);
+
+    mRequest.reset();
+    mRequest.setParameter(1, handle);
+    if (sendRequest(MTP_OPERATION_DELETE_OBJECT)) {
+        MtpResponseCode ret = readResponse();
+        if (ret == MTP_RESPONSE_OK)
+            return true;
+    }
+    return false;
+}
+
+MtpObjectHandle MtpDevice::getParent(MtpObjectHandle handle) {
+    MtpObjectInfo* info = getObjectInfo(handle);
+    if (info)
+        return info->mParent;
+    else
+        return -1;
+}
+
+MtpObjectHandle MtpDevice::getStorageID(MtpObjectHandle handle) {
+    MtpObjectInfo* info = getObjectInfo(handle);
+    if (info)
+        return info->mStorageID;
+    else
+        return -1;
+}
+
+MtpProperty* MtpDevice::getDevicePropDesc(MtpDeviceProperty code) {
+    Mutex::Autolock autoLock(mMutex);
+
+    mRequest.reset();
+    mRequest.setParameter(1, code);
+    if (!sendRequest(MTP_OPERATION_GET_DEVICE_PROP_DESC))
+        return NULL;
+    if (!readData())
+        return NULL;
+    MtpResponseCode ret = readResponse();
+    if (ret == MTP_RESPONSE_OK) {
+        MtpProperty* property = new MtpProperty;
+        property->read(mData, true);
+        return property;
+    }
+    return NULL;
+}
+
+class ReadObjectThread : public Thread {
+private:
+    MtpDevice*          mDevice;
+    MtpObjectHandle     mHandle;
+    int                 mObjectSize;
+    void*               mInitialData;
+    int                 mInitialDataLength;
+    int                 mFD;
+
+public:
+    ReadObjectThread(MtpDevice* device, MtpObjectHandle handle, int objectSize)
+        : mDevice(device),
+          mHandle(handle),
+          mObjectSize(objectSize),
+          mInitialData(NULL),
+          mInitialDataLength(0)
+    {
+    }
+
+    virtual ~ReadObjectThread() {
+        if (mFD >= 0)
+            close(mFD);
+        free(mInitialData);
+    }
+
+    // returns file descriptor
+    int init() {
+        mDevice->mRequest.reset();
+        mDevice->mRequest.setParameter(1, mHandle);
+        if (mDevice->sendRequest(MTP_OPERATION_GET_OBJECT)
+                && mDevice->mData.readDataHeader(mDevice->mEndpointIn)) {
+
+            // mData will contain header and possibly the beginning of the object data
+            mInitialData = mDevice->mData.getData(mInitialDataLength);
+
+            // create a pipe for the client to read from
+            int pipefd[2];
+            if (pipe(pipefd) < 0) {
+                LOGE("pipe failed (%s)", strerror(errno));
+                return -1;
+            }
+
+            mFD = pipefd[1];
+            return pipefd[0];
+        } else {
+           return -1;
+        }
+    }
+
+    virtual bool threadLoop() {
+        int remaining = mObjectSize;
+        if (mInitialData) {
+            write(mFD, mInitialData, mInitialDataLength);
+            remaining -= mInitialDataLength;
+            free(mInitialData);
+            mInitialData = NULL;
+        }
+
+        char buffer[16384];
+        while (remaining > 0) {
+            int readSize = (remaining > sizeof(buffer) ? sizeof(buffer) : remaining);
+            int count = mDevice->mData.readData(mDevice->mEndpointIn, buffer, readSize);
+            int written;
+            if (count >= 0) {
+                int written = write(mFD, buffer, count);
+                // FIXME check error
+                remaining -= count;
+            } else {
+                break;
+            }
+        }
+
+        MtpResponseCode ret = mDevice->readResponse();
+        mDevice->mMutex.unlock();
+        return false;
+    }
+};
+
+    // returns the file descriptor for a pipe to read the object's data
+int MtpDevice::readObject(MtpObjectHandle handle, int objectSize) {
+    mMutex.lock();
+
+    ReadObjectThread* thread = new ReadObjectThread(this, handle, objectSize);
+    int fd = thread->init();
+    if (fd < 0) {
+        delete thread;
+        mMutex.unlock();
+    } else {
+        thread->run("ReadObjectThread");
+    }
+    return fd;
+}
+
+bool MtpDevice::sendRequest(MtpOperationCode operation) {
+    LOGV("sendRequest: %s\n", MtpDebug::getOperationCodeName(operation));
+    mRequest.setOperationCode(operation);
+    if (mTransactionID > 0)
+        mRequest.setTransactionID(mTransactionID++);
+    int ret = mRequest.write(mEndpointOut);
+    mRequest.dump();
+    return (ret > 0);
+}
+
+bool MtpDevice::sendData() {
+    LOGV("sendData\n");
+    mData.setOperationCode(mRequest.getOperationCode());
+    mData.setTransactionID(mRequest.getTransactionID());
+    int ret = mData.write(mEndpointOut);
+    mData.dump();
+    return (ret > 0);
+}
+
+bool MtpDevice::readData() {
+    mData.reset();
+    int ret = mData.read(mEndpointIn);
+    LOGV("readData returned %d\n", ret);
+    if (ret >= MTP_CONTAINER_HEADER_SIZE) {
+        mData.dump();
+        return true;
+    }
+    else {
+        LOGV("readResponse failed\n");
+        return false;
+    }
+}
+
+bool MtpDevice::writeDataHeader(MtpOperationCode operation, int dataLength) {
+    mData.setOperationCode(operation);
+    mData.setTransactionID(mRequest.getTransactionID());
+    return (!mData.writeDataHeader(mEndpointOut, dataLength));
+}
+
+MtpResponseCode MtpDevice::readResponse() {
+    LOGV("readResponse\n");
+    int ret = mResponse.read(mEndpointIn);
+    if (ret >= MTP_CONTAINER_HEADER_SIZE) {
+        mResponse.dump();
+        return mResponse.getResponseCode();
+    }
+    else {
+        LOGD("readResponse failed\n");
+        return -1;
+    }
+}
+
+}  // namespace android
diff --git a/media/mtp/MtpDevice.h b/media/mtp/MtpDevice.h
new file mode 100644
index 0000000..57f492f
--- /dev/null
+++ b/media/mtp/MtpDevice.h
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _MTP_DEVICE_H
+#define _MTP_DEVICE_H
+
+#include "MtpRequestPacket.h"
+#include "MtpDataPacket.h"
+#include "MtpResponsePacket.h"
+#include "MtpTypes.h"
+
+#include <utils/threads.h>
+
+struct usb_device;
+
+namespace android {
+
+class MtpDeviceInfo;
+class MtpObjectInfo;
+class MtpStorageInfo;
+
+class MtpDevice {
+private:
+    struct usb_device*      mDevice;
+    int                     mInterface;
+    struct usb_endpoint*    mEndpointIn;
+    struct usb_endpoint*    mEndpointOut;
+    struct usb_endpoint*    mEndpointIntr;
+    MtpDeviceInfo*          mDeviceInfo;
+    MtpPropertyList         mDeviceProperties;
+
+    // a unique ID for the device
+    int                     mID;
+
+    // current session ID
+    MtpSessionID            mSessionID;
+    // current transaction ID
+    MtpTransactionID        mTransactionID;
+
+    MtpRequestPacket        mRequest;
+    MtpDataPacket           mData;
+    MtpResponsePacket       mResponse;
+
+    // to ensure only one MTP transaction at a time
+    Mutex                   mMutex;
+
+public:
+                            MtpDevice(struct usb_device* device, int interface,
+                                    struct usb_endpoint *ep_in, struct usb_endpoint *ep_out,
+                                    struct usb_endpoint *ep_intr);
+    virtual                 ~MtpDevice();
+
+    inline int              getID() const { return mID; }
+
+    void                    initialize();
+    void                    close();
+    const char*             getDeviceName();
+
+    bool                    openSession();
+    bool                    closeSession();
+
+    MtpDeviceInfo*          getDeviceInfo();
+    MtpStorageIDList*       getStorageIDs();
+    MtpStorageInfo*         getStorageInfo(MtpStorageID storageID);
+    MtpObjectHandleList*    getObjectHandles(MtpStorageID storageID, MtpObjectFormat format, MtpObjectHandle parent);
+    MtpObjectInfo*          getObjectInfo(MtpObjectHandle handle);
+    void*                   getThumbnail(MtpObjectHandle handle, int& outLength);
+    MtpObjectHandle         sendObjectInfo(MtpObjectInfo* info);
+    bool                    sendObject(MtpObjectInfo* info, int srcFD);
+    bool                    deleteObject(MtpObjectHandle handle);
+    MtpObjectHandle         getParent(MtpObjectHandle handle);
+    MtpObjectHandle         getStorageID(MtpObjectHandle handle);
+
+    MtpProperty*            getDevicePropDesc(MtpDeviceProperty code);
+
+    // returns the file descriptor for a pipe to read the object's data
+    int                     readObject(MtpObjectHandle handle, int objectSize);
+
+private:
+    friend class ReadObjectThread;
+
+    bool                    sendRequest(MtpOperationCode operation);
+    bool                    sendData();
+    bool                    readData();
+    bool                    writeDataHeader(MtpOperationCode operation, int dataLength);
+    MtpResponseCode         readResponse();
+
+};
+
+}; // namespace android
+
+#endif // _MTP_DEVICE_H
diff --git a/media/mtp/MtpDeviceInfo.cpp b/media/mtp/MtpDeviceInfo.cpp
new file mode 100644
index 0000000..5a9322e
--- /dev/null
+++ b/media/mtp/MtpDeviceInfo.cpp
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "MtpDeviceInfo"
+
+#include "MtpDebug.h"
+#include "MtpDataPacket.h"
+#include "MtpDeviceInfo.h"
+#include "MtpStringBuffer.h"
+
+namespace android {
+
+MtpDeviceInfo::MtpDeviceInfo()
+    :   mStandardVersion(0),
+        mVendorExtensionID(0),
+        mVendorExtensionVersion(0),
+        mVendorExtensionDesc(NULL),
+        mFunctionalCode(0),
+        mOperations(NULL),
+        mEvents(NULL),
+        mDeviceProperties(NULL),
+        mCaptureFormats(NULL),
+        mPlaybackFormats(NULL),
+        mManufacturer(NULL),
+        mModel(NULL),
+        mVersion(NULL),
+        mSerial(NULL)
+{
+}
+
+MtpDeviceInfo::~MtpDeviceInfo() {
+    if (mVendorExtensionDesc)
+        free(mVendorExtensionDesc);
+    delete mOperations;
+    delete mEvents;
+    delete mDeviceProperties;
+    delete mCaptureFormats;
+    delete mPlaybackFormats;
+    if (mManufacturer)
+        free(mManufacturer);
+    if (mModel)
+        free(mModel);
+    if (mVersion)
+        free(mVersion);
+    if (mSerial)
+        free(mSerial);
+}
+
+void MtpDeviceInfo::read(MtpDataPacket& packet) {
+    MtpStringBuffer string;
+
+    // read the device info
+    mStandardVersion = packet.getUInt16();
+    mVendorExtensionID = packet.getUInt32();
+    mVendorExtensionVersion = packet.getUInt16();
+
+    packet.getString(string);
+    mVendorExtensionDesc = strdup((const char *)string);
+
+    mFunctionalCode = packet.getUInt16();
+    mOperations = packet.getAUInt16();
+    mEvents = packet.getAUInt16();
+    mDeviceProperties = packet.getAUInt16();
+    mCaptureFormats = packet.getAUInt16();
+    mPlaybackFormats = packet.getAUInt16();
+
+    packet.getString(string);
+    mManufacturer = strdup((const char *)string);
+    packet.getString(string);
+    mModel = strdup((const char *)string);
+    packet.getString(string);
+    mVersion = strdup((const char *)string);
+    packet.getString(string);
+    mSerial = strdup((const char *)string);
+}
+
+void MtpDeviceInfo::print() {
+    LOGV("Device Info:\n\tmStandardVersion: %d\n\tmVendorExtensionID: %d\n\tmVendorExtensionVersiony: %d\n",
+            mStandardVersion, mVendorExtensionID, mVendorExtensionVersion);
+    LOGV("\tmVendorExtensionDesc: %s\n\tmFunctionalCode: %d\n\tmManufacturer: %s\n\tmModel: %s\n\tmVersion: %s\n\tmSerial: %s\n",
+            mVendorExtensionDesc, mFunctionalCode, mManufacturer, mModel, mVersion, mSerial);
+}
+
+}  // namespace android
diff --git a/media/mtp/MtpDeviceInfo.h b/media/mtp/MtpDeviceInfo.h
new file mode 100644
index 0000000..2abaa10
--- /dev/null
+++ b/media/mtp/MtpDeviceInfo.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _MTP_DEVICE_INFO_H
+#define _MTP_DEVICE_INFO_H
+
+struct stat;
+
+namespace android {
+
+class MtpDataPacket;
+
+class MtpDeviceInfo {
+public:
+    uint16_t                mStandardVersion;
+    uint32_t                mVendorExtensionID;
+    uint16_t                mVendorExtensionVersion;
+    char*                   mVendorExtensionDesc;
+    uint16_t                mFunctionalCode;
+    UInt16List*             mOperations;
+    UInt16List*             mEvents;
+    MtpDevicePropertyList*  mDeviceProperties;
+    MtpObjectFormatList*    mCaptureFormats;
+    MtpObjectFormatList*    mPlaybackFormats;
+    char*                   mManufacturer;
+    char*                   mModel;
+    char*                   mVersion;
+    char*                   mSerial;
+
+public:
+                            MtpDeviceInfo();
+    virtual                 ~MtpDeviceInfo();
+
+    void                    read(MtpDataPacket& packet);
+
+    void                    print();
+};
+
+}; // namespace android
+
+#endif // _MTP_DEVICE_INFO_H
diff --git a/media/mtp/MtpEventPacket.cpp b/media/mtp/MtpEventPacket.cpp
new file mode 100644
index 0000000..fc74542
--- /dev/null
+++ b/media/mtp/MtpEventPacket.cpp
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "MtpEventPacket"
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+
+#ifdef MTP_DEVICE
+#include <linux/usb/f_mtp.h>
+#endif
+
+#include "MtpEventPacket.h"
+
+namespace android {
+
+MtpEventPacket::MtpEventPacket()
+    :   MtpPacket(512)
+{
+}
+
+MtpEventPacket::~MtpEventPacket() {
+}
+
+#ifdef MTP_DEVICE
+int MtpEventPacket::write(int fd) {
+    struct mtp_event    event;
+
+    putUInt32(MTP_CONTAINER_LENGTH_OFFSET, mPacketSize);
+    putUInt16(MTP_CONTAINER_TYPE_OFFSET, MTP_CONTAINER_TYPE_EVENT);
+
+    event.data = mBuffer;
+    event.length = mPacketSize;
+    int ret = ::ioctl(fd, MTP_SEND_EVENT, (unsigned long)&event);
+    return (ret < 0 ? ret : 0);
+}
+#endif
+
+#ifdef MTP_HOST
+    // read our buffer from the given endpoint
+int MtpEventPacket::read(struct usb_endpoint *ep) {
+    int ret = transfer(ep, mBuffer, mBufferSize);
+     if (ret >= 0)
+        mPacketSize = ret;
+    else
+        mPacketSize = 0;
+    return ret;
+}
+#endif
+
+}  // namespace android
+
diff --git a/media/mtp/MtpEventPacket.h b/media/mtp/MtpEventPacket.h
new file mode 100644
index 0000000..30ae869
--- /dev/null
+++ b/media/mtp/MtpEventPacket.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _MTP_EVENT_PACKET_H
+#define _MTP_EVENT_PACKET_H
+
+#include "MtpPacket.h"
+#include "mtp.h"
+
+namespace android {
+
+class MtpEventPacket : public MtpPacket {
+
+public:
+                        MtpEventPacket();
+    virtual             ~MtpEventPacket();
+
+#ifdef MTP_DEVICE
+    // write our data to the given file descriptor
+    int                 write(int fd);
+#endif
+
+#ifdef MTP_HOST
+    // read our buffer from the given endpoint
+    int                 read(struct usb_endpoint *ep);
+#endif
+
+    inline MtpEventCode     getEventCode() const { return getContainerCode(); }
+    inline void             setEventCode(MtpEventCode code)
+                                                     { return setContainerCode(code); }
+};
+
+}; // namespace android
+
+#endif // _MTP_EVENT_PACKET_H
diff --git a/media/mtp/MtpObjectInfo.cpp b/media/mtp/MtpObjectInfo.cpp
new file mode 100644
index 0000000..dd4304e
--- /dev/null
+++ b/media/mtp/MtpObjectInfo.cpp
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "MtpObjectInfo"
+
+#include "MtpDebug.h"
+#include "MtpDataPacket.h"
+#include "MtpObjectInfo.h"
+#include "MtpStringBuffer.h"
+#include "MtpUtils.h"
+
+namespace android {
+
+MtpObjectInfo::MtpObjectInfo(MtpObjectHandle handle)
+    :   mHandle(handle),
+        mStorageID(0),
+        mFormat(0),
+        mProtectionStatus(0),
+        mCompressedSize(0),
+        mThumbFormat(0),
+        mThumbCompressedSize(0),
+        mThumbPixWidth(0),
+        mThumbPixHeight(0),
+        mImagePixWidth(0),
+        mImagePixHeight(0),
+        mImagePixDepth(0),
+        mParent(0),
+        mAssociationType(0),
+        mAssociationDesc(0),
+        mSequenceNumber(0),
+        mName(NULL),
+        mDateCreated(0),
+        mDateModified(0),
+        mKeywords(NULL)
+{
+}
+
+MtpObjectInfo::~MtpObjectInfo() {
+    if (mName)
+        free(mName);
+    if (mKeywords)
+        free(mKeywords);
+}
+
+void MtpObjectInfo::read(MtpDataPacket& packet) {
+    MtpStringBuffer string;
+    time_t time;
+
+    mStorageID = packet.getUInt32();
+    mFormat = packet.getUInt16();
+    mProtectionStatus = packet.getUInt16();
+    mCompressedSize = packet.getUInt32();
+    mThumbFormat = packet.getUInt16();
+    mThumbCompressedSize = packet.getUInt32();
+    mThumbPixWidth = packet.getUInt32();
+    mThumbPixHeight = packet.getUInt32();
+    mImagePixWidth = packet.getUInt32();
+    mImagePixHeight = packet.getUInt32();
+    mImagePixDepth = packet.getUInt32();
+    mParent = packet.getUInt32();
+    mAssociationType = packet.getUInt16();
+    mAssociationDesc = packet.getUInt32();
+    mSequenceNumber = packet.getUInt32();
+
+    packet.getString(string);
+    mName = strdup((const char *)string);
+
+    packet.getString(string);
+    if (parseDateTime((const char*)string, time))
+        mDateCreated = time;
+
+    packet.getString(string);
+    if (parseDateTime((const char*)string, time))
+        mDateModified = time;
+
+    packet.getString(string);
+    mKeywords = strdup((const char *)string);
+}
+
+void MtpObjectInfo::print() {
+    LOGD("MtpObject Info %08X: %s\n", mHandle, mName);
+    LOGD("  mStorageID: %08X mFormat: %04X mProtectionStatus: %d\n",
+            mStorageID, mFormat, mProtectionStatus);
+    LOGD("  mCompressedSize: %d mThumbFormat: %04X mThumbCompressedSize: %d\n",
+            mCompressedSize, mFormat, mThumbCompressedSize);
+    LOGD("  mThumbPixWidth: %d mThumbPixHeight: %d\n", mThumbPixWidth, mThumbPixHeight);
+    LOGD("  mImagePixWidth: %d mImagePixHeight: %d mImagePixDepth: %d\n",
+            mImagePixWidth, mImagePixHeight, mImagePixDepth);
+    LOGD("  mParent: %08X mAssociationType: %04X mAssociationDesc: %04X\n",
+            mParent, mAssociationType, mAssociationDesc);
+    LOGD("  mSequenceNumber: %d mDateCreated: %d mDateModified: %d mKeywords: %s\n",
+            mSequenceNumber, mDateCreated, mDateModified, mKeywords);
+}
+
+}  // namespace android
diff --git a/media/mtp/MtpObjectInfo.h b/media/mtp/MtpObjectInfo.h
new file mode 100644
index 0000000..c7a449ca
--- /dev/null
+++ b/media/mtp/MtpObjectInfo.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _MTP_OBJECT_INFO_H
+#define _MTP_OBJECT_INFO_H
+
+#include "MtpTypes.h"
+
+namespace android {
+
+class MtpDataPacket;
+
+class MtpObjectInfo {
+public:
+    MtpObjectHandle     mHandle;
+    MtpStorageID        mStorageID;
+    MtpObjectFormat     mFormat;
+    uint16_t            mProtectionStatus;
+    uint32_t            mCompressedSize;
+    MtpObjectFormat     mThumbFormat;
+    uint32_t            mThumbCompressedSize;
+    uint32_t            mThumbPixWidth;
+    uint32_t            mThumbPixHeight;
+    uint32_t            mImagePixWidth;
+    uint32_t            mImagePixHeight;
+    uint32_t            mImagePixDepth;
+    MtpObjectHandle     mParent;
+    uint16_t            mAssociationType;
+    uint32_t            mAssociationDesc;
+    uint32_t            mSequenceNumber;
+    char*               mName;
+    time_t              mDateCreated;
+    time_t              mDateModified;
+    char*               mKeywords;
+
+public:
+                        MtpObjectInfo(MtpObjectHandle handle);
+    virtual             ~MtpObjectInfo();
+
+    void                read(MtpDataPacket& packet);
+
+    void                print();
+};
+
+}; // namespace android
+
+#endif // _MTP_OBJECT_INFO_H
diff --git a/media/mtp/MtpPacket.cpp b/media/mtp/MtpPacket.cpp
new file mode 100644
index 0000000..42bf8ba
--- /dev/null
+++ b/media/mtp/MtpPacket.cpp
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "MtpPacket"
+
+#include "MtpDebug.h"
+#include "MtpPacket.h"
+#include "mtp.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <usbhost/usbhost.h>
+
+namespace android {
+
+MtpPacket::MtpPacket(int bufferSize)
+    :   mBuffer(NULL),
+        mBufferSize(bufferSize),
+        mAllocationIncrement(bufferSize),
+        mPacketSize(0)
+{
+    mBuffer = (uint8_t *)malloc(bufferSize);
+    if (!mBuffer) {
+        LOGE("out of memory!");
+        abort();
+    }
+}
+
+MtpPacket::~MtpPacket() {
+    if (mBuffer)
+        free(mBuffer);
+}
+
+void MtpPacket::reset() {
+    allocate(MTP_CONTAINER_HEADER_SIZE);
+    mPacketSize = MTP_CONTAINER_HEADER_SIZE;
+    memset(mBuffer, 0, mBufferSize);
+}
+
+void MtpPacket::allocate(int length) {
+    if (length > mBufferSize) {
+        int newLength = length + mAllocationIncrement;
+        mBuffer = (uint8_t *)realloc(mBuffer, newLength);
+        if (!mBuffer) {
+            LOGE("out of memory!");
+            abort();
+        }
+        mBufferSize = newLength;
+    }
+}
+
+void MtpPacket::dump() {
+#define DUMP_BYTES_PER_ROW  16
+    char buffer[500];
+    char* bufptr = buffer;
+
+    for (int i = 0; i < mPacketSize; i++) {
+        sprintf(bufptr, "%02X ", mBuffer[i]);
+        bufptr += strlen(bufptr);
+        if (i % DUMP_BYTES_PER_ROW == (DUMP_BYTES_PER_ROW - 1)) {
+            LOGV("%s", buffer);
+            bufptr = buffer;
+        }
+    }
+    if (bufptr != buffer) {
+        // print last line
+        LOGV("%s", buffer);
+    }
+    LOGV("\n");
+}
+
+uint16_t MtpPacket::getUInt16(int offset) const {
+    return ((uint16_t)mBuffer[offset + 1] << 8) | (uint16_t)mBuffer[offset];
+}
+
+uint32_t MtpPacket::getUInt32(int offset) const {
+    return ((uint32_t)mBuffer[offset + 3] << 24) | ((uint32_t)mBuffer[offset + 2] << 16) |
+           ((uint32_t)mBuffer[offset + 1] << 8)  | (uint32_t)mBuffer[offset];
+}
+
+void MtpPacket::putUInt16(int offset, uint16_t value) {
+    mBuffer[offset++] = (uint8_t)(value & 0xFF);
+    mBuffer[offset++] = (uint8_t)((value >> 8) & 0xFF);
+}
+
+void MtpPacket::putUInt32(int offset, uint32_t value) {
+    mBuffer[offset++] = (uint8_t)(value & 0xFF);
+    mBuffer[offset++] = (uint8_t)((value >> 8) & 0xFF);
+    mBuffer[offset++] = (uint8_t)((value >> 16) & 0xFF);
+    mBuffer[offset++] = (uint8_t)((value >> 24) & 0xFF);
+}
+
+uint16_t MtpPacket::getContainerCode() const {
+    return getUInt16(MTP_CONTAINER_CODE_OFFSET);
+}
+
+void MtpPacket::setContainerCode(uint16_t code) {
+    putUInt16(MTP_CONTAINER_CODE_OFFSET, code);
+}
+
+MtpTransactionID MtpPacket::getTransactionID() const {
+    return getUInt32(MTP_CONTAINER_TRANSACTION_ID_OFFSET);
+}
+
+void MtpPacket::setTransactionID(MtpTransactionID id) {
+    putUInt32(MTP_CONTAINER_TRANSACTION_ID_OFFSET, id);
+}
+
+uint32_t MtpPacket::getParameter(int index) const {
+    if (index < 1 || index > 5) {
+        LOGE("index %d out of range in MtpRequestPacket::getParameter", index);
+        return 0;
+    }
+    return getUInt32(MTP_CONTAINER_PARAMETER_OFFSET + (index - 1) * sizeof(uint32_t));
+}
+
+void MtpPacket::setParameter(int index, uint32_t value) {
+    if (index < 1 || index > 5) {
+        LOGE("index %d out of range in MtpResponsePacket::setParameter", index);
+        return;
+    }
+    int offset = MTP_CONTAINER_PARAMETER_OFFSET + (index - 1) * sizeof(uint32_t);
+    if (mPacketSize < offset + sizeof(uint32_t))
+        mPacketSize = offset + sizeof(uint32_t);
+    putUInt32(offset, value);
+}
+
+#ifdef MTP_HOST
+int MtpPacket::transfer(struct usb_endpoint *ep, void* buffer, int length) {
+    if (usb_endpoint_queue(ep, buffer, length)) {
+        LOGE("usb_endpoint_queue failed, errno: %d", errno);
+        return -1;
+    }
+    int ep_num;
+    return usb_endpoint_wait(usb_endpoint_get_device(ep), &ep_num);
+}
+#endif
+
+}  // namespace android
diff --git a/media/mtp/MtpPacket.h b/media/mtp/MtpPacket.h
new file mode 100644
index 0000000..9c8d6da
--- /dev/null
+++ b/media/mtp/MtpPacket.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _MTP_PACKET_H
+#define _MTP_PACKET_H
+
+#include "MtpTypes.h"
+
+struct usb_endpoint;
+
+namespace android {
+
+class MtpPacket {
+
+protected:
+    uint8_t*            mBuffer;
+    // current size of the buffer
+    int                 mBufferSize;
+    // number of bytes to add when resizing the buffer
+    int                 mAllocationIncrement;
+    // size of the data in the packet
+    int                 mPacketSize;
+
+public:
+                        MtpPacket(int bufferSize);
+    virtual             ~MtpPacket();
+
+    // sets packet size to the default container size and sets buffer to zero
+    virtual void        reset();
+
+    void                allocate(int length);
+    void                dump();
+
+    uint16_t            getContainerCode() const;
+    void                setContainerCode(uint16_t code);
+
+    MtpTransactionID    getTransactionID() const;
+    void                setTransactionID(MtpTransactionID id);
+
+    uint32_t            getParameter(int index) const;
+    void                setParameter(int index, uint32_t value);
+
+#ifdef MTP_HOST
+    int                 transfer(struct usb_endpoint *ep, void* buffer, int length);
+#endif
+
+protected:
+    uint16_t            getUInt16(int offset) const;
+    uint32_t            getUInt32(int offset) const;
+    void                putUInt16(int offset, uint16_t value);
+    void                putUInt32(int offset, uint32_t value);
+};
+
+}; // namespace android
+
+#endif // _MTP_PACKET_H
diff --git a/media/mtp/MtpProperty.cpp b/media/mtp/MtpProperty.cpp
new file mode 100644
index 0000000..d6beb8a
--- /dev/null
+++ b/media/mtp/MtpProperty.cpp
@@ -0,0 +1,323 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "MtpProperty"
+
+#include "MtpDataPacket.h"
+#include "MtpProperty.h"
+#include "MtpStringBuffer.h"
+#include "MtpUtils.h"
+
+namespace android {
+
+MtpProperty::MtpProperty()
+    :   mCode(0),
+        mType(0),
+        mWriteable(false),
+        mDefaultArrayLength(0),
+        mDefaultArrayValues(NULL),
+        mCurrentArrayLength(0),
+        mCurrentArrayValues(NULL),
+        mFormFlag(kFormNone),
+        mEnumLength(0),
+        mEnumValues(NULL)
+{
+    mDefaultValue.str = NULL;
+    mCurrentValue.str = NULL;
+    mMinimumValue.str = NULL;
+    mMaximumValue.str = NULL;
+}
+
+MtpProperty::MtpProperty(MtpPropertyCode propCode,
+                         MtpDataType type,
+                         bool writeable,
+                         int defaultValue)
+    :   mCode(propCode),
+        mType(type),
+        mWriteable(writeable),
+        mDefaultArrayLength(0),
+        mDefaultArrayValues(NULL),
+        mCurrentArrayLength(0),
+        mCurrentArrayValues(NULL),
+        mFormFlag(kFormNone),
+        mEnumLength(0),
+        mEnumValues(NULL)
+{
+    memset(&mDefaultValue, 0, sizeof(mDefaultValue));
+    memset(&mCurrentValue, 0, sizeof(mCurrentValue));
+    memset(&mMinimumValue, 0, sizeof(mMinimumValue));
+    memset(&mMaximumValue, 0, sizeof(mMaximumValue));
+
+    if (defaultValue) {
+        switch (type) {
+            case MTP_TYPE_INT8:
+                mDefaultValue.i8 = defaultValue;
+                break;
+            case MTP_TYPE_UINT8:
+                mDefaultValue.u8 = defaultValue;
+                break;
+            case MTP_TYPE_INT16:
+                mDefaultValue.i16 = defaultValue;
+                break;
+            case MTP_TYPE_UINT16:
+                mDefaultValue.u16 = defaultValue;
+                break;
+            case MTP_TYPE_INT32:
+                mDefaultValue.i32 = defaultValue;
+                break;
+            case MTP_TYPE_UINT32:
+                mDefaultValue.u32 = defaultValue;
+                break;
+            case MTP_TYPE_INT64:
+                mDefaultValue.i64 = defaultValue;
+                break;
+            case MTP_TYPE_UINT64:
+                mDefaultValue.u64 = defaultValue;
+                break;
+            default:
+                LOGE("unknown type %04X in MtpProperty::MtpProperty", type);
+        }
+    }
+}
+
+MtpProperty::~MtpProperty() {
+    if (mType == MTP_TYPE_STR) {
+        // free all strings
+        free(mDefaultValue.str);
+        free(mCurrentValue.str);
+        free(mMinimumValue.str);
+        free(mMaximumValue.str);
+        if (mDefaultArrayValues) {
+            for (int i = 0; i < mDefaultArrayLength; i++)
+                free(mDefaultArrayValues[i].str);
+        }
+        if (mCurrentArrayValues) {
+            for (int i = 0; i < mCurrentArrayLength; i++)
+                free(mCurrentArrayValues[i].str);
+        }
+        if (mEnumValues) {
+            for (int i = 0; i < mEnumLength; i++)
+                free(mEnumValues[i].str);
+        }
+    }
+    delete[] mDefaultArrayValues;
+    delete[] mCurrentArrayValues;
+    delete[] mEnumValues;
+}
+
+void MtpProperty::read(MtpDataPacket& packet, bool deviceProp) {
+
+    mCode = packet.getUInt16();
+    mType = packet.getUInt16();
+    mWriteable = (packet.getUInt8() == 1);
+    switch (mType) {
+        case MTP_TYPE_AINT8:
+        case MTP_TYPE_AUINT8:
+        case MTP_TYPE_AINT16:
+        case MTP_TYPE_AUINT16:
+        case MTP_TYPE_AINT32:
+        case MTP_TYPE_AUINT32:
+        case MTP_TYPE_AINT64:
+        case MTP_TYPE_AUINT64:
+        case MTP_TYPE_AINT128:
+        case MTP_TYPE_AUINT128:
+            mDefaultArrayValues = readArrayValues(packet, mDefaultArrayLength);
+            mCurrentArrayValues = readArrayValues(packet, mCurrentArrayLength);
+            break;
+        default:
+            readValue(packet, mDefaultValue);
+            if (deviceProp)
+                readValue(packet, mCurrentValue);
+    }
+    mFormFlag = packet.getUInt8();
+
+    if (mFormFlag == kFormRange) {
+            readValue(packet, mMinimumValue);
+            readValue(packet, mMaximumValue);
+            readValue(packet, mStepSize);
+    } else if (mFormFlag == kFormEnum) {
+        mEnumLength = packet.getUInt16();
+        mEnumValues = new MtpPropertyValue[mEnumLength];
+        for (int i = 0; i < mEnumLength; i++)
+            readValue(packet, mEnumValues[i]);
+    }
+}
+
+// FIXME - only works for object properties
+void MtpProperty::write(MtpDataPacket& packet) {
+    packet.putUInt16(mCode);
+    packet.putUInt16(mType);
+    packet.putUInt8(mWriteable ? 1 : 0);
+
+    switch (mType) {
+        case MTP_TYPE_AINT8:
+        case MTP_TYPE_AUINT8:
+        case MTP_TYPE_AINT16:
+        case MTP_TYPE_AUINT16:
+        case MTP_TYPE_AINT32:
+        case MTP_TYPE_AUINT32:
+        case MTP_TYPE_AINT64:
+        case MTP_TYPE_AUINT64:
+        case MTP_TYPE_AINT128:
+        case MTP_TYPE_AUINT128:
+            writeArrayValues(packet, mDefaultArrayValues, mDefaultArrayLength);
+            break;
+        default:
+            writeValue(packet, mDefaultValue);
+    }
+    packet.putUInt8(mFormFlag);
+    if (mFormFlag == kFormRange) {
+            writeValue(packet, mMinimumValue);
+            writeValue(packet, mMaximumValue);
+            writeValue(packet, mStepSize);
+    } else if (mFormFlag == kFormEnum) {
+        packet.putUInt16(mEnumLength);
+        for (int i = 0; i < mEnumLength; i++)
+            writeValue(packet, mEnumValues[i]);
+    }
+}
+
+void MtpProperty::print() {
+    LOGV("MtpProperty %04X\n", mCode);
+    LOGV("    type %04X\n", mType);
+    LOGV("    writeable %s\n", (mWriteable ? "true" : "false"));
+}
+
+void MtpProperty::readValue(MtpDataPacket& packet, MtpPropertyValue& value) {
+    MtpStringBuffer stringBuffer;
+
+    switch (mType) {
+        case MTP_TYPE_INT8:
+        case MTP_TYPE_AINT8:
+            value.i8 = packet.getInt8();
+            break;
+        case MTP_TYPE_UINT8:
+        case MTP_TYPE_AUINT8:
+            value.u8 = packet.getUInt8();
+            break;
+        case MTP_TYPE_INT16:
+        case MTP_TYPE_AINT16:
+            value.i16 = packet.getInt16();
+            break;
+        case MTP_TYPE_UINT16:
+        case MTP_TYPE_AUINT16:
+            value.u16 = packet.getUInt16();
+            break;
+        case MTP_TYPE_INT32:
+        case MTP_TYPE_AINT32:
+            value.i32 = packet.getInt32();
+            break;
+        case MTP_TYPE_UINT32:
+        case MTP_TYPE_AUINT32:
+            value.u32 = packet.getUInt32();
+            break;
+        case MTP_TYPE_INT64:
+        case MTP_TYPE_AINT64:
+            value.i64 = packet.getInt64();
+            break;
+        case MTP_TYPE_UINT64:
+        case MTP_TYPE_AUINT64:
+            value.u64 = packet.getUInt64();
+            break;
+        case MTP_TYPE_INT128:
+        case MTP_TYPE_AINT128:
+            packet.getInt128(value.i128);
+            break;
+        case MTP_TYPE_UINT128:
+        case MTP_TYPE_AUINT128:
+            packet.getUInt128(value.u128);
+            break;
+        case MTP_TYPE_STR:
+            packet.getString(stringBuffer);
+            value.str = strdup(stringBuffer);
+            break;
+        default:
+            LOGE("unknown type %04X in MtpProperty::readValue", mType);
+    }
+}
+
+void MtpProperty::writeValue(MtpDataPacket& packet, MtpPropertyValue& value) {
+    MtpStringBuffer stringBuffer;
+
+    switch (mType) {
+        case MTP_TYPE_INT8:
+        case MTP_TYPE_AINT8:
+            packet.putInt8(value.i8);
+            break;
+        case MTP_TYPE_UINT8:
+        case MTP_TYPE_AUINT8:
+            packet.putUInt8(value.u8);
+            break;
+        case MTP_TYPE_INT16:
+        case MTP_TYPE_AINT16:
+            packet.putInt16(value.i16);
+            break;
+        case MTP_TYPE_UINT16:
+        case MTP_TYPE_AUINT16:
+            packet.putUInt16(value.u16);
+            break;
+        case MTP_TYPE_INT32:
+        case MTP_TYPE_AINT32:
+            packet.putInt32(value.i32);
+            break;
+        case MTP_TYPE_UINT32:
+        case MTP_TYPE_AUINT32:
+            packet.putUInt32(value.u32);
+            break;
+        case MTP_TYPE_INT64:
+        case MTP_TYPE_AINT64:
+            packet.putInt64(value.i64);
+            break;
+        case MTP_TYPE_UINT64:
+        case MTP_TYPE_AUINT64:
+            packet.putUInt64(value.u64);
+            break;
+        case MTP_TYPE_INT128:
+        case MTP_TYPE_AINT128:
+            packet.putInt128(value.i128);
+            break;
+        case MTP_TYPE_UINT128:
+        case MTP_TYPE_AUINT128:
+            packet.putUInt128(value.u128);
+            break;
+        case MTP_TYPE_STR:
+            if (value.str)
+                packet.putString(value.str);
+            else
+                packet.putEmptyString();
+            break;
+        default:
+            LOGE("unknown type %04X in MtpProperty::writeValue", mType);
+    }
+}
+
+MtpPropertyValue* MtpProperty::readArrayValues(MtpDataPacket& packet, int& length) {
+    length = packet.getUInt32();
+    if (length == 0)
+        return NULL;
+    MtpPropertyValue* result = new MtpPropertyValue[length];
+    for (int i = 0; i < length; i++)
+        readValue(packet, result[i]);
+    return result;
+}
+
+void MtpProperty::writeArrayValues(MtpDataPacket& packet, MtpPropertyValue* values, int length) {
+    packet.putUInt32(length);
+    for (int i = 0; i < length; i++)
+        writeValue(packet, values[i]);
+}
+
+}  // namespace android
diff --git a/media/mtp/MtpProperty.h b/media/mtp/MtpProperty.h
new file mode 100644
index 0000000..4923d40
--- /dev/null
+++ b/media/mtp/MtpProperty.h
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _MTP_PROPERTY_H
+#define _MTP_PROPERTY_H
+
+#include "MtpTypes.h"
+
+namespace android {
+
+class MtpDataPacket;
+
+class MtpProperty {
+public:
+    MtpPropertyCode     mCode;
+    MtpDataType         mType;
+    bool                mWriteable;
+    MtpPropertyValue    mDefaultValue;
+    MtpPropertyValue    mCurrentValue;
+
+    // for array types
+    int                 mDefaultArrayLength;
+    MtpPropertyValue*   mDefaultArrayValues;
+    int                 mCurrentArrayLength;
+    MtpPropertyValue*   mCurrentArrayValues;
+
+    enum {
+        kFormNone = 0,
+        kFormRange = 1,
+        kFormEnum = 2,
+    };
+    uint8_t             mFormFlag;
+
+    // for range form
+    MtpPropertyValue    mMinimumValue;
+    MtpPropertyValue    mMaximumValue;
+    MtpPropertyValue    mStepSize;
+
+    // for enum form
+    int                 mEnumLength;
+    MtpPropertyValue*   mEnumValues;
+
+public:
+                        MtpProperty();
+                        MtpProperty(MtpPropertyCode propCode,
+                                     MtpDataType type,
+                                     bool writeable = false,
+                                     int defaultValue = 0);
+    virtual             ~MtpProperty();
+
+    inline MtpPropertyCode getPropertyCode() const { return mCode; }
+
+    void                read(MtpDataPacket& packet, bool deviceProp);
+    void                write(MtpDataPacket& packet);
+
+    void                print();
+
+private:
+    void                readValue(MtpDataPacket& packet, MtpPropertyValue& value);
+    void                writeValue(MtpDataPacket& packet, MtpPropertyValue& value);
+    MtpPropertyValue*   readArrayValues(MtpDataPacket& packet, int& length);
+    void                writeArrayValues(MtpDataPacket& packet, MtpPropertyValue* values, int length);
+};
+
+}; // namespace android
+
+#endif // _MTP_PROPERTY_H
diff --git a/media/mtp/MtpRequestPacket.cpp b/media/mtp/MtpRequestPacket.cpp
new file mode 100644
index 0000000..8ece580
--- /dev/null
+++ b/media/mtp/MtpRequestPacket.cpp
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "MtpRequestPacket"
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <fcntl.h>
+
+#include "MtpRequestPacket.h"
+
+namespace android {
+
+MtpRequestPacket::MtpRequestPacket()
+    :   MtpPacket(512)
+{
+}
+
+MtpRequestPacket::~MtpRequestPacket() {
+}
+
+#ifdef MTP_DEVICE
+int MtpRequestPacket::read(int fd) {
+    int ret = ::read(fd, mBuffer, mBufferSize);
+    if (ret >= 0)
+        mPacketSize = ret;
+    else
+        mPacketSize = 0;
+    return ret;
+}
+#endif
+
+#ifdef MTP_HOST
+    // write our buffer to the given endpoint (host mode)
+int MtpRequestPacket::write(struct usb_endpoint *ep)
+{
+    putUInt32(MTP_CONTAINER_LENGTH_OFFSET, mPacketSize);
+    putUInt16(MTP_CONTAINER_TYPE_OFFSET, MTP_CONTAINER_TYPE_COMMAND);
+    return transfer(ep, mBuffer, mPacketSize);
+}
+#endif
+
+}  // namespace android
diff --git a/media/mtp/MtpRequestPacket.h b/media/mtp/MtpRequestPacket.h
new file mode 100644
index 0000000..df518f2
--- /dev/null
+++ b/media/mtp/MtpRequestPacket.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _MTP_REQUEST_PACKET_H
+#define _MTP_REQUEST_PACKET_H
+
+#include "MtpPacket.h"
+#include "mtp.h"
+
+namespace android {
+
+class MtpRequestPacket : public MtpPacket {
+
+public:
+                        MtpRequestPacket();
+    virtual             ~MtpRequestPacket();
+
+#ifdef MTP_DEVICE
+    // fill our buffer with data from the given file descriptor
+    int                 read(int fd);
+#endif
+
+#ifdef MTP_HOST
+    // write our buffer to the given endpoint
+    int                 write(struct usb_endpoint *ep);
+#endif
+
+    inline MtpOperationCode    getOperationCode() const { return getContainerCode(); }
+    inline void                setOperationCode(MtpOperationCode code)
+                                                    { return setContainerCode(code); }
+};
+
+}; // namespace android
+
+#endif // _MTP_REQUEST_PACKET_H
diff --git a/media/mtp/MtpResponsePacket.cpp b/media/mtp/MtpResponsePacket.cpp
new file mode 100644
index 0000000..3ef714e
--- /dev/null
+++ b/media/mtp/MtpResponsePacket.cpp
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "MtpResponsePacket"
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <fcntl.h>
+
+#include "MtpResponsePacket.h"
+
+namespace android {
+
+MtpResponsePacket::MtpResponsePacket()
+    :   MtpPacket(512)
+{
+}
+
+MtpResponsePacket::~MtpResponsePacket() {
+}
+
+#ifdef MTP_DEVICE
+int MtpResponsePacket::write(int fd) {
+    putUInt32(MTP_CONTAINER_LENGTH_OFFSET, mPacketSize);
+    putUInt16(MTP_CONTAINER_TYPE_OFFSET, MTP_CONTAINER_TYPE_RESPONSE);
+    int ret = ::write(fd, mBuffer, mPacketSize);
+    return (ret < 0 ? ret : 0);
+}
+#endif
+
+#ifdef MTP_HOST
+    // read our buffer from the given endpoint
+int MtpResponsePacket::read(struct usb_endpoint *ep) {
+    int ret = transfer(ep, mBuffer, mBufferSize);
+     if (ret >= 0)
+        mPacketSize = ret;
+    else
+        mPacketSize = 0;
+    return ret;
+}
+#endif
+
+}  // namespace android
+
diff --git a/media/mtp/MtpResponsePacket.h b/media/mtp/MtpResponsePacket.h
new file mode 100644
index 0000000..373f8f9
--- /dev/null
+++ b/media/mtp/MtpResponsePacket.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _MTP_RESPONSE_PACKET_H
+#define _MTP_RESPONSE_PACKET_H
+
+#include "MtpPacket.h"
+#include "mtp.h"
+
+namespace android {
+
+class MtpResponsePacket : public MtpPacket {
+
+public:
+                        MtpResponsePacket();
+    virtual             ~MtpResponsePacket();
+
+#ifdef MTP_DEVICE
+    // write our data to the given file descriptor
+    int                 write(int fd);
+#endif
+
+#ifdef MTP_HOST
+    // read our buffer from the given endpoint
+    int                 read(struct usb_endpoint *ep);
+#endif
+
+    inline MtpResponseCode      getResponseCode() const { return getContainerCode(); }
+    inline void                 setResponseCode(MtpResponseCode code)
+                                                     { return setContainerCode(code); }
+};
+
+}; // namespace android
+
+#endif // _MTP_RESPONSE_PACKET_H
diff --git a/media/mtp/MtpServer.cpp b/media/mtp/MtpServer.cpp
new file mode 100644
index 0000000..5f5cadf
--- /dev/null
+++ b/media/mtp/MtpServer.cpp
@@ -0,0 +1,720 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#include <cutils/properties.h>
+
+#include "MtpDebug.h"
+#include "MtpDatabase.h"
+#include "MtpProperty.h"
+#include "MtpServer.h"
+#include "MtpStorage.h"
+#include "MtpStringBuffer.h"
+
+#include <linux/usb/f_mtp.h>
+
+namespace android {
+
+static const MtpOperationCode kSupportedOperationCodes[] = {
+    MTP_OPERATION_GET_DEVICE_INFO,
+    MTP_OPERATION_OPEN_SESSION,
+    MTP_OPERATION_CLOSE_SESSION,
+    MTP_OPERATION_GET_STORAGE_IDS,
+    MTP_OPERATION_GET_STORAGE_INFO,
+    MTP_OPERATION_GET_NUM_OBJECTS,
+    MTP_OPERATION_GET_OBJECT_HANDLES,
+    MTP_OPERATION_GET_OBJECT_INFO,
+    MTP_OPERATION_GET_OBJECT,
+//    MTP_OPERATION_GET_THUMB,
+    MTP_OPERATION_DELETE_OBJECT,
+    MTP_OPERATION_SEND_OBJECT_INFO,
+    MTP_OPERATION_SEND_OBJECT,
+//    MTP_OPERATION_INITIATE_CAPTURE,
+//    MTP_OPERATION_FORMAT_STORE,
+//    MTP_OPERATION_RESET_DEVICE,
+//    MTP_OPERATION_SELF_TEST,
+//    MTP_OPERATION_SET_OBJECT_PROTECTION,
+//    MTP_OPERATION_POWER_DOWN,
+//    MTP_OPERATION_GET_DEVICE_PROP_DESC,
+//    MTP_OPERATION_GET_DEVICE_PROP_VALUE,
+//    MTP_OPERATION_SET_DEVICE_PROP_VALUE,
+//    MTP_OPERATION_RESET_DEVICE_PROP_VALUE,
+//    MTP_OPERATION_TERMINATE_OPEN_CAPTURE,
+//    MTP_OPERATION_MOVE_OBJECT,
+//    MTP_OPERATION_COPY_OBJECT,
+//    MTP_OPERATION_GET_PARTIAL_OBJECT,
+//    MTP_OPERATION_INITIATE_OPEN_CAPTURE,
+    MTP_OPERATION_GET_OBJECT_PROPS_SUPPORTED,
+//    MTP_OPERATION_GET_OBJECT_PROP_DESC,
+    MTP_OPERATION_GET_OBJECT_PROP_VALUE,
+//    MTP_OPERATION_SET_OBJECT_PROP_VALUE,
+    MTP_OPERATION_GET_OBJECT_REFERENCES,
+    MTP_OPERATION_SET_OBJECT_REFERENCES,
+//    MTP_OPERATION_SKIP,
+};
+
+static const MtpEventCode kSupportedEventCodes[] = {
+    MTP_EVENT_OBJECT_ADDED,
+    MTP_EVENT_OBJECT_REMOVED,
+};
+
+static const MtpObjectProperty kSupportedObjectProperties[] = {
+    MTP_PROPERTY_STORAGE_ID,
+    MTP_PROPERTY_OBJECT_FORMAT,
+    MTP_PROPERTY_OBJECT_SIZE,
+    MTP_PROPERTY_OBJECT_FILE_NAME,
+    MTP_PROPERTY_PARENT_OBJECT,
+};
+
+static const MtpObjectFormat kSupportedPlaybackFormats[] = {
+    // MTP_FORMAT_UNDEFINED,
+    MTP_FORMAT_ASSOCIATION,
+    // MTP_FORMAT_TEXT,
+    // MTP_FORMAT_HTML,
+    MTP_FORMAT_MP3,
+    //MTP_FORMAT_AVI,
+    MTP_FORMAT_MPEG,
+    // MTP_FORMAT_ASF,
+    MTP_FORMAT_EXIF_JPEG,
+    MTP_FORMAT_TIFF_EP,
+    // MTP_FORMAT_BMP,
+    MTP_FORMAT_GIF,
+    MTP_FORMAT_JFIF,
+    MTP_FORMAT_PNG,
+    MTP_FORMAT_TIFF,
+    MTP_FORMAT_WMA,
+    MTP_FORMAT_OGG,
+    MTP_FORMAT_AAC,
+    // MTP_FORMAT_FLAC,
+    // MTP_FORMAT_WMV,
+    MTP_FORMAT_MP4_CONTAINER,
+    MTP_FORMAT_MP2,
+    MTP_FORMAT_3GP_CONTAINER,
+    // MTP_FORMAT_ABSTRACT_AUDIO_ALBUM,
+    MTP_FORMAT_ABSTRACT_AV_PLAYLIST,
+    MTP_FORMAT_WPL_PLAYLIST,
+    MTP_FORMAT_M3U_PLAYLIST,
+    // MTP_FORMAT_MPL_PLAYLIST,
+    MTP_FORMAT_PLS_PLAYLIST,
+};
+
+MtpServer::MtpServer(int fd, MtpDatabase* database,
+                    int fileGroup, int filePerm, int directoryPerm)
+    :   mFD(fd),
+        mDatabase(database),
+        mFileGroup(fileGroup),
+        mFilePermission(filePerm),
+        mDirectoryPermission(directoryPerm),
+        mSessionID(0),
+        mSessionOpen(false),
+        mSendObjectHandle(kInvalidObjectHandle),
+        mSendObjectFormat(0),
+        mSendObjectFileSize(0)
+{
+    initObjectProperties();
+}
+
+MtpServer::~MtpServer() {
+}
+
+void MtpServer::addStorage(const char* filePath) {
+    int index = mStorages.size() + 1;
+    index |= index << 16;   // set high and low part to our index
+    MtpStorage* storage = new MtpStorage(index, filePath, mDatabase);
+    addStorage(storage);
+}
+
+MtpStorage* MtpServer::getStorage(MtpStorageID id) {
+    for (int i = 0; i < mStorages.size(); i++) {
+        MtpStorage* storage =  mStorages[i];
+        if (storage->getStorageID() == id)
+            return storage;
+    }
+    return NULL;
+}
+
+void MtpServer::run() {
+    int fd = mFD;
+
+    LOGV("MtpServer::run fd: %d\n", fd);
+
+    while (1) {
+        int ret = mRequest.read(fd);
+        if (ret < 0) {
+            LOGE("request read returned %d, errno: %d", ret, errno);
+            if (errno == ECANCELED) {
+                // return to top of loop and wait for next command
+                continue;
+            }
+            break;
+        }
+        MtpOperationCode operation = mRequest.getOperationCode();
+        MtpTransactionID transaction = mRequest.getTransactionID();
+
+        LOGV("operation: %s", MtpDebug::getOperationCodeName(operation));
+        mRequest.dump();
+
+        // FIXME need to generalize this
+        bool dataIn = (operation == MTP_OPERATION_SEND_OBJECT_INFO
+                    || operation == MTP_OPERATION_SET_OBJECT_REFERENCES);
+        if (dataIn) {
+            int ret = mData.read(fd);
+            if (ret < 0) {
+                LOGE("data read returned %d, errno: %d", ret, errno);
+                if (errno == ECANCELED) {
+                    // return to top of loop and wait for next command
+                    continue;
+                }
+                break;
+            }
+            LOGV("received data:");
+            mData.dump();
+        } else {
+            mData.reset();
+        }
+
+        if (handleRequest()) {
+            if (!dataIn && mData.hasData()) {
+                mData.setOperationCode(operation);
+                mData.setTransactionID(transaction);
+                LOGV("sending data:");
+                mData.dump();
+                ret = mData.write(fd);
+                if (ret < 0) {
+                    LOGE("request write returned %d, errno: %d", ret, errno);
+                    if (errno == ECANCELED) {
+                        // return to top of loop and wait for next command
+                        continue;
+                    }
+                    break;
+                }
+            }
+
+            mResponse.setTransactionID(transaction);
+            LOGV("sending response %04X", mResponse.getResponseCode());
+            ret = mResponse.write(fd);
+            if (ret < 0) {
+                LOGE("request write returned %d, errno: %d", ret, errno);
+                if (errno == ECANCELED) {
+                    // return to top of loop and wait for next command
+                    continue;
+                }
+                break;
+            }
+        } else {
+            LOGV("skipping response\n");
+        }
+    }
+}
+
+MtpProperty* MtpServer::getObjectProperty(MtpPropertyCode propCode) {
+    for (int i = 0; i < mObjectProperties.size(); i++) {
+        MtpProperty* property = mObjectProperties[i];
+        if (property->getPropertyCode() == propCode)
+            return property;
+    }
+    return NULL;
+}
+
+MtpProperty* MtpServer::getDeviceProperty(MtpPropertyCode propCode) {
+    for (int i = 0; i < mDeviceProperties.size(); i++) {
+        MtpProperty* property = mDeviceProperties[i];
+        if (property->getPropertyCode() == propCode)
+            return property;
+    }
+    return NULL;
+}
+
+void MtpServer::sendObjectAdded(MtpObjectHandle handle) {
+    if (mSessionOpen) {
+        LOGD("sendObjectAdded %d\n", handle);
+        mEvent.setEventCode(MTP_EVENT_OBJECT_ADDED);
+        mEvent.setTransactionID(mRequest.getTransactionID());
+        mEvent.setParameter(1, handle);
+        int ret = mEvent.write(mFD);
+        LOGD("mEvent.write returned %d\n", ret);
+    }
+}
+
+void MtpServer::sendObjectRemoved(MtpObjectHandle handle) {
+    if (mSessionOpen) {
+        LOGD("sendObjectRemoved %d\n", handle);
+        mEvent.setEventCode(MTP_EVENT_OBJECT_REMOVED);
+        mEvent.setTransactionID(mRequest.getTransactionID());
+        mEvent.setParameter(1, handle);
+        int ret = mEvent.write(mFD);
+        LOGD("mEvent.write returned %d\n", ret);
+    }
+}
+
+void MtpServer::initObjectProperties() {
+    mObjectProperties.push(new MtpProperty(MTP_PROPERTY_STORAGE_ID, MTP_TYPE_UINT16));
+    mObjectProperties.push(new MtpProperty(MTP_PROPERTY_OBJECT_FORMAT, MTP_TYPE_UINT16));
+    mObjectProperties.push(new MtpProperty(MTP_PROPERTY_OBJECT_SIZE, MTP_TYPE_UINT64));
+    mObjectProperties.push(new MtpProperty(MTP_PROPERTY_OBJECT_FILE_NAME, MTP_TYPE_STR));
+    mObjectProperties.push(new MtpProperty(MTP_PROPERTY_PARENT_OBJECT, MTP_TYPE_UINT32));
+}
+
+bool MtpServer::handleRequest() {
+    MtpOperationCode operation = mRequest.getOperationCode();
+    MtpResponseCode response;
+
+    mResponse.reset();
+
+    if (mSendObjectHandle != kInvalidObjectHandle && operation != MTP_OPERATION_SEND_OBJECT) {
+        // FIXME - need to delete mSendObjectHandle from the database
+        LOGE("expected SendObject after SendObjectInfo");
+        mSendObjectHandle = kInvalidObjectHandle;
+    }
+
+    switch (operation) {
+        case MTP_OPERATION_GET_DEVICE_INFO:
+            response = doGetDeviceInfo();
+            break;
+        case MTP_OPERATION_OPEN_SESSION:
+            response = doOpenSession();
+            break;
+        case MTP_OPERATION_CLOSE_SESSION:
+            response = doCloseSession();
+            break;
+        case MTP_OPERATION_GET_STORAGE_IDS:
+            response = doGetStorageIDs();
+            break;
+         case MTP_OPERATION_GET_STORAGE_INFO:
+            response = doGetStorageInfo();
+            break;
+        case MTP_OPERATION_GET_OBJECT_PROPS_SUPPORTED:
+            response = doGetObjectPropsSupported();
+            break;
+        case MTP_OPERATION_GET_OBJECT_HANDLES:
+            response = doGetObjectHandles();
+            break;
+        case MTP_OPERATION_GET_NUM_OBJECTS:
+            response = doGetNumObjects();
+            break;
+        case MTP_OPERATION_GET_OBJECT_REFERENCES:
+            response = doGetObjectReferences();
+            break;
+        case MTP_OPERATION_SET_OBJECT_REFERENCES:
+            response = doSetObjectReferences();
+            break;
+        case MTP_OPERATION_GET_OBJECT_PROP_VALUE:
+            response = doGetObjectPropValue();
+            break;
+        case MTP_OPERATION_GET_OBJECT_INFO:
+            response = doGetObjectInfo();
+            break;
+        case MTP_OPERATION_GET_OBJECT:
+            response = doGetObject();
+            break;
+        case MTP_OPERATION_SEND_OBJECT_INFO:
+            response = doSendObjectInfo();
+            break;
+        case MTP_OPERATION_SEND_OBJECT:
+            response = doSendObject();
+            break;
+        case MTP_OPERATION_DELETE_OBJECT:
+            response = doDeleteObject();
+            break;
+        case MTP_OPERATION_GET_OBJECT_PROP_DESC:
+            response = doGetObjectPropDesc();
+            break;
+        default:
+            response = MTP_RESPONSE_OPERATION_NOT_SUPPORTED;
+            break;
+    }
+
+    if (response == MTP_RESPONSE_TRANSACTION_CANCELLED)
+        return false;
+    mResponse.setResponseCode(response);
+    return true;
+}
+
+MtpResponseCode MtpServer::doGetDeviceInfo() {
+    MtpStringBuffer   string;
+    char prop_value[PROPERTY_VALUE_MAX];
+
+    // fill in device info
+    mData.putUInt16(MTP_STANDARD_VERSION);
+    mData.putUInt32(6); // MTP Vendor Extension ID
+    mData.putUInt16(MTP_STANDARD_VERSION);
+    string.set("microsoft.com: 1.0;");
+    mData.putString(string); // MTP Extensions
+    mData.putUInt16(0); //Functional Mode
+    mData.putAUInt16(kSupportedOperationCodes,
+            sizeof(kSupportedOperationCodes) / sizeof(uint16_t)); // Operations Supported
+    mData.putAUInt16(kSupportedEventCodes,
+            sizeof(kSupportedEventCodes) / sizeof(uint16_t)); // Events Supported
+    mData.putEmptyArray(); // Device Properties Supported
+    mData.putEmptyArray(); // Capture Formats
+    mData.putAUInt16(kSupportedPlaybackFormats,
+            sizeof(kSupportedPlaybackFormats) / sizeof(uint16_t)); // Playback Formats
+    // FIXME
+    string.set("Google, Inc.");
+    mData.putString(string);   // Manufacturer
+
+    property_get("ro.product.model", prop_value, "MTP Device");
+    string.set(prop_value);
+    mData.putString(string);   // Model
+    string.set("1.0");
+    mData.putString(string);   // Device Version
+
+    property_get("ro.serialno", prop_value, "????????");
+    string.set(prop_value);
+    mData.putString(string);   // Serial Number
+
+    return MTP_RESPONSE_OK;
+}
+
+MtpResponseCode MtpServer::doOpenSession() {
+    if (mSessionOpen) {
+        mResponse.setParameter(1, mSessionID);
+        return MTP_RESPONSE_SESSION_ALREADY_OPEN;
+    }
+    mSessionID = mRequest.getParameter(1);
+    mSessionOpen = true;
+    return MTP_RESPONSE_OK;
+}
+
+MtpResponseCode MtpServer::doCloseSession() {
+    if (!mSessionOpen)
+        return MTP_RESPONSE_SESSION_NOT_OPEN;
+    mSessionID = 0;
+    mSessionOpen = false;
+    return MTP_RESPONSE_OK;
+}
+
+MtpResponseCode MtpServer::doGetStorageIDs() {
+    if (!mSessionOpen)
+        return MTP_RESPONSE_SESSION_NOT_OPEN;
+
+    int count = mStorages.size();
+    mData.putUInt32(count);
+    for (int i = 0; i < count; i++)
+        mData.putUInt32(mStorages[i]->getStorageID());
+
+    return MTP_RESPONSE_OK;
+}
+
+MtpResponseCode MtpServer::doGetStorageInfo() {
+    MtpStringBuffer   string;
+
+    if (!mSessionOpen)
+        return MTP_RESPONSE_SESSION_NOT_OPEN;
+    MtpStorageID id = mRequest.getParameter(1);
+    MtpStorage* storage = getStorage(id);
+    if (!storage)
+        return MTP_RESPONSE_INVALID_STORAGE_ID;
+
+    mData.putUInt16(storage->getType());
+    mData.putUInt16(storage->getFileSystemType());
+    mData.putUInt16(storage->getAccessCapability());
+    mData.putUInt64(storage->getMaxCapacity());
+    mData.putUInt64(storage->getFreeSpace());
+    mData.putUInt32(1024*1024*1024); // Free Space in Objects
+    string.set(storage->getDescription());
+    mData.putString(string);
+    mData.putEmptyString();   // Volume Identifier
+
+    return MTP_RESPONSE_OK;
+}
+
+MtpResponseCode MtpServer::doGetObjectPropsSupported() {
+    if (!mSessionOpen)
+        return MTP_RESPONSE_SESSION_NOT_OPEN;
+    MtpObjectFormat format = mRequest.getParameter(1);
+    mData.putAUInt16(kSupportedObjectProperties,
+            sizeof(kSupportedObjectProperties) / sizeof(uint16_t));
+    return MTP_RESPONSE_OK;
+}
+
+MtpResponseCode MtpServer::doGetObjectHandles() {
+    if (!mSessionOpen)
+        return MTP_RESPONSE_SESSION_NOT_OPEN;
+    MtpStorageID storageID = mRequest.getParameter(1);      // 0xFFFFFFFF for all storage
+    MtpObjectFormat format = mRequest.getParameter(2);      // 0 for all formats
+    MtpObjectHandle parent = mRequest.getParameter(3);      // 0xFFFFFFFF for objects with no parent
+                                                            // 0x00000000 for all objects?
+    if (parent == 0xFFFFFFFF)
+        parent = 0;
+
+    MtpObjectHandleList* handles = mDatabase->getObjectList(storageID, format, parent);
+    mData.putAUInt32(handles);
+    delete handles;
+    return MTP_RESPONSE_OK;
+}
+
+MtpResponseCode MtpServer::doGetNumObjects() {
+    if (!mSessionOpen)
+        return MTP_RESPONSE_SESSION_NOT_OPEN;
+    MtpStorageID storageID = mRequest.getParameter(1);      // 0xFFFFFFFF for all storage
+    MtpObjectFormat format = mRequest.getParameter(2);      // 0 for all formats
+    MtpObjectHandle parent = mRequest.getParameter(3);      // 0xFFFFFFFF for objects with no parent
+                                                            // 0x00000000 for all objects?
+    if (parent == 0xFFFFFFFF)
+        parent = 0;
+
+    int count = mDatabase->getNumObjects(storageID, format, parent);
+    if (count >= 0) {
+        mResponse.setParameter(1, count);
+        return MTP_RESPONSE_OK;
+    } else {
+        mResponse.setParameter(1, 0);
+        return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
+    }
+}
+
+MtpResponseCode MtpServer::doGetObjectReferences() {
+    if (!mSessionOpen)
+        return MTP_RESPONSE_SESSION_NOT_OPEN;
+    MtpStorageID handle = mRequest.getParameter(1);
+    MtpObjectHandleList* handles = mDatabase->getObjectReferences(handle);
+    if (!handles) {
+        mData.putEmptyArray();
+        return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
+    }
+    mData.putAUInt32(handles);
+    delete handles;
+    return MTP_RESPONSE_OK;
+}
+
+MtpResponseCode MtpServer::doSetObjectReferences() {
+    if (!mSessionOpen)
+        return MTP_RESPONSE_SESSION_NOT_OPEN;
+    MtpStorageID handle = mRequest.getParameter(1);
+    MtpObjectHandleList* references = mData.getAUInt32();
+    MtpResponseCode result = mDatabase->setObjectReferences(handle, references);
+    delete references;
+    return result;
+}
+
+MtpResponseCode MtpServer::doGetObjectPropValue() {
+    MtpObjectHandle handle = mRequest.getParameter(1);
+    MtpObjectProperty property = mRequest.getParameter(2);
+
+    return mDatabase->getObjectProperty(handle, property, mData);
+}
+
+MtpResponseCode MtpServer::doGetObjectInfo() {
+    MtpObjectHandle handle = mRequest.getParameter(1);
+    return mDatabase->getObjectInfo(handle, mData);
+}
+
+MtpResponseCode MtpServer::doGetObject() {
+    MtpObjectHandle handle = mRequest.getParameter(1);
+    MtpString pathBuf;
+    int64_t fileLength;
+    int result = mDatabase->getObjectFilePath(handle, pathBuf, fileLength);
+    if (result != MTP_RESPONSE_OK)
+        return result;
+
+    const char* filePath = (const char *)pathBuf;
+    mtp_file_range  mfr;
+    mfr.fd = open(filePath, O_RDONLY);
+    if (mfr.fd < 0) {
+        return MTP_RESPONSE_GENERAL_ERROR;
+    }
+    mfr.offset = 0;
+    mfr.length = fileLength;
+
+    // send data header
+    mData.setOperationCode(mRequest.getOperationCode());
+    mData.setTransactionID(mRequest.getTransactionID());
+    mData.writeDataHeader(mFD, fileLength);
+
+    // then transfer the file
+    int ret = ioctl(mFD, MTP_SEND_FILE, (unsigned long)&mfr);
+    close(mfr.fd);
+    if (ret < 0) {
+        if (errno == ECANCELED)
+            return MTP_RESPONSE_TRANSACTION_CANCELLED;
+        else
+            return MTP_RESPONSE_GENERAL_ERROR;
+    }
+    return MTP_RESPONSE_OK;
+}
+
+MtpResponseCode MtpServer::doSendObjectInfo() {
+    MtpString path;
+    MtpStorageID storageID = mRequest.getParameter(1);
+    MtpStorage* storage = getStorage(storageID);
+    MtpObjectHandle parent = mRequest.getParameter(2);
+    if (!storage)
+        return MTP_RESPONSE_INVALID_STORAGE_ID;
+
+    // special case the root
+    if (parent == MTP_PARENT_ROOT) {
+        path = storage->getPath();
+        parent = 0;
+    } else {
+        int64_t dummy;
+        int result = mDatabase->getObjectFilePath(parent, path, dummy);
+        if (result != MTP_RESPONSE_OK)
+            return result;
+    }
+
+    // read only the fields we need
+    mData.getUInt32();  // storage ID
+    MtpObjectFormat format = mData.getUInt16();
+    mData.getUInt16();  // protection status
+    mSendObjectFileSize = mData.getUInt32();
+    mData.getUInt16();  // thumb format
+    mData.getUInt32();  // thumb compressed size
+    mData.getUInt32();  // thumb pix width
+    mData.getUInt32();  // thumb pix height
+    mData.getUInt32();  // image pix width
+    mData.getUInt32();  // image pix height
+    mData.getUInt32();  // image bit depth
+    mData.getUInt32();  // parent
+    uint16_t associationType = mData.getUInt16();
+    uint32_t associationDesc = mData.getUInt32();   // association desc
+    mData.getUInt32();  // sequence number
+    MtpStringBuffer name, created, modified;
+    mData.getString(name);    // file name
+    mData.getString(created);      // date created
+    mData.getString(modified);     // date modified
+    // keywords follow
+
+    time_t modifiedTime;
+    if (!parseDateTime(modified, modifiedTime))
+        modifiedTime = 0;
+
+    if (path[path.size() - 1] != '/')
+        path += "/";
+    path += (const char *)name;
+
+    MtpObjectHandle handle = mDatabase->beginSendObject((const char*)path,
+            format, parent, storageID, mSendObjectFileSize, modifiedTime);
+    if (handle == kInvalidObjectHandle) {
+        return MTP_RESPONSE_GENERAL_ERROR;
+    }
+
+  if (format == MTP_FORMAT_ASSOCIATION) {
+        mode_t mask = umask(0);
+        int ret = mkdir((const char *)path, mDirectoryPermission);
+        umask(mask);
+        if (ret && ret != -EEXIST)
+            return MTP_RESPONSE_GENERAL_ERROR;
+        chown((const char *)path, getuid(), mFileGroup);
+    } else {
+        mSendObjectFilePath = path;
+        // save the handle for the SendObject call, which should follow
+        mSendObjectHandle = handle;
+        mSendObjectFormat = format;
+    }
+
+    mResponse.setParameter(1, storageID);
+    mResponse.setParameter(2, (parent == 0 ? 0xFFFFFFFF: parent));
+    mResponse.setParameter(3, handle);
+
+    return MTP_RESPONSE_OK;
+}
+
+MtpResponseCode MtpServer::doSendObject() {
+    MtpResponseCode result = MTP_RESPONSE_OK;
+    mode_t mask;
+    int ret;
+
+    if (mSendObjectHandle == kInvalidObjectHandle) {
+        LOGE("Expected SendObjectInfo before SendObject");
+        result = MTP_RESPONSE_NO_VALID_OBJECT_INFO;
+        goto done;
+    }
+
+    // read the header
+    ret = mData.readDataHeader(mFD);
+    // FIXME - check for errors here.
+
+    // reset so we don't attempt to send this back
+    mData.reset();
+
+    mtp_file_range  mfr;
+    mfr.fd = open(mSendObjectFilePath, O_RDWR | O_CREAT | O_TRUNC);
+    if (mfr.fd < 0) {
+        result = MTP_RESPONSE_GENERAL_ERROR;
+        goto done;
+    }
+    fchown(mfr.fd, getuid(), mFileGroup);
+    // set permissions
+    mask = umask(0);
+    fchmod(mfr.fd, mFilePermission);
+    umask(mask);
+
+    mfr.offset = 0;
+    mfr.length = mSendObjectFileSize;
+
+    // transfer the file
+    ret = ioctl(mFD, MTP_RECEIVE_FILE, (unsigned long)&mfr);
+    close(mfr.fd);
+
+    LOGV("MTP_RECEIVE_FILE returned %d", ret);
+
+    if (ret < 0) {
+        unlink(mSendObjectFilePath);
+        if (errno == ECANCELED)
+            result = MTP_RESPONSE_TRANSACTION_CANCELLED;
+        else
+            result = MTP_RESPONSE_GENERAL_ERROR;
+    }
+
+done:
+    mDatabase->endSendObject(mSendObjectFilePath, mSendObjectHandle, mSendObjectFormat,
+            result == MTP_RESPONSE_OK);
+    mSendObjectHandle = kInvalidObjectHandle;
+    mSendObjectFormat = 0;
+    return result;
+}
+
+MtpResponseCode MtpServer::doDeleteObject() {
+    MtpObjectHandle handle = mRequest.getParameter(1);
+    MtpObjectFormat format = mRequest.getParameter(1);
+    // FIXME - support deleting all objects if handle is 0xFFFFFFFF
+    // FIXME - implement deleting objects by format
+    // FIXME - handle non-empty directories
+
+    MtpString filePath;
+    int64_t fileLength;
+    int result = mDatabase->getObjectFilePath(handle, filePath, fileLength);
+    if (result == MTP_RESPONSE_OK) {
+        LOGV("deleting %s", (const char *)filePath);
+        // one of these should work
+        rmdir((const char *)filePath);
+        unlink((const char *)filePath);
+        return mDatabase->deleteFile(handle);
+    } else {
+        return result;
+    }
+}
+
+MtpResponseCode MtpServer::doGetObjectPropDesc() {
+    MtpObjectProperty propCode = mRequest.getParameter(1);
+    MtpObjectFormat format = mRequest.getParameter(2);
+    MtpProperty* property = getObjectProperty(propCode);
+    if (!property)
+        return MTP_RESPONSE_OBJECT_PROP_NOT_SUPPORTED;
+
+    property->write(mData);
+    return MTP_RESPONSE_OK;
+}
+
+}  // namespace android
diff --git a/media/mtp/MtpServer.h b/media/mtp/MtpServer.h
new file mode 100644
index 0000000..19ccf24
--- /dev/null
+++ b/media/mtp/MtpServer.h
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _MTP_SERVER_H
+#define _MTP_SERVER_H
+
+#include "MtpRequestPacket.h"
+#include "MtpDataPacket.h"
+#include "MtpResponsePacket.h"
+#include "MtpEventPacket.h"
+#include "mtp.h"
+
+#include "MtpUtils.h"
+
+namespace android {
+
+class MtpDatabase;
+class MtpProperty;
+class MtpStorage;
+
+class MtpServer {
+
+private:
+    // file descriptor for MTP kernel driver
+    int                 mFD;
+
+    MtpDatabase*        mDatabase;
+
+    // group to own new files and folders
+    int                 mFileGroup;
+    // permissions for new files and directories
+    int                 mFilePermission;
+    int                 mDirectoryPermission;
+
+    // current session ID
+    MtpSessionID        mSessionID;
+    // true if we have an open session and mSessionID is valid
+    bool                mSessionOpen;
+
+    MtpRequestPacket    mRequest;
+    MtpDataPacket       mData;
+    MtpResponsePacket   mResponse;
+    MtpEventPacket      mEvent;
+
+    MtpStorageList      mStorages;
+
+    MtpPropertyList     mObjectProperties;
+    MtpPropertyList     mDeviceProperties;
+
+    // handle for new object, set by SendObjectInfo and used by SendObject
+    MtpObjectHandle     mSendObjectHandle;
+    MtpObjectFormat     mSendObjectFormat;
+    MtpString           mSendObjectFilePath;
+    size_t              mSendObjectFileSize;
+
+public:
+                        MtpServer(int fd, MtpDatabase* database,
+                                    int fileGroup, int filePerm, int directoryPerm);
+    virtual             ~MtpServer();
+
+    void                addStorage(const char* filePath);
+    inline void         addStorage(MtpStorage* storage) { mStorages.push(storage); }
+    MtpStorage*         getStorage(MtpStorageID id);
+    void                run();
+
+    MtpProperty*        getObjectProperty(MtpPropertyCode propCode);
+    MtpProperty*        getDeviceProperty(MtpPropertyCode propCode);
+
+    void                sendObjectAdded(MtpObjectHandle handle);
+    void                sendObjectRemoved(MtpObjectHandle handle);
+
+private:
+    void                initObjectProperties();
+
+    bool                handleRequest();
+
+    MtpResponseCode     doGetDeviceInfo();
+    MtpResponseCode     doOpenSession();
+    MtpResponseCode     doCloseSession();
+    MtpResponseCode     doGetStorageIDs();
+    MtpResponseCode     doGetStorageInfo();
+    MtpResponseCode     doGetObjectPropsSupported();
+    MtpResponseCode     doGetObjectHandles();
+    MtpResponseCode     doGetNumObjects();
+    MtpResponseCode     doGetObjectReferences();
+    MtpResponseCode     doSetObjectReferences();
+    MtpResponseCode     doGetObjectPropValue();
+    MtpResponseCode     doGetObjectInfo();
+    MtpResponseCode     doGetObject();
+    MtpResponseCode     doSendObjectInfo();
+    MtpResponseCode     doSendObject();
+    MtpResponseCode     doDeleteObject();
+    MtpResponseCode     doGetObjectPropDesc();
+};
+
+}; // namespace android
+
+#endif // _MTP_SERVER_H
diff --git a/media/mtp/MtpStorage.cpp b/media/mtp/MtpStorage.cpp
new file mode 100644
index 0000000..eccf186
--- /dev/null
+++ b/media/mtp/MtpStorage.cpp
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "MtpStorage"
+
+#include "MtpDebug.h"
+#include "MtpDatabase.h"
+#include "MtpStorage.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/statfs.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <errno.h>
+#include <string.h>
+#include <stdio.h>
+#include <limits.h>
+
+namespace android {
+
+MtpStorage::MtpStorage(MtpStorageID id, const char* filePath, MtpDatabase* db)
+    :   mStorageID(id),
+        mFilePath(filePath),
+        mDatabase(db),
+        mMaxCapacity(0)
+{
+    LOGD("MtpStorage id: %d path: %s\n", id, filePath);
+}
+
+MtpStorage::~MtpStorage() {
+}
+
+int MtpStorage::getType() const {
+    return MTP_STORAGE_FIXED_RAM;
+}
+
+int MtpStorage::getFileSystemType() const {
+    return MTP_STORAGE_FILESYSTEM_HIERARCHICAL;
+}
+
+int MtpStorage::getAccessCapability() const {
+    return MTP_STORAGE_READ_WRITE;
+}
+
+uint64_t MtpStorage::getMaxCapacity() {
+    if (mMaxCapacity == 0) {
+        struct statfs   stat;
+        if (statfs(mFilePath, &stat))
+            return -1;
+        mMaxCapacity = (uint64_t)stat.f_blocks * (uint64_t)stat.f_bsize;
+    }
+    return mMaxCapacity;
+}
+
+uint64_t MtpStorage::getFreeSpace() {
+    struct statfs   stat;
+    if (statfs(mFilePath, &stat))
+        return -1;
+    return (uint64_t)stat.f_bavail * (uint64_t)stat.f_bsize;
+}
+
+const char* MtpStorage::getDescription() const {
+    return "Device Storage";
+}
+
+}  // namespace android
diff --git a/media/mtp/MtpStorage.h b/media/mtp/MtpStorage.h
new file mode 100644
index 0000000..b13b926
--- /dev/null
+++ b/media/mtp/MtpStorage.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _MTP_STORAGE_H
+#define _MTP_STORAGE_H
+
+#include "mtp.h"
+
+namespace android {
+
+class MtpDatabase;
+
+class MtpStorage {
+
+private:
+    MtpStorageID            mStorageID;
+    const char*             mFilePath;
+    MtpDatabase*            mDatabase;
+    uint64_t                mMaxCapacity;
+
+public:
+                            MtpStorage(MtpStorageID id, const char* filePath, MtpDatabase* db);
+    virtual                 ~MtpStorage();
+
+    inline MtpStorageID     getStorageID() const { return mStorageID; }
+    int                     getType() const;
+    int                     getFileSystemType() const;
+    int                     getAccessCapability() const;
+    uint64_t                getMaxCapacity();
+    uint64_t                getFreeSpace();
+    const char*             getDescription() const;
+    inline const char*      getPath() const { return mFilePath; }
+};
+
+}; // namespace android
+
+#endif // _MTP_STORAGE_H
diff --git a/media/mtp/MtpStorageInfo.cpp b/media/mtp/MtpStorageInfo.cpp
new file mode 100644
index 0000000..ca64ac0
--- /dev/null
+++ b/media/mtp/MtpStorageInfo.cpp
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "MtpStorageInfo"
+
+#include "MtpDebug.h"
+#include "MtpDataPacket.h"
+#include "MtpStorageInfo.h"
+#include "MtpStringBuffer.h"
+
+namespace android {
+
+MtpStorageInfo::MtpStorageInfo(MtpStorageID id)
+    :   mStorageID(id),
+        mStorageType(0),
+        mFileSystemType(0),
+        mAccessCapability(0),
+        mMaxCapacity(0),
+        mFreeSpaceBytes(0),
+        mFreeSpaceObjects(0),
+        mStorageDescription(NULL),
+        mVolumeIdentifier(NULL)
+{
+}
+
+MtpStorageInfo::~MtpStorageInfo() {
+    if (mStorageDescription)
+        free(mStorageDescription);
+    if (mVolumeIdentifier)
+        free(mVolumeIdentifier);
+}
+
+void MtpStorageInfo::read(MtpDataPacket& packet) {
+    MtpStringBuffer string;
+
+    // read the device info
+    mStorageType = packet.getUInt16();
+    mFileSystemType = packet.getUInt16();
+    mAccessCapability = packet.getUInt16();
+    mMaxCapacity = packet.getUInt64();
+    mFreeSpaceBytes = packet.getUInt64();
+    mFreeSpaceObjects = packet.getUInt32();
+
+    packet.getString(string);
+    mStorageDescription = strdup((const char *)string);
+    packet.getString(string);
+    mVolumeIdentifier = strdup((const char *)string);
+}
+
+void MtpStorageInfo::print() {
+    LOGD("Storage Info %08X:\n\tmStorageType: %d\n\tmFileSystemType: %d\n\tmAccessCapability: %d\n",
+            mStorageID, mStorageType, mFileSystemType, mAccessCapability);
+    LOGD("\tmMaxCapacity: %lld\n\tmFreeSpaceBytes: %lld\n\tmFreeSpaceObjects: %d\n",
+            mMaxCapacity, mFreeSpaceBytes, mFreeSpaceObjects);
+    LOGD("\tmStorageDescription: %s\n\tmVolumeIdentifier: %s\n",
+            mStorageDescription, mVolumeIdentifier);
+}
+
+}  // namespace android
diff --git a/media/mtp/MtpStorageInfo.h b/media/mtp/MtpStorageInfo.h
new file mode 100644
index 0000000..2cb626e
--- /dev/null
+++ b/media/mtp/MtpStorageInfo.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _MTP_STORAGE_INFO_H
+#define _MTP_STORAGE_INFO_H
+
+#include "MtpTypes.h"
+
+namespace android {
+
+class MtpDataPacket;
+
+class MtpStorageInfo {
+public:
+    MtpStorageID        mStorageID;
+    uint16_t            mStorageType;
+    uint16_t            mFileSystemType;
+    uint16_t            mAccessCapability;
+    uint64_t            mMaxCapacity;
+    uint64_t            mFreeSpaceBytes;
+    uint32_t            mFreeSpaceObjects;
+    char*               mStorageDescription;
+    char*               mVolumeIdentifier;
+
+public:
+                        MtpStorageInfo(MtpStorageID id);
+    virtual             ~MtpStorageInfo();
+
+    void                read(MtpDataPacket& packet);
+
+    void                print();
+};
+
+}; // namespace android
+
+#endif // _MTP_STORAGE_INFO_H
diff --git a/media/mtp/MtpStringBuffer.cpp b/media/mtp/MtpStringBuffer.cpp
new file mode 100644
index 0000000..2d3cf69
--- /dev/null
+++ b/media/mtp/MtpStringBuffer.cpp
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "MtpStringBuffer"
+
+#include <string.h>
+
+#include "MtpDataPacket.h"
+#include "MtpStringBuffer.h"
+
+namespace android {
+
+MtpStringBuffer::MtpStringBuffer()
+    :   mCharCount(0),
+        mByteCount(1)
+{
+    mBuffer[0] = 0;
+}
+
+MtpStringBuffer::MtpStringBuffer(const char* src)
+    :   mCharCount(0),
+        mByteCount(1)
+{
+    set(src);
+}
+
+MtpStringBuffer::MtpStringBuffer(const MtpStringBuffer& src)
+    :   mCharCount(src.mCharCount),
+        mByteCount(src.mByteCount)
+{
+    memcpy(mBuffer, src.mBuffer, mByteCount);
+}
+
+
+MtpStringBuffer::~MtpStringBuffer() {
+}
+
+void MtpStringBuffer::set(const char* src) {
+    int length = strlen(src);
+    if (length >= sizeof(mBuffer))
+        length = sizeof(mBuffer) - 1;
+    memcpy(mBuffer, src, length);
+
+    // count the characters
+    int count = 0;
+    char ch;
+    while ((ch = *src++) != 0) {
+        if ((ch & 0x80) == 0) {
+            // single byte character
+        } else if ((ch & 0xE0) == 0xC0) {
+            // two byte character
+            if (! *src++) {
+                // last character was truncated, so ignore last byte
+                length--;
+                break;
+            }
+        } else if ((ch & 0xF0) == 0xE0) {
+            // 3 byte char
+            if (! *src++) {
+                // last character was truncated, so ignore last byte
+                length--;
+                break;
+            }
+            if (! *src++) {
+                // last character was truncated, so ignore last two bytes
+                length -= 2;
+                break;
+            }
+        }
+        count++;
+    }
+
+    mByteCount = length + 1;
+    mBuffer[length] = 0;
+    mCharCount = count;
+}
+
+void MtpStringBuffer::readFromPacket(MtpDataPacket* packet) {
+    int count = packet->getUInt8();
+    uint8_t* dest = mBuffer;
+    for (int i = 0; i < count; i++) {
+        uint16_t ch = packet->getUInt16();
+        if (ch >= 0x0800) {
+            *dest++ = (uint8_t)(0xE0 | (ch >> 12));
+            *dest++ = (uint8_t)(0x80 | ((ch >> 6) & 0x3F));
+            *dest++ = (uint8_t)(0x80 | (ch & 0x3F));
+        } else if (ch >= 0x80) {
+            *dest++ = (uint8_t)(0xC0 | (ch >> 6));
+            *dest++ = (uint8_t)(0x80 | (ch & 0x3F));
+        } else {
+            *dest++ = ch;
+        }
+    }
+    *dest++ = 0;
+    mCharCount = count;
+    mByteCount = dest - mBuffer;
+}
+
+void MtpStringBuffer::writeToPacket(MtpDataPacket* packet) const {
+    int count = mCharCount;
+    const uint8_t* src = mBuffer;
+    packet->putUInt8(count);
+
+    // expand utf8 to 16 bit chars
+    for (int i = 0; i < count; i++) {
+        uint16_t ch;
+        uint16_t ch1 = *src++;
+        if ((ch1 & 0x80) == 0) {
+            // single byte character
+            ch = ch1;
+        } else if ((ch1 & 0xE0) == 0xC0) {
+            // two byte character
+            uint16_t ch2 = *src++;
+            ch = ((ch1 & 0x1F) << 6) | (ch2 & 0x3F);
+        } else {
+            // three byte character
+            uint16_t ch2 = *src++;
+            uint16_t ch3 = *src++;
+            ch = ((ch1 & 0x0F) << 12) | ((ch2 & 0x3F) << 6) | (ch3 & 0x3F);
+        }
+        packet->putUInt16(ch);
+    }
+}
+
+}  // namespace android
diff --git a/media/mtp/MtpStringBuffer.h b/media/mtp/MtpStringBuffer.h
new file mode 100644
index 0000000..4641c3f
--- /dev/null
+++ b/media/mtp/MtpStringBuffer.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _MTP_STRING_BUFFER_H
+#define _MTP_STRING_BUFFER_H
+
+#include <stdint.h>
+
+namespace android {
+
+class MtpDataPacket;
+
+// Represents a utf8 string, with a maximum of 255 characters
+class MtpStringBuffer {
+
+private:
+    // maximum 3 bytes/character, with 1 extra for zero termination
+    uint8_t         mBuffer[255 * 3 + 1];
+    int             mCharCount;
+    int             mByteCount;
+
+public:
+                    MtpStringBuffer();
+                    MtpStringBuffer(const char* src);
+                    MtpStringBuffer(const MtpStringBuffer& src);
+    virtual         ~MtpStringBuffer();
+
+    void            set(const char* src);
+
+    void            readFromPacket(MtpDataPacket* packet);
+    void            writeToPacket(MtpDataPacket* packet) const;
+
+    inline int      getCharCount() const { return mCharCount; }
+    inline int      getByteCount() const { return mByteCount; }
+
+	inline operator const char*() const { return (const char *)mBuffer; }
+};
+
+}; // namespace android
+
+#endif // _MTP_STRING_BUFFER_H
diff --git a/media/mtp/MtpTypes.h b/media/mtp/MtpTypes.h
new file mode 100644
index 0000000..2a895a7
--- /dev/null
+++ b/media/mtp/MtpTypes.h
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _MTP_TYPES_H
+#define _MTP_TYPES_H
+
+#include <stdint.h>
+#include "utils/String8.h"
+#include "utils/Vector.h"
+
+namespace android {
+
+typedef int32_t int128_t[4];
+typedef uint32_t uint128_t[4];
+
+typedef uint16_t MtpOperationCode;
+typedef uint16_t MtpResponseCode;
+typedef uint16_t MtpEventCode;
+typedef uint32_t MtpSessionID;
+typedef uint32_t MtpStorageID;
+typedef uint32_t MtpTransactionID;
+typedef uint16_t MtpPropertyCode;
+typedef uint16_t MtpDataType;
+typedef uint16_t MtpObjectFormat;
+typedef MtpPropertyCode MtpDeviceProperty;
+typedef MtpPropertyCode MtpObjectProperty;
+
+// object handles are unique across all storage but only within a single session.
+// object handles cannot be reused after an object is deleted.
+// values 0x00000000 and 0xFFFFFFFF are reserved for special purposes.
+typedef uint32_t MtpObjectHandle;
+
+union MtpPropertyValue {
+    int8_t          i8;
+    uint8_t         u8;
+    int16_t         i16;
+    uint16_t        u16;
+    int32_t         i32;
+    uint32_t        u32;
+    int64_t         i64;
+    uint64_t        u64;
+    int128_t        i128;
+    uint128_t       u128;
+    char*           str;
+};
+
+// Special values
+#define MTP_PARENT_ROOT         0xFFFFFFFF       // parent is root of the storage
+#define kInvalidObjectHandle    0xFFFFFFFF
+
+class MtpStorage;
+class MtpDevice;
+class MtpProperty;
+
+typedef Vector<MtpStorage *> MtpStorageList;
+typedef Vector<MtpDevice*> MtpDeviceList;
+typedef Vector<MtpProperty*> MtpPropertyList;
+
+typedef Vector<uint8_t> UInt8List;
+typedef Vector<uint16_t> UInt16List;
+typedef Vector<uint32_t> UInt32List;
+typedef Vector<uint64_t> UInt64List;
+typedef Vector<int8_t> Int8List;
+typedef Vector<int16_t> Int16List;
+typedef Vector<int32_t> Int32List;
+typedef Vector<int64_t> Int64List;
+
+typedef UInt16List MtpDevicePropertyList;
+typedef UInt16List MtpObjectFormatList;
+typedef UInt32List MtpObjectHandleList;
+typedef UInt16List MtpObjectPropertyList;
+typedef UInt32List MtpStorageIDList;
+
+typedef String8    MtpString;
+
+}; // namespace android
+
+#endif // _MTP_TYPES_H
diff --git a/media/mtp/MtpUtils.cpp b/media/mtp/MtpUtils.cpp
new file mode 100644
index 0000000..ab01ef5
--- /dev/null
+++ b/media/mtp/MtpUtils.cpp
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "MtpUtils"
+
+#include <stdio.h>
+#include <time.h>
+
+#include <cutils/tztime.h>
+#include "MtpUtils.h"
+
+namespace android {
+
+/*
+DateTime strings follow a compatible subset of the definition found in ISO 8601, and
+take the form of a Unicode string formatted as: "YYYYMMDDThhmmss.s". In this
+representation, YYYY shall be replaced by the year, MM replaced by the month (01-12),
+DD replaced by the day (01-31), T is a constant character 'T' delimiting time from date,
+hh is replaced by the hour (00-23), mm is replaced by the minute (00-59), and ss by the
+second (00-59). The ".s" is optional, and represents tenths of a second.
+*/
+
+bool parseDateTime(const char* dateTime, time_t& outSeconds) {
+    int year, month, day, hour, minute, second;
+    struct tm tm;
+
+    if (sscanf(dateTime, "%04d%02d%02dT%02d%02d%02d",
+            &year, &month, &day, &hour, &minute, &second) != 6)
+        return false;
+    const char* tail = dateTime + 15;
+    // skip optional tenth of second
+    if (tail[0] == '.' && tail[1])
+        tail += 2;
+    //FIXME - support +/-hhmm
+    bool useUTC = (tail[0] == 'Z');
+
+    // hack to compute timezone
+    time_t dummy;
+    localtime_r(&dummy, &tm);
+
+    tm.tm_sec = second;
+    tm.tm_min = minute;
+    tm.tm_hour = hour;
+    tm.tm_mday = day;
+    tm.tm_mon = month;
+    tm.tm_year = year - 1900;
+    tm.tm_wday = 0;
+    tm.tm_isdst = -1;
+    if (useUTC)
+        outSeconds = mktime(&tm);
+    else
+        outSeconds = mktime_tz(&tm, tm.tm_zone);
+
+    return true;
+}
+
+void formatDateTime(time_t seconds, char* buffer, int bufferLength) {
+    struct tm tm;
+
+    localtime_r(&seconds, &tm);
+    snprintf(buffer, bufferLength, "%04d%02d%02dT%02d%02d%02d",
+        tm.tm_year + 1900, tm.tm_mon, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec);
+}
+
+}  // namespace android
diff --git a/media/mtp/MtpUtils.h b/media/mtp/MtpUtils.h
new file mode 100644
index 0000000..61f9055
--- /dev/null
+++ b/media/mtp/MtpUtils.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _MTP_UTILS_H
+#define _MTP_UTILS_H
+
+#include <stdint.h>
+
+namespace android {
+
+bool parseDateTime(const char* dateTime, time_t& outSeconds);
+void formatDateTime(time_t seconds, char* buffer, int bufferLength);
+
+}; // namespace android
+
+#endif // _MTP_UTILS_H
diff --git a/media/mtp/mtp.h b/media/mtp/mtp.h
new file mode 100644
index 0000000..224cfb9
--- /dev/null
+++ b/media/mtp/mtp.h
@@ -0,0 +1,475 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _MTP_H
+#define _MTP_H
+
+#include <stdint.h>
+#include <stdlib.h>
+
+#define MTP_STANDARD_VERSION            100
+
+// Container Types
+#define MTP_CONTAINER_TYPE_UNDEFINED    0
+#define MTP_CONTAINER_TYPE_COMMAND      1
+#define MTP_CONTAINER_TYPE_DATA         2
+#define MTP_CONTAINER_TYPE_RESPONSE     3
+#define MTP_CONTAINER_TYPE_EVENT        4
+
+// Container Offsets
+#define MTP_CONTAINER_LENGTH_OFFSET             0
+#define MTP_CONTAINER_TYPE_OFFSET               4
+#define MTP_CONTAINER_CODE_OFFSET               6
+#define MTP_CONTAINER_TRANSACTION_ID_OFFSET     8
+#define MTP_CONTAINER_PARAMETER_OFFSET          12
+#define MTP_CONTAINER_HEADER_SIZE               12
+
+// MTP Types
+#define MTP_TYPE_UNDEFINED      0x0000          // Undefined
+#define MTP_TYPE_INT8           0x0001          // Signed 8-bit integer
+#define MTP_TYPE_UINT8          0x0002          // Unsigned 8-bit integer
+#define MTP_TYPE_INT16          0x0003          // Signed 16-bit integer
+#define MTP_TYPE_UINT16         0x0004          // Unsigned 16-bit integer
+#define MTP_TYPE_INT32          0x0005          // Signed 32-bit integer
+#define MTP_TYPE_UINT32         0x0006          // Unsigned 32-bit integer
+#define MTP_TYPE_INT64          0x0007          // Signed 64-bit integer
+#define MTP_TYPE_UINT64         0x0008          // Unsigned 64-bit integer
+#define MTP_TYPE_INT128         0x0009          // Signed 128-bit integer
+#define MTP_TYPE_UINT128        0x000A          // Unsigned 128-bit integer
+#define MTP_TYPE_AINT8          0x4001          // Array of signed 8-bit integers
+#define MTP_TYPE_AUINT8         0x4002          // Array of unsigned 8-bit integers
+#define MTP_TYPE_AINT16         0x4003          // Array of signed 16-bit integers
+#define MTP_TYPE_AUINT16        0x4004          // Array of unsigned 16-bit integers
+#define MTP_TYPE_AINT32         0x4005          // Array of signed 32-bit integers
+#define MTP_TYPE_AUINT32        0x4006          // Array of unsigned 32-bit integers
+#define MTP_TYPE_AINT64         0x4007          // Array of signed 64-bit integers
+#define MTP_TYPE_AUINT64        0x4008          // Array of unsigned 64-bit integers
+#define MTP_TYPE_AINT128        0x4009          // Array of signed 128-bit integers
+#define MTP_TYPE_AUINT128       0x400A          // Array of unsigned 128-bit integers
+#define MTP_TYPE_STR            0xFFFF          // Variable-length Unicode string
+
+// MTP Format Codes
+#define MTP_FORMAT_UNDEFINED                            0x3000   // Undefined object
+#define MTP_FORMAT_ASSOCIATION                          0x3001   // Association (for example, a folder)
+#define MTP_FORMAT_SCRIPT                               0x3002   // Device model-specific script
+#define MTP_FORMAT_EXECUTABLE                           0x3003   // Device model-specific binary executable
+#define MTP_FORMAT_TEXT                                 0x3004   // Text file
+#define MTP_FORMAT_HTML                                 0x3005   // Hypertext Markup Language file (text)
+#define MTP_FORMAT_DPOF                                 0x3006   // Digital Print Order Format file (text)
+#define MTP_FORMAT_AIFF                                 0x3007   // Audio clip
+#define MTP_FORMAT_WAV                                  0x3008   // Audio clip
+#define MTP_FORMAT_MP3                                  0x3009   // Audio clip
+#define MTP_FORMAT_AVI                                  0x300A   // Video clip
+#define MTP_FORMAT_MPEG                                 0x300B   // Video clip
+#define MTP_FORMAT_ASF                                  0x300C   // Microsoft Advanced Streaming Format (video)
+#define MTP_FORMAT_DEFINED                              0x3800   // Unknown image object
+#define MTP_FORMAT_EXIF_JPEG                            0x3801   // Exchangeable File Format, JEIDA standard
+#define MTP_FORMAT_TIFF_EP                              0x3802   // Tag Image File Format for Electronic Photography
+#define MTP_FORMAT_FLASHPIX                             0x3803   // Structured Storage Image Format
+#define MTP_FORMAT_BMP                                  0x3804   // Microsoft Windows Bitmap file
+#define MTP_FORMAT_CIFF                                 0x3805   // Canon Camera Image File Format
+#define MTP_FORMAT_GIF                                  0x3807   // Graphics Interchange Format
+#define MTP_FORMAT_JFIF                                 0x3808   // JPEG File Interchange Format
+#define MTP_FORMAT_CD                                   0x3809   // PhotoCD Image Pac
+#define MTP_FORMAT_PICT                                 0x380A   // Quickdraw Image Format
+#define MTP_FORMAT_PNG                                  0x380B   // Portable Network Graphics
+#define MTP_FORMAT_TIFF                                 0x380D   // Tag Image File Format
+#define MTP_FORMAT_TIFF_IT                              0x380E   // Tag Image File Format for Information Technology (graphic arts)
+#define MTP_FORMAT_JP2                                  0x380F   // JPEG2000 Baseline File Format
+#define MTP_FORMAT_JPX                                  0x3810   // JPEG2000 Extended File Format
+#define MTP_FORMAT_UNDEFINED_FIRMWARE                   0xB802
+#define MTP_FORMAT_WINDOWS_IMAGE_FORMAT                 0xB881
+#define MTP_FORMAT_UNDEFINED_AUDIO                      0xB900
+#define MTP_FORMAT_WMA                                  0xB901
+#define MTP_FORMAT_OGG                                  0xB902
+#define MTP_FORMAT_AAC                                  0xB903
+#define MTP_FORMAT_AUDIBLE                              0xB904
+#define MTP_FORMAT_FLAC                                 0xB906
+#define MTP_FORMAT_UNDEFINED_VIDEO                      0xB980
+#define MTP_FORMAT_WMV                                  0xB981
+#define MTP_FORMAT_MP4_CONTAINER                        0xB982  // ISO 14496-1
+#define MTP_FORMAT_MP2                                  0xB983
+#define MTP_FORMAT_3GP_CONTAINER                        0xB984  // 3GPP file format. Details: http://www.3gpp.org/ftp/Specs/html-info/26244.htm (page title - \u201cTransparent end-to-end packet switched streaming service, 3GPP file format\u201d).
+#define MTP_FORMAT_UNDEFINED_COLLECTION                 0xBA00
+#define MTP_FORMAT_ABSTRACT_MULTIMEDIA_ALBUM            0xBA01
+#define MTP_FORMAT_ABSTRACT_IMAGE_ALBUM                 0xBA02
+#define MTP_FORMAT_ABSTRACT_AUDIO_ALBUM                 0xBA03
+#define MTP_FORMAT_ABSTRACT_VIDEO_ALBUM                 0xBA04
+#define MTP_FORMAT_ABSTRACT_AV_PLAYLIST                 0xBA05
+#define MTP_FORMAT_ABSTRACT_CONTACT_GROUP               0xBA06
+#define MTP_FORMAT_ABSTRACT_MESSAGE_FOLDER              0xBA07
+#define MTP_FORMAT_ABSTRACT_CHAPTERED_PRODUCTION        0xBA08
+#define MTP_FORMAT_ABSTRACT_AUDIO_PLAYLIST              0xBA09
+#define MTP_FORMAT_ABSTRACT_VIDEO_PLAYLIST              0xBA0A
+#define MTP_FORMAT_ABSTRACT_MEDIACAST                   0xBA0B // For use with mediacasts; references multimedia enclosures of RSS feeds or episodic content
+#define MTP_FORMAT_WPL_PLAYLIST                         0xBA10
+#define MTP_FORMAT_M3U_PLAYLIST                         0xBA11
+#define MTP_FORMAT_MPL_PLAYLIST                         0xBA12
+#define MTP_FORMAT_ASX_PLAYLIST                         0xBA13
+#define MTP_FORMAT_PLS_PLAYLIST                         0xBA14
+#define MTP_FORMAT_UNDEFINED_DOCUMENT                   0xBA80
+#define MTP_FORMAT_ABSTRACT_DOCUMENT                    0xBA81
+#define MTP_FORMAT_XML_DOCUMENT                         0xBA82
+#define MTP_FORMAT_MS_WORD_DOCUMENT                     0xBA83
+#define MTP_FORMAT_MHT_COMPILED_HTML_DOCUMENT           0xBA84
+#define MTP_FORMAT_MS_EXCEL_SPREADSHEET                 0xBA85
+#define MTP_FORMAT_MS_POWERPOINT_PRESENTATION           0xBA86
+#define MTP_FORMAT_UNDEFINED_MESSAGE                    0xBB00
+#define MTP_FORMAT_ABSTRACT_MESSSAGE                    0xBB01
+#define MTP_FORMAT_UNDEFINED_CONTACT                    0xBB80
+#define MTP_FORMAT_ABSTRACT_CONTACT                     0xBB81
+#define MTP_FORMAT_VCARD_2                              0xBB82
+
+// MTP Object Property Codes
+#define MTP_PROPERTY_STORAGE_ID                             0xDC01
+#define MTP_PROPERTY_OBJECT_FORMAT                          0xDC02
+#define MTP_PROPERTY_PROTECTION_STATUS                      0xDC03
+#define MTP_PROPERTY_OBJECT_SIZE                            0xDC04
+#define MTP_PROPERTY_ASSOCIATION_TYPE                       0xDC05
+#define MTP_PROPERTY_ASSOCIATION_DESC                       0xDC06
+#define MTP_PROPERTY_OBJECT_FILE_NAME                       0xDC07
+#define MTP_PROPERTY_DATE_CREATED                           0xDC08
+#define MTP_PROPERTY_DATE_MODIFIED                          0xDC09
+#define MTP_PROPERTY_KEYWORDS                               0xDC0A
+#define MTP_PROPERTY_PARENT_OBJECT                          0xDC0B
+#define MTP_PROPERTY_ALLOWED_FOLDER_CONTENTS                0xDC0C
+#define MTP_PROPERTY_HIDDEN                                 0xDC0D
+#define MTP_PROPERTY_SYSTEM_OBJECT                          0xDC0E
+#define MTP_PROPERTY_PERSISTENT_UID                         0xDC41
+#define MTP_PROPERTY_SYNC_ID                                0xDC42
+#define MTP_PROPERTY_PROPERTY_BAG                           0xDC43
+#define MTP_PROPERTY_NAME                                   0xDC44
+#define MTP_PROPERTY_CREATED_BY                             0xDC45
+#define MTP_PROPERTY_ARTIST                                 0xDC46
+#define MTP_PROPERTY_DATE_AUTHORED                          0xDC47
+#define MTP_PROPERTY_DESCRIPTION                            0xDC48
+#define MTP_PROPERTY_URL_REFERENCE                          0xDC49
+#define MTP_PROPERTY_LANGUAGE_LOCALE                        0xDC4A
+#define MTP_PROPERTY_COPYRIGHT_INFORMATION                  0xDC4B
+#define MTP_PROPERTY_SOURCE                                 0xDC4C
+#define MTP_PROPERTY_ORIGIN_LOCATION                        0xDC4D
+#define MTP_PROPERTY_DATE_ADDED                             0xDC4E
+#define MTP_PROPERTY_NON_CONSUMABLE                         0xDC4F
+#define MTP_PROPERTY_CORRUPT_UNPLAYABLE                     0xDC50
+#define MTP_PROPERTY_PRODUCER_SERIAL_NUMBER                 0xDC51
+#define MTP_PROPERTY_REPRESENTATIVE_SAMPLE_FORMAT           0xDC81
+#define MTP_PROPERTY_REPRESENTATIVE_SAMPLE_SIZE             0xDC82
+#define MTP_PROPERTY_REPRESENTATIVE_SAMPLE_HEIGHT           0xDC83
+#define MTP_PROPERTY_REPRESENTATIVE_SAMPLE_WIDTH            0xDC84
+#define MTP_PROPERTY_REPRESENTATIVE_SAMPLE_DURATION         0xDC85
+#define MTP_PROPERTY_REPRESENTATIVE_SAMPLE_DATA             0xDC86
+#define MTP_PROPERTY_WIDTH                                  0xDC87
+#define MTP_PROPERTY_HEIGHT                                 0xDC88
+#define MTP_PROPERTY_DURATION                               0xDC89
+#define MTP_PROPERTY_RATING                                 0xDC8A
+#define MTP_PROPERTY_TRACK                                  0xDC8B
+#define MTP_PROPERTY_GENRE                                  0xDC8C
+#define MTP_PROPERTY_CREDITS                                0xDC8D
+#define MTP_PROPERTY_LYRICS                                 0xDC8E
+#define MTP_PROPERTY_SUBSCRIPTION_CONTENT_ID                0xDC8F
+#define MTP_PROPERTY_PRODUCED_BY                            0xDC90
+#define MTP_PROPERTY_USE_COUNT                              0xDC91
+#define MTP_PROPERTY_SKIP_COUNT                             0xDC92
+#define MTP_PROPERTY_LAST_ACCESSED                          0xDC93
+#define MTP_PROPERTY_PARENTAL_RATING                        0xDC94
+#define MTP_PROPERTY_META_GENRE                             0xDC95
+#define MTP_PROPERTY_COMPOSER                               0xDC96
+#define MTP_PROPERTY_EFFECTIVE_RATING                       0xDC97
+#define MTP_PROPERTY_SUBTITLE                               0xDC98
+#define MTP_PROPERTY_ORIGINAL_RELEASE_DATE                  0xDC99
+#define MTP_PROPERTY_ALBUM_NAME                             0xDC9A
+#define MTP_PROPERTY_ALBUM_ARTIST                           0xDC9B
+#define MTP_PROPERTY_MOOD                                   0xDC9C
+#define MTP_PROPERTY_DRM_STATUS                             0xDC9D
+#define MTP_PROPERTY_SUB_DESCRIPTION                        0xDC9E
+#define MTP_PROPERTY_IS_CROPPED                             0xDCD1
+#define MTP_PROPERTY_IS_COLOUR_CORRECTED                    0xDCD2
+#define MTP_PROPERTY_IMAGE_BIT_DEPTH                        0xDCD3
+#define MTP_PROPERTY_F_NUMBER                               0xDCD4
+#define MTP_PROPERTY_EXPOSURE_TIME                          0xDCD5
+#define MTP_PROPERTY_EXPOSURE_INDEX                         0xDCD6
+#define MTP_PROPERTY_TOTAL_BITRATE                          0xDE91
+#define MTP_PROPERTY_BITRATE_TYPE                           0xDE92
+#define MTP_PROPERTY_SAMPLE_RATE                            0xDE93
+#define MTP_PROPERTY_NUMBER_OF_CHANNELS                     0xDE94
+#define MTP_PROPERTY_AUDIO_BIT_DEPTH                        0xDE95
+#define MTP_PROPERTY_SCAN_TYPE                              0xDE97
+#define MTP_PROPERTY_AUDIO_WAVE_CODEC                       0xDE99
+#define MTP_PROPERTY_AUDIO_BITRATE                          0xDE9A
+#define MTP_PROPERTY_VIDEO_FOURCC_CODEC                     0xDE9B
+#define MTP_PROPERTY_VIDEO_BITRATE                          0xDE9C
+#define MTP_PROPERTY_FRAMES_PER_THOUSAND_SECONDS            0xDE9D
+#define MTP_PROPERTY_KEYFRAME_DISTANCE                      0xDE9E
+#define MTP_PROPERTY_BUFFER_SIZE                            0xDE9F
+#define MTP_PROPERTY_ENCODING_QUALITY                       0xDEA0
+#define MTP_PROPERTY_ENCODING_PROFILE                       0xDEA1
+#define MTP_PROPERTY_DISPLAY_NAME                           0xDCE0
+#define MTP_PROPERTY_BODY_TEXT                              0xDCE1
+#define MTP_PROPERTY_SUBJECT                                0xDCE2
+#define MTP_PROPERTY_PRIORITY                               0xDCE3
+#define MTP_PROPERTY_GIVEN_NAME                             0xDD00
+#define MTP_PROPERTY_MIDDLE_NAMES                           0xDD01
+#define MTP_PROPERTY_FAMILY_NAME                            0xDD02
+#define MTP_PROPERTY_PREFIX                                 0xDD03
+#define MTP_PROPERTY_SUFFIX                                 0xDD04
+#define MTP_PROPERTY_PHONETIC_GIVEN_NAME                    0xDD05
+#define MTP_PROPERTY_PHONETIC_FAMILY_NAME                   0xDD06
+#define MTP_PROPERTY_EMAIL_PRIMARY                          0xDD07
+#define MTP_PROPERTY_EMAIL_PERSONAL_1                       0xDD08
+#define MTP_PROPERTY_EMAIL_PERSONAL_2                       0xDD09
+#define MTP_PROPERTY_EMAIL_BUSINESS_1                       0xDD0A
+#define MTP_PROPERTY_EMAIL_BUSINESS_2                       0xDD0B
+#define MTP_PROPERTY_EMAIL_OTHERS                           0xDD0C
+#define MTP_PROPERTY_PHONE_NUMBER_PRIMARY                   0xDD0D
+#define MTP_PROPERTY_PHONE_NUMBER_PERSONAL                  0xDD0E
+#define MTP_PROPERTY_PHONE_NUMBER_PERSONAL_2                0xDD0F
+#define MTP_PROPERTY_PHONE_NUMBER_BUSINESS                  0xDD10
+#define MTP_PROPERTY_PHONE_NUMBER_BUSINESS_2                0xDD11
+#define MTP_PROPERTY_PHONE_NUMBER_MOBILE                    0xDD12
+#define MTP_PROPERTY_PHONE_NUMBER_MOBILE_2                  0xDD13
+#define MTP_PROPERTY_FAX_NUMBER_PRIMARY                     0xDD14
+#define MTP_PROPERTY_FAX_NUMBER_PERSONAL                    0xDD15
+#define MTP_PROPERTY_FAX_NUMBER_BUSINESS                    0xDD16
+#define MTP_PROPERTY_PAGER_NUMBER                           0xDD17
+#define MTP_PROPERTY_PHONE_NUMBER_OTHERS                    0xDD18
+#define MTP_PROPERTY_PRIMARY_WEB_ADDRESS                    0xDD19
+#define MTP_PROPERTY_PERSONAL_WEB_ADDRESS                   0xDD1A
+#define MTP_PROPERTY_BUSINESS_WEB_ADDRESS                   0xDD1B
+#define MTP_PROPERTY_INSTANT_MESSANGER_ADDRESS              0xDD1C
+#define MTP_PROPERTY_INSTANT_MESSANGER_ADDRESS_2            0xDD1D
+#define MTP_PROPERTY_INSTANT_MESSANGER_ADDRESS_3            0xDD1E
+#define MTP_PROPERTY_POSTAL_ADDRESS_PERSONAL_FULL           0xDD1F
+#define MTP_PROPERTY_POSTAL_ADDRESS_PERSONAL_LINE_1         0xDD20
+#define MTP_PROPERTY_POSTAL_ADDRESS_PERSONAL_LINE_2         0xDD21
+#define MTP_PROPERTY_POSTAL_ADDRESS_PERSONAL_CITY           0xDD22
+#define MTP_PROPERTY_POSTAL_ADDRESS_PERSONAL_REGION         0xDD23
+#define MTP_PROPERTY_POSTAL_ADDRESS_PERSONAL_POSTAL_CODE    0xDD24
+#define MTP_PROPERTY_POSTAL_ADDRESS_PERSONAL_COUNTRY        0xDD25
+#define MTP_PROPERTY_POSTAL_ADDRESS_BUSINESS_FULL           0xDD26
+#define MTP_PROPERTY_POSTAL_ADDRESS_BUSINESS_LINE_1         0xDD27
+#define MTP_PROPERTY_POSTAL_ADDRESS_BUSINESS_LINE_2         0xDD28
+#define MTP_PROPERTY_POSTAL_ADDRESS_BUSINESS_CITY           0xDD29
+#define MTP_PROPERTY_POSTAL_ADDRESS_BUSINESS_REGION         0xDD2A
+#define MTP_PROPERTY_POSTAL_ADDRESS_BUSINESS_POSTAL_CODE    0xDD2B
+#define MTP_PROPERTY_POSTAL_ADDRESS_BUSINESS_COUNTRY        0xDD2C
+#define MTP_PROPERTY_POSTAL_ADDRESS_OTHER_FULL              0xDD2D
+#define MTP_PROPERTY_POSTAL_ADDRESS_OTHER_LINE_1            0xDD2E
+#define MTP_PROPERTY_POSTAL_ADDRESS_OTHER_LINE_2            0xDD2F
+#define MTP_PROPERTY_POSTAL_ADDRESS_OTHER_CITY              0xDD30
+#define MTP_PROPERTY_POSTAL_ADDRESS_OTHER_REGION            0xDD31
+#define MTP_PROPERTY_POSTAL_ADDRESS_OTHER_POSTAL_CODE       0xDD32
+#define MTP_PROPERTY_POSTAL_ADDRESS_OTHER_COUNTRY           0xDD33
+#define MTP_PROPERTY_ORGANIZATION_NAME                      0xDD34
+#define MTP_PROPERTY_PHONETIC_ORGANIZATION_NAME             0xDD35
+#define MTP_PROPERTY_ROLE                                   0xDD36
+#define MTP_PROPERTY_BIRTHDATE                              0xDD37
+#define MTP_PROPERTY_MESSAGE_TO                             0xDD40
+#define MTP_PROPERTY_MESSAGE_CC                             0xDD41
+#define MTP_PROPERTY_MESSAGE_BCC                            0xDD42
+#define MTP_PROPERTY_MESSAGE_READ                           0xDD43
+#define MTP_PROPERTY_MESSAGE_RECEIVED_TIME                  0xDD44
+#define MTP_PROPERTY_MESSAGE_SENDER                         0xDD45
+#define MTP_PROPERTY_ACTIVITY_BEGIN_TIME                    0xDD50
+#define MTP_PROPERTY_ACTIVITY_END_TIME                      0xDD51
+#define MTP_PROPERTY_ACTIVITY_LOCATION                      0xDD52
+#define MTP_PROPERTY_ACTIVITY_REQUIRED_ATTENDEES            0xDD54
+#define MTP_PROPERTY_ACTIVITY_OPTIONAL_ATTENDEES            0xDD55
+#define MTP_PROPERTY_ACTIVITY_RESOURCES                     0xDD56
+#define MTP_PROPERTY_ACTIVITY_ACCEPTED                      0xDD57
+#define MTP_PROPERTY_ACTIVITY_TENTATIVE                     0xDD58
+#define MTP_PROPERTY_ACTIVITY_DECLINED                      0xDD59
+#define MTP_PROPERTY_ACTIVITY_REMAINDER_TIME                0xDD5A
+#define MTP_PROPERTY_ACTIVITY_OWNER                         0xDD5B
+#define MTP_PROPERTY_ACTIVITY_STATUS                        0xDD5C
+#define MTP_PROPERTY_OWNER                                  0xDD5D
+#define MTP_PROPERTY_EDITOR                                 0xDD5E
+#define MTP_PROPERTY_WEBMASTER                              0xDD5F
+#define MTP_PROPERTY_URL_SOURCE                             0xDD60
+#define MTP_PROPERTY_URL_DESTINATION                        0xDD61
+#define MTP_PROPERTY_TIME_BOOKMARK                          0xDD62
+#define MTP_PROPERTY_OBJECT_BOOKMARK                        0xDD63
+#define MTP_PROPERTY_BYTE_BOOKMARK                          0xDD64
+#define MTP_PROPERTY_LAST_BUILD_DATE                        0xDD70
+#define MTP_PROPERTY_TIME_TO_LIVE                           0xDD71
+#define MTP_PROPERTY_MEDIA_GUID                             0xDD72
+
+// MTP Device Property Codes
+#define MTP_DEVICE_PROPERTY_UNDEFINED                       0x5000
+#define MTP_DEVICE_PROPERTY_BATTERY_LEVEL                   0x5001
+#define MTP_DEVICE_PROPERTY_FUNCTIONAL_MODE                 0x5002
+#define MTP_DEVICE_PROPERTY_IMAGE_SIZE                      0x5003
+#define MTP_DEVICE_PROPERTY_COMPRESSION_SETTING             0x5004
+#define MTP_DEVICE_PROPERTY_WHITE_BALANCE                   0x5005
+#define MTP_DEVICE_PROPERTY_RGB_GAIN                        0x5006
+#define MTP_DEVICE_PROPERTY_F_NUMBER                        0x5007
+#define MTP_DEVICE_PROPERTY_FOCAL_LENGTH                    0x5008
+#define MTP_DEVICE_PROPERTY_FOCUS_DISTANCE                  0x5009
+#define MTP_DEVICE_PROPERTY_FOCUS_MODE                      0x500A
+#define MTP_DEVICE_PROPERTY_EXPOSURE_METERING_MODE          0x500B
+#define MTP_DEVICE_PROPERTY_FLASH_MODE                      0x500C
+#define MTP_DEVICE_PROPERTY_EXPOSURE_TIME                   0x500D
+#define MTP_DEVICE_PROPERTY_EXPOSURE_PROGRAM_MODE           0x500E
+#define MTP_DEVICE_PROPERTY_EXPOSURE_INDEX                  0x500F
+#define MTP_DEVICE_PROPERTY_EXPOSURE_BIAS_COMPENSATION      0x5010
+#define MTP_DEVICE_PROPERTY_DATETIME                        0x5011
+#define MTP_DEVICE_PROPERTY_CAPTURE_DELAY                   0x5012
+#define MTP_DEVICE_PROPERTY_STILL_CAPTURE_MODE              0x5013
+#define MTP_DEVICE_PROPERTY_CONTRAST                        0x5014
+#define MTP_DEVICE_PROPERTY_SHARPNESS                       0x5015
+#define MTP_DEVICE_PROPERTY_DIGITAL_ZOOM                    0x5016
+#define MTP_DEVICE_PROPERTY_EFFECT_MODE                     0x5017
+#define MTP_DEVICE_PROPERTY_BURST_NUMBER                    0x5018
+#define MTP_DEVICE_PROPERTY_BURST_INTERVAL                  0x5019
+#define MTP_DEVICE_PROPERTY_TIMELAPSE_NUMBER                0x501A
+#define MTP_DEVICE_PROPERTY_TIMELAPSE_INTERVAL              0x501B
+#define MTP_DEVICE_PROPERTY_FOCUS_METERING_MODE             0x501C
+#define MTP_DEVICE_PROPERTY_UPLOAD_URL                      0x501D
+#define MTP_DEVICE_PROPERTY_ARTIST                          0x501E
+#define MTP_DEVICE_PROPERTY_COPYRIGHT_INFO                  0x501F
+#define MTP_DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER         0xD401
+#define MTP_DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME            0xD402
+#define MTP_DEVICE_PROPERTY_VOLUME                          0xD403
+#define MTP_DEVICE_PROPERTY_SUPPORTED_FORMATS_ORDERED       0xD404
+#define MTP_DEVICE_PROPERTY_DEVICE_ICON                     0xD405
+#define MTP_DEVICE_PROPERTY_PLAYBACK_RATE                   0xD410
+#define MTP_DEVICE_PROPERTY_PLAYBACK_OBJECT                 0xD411
+#define MTP_DEVICE_PROPERTY_PLAYBACK_CONTAINER_INDEX        0xD412
+#define MTP_DEVICE_PROPERTY_SESSION_INITIATOR_VERSION_INFO  0xD406
+#define MTP_DEVICE_PROPERTY_PERCEIVED_DEVICE_TYPE           0xD407
+
+// MTP Operation Codes
+#define MTP_OPERATION_GET_DEVICE_INFO                       0x1001
+#define MTP_OPERATION_OPEN_SESSION                          0x1002
+#define MTP_OPERATION_CLOSE_SESSION                         0x1003
+#define MTP_OPERATION_GET_STORAGE_IDS                       0x1004
+#define MTP_OPERATION_GET_STORAGE_INFO                      0x1005
+#define MTP_OPERATION_GET_NUM_OBJECTS                       0x1006
+#define MTP_OPERATION_GET_OBJECT_HANDLES                    0x1007
+#define MTP_OPERATION_GET_OBJECT_INFO                       0x1008
+#define MTP_OPERATION_GET_OBJECT                            0x1009
+#define MTP_OPERATION_GET_THUMB                             0x100A
+#define MTP_OPERATION_DELETE_OBJECT                         0x100B
+#define MTP_OPERATION_SEND_OBJECT_INFO                      0x100C
+#define MTP_OPERATION_SEND_OBJECT                           0x100D
+#define MTP_OPERATION_INITIATE_CAPTURE                      0x100E
+#define MTP_OPERATION_FORMAT_STORE                          0x100F
+#define MTP_OPERATION_RESET_DEVICE                          0x1010
+#define MTP_OPERATION_SELF_TEST                             0x1011
+#define MTP_OPERATION_SET_OBJECT_PROTECTION                 0x1012
+#define MTP_OPERATION_POWER_DOWN                            0x1013
+#define MTP_OPERATION_GET_DEVICE_PROP_DESC                  0x1014
+#define MTP_OPERATION_GET_DEVICE_PROP_VALUE                 0x1015
+#define MTP_OPERATION_SET_DEVICE_PROP_VALUE                 0x1016
+#define MTP_OPERATION_RESET_DEVICE_PROP_VALUE               0x1017
+#define MTP_OPERATION_TERMINATE_OPEN_CAPTURE                0x1018
+#define MTP_OPERATION_MOVE_OBJECT                           0x1019
+#define MTP_OPERATION_COPY_OBJECT                           0x101A
+#define MTP_OPERATION_GET_PARTIAL_OBJECT                    0x101B
+#define MTP_OPERATION_INITIATE_OPEN_CAPTURE                 0x101C
+#define MTP_OPERATION_GET_OBJECT_PROPS_SUPPORTED            0x9801
+#define MTP_OPERATION_GET_OBJECT_PROP_DESC                  0x9802
+#define MTP_OPERATION_GET_OBJECT_PROP_VALUE                 0x9803
+#define MTP_OPERATION_SET_OBJECT_PROP_VALUE                 0x9804
+#define MTP_OPERATION_GET_OBJECT_REFERENCES                 0x9810
+#define MTP_OPERATION_SET_OBJECT_REFERENCES                 0x9811
+#define MTP_OPERATION_SKIP                                  0x9820
+
+// MTP Response Codes
+#define MTP_RESPONSE_UNDEFINED                                  0x2000
+#define MTP_RESPONSE_OK                                         0x2001
+#define MTP_RESPONSE_GENERAL_ERROR                              0x2002
+#define MTP_RESPONSE_SESSION_NOT_OPEN                           0x2003
+#define MTP_RESPONSE_INVALID_TRANSACTION_ID                     0x2004
+#define MTP_RESPONSE_OPERATION_NOT_SUPPORTED                    0x2005
+#define MTP_RESPONSE_PARAMETER_NOT_SUPPORTED                    0x2006
+#define MTP_RESPONSE_INCOMPLETE_TRANSFER                        0x2007
+#define MTP_RESPONSE_INVALID_STORAGE_ID                         0x2008
+#define MTP_RESPONSE_INVALID_OBJECT_HANDLE                      0x2009
+#define MTP_RESPONSE_DEVICE_PROP_NOT_SUPPORTED                  0x200A
+#define MTP_RESPONSE_INVALID_OBJECT_FORMAT_CODE                 0x200B
+#define MTP_RESPONSE_STORAGE_FULL                               0x200C
+#define MTP_RESPONSE_OBJECT_WRITE_PROTECTED                     0x200D
+#define MTP_RESPONSE_STORE_READ_ONLY                            0x200E
+#define MTP_RESPONSE_ACCESS_DENIED                              0x200F
+#define MTP_RESPONSE_NO_THUMBNAIL_PRESENT                       0x2010
+#define MTP_RESPONSE_SELF_TEST_FAILED                           0x2011
+#define MTP_RESPONSE_PARTIAL_DELETION                           0x2012
+#define MTP_RESPONSE_STORE_NOT_AVAILABLE                        0x2013
+#define MTP_RESPONSE_SPECIFICATION_BY_FORMAT_UNSUPPORTED        0x2014
+#define MTP_RESPONSE_NO_VALID_OBJECT_INFO                       0x2015
+#define MTP_RESPONSE_INVALID_CODE_FORMAT                        0x2016
+#define MTP_RESPONSE_UNKNOWN_VENDOR_CODE                        0x2017
+#define MTP_RESPONSE_CAPTURE_ALREADY_TERMINATED                 0x2018
+#define MTP_RESPONSE_DEVICE_BUSY                                0x2019
+#define MTP_RESPONSE_INVALID_PARENT_OBJECT                      0x201A
+#define MTP_RESPONSE_INVALID_DEVICE_PROP_FORMAT                 0x201B
+#define MTP_RESPONSE_INVALID_DEVICE_PROP_VALUE                  0x201C
+#define MTP_RESPONSE_INVALID_PARAMETER                          0x201D
+#define MTP_RESPONSE_SESSION_ALREADY_OPEN                       0x201E
+#define MTP_RESPONSE_TRANSACTION_CANCELLED                      0x201F
+#define MTP_RESPONSE_SPECIFICATION_OF_DESTINATION_UNSUPPORTED   0x2020
+#define MTP_RESPONSE_INVALID_OBJECT_PROP_CODE                   0xA801
+#define MTP_RESPONSE_INVALID_OBJECT_PROP_FORMAT                 0xA802
+#define MTP_RESPONSE_INVALID_OBJECT_PROP_VALUE                  0xA803
+#define MTP_RESPONSE_INVALID_OBJECT_REFERENCE                   0xA804
+#define MTP_RESPONSE_GROUP_NOT_SUPPORTED                        0xA805
+#define MTP_RESPONSE_INVALID_DATASET                            0xA806
+#define MTP_RESPONSE_SPECIFICATION_BY_GROUP_UNSUPPORTED         0xA807
+#define MTP_RESPONSE_SPECIFICATION_BY_DEPTH_UNSUPPORTED         0xA808
+#define MTP_RESPONSE_OBJECT_TOO_LARGE                           0xA809
+#define MTP_RESPONSE_OBJECT_PROP_NOT_SUPPORTED                  0xA80A
+
+// MTP Event Codes
+#define MTP_EVENT_UNDEFINED                         0x4000
+#define MTP_EVENT_CANCEL_TRANSACTION                0x4001
+#define MTP_EVENT_OBJECT_ADDED                      0x4002
+#define MTP_EVENT_OBJECT_REMOVED                    0x4003
+#define MTP_EVENT_STORE_ADDED                       0x4004
+#define MTP_EVENT_STORE_REMOVED                     0x4005
+#define MTP_EVENT_DEVICE_PROP_CHANGED               0x4006
+#define MTP_EVENT_OBJECT_INFO_CHANGED               0x4007
+#define MTP_EVENT_DEVICE_INFO_CHANGED               0x4008
+#define MTP_EVENT_REQUEST_OBJECT_TRANSFER           0x4009
+#define MTP_EVENT_STORE_FULL                        0x400A
+#define MTP_EVENT_DEVICE_RESET                      0x400B
+#define MTP_EVENT_STORAGE_INFO_CHANGED              0x400C
+#define MTP_EVENT_CAPTURE_COMPLETE                  0x400D
+#define MTP_EVENT_UNREPORTED_STATUS                 0x400E
+#define MTP_EVENT_OBJECT_PROP_CHANGED               0xC801
+#define MTP_EVENT_OBJECT_PROP_DESC_CHANGED          0xC802
+#define MTP_EVENT_OBJECT_REFERENCES_CHANGED         0xC803
+
+// Storage Type
+#define MTP_STORAGE_FIXED_ROM                       0x0001
+#define MTP_STORAGE_REMOVABLE_ROM                   0x0002
+#define MTP_STORAGE_FIXED_RAM                       0x0003
+#define MTP_STORAGE_REMOVABLE_RAM                   0x0004
+
+// Storage File System
+#define MTP_STORAGE_FILESYSTEM_FLAT                 0x0001
+#define MTP_STORAGE_FILESYSTEM_HIERARCHICAL         0x0002
+#define MTP_STORAGE_FILESYSTEM_DCF                  0x0003
+
+// Storage Access Capability
+#define MTP_STORAGE_READ_WRITE                      0x0000
+#define MTP_STORAGE_READ_ONLY_WITHOUT_DELETE        0x0000
+#define MTP_STORAGE_READ_ONLY_WITH_DELETE           0x0000
+
+// Association Type
+#define MTP_ASSOCIATION_TYPE_UNDEFINED              0x0000
+#define MTP_ASSOCIATION_TYPE_GENERIC_FOLDER         0x0001
+
+#endif // _MTP_H
diff --git a/media/tests/CameraBrowser/Android.mk b/media/tests/CameraBrowser/Android.mk
new file mode 100644
index 0000000..33d2976
--- /dev/null
+++ b/media/tests/CameraBrowser/Android.mk
@@ -0,0 +1,8 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_PACKAGE_NAME := CameraBrowser
+
+include $(BUILD_PACKAGE)
diff --git a/media/tests/CameraBrowser/AndroidManifest.xml b/media/tests/CameraBrowser/AndroidManifest.xml
new file mode 100644
index 0000000..eae0b01
--- /dev/null
+++ b/media/tests/CameraBrowser/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.camerabrowser">
+
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+
+    <application android:label="@string/app_label">
+        <activity android:name="CameraBrowser" android:label="Camera Browser">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+
+        <activity android:name="StorageBrowser" />
+        <activity android:name="ObjectBrowser" />
+        <activity android:name="ObjectViewer" />
+
+        <receiver android:name="UsbReceiver">
+            <intent-filter>
+                <action android:name="android.hardware.action.USB_CAMERA_ATTACHED" />
+                <data android:scheme="content"/>
+            </intent-filter>
+        </receiver>
+
+    </application>
+
+
+</manifest>
diff --git a/media/tests/CameraBrowser/res/layout/object_info.xml b/media/tests/CameraBrowser/res/layout/object_info.xml
new file mode 100644
index 0000000..c7fd830
--- /dev/null
+++ b/media/tests/CameraBrowser/res/layout/object_info.xml
@@ -0,0 +1,147 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 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.
+*/
+-->
+<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/object_info"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content">
+    <TableRow>
+        <TextView android:id="@+id/name_label"
+            android:text="@string/name_label"
+            android:layout_gravity="right"
+            android:layout_marginRight="8dip"
+            style="@style/info_label" />
+
+        <TextView android:id="@+id/name"
+            style="@style/info_value" />
+    </TableRow>
+    <TableRow>
+        <TextView android:id="@+id/size_label"
+            android:text="@string/size_label"
+            android:layout_gravity="right"
+            android:layout_marginRight="8dip"
+            style="@style/info_label" />
+
+        <TextView android:id="@+id/size"
+            style="@style/info_value" />
+    </TableRow>
+    <TableRow>
+        <TextView android:id="@+id/thumb_width_label"
+            android:text="@string/thumb_width_label"
+            android:layout_gravity="right"
+            android:layout_marginRight="8dip"
+            style="@style/info_label" />
+
+        <TextView android:id="@+id/thumb_width"
+            style="@style/info_value" />
+    </TableRow>
+    <TableRow>
+        <TextView android:id="@+id/thumb_height_label"
+            android:text="@string/thumb_height_label"
+            android:layout_gravity="right"
+            android:layout_marginRight="8dip"
+            style="@style/info_label" />
+
+        <TextView android:id="@+id/thumb_height"
+            style="@style/info_value" />
+    </TableRow>
+    <TableRow>
+        <TextView android:id="@+id/thumb_size_label"
+            android:text="@string/thumb_size_label"
+            android:layout_gravity="right"
+            android:layout_marginRight="8dip"
+            style="@style/info_label" />
+
+        <TextView android:id="@+id/thumb_size"
+            style="@style/info_value" />
+    </TableRow>
+    <TableRow>
+        <TextView android:id="@+id/width_label"
+            android:text="@string/width_label"
+            android:layout_gravity="right"
+            android:layout_marginRight="8dip"
+            style="@style/info_label" />
+
+        <TextView android:id="@+id/width"
+            style="@style/info_value" />
+    </TableRow>
+    <TableRow>
+        <TextView android:id="@+id/height_label"
+            android:text="@string/height_label"
+            android:layout_gravity="right"
+            android:layout_marginRight="8dip"
+            style="@style/info_label" />
+
+        <TextView android:id="@+id/height"
+            style="@style/info_value" />
+    </TableRow>
+    <TableRow>
+        <TextView android:id="@+id/depth_label"
+            android:text="@string/depth_label"
+            android:layout_gravity="right"
+            android:layout_marginRight="8dip"
+            style="@style/info_label" />
+
+        <TextView android:id="@+id/depth"
+            style="@style/info_value" />
+    </TableRow>
+    <TableRow>
+        <TextView android:id="@+id/sequence_label"
+            android:text="@string/sequence_label"
+            android:layout_gravity="right"
+            android:layout_marginRight="8dip"
+            style="@style/info_label" />
+
+        <TextView android:id="@+id/sequence"
+            style="@style/info_value" />
+    </TableRow>
+    <TableRow>
+        <TextView android:id="@+id/created_label"
+            android:text="@string/created_label"
+            android:layout_gravity="right"
+            android:layout_marginRight="8dip"
+            style="@style/info_label" />
+
+        <TextView android:id="@+id/created"
+            style="@style/info_value" />
+    </TableRow>
+    <TableRow>
+        <TextView android:id="@+id/modified_label"
+            android:text="@string/modified_label"
+            android:layout_gravity="right"
+            android:layout_marginRight="8dip"
+            style="@style/info_label" />
+
+        <TextView android:id="@+id/modified"
+            style="@style/info_value" />
+    </TableRow>
+    <TableRow>
+        <TextView android:id="@+id/keywords_label"
+            android:text="@string/keywords_label"
+            android:layout_gravity="right"
+            android:layout_marginRight="8dip"
+            style="@style/info_label" />
+
+        <TextView android:id="@+id/keywords"
+            style="@style/info_value" />
+    </TableRow>
+    <TableRow>
+        <ImageView android:id="@+id/thumbnail" />
+    </TableRow>
+</TableLayout>
+
diff --git a/media/tests/CameraBrowser/res/layout/object_list.xml b/media/tests/CameraBrowser/res/layout/object_list.xml
new file mode 100644
index 0000000..30c18bb
--- /dev/null
+++ b/media/tests/CameraBrowser/res/layout/object_list.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="horizontal"
+    android:layout_width="fill_parent" 
+    android:layout_height="fill_parent">
+    
+    <ImageView android:id="@+id/thumbnail"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content" />
+
+    <TextView android:id="@+id/name"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textAppearance="?android:attr/textAppearanceLarge"
+        android:gravity="center_vertical"
+        android:paddingLeft="6dip"
+        android:minHeight="?android:attr/listPreferredItemHeight" />
+</LinearLayout>
diff --git a/media/tests/CameraBrowser/res/menu/object_menu.xml b/media/tests/CameraBrowser/res/menu/object_menu.xml
new file mode 100644
index 0000000..a0865f0
--- /dev/null
+++ b/media/tests/CameraBrowser/res/menu/object_menu.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <item android:id="@+id/save"
+        android:title="@string/save_item" />
+    <item android:id="@+id/delete"
+        android:title="@string/delete_item" />
+</menu>
diff --git a/media/tests/CameraBrowser/res/values/strings.xml b/media/tests/CameraBrowser/res/values/strings.xml
new file mode 100644
index 0000000..56c5111
--- /dev/null
+++ b/media/tests/CameraBrowser/res/values/strings.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<resources>
+    <string name="app_label">Camera Browser</string>
+
+    <!-- for object info -->
+    <string name="name_label">Name: </string>
+    <string name="size_label">Size: </string>
+    <string name="thumb_width_label">Thumb Width: </string>
+    <string name="thumb_height_label">Thumb Height: </string>
+    <string name="thumb_size_label">Thumb Size: </string>
+    <string name="width_label">Width: </string>
+    <string name="height_label">Height: </string>
+    <string name="depth_label">Depth: </string>
+    <string name="sequence_label">Sequence: </string>
+    <string name="created_label">Created: </string>
+    <string name="modified_label">Modified: </string>
+    <string name="keywords_label">Keywords: </string>
+
+    <!-- menu items -->
+    <string name="save_item">Save</string>
+    <string name="delete_item">Delete</string>
+
+    <!-- toasts -->
+    <string name="object_saved_message">Object saved</string>
+    <string name="save_failed_message">Could not save object</string>
+    <string name="object_deleted_message">Object deleted</string>
+    <string name="delete_failed_message">Could not delete object</string>
+
+</resources>
diff --git a/media/tests/CameraBrowser/res/values/styles.xml b/media/tests/CameraBrowser/res/values/styles.xml
new file mode 100644
index 0000000..c869985
--- /dev/null
+++ b/media/tests/CameraBrowser/res/values/styles.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<resources>
+    <style name="info_label">
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:layout_width">wrap_content</item>
+        <item name="android:textSize">14sp</item>
+        <item name="android:textStyle">bold</item>
+        <item name="android:paddingRight">4dip</item>
+    </style>
+
+    <style name="info_value">
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:layout_width">wrap_content</item>
+        <item name="android:textSize">14sp</item>
+        <item name="android:textStyle">normal</item>
+    </style>
+
+</resources>
+
diff --git a/media/tests/CameraBrowser/src/com/android/camerabrowser/CameraBrowser.java b/media/tests/CameraBrowser/src/com/android/camerabrowser/CameraBrowser.java
new file mode 100644
index 0000000..c04873a
--- /dev/null
+++ b/media/tests/CameraBrowser/src/com/android/camerabrowser/CameraBrowser.java
@@ -0,0 +1,103 @@
+/*
+ * 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.camerabrowser;
+
+import android.app.ListActivity;
+import android.content.ContentResolver;
+import android.content.Intent;
+import android.database.ContentObserver;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.provider.Mtp;
+import android.util.Log;
+import android.view.View;
+import android.widget.ListAdapter;
+import android.widget.ListView;
+import android.widget.SimpleCursorAdapter;
+
+ /**
+ * A list view displaying all connected cameras.
+ */
+public class CameraBrowser extends ListActivity {
+
+    private static final String TAG = "CameraBrowser";
+
+    private ListAdapter mAdapter;
+    private ContentResolver mResolver;
+    private DeviceObserver mDeviceObserver;
+    private Cursor mCursor;
+
+    private class DeviceObserver extends ContentObserver {
+        DeviceObserver(Handler handler) {
+            super(handler);
+        }
+
+        @Override
+        public void onChange(boolean selfChange) {
+            Log.d(TAG, "DeviceObserver.onChange");
+            if (mCursor != null) {
+                mCursor.requery();
+            }
+        }
+    }
+
+    private static final String[] DEVICE_COLUMNS =
+         new String[] { Mtp.Device._ID, Mtp.Device.MANUFACTURER, Mtp.Device.MODEL };
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        mResolver = getContentResolver();
+        mDeviceObserver = new DeviceObserver(new Handler());
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+
+        Cursor c = getContentResolver().query(Mtp.Device.CONTENT_URI,
+                DEVICE_COLUMNS, null, null, null);
+        Log.d(TAG, "query returned " + c);
+        startManagingCursor(c);
+        mCursor = c;
+
+        // Map Cursor columns to views defined in simple_list_item_2.xml
+        mAdapter = new SimpleCursorAdapter(this,
+                android.R.layout.simple_list_item_2, c,
+                        new String[] { Mtp.Device.MANUFACTURER, Mtp.Device.MODEL },
+                        new int[] { android.R.id.text1, android.R.id.text2 });
+        setListAdapter(mAdapter);
+
+        // register for changes to the device list
+        mResolver.registerContentObserver(Mtp.Device.CONTENT_URI, true, mDeviceObserver);
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+        mResolver.unregisterContentObserver(mDeviceObserver);
+    }
+
+    @Override
+    protected void onListItemClick(ListView l, View v, int position, long id) {
+        Intent intent = new Intent(this, StorageBrowser.class);
+        intent.putExtra("device", (int)mAdapter.getItemId(position));
+        startActivity(intent);
+    }
+}
diff --git a/media/tests/CameraBrowser/src/com/android/camerabrowser/ObjectBrowser.java b/media/tests/CameraBrowser/src/com/android/camerabrowser/ObjectBrowser.java
new file mode 100644
index 0000000..a002028
--- /dev/null
+++ b/media/tests/CameraBrowser/src/com/android/camerabrowser/ObjectBrowser.java
@@ -0,0 +1,146 @@
+/*
+ * 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.camerabrowser;
+
+import android.app.ListActivity;
+import android.content.Context;
+import android.content.Intent;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.Mtp;
+import android.util.Log;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.ResourceCursorAdapter;
+import android.widget.TextView;
+
+ /**
+ * A list view displaying all objects within a container (folder or storage unit).
+ */
+public class ObjectBrowser extends ListActivity {
+
+    private static final String TAG = "ObjectBrowser";
+
+    private Cursor mCursor;
+    private ObjectCursorAdapter mAdapter;
+    private int mDeviceID;
+    private int mStorageID;
+    private int mObjectID;
+
+    private static final String[] OBJECT_COLUMNS =
+        new String[] { Mtp.Object._ID, Mtp.Object.NAME, Mtp.Object.FORMAT, Mtp.Object.THUMB };
+
+    static final int ID_COLUMN = 0;
+    static final int NAME_COLUMN = 1;
+    static final int FORMAT_COLUMN = 2;
+    static final int THUMB_COLUMN = 3;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+
+        mDeviceID = getIntent().getIntExtra("device", 0);
+        mStorageID = getIntent().getIntExtra("storage", 0);
+        mObjectID = getIntent().getIntExtra("object", 0);
+        if (mDeviceID != 0 && mStorageID != 0) {
+            Cursor c;
+            Uri uri;
+            if (mObjectID == 0) {
+                uri = Mtp.Object.getContentUriForStorageChildren(mDeviceID, mStorageID);
+            } else {
+                uri = Mtp.Object.getContentUriForObjectChildren(mDeviceID, mObjectID);
+            }
+            Log.d(TAG, "query " + uri);
+            c = getContentResolver().query(uri, OBJECT_COLUMNS, null, null, null);
+            startManagingCursor(c);
+            mCursor = c;
+
+            // Map Cursor columns to views defined in simple_list_item_1.xml
+            mAdapter = new ObjectCursorAdapter(this, c);
+            setListAdapter(mAdapter);
+        }
+    }
+
+    @Override
+    protected void onListItemClick(ListView l, View v, int position, long id) {
+        int rowID = (int)mAdapter.getItemId(position);
+        Cursor c = getContentResolver().query(
+                        Mtp.Object.getContentUri(mDeviceID, rowID),
+                        OBJECT_COLUMNS, null, null, null);
+        Log.d(TAG, "query returned " + c + " count: " + c.getCount());
+        long format = 0;
+        if (c != null && c.getCount() == 1) {
+            c.moveToFirst();
+            long rowId = c.getLong(ID_COLUMN);
+            String name = c.getString(NAME_COLUMN);
+            format = c.getLong(FORMAT_COLUMN);
+            Log.d(TAG, "rowId: " + rowId + " name: " + name + " format: " + format);
+        }
+        if (format == Mtp.Object.FORMAT_JFIF) {
+            Intent intent = new Intent(this, ObjectViewer.class);
+            intent.putExtra("device", mDeviceID);
+            intent.putExtra("storage", mStorageID);
+            intent.putExtra("object",rowID);
+            startActivity(intent);
+        } else {
+            Intent intent = new Intent(this, ObjectBrowser.class);
+            intent.putExtra("device", mDeviceID);
+            intent.putExtra("storage", mStorageID);
+            intent.putExtra("object", rowID);
+            startActivity(intent);
+        }
+    }
+
+    private class ObjectCursorAdapter extends ResourceCursorAdapter {
+
+        public ObjectCursorAdapter(Context context, Cursor c) {
+            super(context, R.layout.object_list, c);
+        }
+
+        @Override
+        public void bindView(View view, Context context, Cursor cursor) {
+            ImageView thumbView = (ImageView)view.findViewById(R.id.thumbnail);
+            TextView nameView = (TextView)view.findViewById(R.id.name);
+
+            // get the thumbnail
+            byte[] thumbnail = cursor.getBlob(THUMB_COLUMN);
+            if (thumbnail != null) {
+                Bitmap bitmap = BitmapFactory.decodeByteArray(thumbnail, 0, thumbnail.length);
+                if (bitmap != null) {
+                    thumbView.setImageBitmap(bitmap);
+                }
+            }
+
+            // get the name
+            String name = cursor.getString(NAME_COLUMN);
+            if (name == null) {
+                name = "";
+            }
+            nameView.setText(name);
+        }
+    }
+}
diff --git a/media/tests/CameraBrowser/src/com/android/camerabrowser/ObjectViewer.java b/media/tests/CameraBrowser/src/com/android/camerabrowser/ObjectViewer.java
new file mode 100644
index 0000000..aa49cd8
--- /dev/null
+++ b/media/tests/CameraBrowser/src/com/android/camerabrowser/ObjectViewer.java
@@ -0,0 +1,252 @@
+/*
+ * 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.camerabrowser;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Environment;
+import android.os.FileUtils;
+import android.os.ParcelFileDescriptor;
+import android.os.Process;
+import android.provider.Mtp;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.util.Calendar;
+import java.util.Date;
+
+/**
+ * A view to display the properties of an object.
+ */
+public class ObjectViewer extends Activity {
+
+    private static final String TAG = "ObjectViewer";
+
+    private int mDeviceID;
+    private int mStorageID;
+    private int mObjectID;
+
+    private static final String[] OBJECT_COLUMNS =
+        new String[] {  Mtp.Object._ID,
+                        Mtp.Object.NAME,
+                        Mtp.Object.SIZE,
+                        Mtp.Object.THUMB_WIDTH,
+                        Mtp.Object.THUMB_HEIGHT,
+                        Mtp.Object.THUMB_SIZE,
+                        Mtp.Object.IMAGE_WIDTH,
+                        Mtp.Object.IMAGE_HEIGHT,
+                        Mtp.Object.IMAGE_DEPTH,
+                        Mtp.Object.SEQUENCE_NUMBER,
+                        Mtp.Object.DATE_CREATED,
+                        Mtp.Object.DATE_MODIFIED,
+                        Mtp.Object.KEYWORDS,
+                        Mtp.Object.THUMB,
+                        };
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.object_info);
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+
+        mDeviceID = getIntent().getIntExtra("device", 0);
+        mStorageID = getIntent().getIntExtra("storage", 0);
+        mObjectID = getIntent().getIntExtra("object", 0);
+
+        if (mDeviceID != 0 && mObjectID != 0) {
+        Cursor c = getContentResolver().query(
+                        Mtp.Object.getContentUri(mDeviceID, mObjectID),
+                        OBJECT_COLUMNS, null, null, null);
+            c.moveToFirst();
+            TextView view = (TextView)findViewById(R.id.name);
+            view.setText(c.getString(1));
+            view = (TextView)findViewById(R.id.size);
+            view.setText(Long.toString(c.getLong(2)));
+            view = (TextView)findViewById(R.id.thumb_width);
+            view.setText(Long.toString(c.getLong(3)));
+            view = (TextView)findViewById(R.id.thumb_height);
+            view.setText(Long.toString(c.getLong(4)));
+            view = (TextView)findViewById(R.id.thumb_size);
+            view.setText(Long.toString(c.getLong(5)));
+            view = (TextView)findViewById(R.id.width);
+            view.setText(Long.toString(c.getLong(6)));
+            view = (TextView)findViewById(R.id.height);
+            view.setText(Long.toString(c.getLong(7)));
+            view = (TextView)findViewById(R.id.depth);
+            view.setText(Long.toString(c.getLong(8)));
+            view = (TextView)findViewById(R.id.sequence);
+            view.setText(Long.toString(c.getLong(9)));
+            view = (TextView)findViewById(R.id.created);
+            Date date = new Date(c.getLong(10) * 1000);
+            view.setText(date.toString());
+            view = (TextView)findViewById(R.id.modified);
+            date = new Date(c.getLong(11) * 1000);
+            view.setText(date.toString());
+            view = (TextView)findViewById(R.id.keywords);
+            view.setText(c.getString(12));
+            byte[] thumbnail = c.getBlob(13);
+            if (thumbnail != null) {
+                ImageView thumbView = (ImageView)findViewById(R.id.thumbnail);
+                Bitmap bitmap = BitmapFactory.decodeByteArray(thumbnail, 0, thumbnail.length);
+                if (bitmap != null) {
+                    thumbView.setImageBitmap(bitmap);
+                }
+            }
+        }
+    }
+
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        MenuInflater inflater = getMenuInflater();
+        inflater.inflate(R.menu.object_menu, menu);
+        return true;
+    }
+
+    @Override
+    public boolean onPrepareOptionsMenu(Menu menu) {
+        MenuItem item = menu.findItem(R.id.save);
+        item.setEnabled(true);
+        item = menu.findItem(R.id.delete);
+        item.setEnabled(true);
+        return true;
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        switch (item.getItemId()) {
+            case R.id.save:
+                save();
+                return true;
+            case R.id.delete:
+                delete();
+                return true;
+        }
+        return false;
+    }
+
+    private static String getTimestamp() {
+        Calendar c = Calendar.getInstance();
+        c.setTimeInMillis(System.currentTimeMillis());
+        return String.format("%tY-%tm-%td-%tH-%tM-%tS", c, c, c, c, c, c);
+    }
+
+    private void save() {
+        boolean success = false;
+        Uri uri = Mtp.Object.getContentUri(mDeviceID, mObjectID);
+        File destFile = null;
+        ParcelFileDescriptor pfd = null;
+        FileInputStream fis = null;
+        FileOutputStream fos = null;
+
+        try {
+            pfd = getContentResolver().openFileDescriptor(uri, "r");
+            Log.d(TAG, "save got pfd " + pfd);
+            if (pfd != null) {
+                fis = new FileInputStream(pfd.getFileDescriptor());
+                Log.d(TAG, "save got fis " + fis);
+                File destDir = Environment.getExternalStoragePublicDirectory(
+                        Environment.DIRECTORY_DCIM);
+                destDir.mkdirs();
+                destFile = new File(destDir, "CameraBrowser-" + getTimestamp() + ".jpeg");
+
+
+                Log.d(TAG, "save got destFile " + destFile);
+
+                if (destFile.exists()) {
+                    destFile.delete();
+                }
+                fos = new FileOutputStream(destFile);
+
+                byte[] buffer = new byte[65536];
+                int bytesRead;
+                while ((bytesRead = fis.read(buffer)) >= 0) {
+                    fos.write(buffer, 0, bytesRead);
+                }
+
+                // temporary workaround until we straighten out permissions in /data/media
+                // 1015 is AID_SDCARD_RW
+                FileUtils.setPermissions(destDir.getPath(), 0775, Process.myUid(), 1015);
+                FileUtils.setPermissions(destFile.getPath(), 0664, Process.myUid(), 1015);
+
+                success = true;
+            }
+        } catch (Exception e) {
+            Log.e(TAG, "Exception in ObjectView.save", e);
+        } finally {
+            if (fis != null) {
+                try {
+                    fis.close();
+                } catch (Exception e) {
+                }
+            }
+            if (fos != null) {
+                try {
+                    fos.close();
+                } catch (Exception e) {
+                }
+            }
+            if (pfd != null) {
+                try {
+                    pfd.close();
+                } catch (Exception e) {
+                }
+            }
+        }
+
+        if (success) {
+            Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
+            intent.setData(Uri.fromFile(destFile));
+            sendBroadcast(intent);
+            Toast.makeText(this, R.string.object_saved_message, Toast.LENGTH_SHORT).show();
+        } else {
+            Toast.makeText(this, R.string.save_failed_message, Toast.LENGTH_SHORT).show();
+        }
+    }
+
+    private void delete() {
+        Uri uri = Mtp.Object.getContentUri(mDeviceID, mObjectID);
+
+        Log.d(TAG, "deleting " + uri);
+
+        int result = getContentResolver().delete(uri, null, null);
+        if (result > 0) {
+            Toast.makeText(this, R.string.object_deleted_message, Toast.LENGTH_SHORT).show();
+            finish();
+        } else {
+            Toast.makeText(this, R.string.delete_failed_message, Toast.LENGTH_SHORT).show();
+        }
+    }
+}
diff --git a/media/tests/CameraBrowser/src/com/android/camerabrowser/StorageBrowser.java b/media/tests/CameraBrowser/src/com/android/camerabrowser/StorageBrowser.java
new file mode 100644
index 0000000..6ddf4e7
--- /dev/null
+++ b/media/tests/CameraBrowser/src/com/android/camerabrowser/StorageBrowser.java
@@ -0,0 +1,76 @@
+/*
+ * 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.camerabrowser;
+
+import android.app.ListActivity;
+import android.content.Intent;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.Mtp;
+import android.util.Log;
+import android.view.View;
+import android.widget.ListAdapter;
+import android.widget.ListView;
+import android.widget.SimpleCursorAdapter;
+
+/**
+ * A list view displaying all storage units on a device.
+ */
+public class StorageBrowser extends ListActivity {
+
+    private static final String TAG = "StorageBrowser";
+
+    private ListAdapter mAdapter;
+    private int mDeviceID;
+
+    private static final String[] STORAGE_COLUMNS =
+        new String[] { Mtp.Storage._ID, Mtp.Storage.DESCRIPTION };
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+
+        mDeviceID = getIntent().getIntExtra("device", 0);
+        if (mDeviceID != 0) {
+            Cursor c = getContentResolver().query(Mtp.Storage.getContentUri(mDeviceID),
+                    STORAGE_COLUMNS, null, null, null);
+            Log.d(TAG, "query returned " + c);
+            startManagingCursor(c);
+
+            // Map Cursor columns to views defined in simple_list_item_1.xml
+            mAdapter = new SimpleCursorAdapter(this,
+                    android.R.layout.simple_list_item_1, c,
+                            new String[] { Mtp.Storage.DESCRIPTION },
+                            new int[] { android.R.id.text1, android.R.id.text2 });
+            setListAdapter(mAdapter);
+        }
+    }
+
+    @Override
+    protected void onListItemClick(ListView l, View v, int position, long id) {
+        Intent intent = new Intent(this, ObjectBrowser.class);
+        intent.putExtra("device", mDeviceID);
+        intent.putExtra("storage", (int)mAdapter.getItemId(position));
+        startActivity(intent);
+    }
+}
diff --git a/media/tests/CameraBrowser/src/com/android/camerabrowser/UsbReceiver.java b/media/tests/CameraBrowser/src/com/android/camerabrowser/UsbReceiver.java
new file mode 100644
index 0000000..c05b239
--- /dev/null
+++ b/media/tests/CameraBrowser/src/com/android/camerabrowser/UsbReceiver.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 com.android.camerabrowser;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.BroadcastReceiver;
+import android.hardware.Usb;
+import android.net.Uri;
+import android.util.Log;
+
+public class UsbReceiver extends BroadcastReceiver
+{
+    private static final String TAG = "UsbReceiver";
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        Log.d(TAG, "onReceive " + intent);
+        if (Usb.ACTION_USB_CAMERA_ATTACHED.equals(intent.getAction())) {
+            Uri uri = intent.getData();
+            intent = new Intent(context, StorageBrowser.class);
+            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            try {
+                // TODO - add a wrapper to Mtp.Device for this
+                int id = Integer.parseInt(uri.getPathSegments().get(1));
+                intent.putExtra("device", id);
+                context.startActivity(intent);
+            } catch (NumberFormatException e) {
+                Log.e(TAG, "bad device Uri " + uri);
+            }
+        }
+    }
+}
diff --git a/media/tests/mtp/Android.mk b/media/tests/mtp/Android.mk
new file mode 100644
index 0000000..a9074ed
--- /dev/null
+++ b/media/tests/mtp/Android.mk
@@ -0,0 +1,61 @@
+LOCAL_PATH:= $(call my-dir)
+
+ifneq ($(TARGET_SIMULATOR),true)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := \
+	mtp.cpp \
+	MtpFile.cpp \
+
+LOCAL_C_INCLUDES += \
+    frameworks/base/media/mtp \
+
+LOCAL_CFLAGS := -DMTP_HOST
+
+LOCAL_MODULE := mtp
+
+LOCAL_STATIC_LIBRARIES := libmtp libusbhost libutils libcutils
+
+include $(BUILD_EXECUTABLE)
+
+endif
+
+ifeq ($(HOST_OS),linux)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := \
+	mtp.cpp \
+	MtpFile.cpp \
+	../../../libs/utils/RefBase.cpp \
+	../../../libs/utils/SharedBuffer.cpp \
+	../../../libs/utils/Threads.cpp \
+	../../../libs/utils/VectorImpl.cpp \
+
+LOCAL_C_INCLUDES += \
+    frameworks/base/media/mtp \
+
+LOCAL_CFLAGS := -DMTP_HOST -g -O0
+
+have_readline := $(wildcard /usr/include/readline/readline.h)
+have_history := $(wildcard /usr/lib/libhistory*)
+ifneq ($(strip $(have_readline)),)
+LOCAL_CFLAGS += -DHAVE_READLINE=1
+endif
+
+LOCAL_LDLIBS += -lpthread
+ifneq ($(strip $(have_readline)),)
+LOCAL_LDLIBS += -lreadline -lncurses
+endif
+ifneq ($(strip $(have_history)),)
+LOCAL_LDLIBS += -lhistory
+endif
+
+LOCAL_MODULE := mtp
+
+LOCAL_STATIC_LIBRARIES := libmtp libusbhost libcutils
+
+include $(BUILD_HOST_EXECUTABLE)
+
+endif
diff --git a/media/tests/mtp/MtpFile.cpp b/media/tests/mtp/MtpFile.cpp
new file mode 100644
index 0000000..00d328e
--- /dev/null
+++ b/media/tests/mtp/MtpFile.cpp
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "MtpClient.h"
+#include "MtpDevice.h"
+#include "MtpDeviceInfo.h"
+#include "MtpObjectInfo.h"
+#include "MtpStorage.h"
+#include "MtpUtils.h"
+
+#include "MtpFile.h"
+
+namespace android {
+
+MtpClient* MtpFile::sClient = NULL;
+
+MtpFile::MtpFile(MtpDevice* device)
+    :   mDevice(device),
+        mStorage(0),
+        mHandle(0)
+{
+}
+
+MtpFile::MtpFile(MtpDevice* device, MtpStorageID storage)
+    :   mDevice(device),
+        mStorage(storage),
+        mHandle(0)
+{
+}
+
+MtpFile::MtpFile(MtpDevice* device, MtpStorageID storage, MtpObjectHandle handle)
+    :   mDevice(device),
+        mStorage(storage),
+        mHandle(handle)
+{
+}
+
+MtpFile::MtpFile(MtpFile* file)
+    :   mDevice(file->mDevice),
+        mStorage(file->mStorage),
+        mHandle(file->mHandle)
+{
+}
+
+MtpFile::~MtpFile() {
+}
+
+void MtpFile::print() {
+    if (mHandle) {
+
+    } else if (mStorage) {
+        printf("%x\n", mStorage);
+    } else {
+        int id = mDevice->getID();
+        MtpDeviceInfo* info = mDevice->getDeviceInfo();
+        if (info)
+            printf("%d\t%s %s %s\n", id, info->mManufacturer, info->mModel, info->mSerial);
+        else
+            printf("%d\t(no device info available)\n", id);
+        delete info;
+    }
+}
+
+MtpObjectInfo* MtpFile::getObjectInfo() {
+    return mDevice->getObjectInfo(mHandle);
+}
+
+void MtpFile::list() {
+    if (mStorage) {
+        MtpObjectHandleList* handles = mDevice->getObjectHandles(mStorage, 0,
+                                                (mHandle ? mHandle : -1));
+        if (handles) {
+            for (int i = 0; i < handles->size(); i++) {
+                MtpObjectHandle handle = (*handles)[i];
+                MtpObjectInfo* info = mDevice->getObjectInfo(handle);
+                if (info) {
+                    char modified[100];
+                    struct tm tm;
+
+                    gmtime_r(&info->mDateModified, &tm);
+                    strftime(modified, sizeof(modified), "%a %b %e %H:%M:%S GMT %Y", &tm);
+                    printf("%s Handle: %d Format: %04X Size: %d Modified: %s\n",
+                            info->mName, handle, info->mFormat, info->mCompressedSize, modified);
+                    delete info;
+                }
+            }
+            delete handles;
+        }
+    } else {
+        // list storage units for device
+        MtpStorageIDList* storageList = mDevice->getStorageIDs();
+        for (int i = 0; i < storageList->size(); i++) {
+            MtpStorageID storageID = (*storageList)[i];
+            printf("%x\n", storageID);
+        }
+    }
+}
+
+void MtpFile::init(MtpClient* client) {
+    sClient = client;
+}
+
+MtpFile* MtpFile::parsePath(MtpFile* base, char* path) {
+    MtpDevice* device = NULL;
+    MtpStorageID storage = 0;
+    MtpObjectHandle handle = 0;
+
+    if (path[0] != '/' && base) {
+        device = base->mDevice;
+        storage = base->mStorage;
+        handle = base->mHandle;
+    }
+
+    // parse an absolute path
+    if (path[0] == '/')
+        path++;
+    char* tok = strtok(path, "/");
+    while (tok) {
+        if (storage) {
+            // find child of current handle
+            MtpObjectHandleList* handles = device->getObjectHandles(storage, 0,
+                                                    (handle ? handle : -1));
+            MtpObjectHandle childHandle = 0;
+
+            if (handles) {
+                for (int i = 0; i < handles->size() && !childHandle; i++) {
+                    MtpObjectHandle handle = (*handles)[i];
+                    MtpObjectInfo* info = device->getObjectInfo(handle);
+                    if (info && !strcmp(tok, info->mName))
+                        childHandle = handle;
+                    delete info;
+                }
+                delete handles;
+            }
+            if (childHandle)
+                handle = childHandle;
+            else
+                return NULL;
+        } else if (device) {
+            unsigned int id;
+            // find storage for the device
+            if (sscanf(tok, "%x", &id) == 1) {
+                MtpStorageIDList* storageList = device->getStorageIDs();
+                bool found = false;
+                for (int i = 0; i < storageList->size(); i++) {
+                    if ((*storageList)[i] == id) {
+                        found = true;
+                        break;
+                    }
+                }
+                if (found)
+                    storage = id;
+                else
+                    return NULL;
+            }
+        } else {
+            // find device
+            unsigned int id;
+            if (sscanf(tok, "%d", &id) == 1)
+                device = sClient->getDevice(id);
+            if (!device)
+                return NULL;
+        }
+
+        tok = strtok(NULL, "/");
+    }
+
+    if (device)
+        return new MtpFile(device, storage, handle);
+    else
+        return NULL;
+}
+
+}
diff --git a/media/tests/mtp/MtpFile.h b/media/tests/mtp/MtpFile.h
new file mode 100644
index 0000000..ab8762b
--- /dev/null
+++ b/media/tests/mtp/MtpFile.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _MTP_FILE_H
+#define _MTP_FILE_H
+
+#include "MtpTypes.h"
+
+namespace android {
+
+class MtpClient;
+class MtpDevice;
+class MtpObjectInfo;
+
+// File-like abstraction for the interactive shell.
+// This can be used to represent an MTP device, storage unit or object
+// (either file or association).
+class MtpFile {
+private:
+    MtpDevice*          mDevice;
+    MtpStorageID        mStorage;
+    MtpObjectHandle     mHandle;
+    static MtpClient*   sClient;
+
+public:
+    MtpFile(MtpDevice* device);
+    MtpFile(MtpDevice* device, MtpStorageID storage);
+    MtpFile(MtpDevice* device, MtpStorageID storage, MtpObjectHandle handle);
+    MtpFile(MtpFile* file);
+    virtual ~MtpFile();
+
+    MtpObjectInfo* getObjectInfo();
+    void print();
+    void list();
+
+    inline MtpDevice* getDevice() const { return mDevice; }
+
+    static void init(MtpClient* client);
+    static MtpFile* parsePath(MtpFile* base, char* path);
+};
+
+}
+
+#endif // _MTP_DIRECTORY_H
diff --git a/media/tests/mtp/mtp.cpp b/media/tests/mtp/mtp.cpp
new file mode 100644
index 0000000..2c5e23b
--- /dev/null
+++ b/media/tests/mtp/mtp.cpp
@@ -0,0 +1,370 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <unistd.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#if HAVE_READLINE
+#include <readline/readline.h>
+#include <readline/history.h>
+#endif
+
+#include "MtpClient.h"
+#include "MtpDevice.h"
+#include "MtpObjectInfo.h"
+
+#include "MtpFile.h"
+
+#define PROMPT  "mtp> "
+
+using namespace android;
+
+static MtpClient* sClient = NULL;
+
+// current working directory information for interactive shell
+static MtpFile* sCurrentDirectory = NULL;
+
+static MtpFile* parse_path(char* path) {
+    return MtpFile::parsePath(sCurrentDirectory, path);
+}
+
+class MyClient : public MtpClient {
+private:
+    virtual void deviceAdded(MtpDevice *device) {
+    }
+
+    virtual void deviceRemoved(MtpDevice *device) {
+    }
+
+public:
+};
+
+static void init() {
+    sClient = new MyClient;
+    sClient->start();
+    MtpFile::init(sClient);
+}
+
+static int set_cwd(int argc, char* argv[]) {
+    if (argc != 1) {
+        fprintf(stderr, "cd should have one argument\n");
+        return -1;
+    }
+    if (!strcmp(argv[0], "/")) {
+        delete sCurrentDirectory;
+        sCurrentDirectory = NULL;
+    }
+    else {
+        MtpFile* file = parse_path(argv[0]);
+        if (file) {
+            delete sCurrentDirectory;
+            sCurrentDirectory = file;
+        } else {
+            fprintf(stderr, "could not find %s\n", argv[0]);
+            return -1;
+        }
+    }
+    return 0;
+}
+
+static void list_devices() {
+    // TODO - need to make sure the list will not change while iterating
+    MtpDeviceList& devices = sClient->getDeviceList();
+    for (int i = 0; i < devices.size(); i++) {
+        MtpDevice* device = devices[i];
+        MtpFile* file = new MtpFile(device);
+        file->print();
+        delete file;
+    }
+}
+
+static int list(int argc, char* argv[]) {
+    if (argc == 0) {
+        // list cwd
+        if (sCurrentDirectory) {
+            sCurrentDirectory->list();
+        } else {
+            list_devices();
+        }
+    }
+
+    for (int i = 0; i < argc; i++) {
+        char* path = argv[i];
+        if (!strcmp(path, "/")) {
+            list_devices();
+        } else {
+            MtpFile* file = parse_path(path);
+            if (!file) {
+                fprintf(stderr, "could not find %s\n", path);
+                return -1;
+            }
+            file->list();
+        }
+    }
+
+    return 0;
+}
+
+static int get_file(int argc, char* argv[]) {
+    int ret = -1;
+    int srcFD = -1;
+    int destFD = -1;
+    MtpFile* srcFile = NULL;
+    MtpObjectInfo* info = NULL;
+    char* dest;
+
+    if (argc < 1) {
+        fprintf(stderr, "not enough arguments\n");
+        return -1;
+    } else if (argc > 2) {
+        fprintf(stderr, "too many arguments\n");
+        return -1;
+    }
+
+    // find source object
+    char* src = argv[0];
+    srcFile = parse_path(src);
+    if (!srcFile) {
+        fprintf(stderr, "could not find %s\n", src);
+        return -1;
+    }
+    info = srcFile->getObjectInfo();
+    if (!info) {
+        fprintf(stderr, "could not find object info for %s\n", src);
+        goto fail;
+    }
+    if (info->mFormat == MTP_FORMAT_ASSOCIATION) {
+        fprintf(stderr, "copying directories not implemented yet\n");
+        goto fail;
+    }
+
+    dest = (argc > 1 ? argv[1] : info->mName);
+    destFD = open(dest, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+    if (destFD < 0) {
+        fprintf(stderr, "could not create %s\n", dest);
+        goto fail;
+    }
+    srcFD = srcFile->getDevice()->readObject(info->mHandle, info->mCompressedSize);
+    if (srcFD < 0)
+        goto fail;
+
+    char buffer[65536];
+    while (1) {
+        int count = read(srcFD, buffer, sizeof(buffer));
+        if (count <= 0)
+            break;
+        write(destFD, buffer, count);
+    }
+    // FIXME - error checking and reporting
+    ret = 0;
+
+fail:
+    delete srcFile;
+    delete info;
+    if (srcFD >= 0)
+        close(srcFD);
+    if (destFD >= 0)
+        close(destFD);
+    return ret;
+}
+
+static int put_file(int argc, char* argv[]) {
+    int ret = -1;
+    int srcFD = -1;
+    MtpFile* destFile = NULL;
+    MtpObjectInfo* srcInfo = NULL;
+    MtpObjectInfo* destInfo = NULL;
+    MtpObjectHandle handle;
+    struct stat statbuf;
+    const char* lastSlash;
+
+    if (argc < 1) {
+        fprintf(stderr, "not enough arguments\n");
+        return -1;
+    } else if (argc > 2) {
+        fprintf(stderr, "too many arguments\n");
+        return -1;
+    }
+    const char* src = argv[0];
+    srcFD = open(src, O_RDONLY);
+    if (srcFD < 0) {
+        fprintf(stderr, "could not open %s\n", src);
+        goto fail;
+    }
+    if (argc == 2) {
+         char* dest = argv[1];
+        destFile = parse_path(dest);
+        if (!destFile) {
+            fprintf(stderr, "could not find %s\n", dest);
+            goto fail;
+        }
+    } else {
+        if (!sCurrentDirectory) {
+            fprintf(stderr, "current working directory not set\n");
+            goto fail;
+        }
+        destFile = new MtpFile(sCurrentDirectory);
+    }
+
+    destInfo = destFile->getObjectInfo();
+    if (!destInfo) {
+        fprintf(stderr, "could not find object info destination directory\n");
+        goto fail;
+    }
+    if (destInfo->mFormat != MTP_FORMAT_ASSOCIATION) {
+        fprintf(stderr, "destination not a directory\n");
+        goto fail;
+    }
+
+    if (fstat(srcFD, &statbuf))
+        goto fail;
+
+    srcInfo = new MtpObjectInfo(0);
+    srcInfo->mStorageID = destInfo->mStorageID;
+    srcInfo->mFormat = MTP_FORMAT_EXIF_JPEG;  // FIXME
+    srcInfo->mCompressedSize = statbuf.st_size;
+    srcInfo->mParent = destInfo->mHandle;
+    lastSlash = strrchr(src, '/');
+    srcInfo->mName = strdup(lastSlash ? lastSlash + 1 : src);
+    srcInfo->mDateModified = statbuf.st_mtime;
+    handle = destFile->getDevice()->sendObjectInfo(srcInfo);
+    if (handle <= 0) {
+        printf("sendObjectInfo returned %04X\n", handle);
+        goto fail;
+    }
+    if (destFile->getDevice()->sendObject(srcInfo, srcFD))
+        ret = 0;
+
+fail:
+    delete destFile;
+    delete srcInfo;
+    delete destInfo;
+    if (srcFD >= 0)
+        close(srcFD);
+    printf("returning %d\n", ret);
+    return ret;
+}
+
+typedef int (* command_func)(int argc, char* argv[]);
+
+struct command_table_entry {
+    const char* name;
+    command_func func;
+};
+
+const command_table_entry command_list[] = {
+    {   "cd",       set_cwd         },
+    {   "ls",       list            },
+    {   "get",      get_file        },
+    {   "put",      put_file        },
+    {   NULL,       NULL            },
+};
+
+
+static int do_command(int argc, char* argv[]) {
+    const command_table_entry* command = command_list;
+    const char* name = *argv++;
+    argc--;
+
+    while (command->name) {
+        if (!strcmp(command->name, name))
+            return command->func(argc, argv);
+        else
+            command++;
+    }
+    fprintf(stderr, "unknown command %s\n", name);
+    return -1;
+}
+
+static int shell() {
+    int argc;
+    int result = 0;
+#define MAX_ARGS    100
+    char* argv[MAX_ARGS];
+
+#if HAVE_READLINE
+    using_history();
+#endif
+
+    while (1) {
+#if HAVE_READLINE
+        char* line = readline(PROMPT);
+        if (!line) {
+            printf("\n");
+            exit(0);
+        }
+#else
+        char    buffer[1000];
+        printf("%s", PROMPT);
+        char* line = NULL;
+        size_t length = 0;
+
+        buffer[0] = 0;
+        fgets(buffer, sizeof(buffer), stdin);
+        int count = strlen(buffer);
+        if (count > 0 && buffer[0] == EOF) {
+            printf("\n");
+            exit(0);
+        }
+        if (count > 0 && line[count - 1] == '\n')
+            line[count - 1] == 0;
+#endif
+        char* tok = strtok(line, " \t\n\r");
+        if (!tok)
+            continue;
+        if (!strcmp(tok, "quit") || !strcmp(tok, "exit")) {
+            exit(0);
+        }
+#if HAVE_READLINE
+        add_history(line);
+#endif
+        argc = 0;
+        while (tok) {
+            if (argc + 1 == MAX_ARGS) {
+                fprintf(stderr, "too many arguments\n");
+                result = -1;
+                goto bottom_of_loop;
+            }
+
+            argv[argc++] = strdup(tok);
+            tok = strtok(NULL, " \t\n\r");
+        }
+
+        result = do_command(argc, argv);
+
+bottom_of_loop:
+        for (int i = 0; i < argc; i++)
+            free(argv[i]);
+        free(line);
+    }
+
+    return result;
+}
+
+int main(int argc, char* argv[]) {
+    init();
+
+    if (argc == 1)
+        return shell();
+    else
+        return do_command(argc - 1, argv + 1);
+}
diff --git a/native/include/android/tts.h b/native/include/android/tts.h
new file mode 100644
index 0000000..fb15108
--- /dev/null
+++ b/native/include/android/tts.h
@@ -0,0 +1,313 @@
+/*
+ * Copyright (C) 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.
+ */
+#ifndef ANDROID_TTS_H
+#define ANDROID_TTS_H 
+
+// This header defines the interface used by the Android platform
+// to access Text-To-Speech functionality in shared libraries that implement
+// speech synthesis and the management of resources associated with the
+// synthesis.
+
+// The shared library must contain a function named "android_getTtsEngine"
+// that returns an 'android_tts_engine_t' instance.
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define ANDROID_TTS_ENGINE_PROPERTY_CONFIG "engineConfig"
+#define ANDROID_TTS_ENGINE_PROPERTY_PITCH  "pitch"
+#define ANDROID_TTS_ENGINE_PROPERTY_RATE   "rate"
+#define ANDROID_TTS_ENGINE_PROPERTY_VOLUME "volume"
+
+typedef enum {
+    ANDROID_TTS_SUCCESS                 = 0,
+    ANDROID_TTS_FAILURE                 = -1,
+    ANDROID_TTS_FEATURE_UNSUPPORTED     = -2,
+    ANDROID_TTS_VALUE_INVALID           = -3,
+    ANDROID_TTS_PROPERTY_UNSUPPORTED    = -4,
+    ANDROID_TTS_PROPERTY_SIZE_TOO_SMALL = -5,
+    ANDROID_TTS_MISSING_RESOURCES       = -6
+} android_tts_result_t;
+
+typedef enum {
+    ANDROID_TTS_LANG_COUNTRY_VAR_AVAILABLE = 2,
+    ANDROID_TTS_LANG_COUNTRY_AVAILABLE    = 1,
+    ANDROID_TTS_LANG_AVAILABLE            = 0,
+    ANDROID_TTS_LANG_MISSING_DATA         = -1,
+    ANDROID_TTS_LANG_NOT_SUPPORTED        = -2
+} android_tts_support_result_t;
+
+typedef enum {
+    ANDROID_TTS_SYNTH_DONE              = 0,
+    ANDROID_TTS_SYNTH_PENDING           = 1
+} android_tts_synth_status_t;
+
+typedef enum {
+    ANDROID_TTS_CALLBACK_HALT           = 0,
+    ANDROID_TTS_CALLBACK_CONTINUE       = 1
+} android_tts_callback_status_t;
+
+// Supported audio formats
+typedef enum {
+    ANDROID_TTS_AUDIO_FORMAT_INVALID    = -1,
+    ANDROID_TTS_AUDIO_FORMAT_DEFAULT    = 0,
+    ANDROID_TTS_AUDIO_FORMAT_PCM_16_BIT = 1,
+    ANDROID_TTS_AUDIO_FORMAT_PCM_8_BIT  = 2,
+} android_tts_audio_format_t;
+
+
+/* An android_tts_engine_t object can be anything, but must have,
+ * as its first field, a pointer to a table of functions.
+ *
+ * See the full definition of struct android_tts_engine_t_funcs_t
+ * below for details.
+ */
+typedef struct android_tts_engine_funcs_t  android_tts_engine_funcs_t;
+
+typedef struct {
+    android_tts_engine_funcs_t *funcs;
+} android_tts_engine_t;
+
+/* This function must be located in the TTS Engine shared library
+ * and must return the address of an android_tts_engine_t library.
+ */
+extern android_tts_engine_t *android_getTtsEngine();
+
+/* Including the old version for legacy support (Froyo compatibility).
+ * This should return the same thing as android_getTtsEngine.
+ */
+extern "C" android_tts_engine_t *getTtsEngine();
+
+// A callback type used to notify the framework of new synthetized
+// audio samples, status will be SYNTH_DONE for the last sample of
+// the last request, of SYNTH_PENDING otherwise.
+//
+// This is passed by the framework to the engine through the
+// 'engine_init' function (see below).
+//
+// The callback for synthesis completed takes:
+// @param [inout] void *&       - The userdata pointer set in the original
+//                                 synth call
+// @param [in]    uint32_t      - Track sampling rate in Hz
+// @param [in]    uint32_t      - The audio format
+// @param [in]    int           - The number of channels
+// @param [inout] int8_t *&     - A buffer of audio data only valid during the
+//                                execution of the callback
+// @param [inout] size_t  &     - The size of the buffer
+// @param [in] tts_synth_status - indicate whether the synthesis is done, or
+//                                 if more data is to be synthesized.
+// @return TTS_CALLBACK_HALT to indicate the synthesis must stop,
+//         TTS_CALLBACK_CONTINUE to indicate the synthesis must continue if
+//            there is more data to produce.
+typedef android_tts_callback_status_t (*android_tts_synth_cb_t)
+            (void **pUserData,
+             uint32_t trackSamplingHz,
+             android_tts_audio_format_t audioFormat,
+             int channelCount,
+             int8_t **pAudioBuffer,
+             size_t *pBufferSize,
+             android_tts_synth_status_t status);
+
+
+// The table of function pointers that the android_tts_engine_t must point to.
+// Note that each of these functions will take a handle to the engine itself
+// as their first parameter.
+//
+
+struct android_tts_engine_funcs_t {
+    // reserved fields, ignored by the framework
+    // they must be placed here to ensure binary compatibility
+    // of legacy binary plugins.
+    void *reserved[2];
+
+    // Initialize the TTS engine and returns whether initialization succeeded.
+    // @param synthDoneCBPtr synthesis callback function pointer
+    // @return TTS_SUCCESS, or TTS_FAILURE
+    android_tts_result_t (*init)
+            (void *engine,
+             android_tts_synth_cb_t synthDonePtr,
+             const char *engineConfig);
+
+    // Shut down the TTS engine and releases all associated resources.
+    // @return TTS_SUCCESS, or TTS_FAILURE
+    android_tts_result_t (*shutdown)
+            (void *engine);
+
+    // Interrupt synthesis and flushes any synthesized data that hasn't been
+    // output yet. This will block until callbacks underway are completed.
+    // @return TTS_SUCCESS, or TTS_FAILURE
+    android_tts_result_t (*stop)
+            (void *engine);
+
+    // Returns the level of support for the language, country and variant.
+    // @return TTS_LANG_COUNTRY_VAR_AVAILABLE if the language, country and variant are supported,
+    //            and the corresponding resources are correctly installed
+    //         TTS_LANG_COUNTRY_AVAILABLE if the language and country are supported and the
+    //             corresponding resources are correctly installed, but there is no match for
+    //             the specified variant
+    //         TTS_LANG_AVAILABLE if the language is supported and the
+    //             corresponding resources are correctly installed, but there is no match for
+    //             the specified country and variant
+    //         TTS_LANG_MISSING_DATA if the required resources to provide any level of support
+    //             for the language are not correctly installed
+    //         TTS_LANG_NOT_SUPPORTED if the language is not supported by the TTS engine.
+    android_tts_support_result_t (*isLanguageAvailable)
+            (void *engine,
+             const char *lang,
+             const char *country,
+             const char *variant);
+
+    // Load the resources associated with the specified language. The loaded
+    // language will only be used once a call to setLanguage() with the same
+    // language value is issued. Language and country values are coded according to the ISO three
+    // letter codes for languages and countries, as can be retrieved from a java.util.Locale
+    // instance. The variant value is encoded as the variant string retrieved from a
+    // java.util.Locale instance built with that variant data.
+    // @param lang pointer to the ISO three letter code for the language
+    // @param country pointer to the ISO three letter code for the country
+    // @param variant pointer to the variant code
+    // @return TTS_SUCCESS, or TTS_FAILURE
+    android_tts_result_t (*loadLanguage)
+            (void *engine,
+             const char *lang,
+             const char *country,
+             const char *variant);
+
+    // Load the resources associated with the specified language, country and Locale variant.
+    // The loaded language will only be used once a call to setLanguageFromLocale() with the same
+    // language value is issued. Language and country values are coded according to the ISO three
+    // letter codes for languages and countries, as can be retrieved from a java.util.Locale
+    // instance. The variant value is encoded as the variant string retrieved from a
+    // java.util.Locale instance built with that variant data.
+    // @param lang pointer to the ISO three letter code for the language
+    // @param country pointer to the ISO three letter code for the country
+    // @param variant pointer to the variant code
+    // @return TTS_SUCCESS, or TTS_FAILURE
+    android_tts_result_t (*setLanguage)
+            (void *engine,
+             const char *lang,
+             const char *country,
+             const char *variant);
+
+    // Retrieve the currently set language, country and variant, or empty strings if none of
+    // parameters have been set. Language and country are represented by their 3-letter ISO code
+    // @param[out]   pointer to the retrieved 3-letter code language value
+    // @param[out]   pointer to the retrieved 3-letter code country value
+    // @param[out]   pointer to the retrieved variant value
+    // @return TTS_SUCCESS, or TTS_FAILURE
+    android_tts_result_t (*getLanguage)
+            (void *engine,
+             char *language,
+             char *country,
+             char *variant);
+
+    // Notifies the engine what audio parameters should be used for the synthesis.
+    // This is meant to be used as a hint, the engine implementation will set the output values
+    // to those of the synthesis format, based on a given hint.
+    // @param[inout] encoding in: the desired audio sample format
+    //                         out: the format used by the TTS engine
+    // @param[inout] rate in: the desired audio sample rate
+    //                         out: the sample rate used by the TTS engine
+    // @param[inout] channels in: the desired number of audio channels
+    //                         out: the number of channels used by the TTS engine
+    // @return TTS_SUCCESS, or TTS_FAILURE
+    android_tts_result_t (*setAudioFormat)
+            (void *engine,
+             android_tts_audio_format_t* pEncoding,
+             uint32_t* pRate,
+             int* pChannels);
+
+    // Set a property for the the TTS engine
+    // "size" is the maximum size of "value" for properties "property"
+    // @param property pointer to the property name
+    // @param value    pointer to the property value
+    // @param size     maximum size required to store this type of property
+    // @return         TTS_PROPERTY_UNSUPPORTED, or TTS_SUCCESS, or TTS_FAILURE,
+    //                  or TTS_VALUE_INVALID
+    android_tts_result_t (*setProperty)
+            (void *engine,
+             const char *property,
+             const char *value,
+             const size_t size);
+
+    // Retrieve a property from the TTS engine
+    // @param        property pointer to the property name
+    // @param[out]   value    pointer to the retrieved language value
+    // @param[inout] iosize   in: stores the size available to store the
+    //                          property value.
+    //                        out: stores the size required to hold the language
+    //                          value if getLanguage() returned
+    //                          TTS_PROPERTY_SIZE_TOO_SMALL, unchanged otherwise
+    // @return TTS_PROPERTY_UNSUPPORTED, or TTS_SUCCESS,
+    //         or TTS_PROPERTY_SIZE_TOO_SMALL
+    android_tts_result_t (*getProperty)
+            (void *engine,
+             const char *property,
+             char *value,
+             size_t *iosize);
+
+    // Synthesize the text.
+    // As the synthesis is performed, the engine invokes the callback to notify
+    // the TTS framework that it has filled the given buffer, and indicates how
+    // many bytes it wrote. The callback is called repeatedly until the engine
+    // has generated all the audio data corresponding to the text.
+    // Note about the format of the input: the text parameter may use the
+    // following elements
+    // and their respective attributes as defined in the SSML 1.0 specification:
+    //    * lang
+    //    * say-as:
+    //          o interpret-as
+    //    * phoneme
+    //    * voice:
+    //          o gender,
+    //          o age,
+    //          o variant,
+    //          o name
+    //    * emphasis
+    //    * break:
+    //          o strength,
+    //          o time
+    //    * prosody:
+    //          o pitch,
+    //          o contour,
+    //          o range,
+    //          o rate,
+    //          o duration,
+    //          o volume
+    //    * mark
+    // Differences between this text format and SSML are:
+    //    * full SSML documents are not supported
+    //    * namespaces are not supported
+    // Text is coded in UTF-8.
+    // @param text      the UTF-8 text to synthesize
+    // @param userdata  pointer to be returned when the call is invoked
+    // @param buffer    the location where the synthesized data must be written
+    // @param bufferSize the number of bytes that can be written in buffer
+    // @return          TTS_SUCCESS or TTS_FAILURE
+    android_tts_result_t (*synthesizeText)
+            (void *engine,
+             const char *text,
+             int8_t *buffer,
+             size_t bufferSize,
+             void *userdata);
+};
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* ANDROID_TTS_H */
diff --git a/opengl/tests/gl_perf/Android.mk b/opengl/tests/gl_perf/Android.mk
new file mode 100644
index 0000000..37647ca
--- /dev/null
+++ b/opengl/tests/gl_perf/Android.mk
@@ -0,0 +1,20 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES:= \
+	gl2_perf.cpp \
+	filltest.cpp
+
+LOCAL_SHARED_LIBRARIES := \
+	libcutils \
+    libEGL \
+    libGLESv2 \
+    libui
+
+LOCAL_MODULE:= test-opengl-gl2_perf
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_CFLAGS := -DGL_GLEXT_PROTOTYPES
+
+include $(BUILD_EXECUTABLE)
diff --git a/opengl/tests/gl_perf/fill_common.cpp b/opengl/tests/gl_perf/fill_common.cpp
new file mode 100644
index 0000000..36db1b0
--- /dev/null
+++ b/opengl/tests/gl_perf/fill_common.cpp
@@ -0,0 +1,316 @@
+/*
+ * 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.
+ */
+
+FILE * fOut = NULL;
+void ptSwap();
+
+static void checkGlError(const char* op) {
+    for (GLint error = glGetError(); error; error
+            = glGetError()) {
+        LOGE("after %s() glError (0x%x)\n", op, error);
+    }
+}
+
+GLuint loadShader(GLenum shaderType, const char* pSource) {
+    GLuint shader = glCreateShader(shaderType);
+    if (shader) {
+        glShaderSource(shader, 1, &pSource, NULL);
+        glCompileShader(shader);
+        GLint compiled = 0;
+        glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
+        if (!compiled) {
+            GLint infoLen = 0;
+            glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLen);
+            if (infoLen) {
+                char* buf = (char*) malloc(infoLen);
+                if (buf) {
+                    glGetShaderInfoLog(shader, infoLen, NULL, buf);
+                    LOGE("Could not compile shader %d:\n%s\n", shaderType, buf);
+                    free(buf);
+                }
+                glDeleteShader(shader);
+                shader = 0;
+            }
+        }
+    }
+    return shader;
+}
+
+enum {
+    A_POS,
+    A_COLOR,
+    A_TEX0,
+    A_TEX1
+};
+
+GLuint createProgram(const char* pVertexSource, const char* pFragmentSource) {
+    GLuint vertexShader = loadShader(GL_VERTEX_SHADER, pVertexSource);
+    if (!vertexShader) {
+        return 0;
+    }
+
+    GLuint pixelShader = loadShader(GL_FRAGMENT_SHADER, pFragmentSource);
+    if (!pixelShader) {
+        return 0;
+    }
+
+    GLuint program = glCreateProgram();
+    if (program) {
+        glAttachShader(program, vertexShader);
+        checkGlError("glAttachShader v");
+        glAttachShader(program, pixelShader);
+        checkGlError("glAttachShader p");
+
+        glBindAttribLocation(program, A_POS, "a_pos");
+        glBindAttribLocation(program, A_COLOR, "a_color");
+        glBindAttribLocation(program, A_TEX0, "a_tex0");
+        glBindAttribLocation(program, A_TEX1, "a_tex1");
+        glLinkProgram(program);
+        GLint linkStatus = GL_FALSE;
+        glGetProgramiv(program, GL_LINK_STATUS, &linkStatus);
+        if (linkStatus != GL_TRUE) {
+            GLint bufLength = 0;
+            glGetProgramiv(program, GL_INFO_LOG_LENGTH, &bufLength);
+            if (bufLength) {
+                char* buf = (char*) malloc(bufLength);
+                if (buf) {
+                    glGetProgramInfoLog(program, bufLength, NULL, buf);
+                    LOGE("Could not link program:\n%s\n", buf);
+                    free(buf);
+                }
+            }
+            glDeleteProgram(program);
+            program = 0;
+        }
+    }
+    checkGlError("createProgram");
+    glUseProgram(program);
+    return program;
+}
+
+uint64_t getTime() {
+    struct timespec t;
+    clock_gettime(CLOCK_MONOTONIC, &t);
+    return t.tv_nsec + ((uint64_t)t.tv_sec * 1000 * 1000 * 1000);
+}
+
+uint64_t gTime;
+void startTimer() {
+    gTime = getTime();
+}
+
+void endTimer(const char *str, int w, int h, double dc, int count) {
+    uint64_t t2 = getTime();
+    double delta = ((double)(t2 - gTime)) / 1000000000;
+    double pixels = dc * (w * h) * count;
+    double mpps = pixels / delta / 1000000;
+    double dc60 = pixels / delta / (w * h) / 60;
+
+    if (fOut) {
+        fprintf(fOut, "%s, %f, %f\r\n", str, mpps, dc60);
+        fflush(fOut);
+    } else {
+        printf("%s, %f, %f\n", str, mpps, dc60);
+    }
+    LOGI("%s, %f, %f\r\n", str, mpps, dc60);
+}
+
+
+static const char gVertexShader[] =
+    "attribute vec4 a_pos;\n"
+    "attribute vec4 a_color;\n"
+    "attribute vec2 a_tex0;\n"
+    "attribute vec2 a_tex1;\n"
+    "varying vec4 v_color;\n"
+    "varying vec2 v_tex0;\n"
+    "varying vec2 v_tex1;\n"
+
+    "void main() {\n"
+    "    v_color = a_color;\n"
+    "    v_tex0 = a_tex0;\n"
+    "    v_tex1 = a_tex1;\n"
+    "    gl_Position = a_pos;\n"
+    "}\n";
+
+static const char gShaderPrefix[] =
+    "precision mediump float;\n"
+    "uniform vec4 u_color;\n"
+    "uniform vec4 u_0;\n"
+    "uniform vec4 u_1;\n"
+    "uniform vec4 u_2;\n"
+    "uniform vec4 u_3;\n"
+    "varying vec4 v_color;\n"
+    "varying vec2 v_tex0;\n"
+    "varying vec2 v_tex1;\n"
+    "uniform sampler2D u_tex0;\n"
+    "uniform sampler2D u_tex1;\n"
+    "void main() {\n";
+
+static const char gShaderPostfix[] =
+    "  gl_FragColor = c;\n"
+    "}\n";
+
+
+static char * append(char *d, const char *s) {
+    size_t len = strlen(s);
+    memcpy(d, s, len);
+    return d + len;
+}
+
+static char * genShader(
+    bool useVarColor,
+    int texCount,
+    bool modulateFirstTex,
+    int extraMath)
+{
+    char *str = (char *)calloc(16 * 1024, 1);
+    char *tmp = append(str, gShaderPrefix);
+
+    if (modulateFirstTex || !texCount) {
+        if (useVarColor) {
+            tmp = append(tmp, "  vec4 c = v_color;\n");
+        } else {
+            tmp = append(tmp, "  vec4 c = u_color;\n");
+        }
+    } else {
+        tmp = append(tmp, "  vec4 c = texture2D(u_tex0, v_tex0);\n");
+    }
+
+    if (modulateFirstTex && texCount) {
+        tmp = append(tmp, "  c *= texture2D(u_tex0, v_tex0);\n");
+    }
+    if (texCount > 1) {
+        tmp = append(tmp, "  c *= texture2D(u_tex1, v_tex1);\n");
+    }
+
+    if (extraMath > 0) {
+        tmp = append(tmp, "  c *= u_0;\n");
+    }
+    if (extraMath > 1) {
+        tmp = append(tmp, "  c += u_1;\n");
+    }
+    if (extraMath > 2) {
+        tmp = append(tmp, "  c *= u_2;\n");
+    }
+    if (extraMath > 3) {
+        tmp = append(tmp, "  c += u_3;\n");
+    }
+
+
+    tmp = append(tmp, gShaderPostfix);
+    tmp[0] = 0;
+
+    //printf("%s", str);
+    return str;
+}
+
+static void setupVA() {
+    static const float vtx[] = {
+        -1.0f,-1.0f,
+         1.0f,-1.0f,
+        -1.0f, 1.0f,
+         1.0f, 1.0f };
+    static const float color[] = {
+        1.0f,0.0f,1.0f,1.0f,
+        0.0f,0.0f,1.0f,1.0f,
+        1.0f,1.0f,0.0f,1.0f,
+        1.0f,1.0f,1.0f,1.0f };
+    static const float tex0[] = {
+        0.0f,0.0f,
+        1.0f,0.0f,
+        1.0f,1.0f,
+        0.0f,1.0f };
+    static const float tex1[] = {
+        1.0f,0.0f,
+        1.0f,1.0f,
+        0.0f,1.0f,
+        0.0f,0.0f };
+
+    glEnableVertexAttribArray(A_POS);
+    glEnableVertexAttribArray(A_COLOR);
+    glEnableVertexAttribArray(A_TEX0);
+    glEnableVertexAttribArray(A_TEX1);
+
+    glVertexAttribPointer(A_POS, 2, GL_FLOAT, false, 8, vtx);
+    glVertexAttribPointer(A_COLOR, 4, GL_FLOAT, false, 16, color);
+    glVertexAttribPointer(A_TEX0, 2, GL_FLOAT, false, 8, tex0);
+    glVertexAttribPointer(A_TEX1, 2, GL_FLOAT, false, 8, tex1);
+}
+
+static void randUniform(int pgm, const char *var) {
+    int loc = glGetUniformLocation(pgm, var);
+    if (loc >= 0) {
+        float x = ((float)rand()) / RAND_MAX;
+        float y = ((float)rand()) / RAND_MAX;
+        float z = ((float)rand()) / RAND_MAX;
+        float w = ((float)rand()) / RAND_MAX;
+        glUniform4f(loc, x, y, z, w);
+    }
+}
+
+static void doLoop(bool clear, int pgm, uint32_t w, uint32_t h, const char *str) {
+    if (clear) {
+        glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
+        glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
+        ptSwap();
+        glFinish();
+        return;
+    }
+
+    startTimer();
+    glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
+    for (int ct=0; ct < 100; ct++) {
+        randUniform(pgm, "u_color");
+        randUniform(pgm, "u_0");
+        randUniform(pgm, "u_1");
+        randUniform(pgm, "u_2");
+        randUniform(pgm, "u_3");
+        glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
+    }
+    ptSwap();
+    glFinish();
+    endTimer(str, w, h, 1, 100);
+}
+
+void genTextures() {
+    uint32_t *m = (uint32_t *)malloc(1024*1024*4);
+    for (int y=0; y < 1024; y++){
+        for (int x=0; x < 1024; x++){
+            m[y*1024 + x] = 0xff0000ff | ((x & 0xff) << 8) | (y << 16);
+        }
+    }
+    glBindTexture(GL_TEXTURE_2D, 1);
+    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1024, 1024, 0, GL_RGBA, GL_UNSIGNED_BYTE, m);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
+
+    for (int y=0; y < 16; y++){
+        for (int x=0; x < 16; x++){
+            m[y*16 + x] = 0xff0000ff | (x<<12) | (y<<20);
+        }
+    }
+    glBindTexture(GL_TEXTURE_2D, 2);
+    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 16, 16, 0, GL_RGBA, GL_UNSIGNED_BYTE, m);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
+
+}
+
+
diff --git a/opengl/tests/gl_perf/filltest.cpp b/opengl/tests/gl_perf/filltest.cpp
new file mode 100644
index 0000000..0dd4e22
--- /dev/null
+++ b/opengl/tests/gl_perf/filltest.cpp
@@ -0,0 +1,100 @@
+/*
+ * 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.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <time.h>
+#include <sched.h>
+#include <sys/resource.h>
+#include <string.h>
+
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
+#include <utils/Timers.h>
+#include <EGL/egl.h>
+#include <utils/Log.h>
+
+
+using namespace android;
+
+
+#include "fill_common.cpp"
+
+static void doSingleTest(uint32_t w, uint32_t h,
+                         bool useVarColor,
+                         int texCount,
+                         bool modulateFirstTex,
+                         int extraMath,
+                         int tex0, int tex1) {
+    char *pgmTxt = genShader(useVarColor, texCount, modulateFirstTex, extraMath);
+    int pgm = createProgram(gVertexShader, pgmTxt);
+    if (!pgm) {
+        printf("error running test\n");
+        return;
+    }
+    int loc = glGetUniformLocation(pgm, "u_tex0");
+    if (loc >= 0) glUniform1i(loc, 0);
+    loc = glGetUniformLocation(pgm, "u_tex1");
+    if (loc >= 0) glUniform1i(loc, 1);
+
+    glActiveTexture(GL_TEXTURE0);
+    glBindTexture(GL_TEXTURE_2D, tex0);
+    glActiveTexture(GL_TEXTURE1);
+    glBindTexture(GL_TEXTURE_2D, tex1);
+    glActiveTexture(GL_TEXTURE0);
+
+    char str2[1024];
+
+    glBlendFunc(GL_ONE, GL_ONE);
+    glDisable(GL_BLEND);
+    //sprintf(str2, "%i, %i, %i, %i, %i, 0",
+            //useVarColor, texCount, modulateFirstTex, extraMath, tex0);
+    //doLoop(true, pgm, w, h, str2);
+    //doLoop(false, pgm, w, h, str2);
+
+    glEnable(GL_BLEND);
+    sprintf(str2, "%i, %i, %i, %i, %i, 1",
+            useVarColor, texCount, modulateFirstTex, extraMath, tex0);
+    doLoop(true, pgm, w, h, str2);
+    doLoop(false, pgm, w, h, str2);
+}
+
+bool doTest(uint32_t w, uint32_t h) {
+    setupVA();
+    genTextures();
+
+    printf("\nvarColor, texCount, modulate, extraMath, texSize, blend, Mpps, DC60\n");
+
+    for (int texCount = 0; texCount < 2; texCount++) {
+        for (int extraMath = 0; extraMath < 5; extraMath++) {
+
+            doSingleTest(w, h, false, texCount, false, extraMath, 1, 1);
+            doSingleTest(w, h, true, texCount, false, extraMath, 1, 1);
+            if (texCount) {
+                doSingleTest(w, h, false, texCount, true, extraMath, 1, 1);
+                doSingleTest(w, h, true, texCount, true, extraMath, 1, 1);
+
+                doSingleTest(w, h, false, texCount, false, extraMath, 2, 2);
+                doSingleTest(w, h, true, texCount, false, extraMath, 2, 2);
+                doSingleTest(w, h, false, texCount, true, extraMath, 2, 2);
+                doSingleTest(w, h, true, texCount, true, extraMath, 2, 2);
+            }
+        }
+    }
+
+    exit(0);
+    return true;
+}
diff --git a/opengl/tests/gl_perf/gl2_perf.cpp b/opengl/tests/gl_perf/gl2_perf.cpp
new file mode 100644
index 0000000..9dfcf1c
--- /dev/null
+++ b/opengl/tests/gl_perf/gl2_perf.cpp
@@ -0,0 +1,136 @@
+/*
+ * 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.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <time.h>
+#include <sched.h>
+#include <sys/resource.h>
+
+#include <EGL/egl.h>
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
+
+#include <utils/Timers.h>
+
+#include <ui/FramebufferNativeWindow.h>
+#include <ui/EGLUtils.h>
+
+using namespace android;
+
+
+static void checkEglError(const char* op, EGLBoolean returnVal = EGL_TRUE) {
+    if (returnVal != EGL_TRUE) {
+        fprintf(stderr, "%s() returned %d\n", op, returnVal);
+    }
+
+    for (EGLint error = eglGetError(); error != EGL_SUCCESS; error
+            = eglGetError()) {
+        fprintf(stderr, "after %s() eglError %s (0x%x)\n", op, EGLUtils::strerror(error),
+                error);
+    }
+}
+
+static void checkGlError(const char* op) {
+    for (GLint error = glGetError(); error; error
+            = glGetError()) {
+        fprintf(stderr, "after %s() glError (0x%x)\n", op, error);
+    }
+}
+
+bool doTest(uint32_t w, uint32_t h);
+
+static EGLDisplay dpy;
+static EGLSurface surface;
+
+int main(int argc, char** argv) {
+    EGLBoolean returnValue;
+    EGLConfig myConfig = {0};
+
+    EGLint context_attribs[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE };
+    EGLint s_configAttribs[] = {
+            EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
+            EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
+            EGL_NONE };
+    EGLint majorVersion;
+    EGLint minorVersion;
+    EGLContext context;
+    EGLint w, h;
+
+
+    checkEglError("<init>");
+    dpy = eglGetDisplay(EGL_DEFAULT_DISPLAY);
+    checkEglError("eglGetDisplay");
+    if (dpy == EGL_NO_DISPLAY) {
+        printf("eglGetDisplay returned EGL_NO_DISPLAY.\n");
+        return 0;
+    }
+
+    returnValue = eglInitialize(dpy, &majorVersion, &minorVersion);
+    checkEglError("eglInitialize", returnValue);
+    if (returnValue != EGL_TRUE) {
+        printf("eglInitialize failed\n");
+        return 0;
+    }
+
+    EGLNativeWindowType window = android_createDisplaySurface();

+    returnValue = EGLUtils::selectConfigForNativeWindow(dpy, s_configAttribs, window, &myConfig);
+    if (returnValue) {
+        printf("EGLUtils::selectConfigForNativeWindow() returned %d", returnValue);
+        return 0;
+    }
+
+    checkEglError("EGLUtils::selectConfigForNativeWindow");
+
+    surface = eglCreateWindowSurface(dpy, myConfig, window, NULL);
+    checkEglError("eglCreateWindowSurface");
+    if (surface == EGL_NO_SURFACE) {
+        printf("gelCreateWindowSurface failed.\n");
+        return 0;
+    }
+
+    context = eglCreateContext(dpy, myConfig, EGL_NO_CONTEXT, context_attribs);
+    checkEglError("eglCreateContext");
+    if (context == EGL_NO_CONTEXT) {
+        printf("eglCreateContext failed\n");
+        return 0;
+    }
+    returnValue = eglMakeCurrent(dpy, surface, surface, context);
+    checkEglError("eglMakeCurrent", returnValue);
+    if (returnValue != EGL_TRUE) {
+        return 0;
+    }
+    eglQuerySurface(dpy, surface, EGL_WIDTH, &w);
+    checkEglError("eglQuerySurface");
+    eglQuerySurface(dpy, surface, EGL_HEIGHT, &h);
+    checkEglError("eglQuerySurface");
+    GLint dim = w < h ? w : h;
+
+    glViewport(0, 0, w, h);
+
+    for (;;) {
+        doTest(w, h);
+        eglSwapBuffers(dpy, surface);
+        checkEglError("eglSwapBuffers");
+    }
+
+    return 0;
+}
+
+void ptSwap() {
+    eglSwapBuffers(dpy, surface);
+}
+
diff --git a/opengl/tests/gl_perfapp/Android.mk b/opengl/tests/gl_perfapp/Android.mk
new file mode 100644
index 0000000..dd75a74
--- /dev/null
+++ b/opengl/tests/gl_perfapp/Android.mk
@@ -0,0 +1,54 @@
+#########################################################################
+# OpenGL ES Perf App
+# 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 := GLPerf
+
+LOCAL_JNI_SHARED_LIBRARIES := libglperf
+
+# Run on Eclair
+LOCAL_SDK_VERSION := 7
+
+include $(BUILD_PACKAGE)
+
+#########################################################################
+# Build JNI Shared Library
+#########################################################################
+
+LOCAL_PATH:= $(LOCAL_PATH)/jni
+
+include $(CLEAR_VARS)
+
+# Optional tag would mean it doesn't get installed by default
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_CFLAGS := -Werror
+
+LOCAL_SRC_FILES:= \
+  gl_code.cpp
+
+LOCAL_SHARED_LIBRARIES := \
+	libutils \
+	libEGL \
+	libGLESv2
+
+LOCAL_MODULE := libglperf
+
+LOCAL_PRELINK_MODULE := false
+
+include $(BUILD_SHARED_LIBRARY)
+
+endif # TARGET_SIMULATOR
diff --git a/opengl/tests/gl_perfapp/AndroidManifest.xml b/opengl/tests/gl_perfapp/AndroidManifest.xml
new file mode 100644
index 0000000..305d95f
--- /dev/null
+++ b/opengl/tests/gl_perfapp/AndroidManifest.xml
@@ -0,0 +1,38 @@
+<?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.glperf"
+    android:versionName="1.0.0" android:versionCode="10000" >
+    <uses-sdk android:targetSdkVersion="7" android:minSdkVersion="7" />
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+    <application
+            android:label="@string/glperf_activity">
+        <activity android:name="GLPerfActivity"
+                android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
+            	android:launchMode="singleTask"
+            	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/gl_perfapp/jni/gl_code.cpp b/opengl/tests/gl_perfapp/jni/gl_code.cpp
new file mode 100644
index 0000000..e643292
--- /dev/null
+++ b/opengl/tests/gl_perfapp/jni/gl_code.cpp
@@ -0,0 +1,194 @@
+// OpenGL ES 2.0 code
+
+#include <nativehelper/jni.h>
+#define LOG_TAG "GLPerf gl_code.cpp"
+#include <utils/Log.h>
+
+#include <EGL/egl.h>
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
+#include <utils/Timers.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+
+#include "../../gl_perf/fill_common.cpp"
+
+
+//////////////////////////
+
+// Width and height of the screen
+
+uint32_t w;
+uint32_t h;
+
+// The stateClock starts at zero and increments by 1 every time we draw a frame. It is used to control which phase of the test we are in.
+
+int stateClock;
+const int doLoopStates = 2;
+const int doSingleTestStates = 2;
+bool done;
+
+// Saves the parameters of the test (so we can print them out when we finish the timing.)
+
+char saveBuf[1024];
+
+
+int pgm;
+
+void ptSwap() {
+}
+
+static void doSingleTest(uint32_t w, uint32_t h,
+                         bool useVarColor,
+                         int texCount,
+                         bool modulateFirstTex,
+                         int extraMath,
+                         int tex0, int tex1) {
+    int doSingleTestState = (stateClock / doLoopStates) % doSingleTestStates;
+    // LOGI("doSingleTest %d\n", doSingleTestState);
+    switch (doSingleTestState) {
+	case 0: {
+	    char *pgmTxt = genShader(useVarColor, texCount, modulateFirstTex, extraMath);
+	    pgm = createProgram(gVertexShader, pgmTxt);
+	    if (!pgm) {
+		LOGE("error running test\n");
+		return;
+	    }
+	    int loc = glGetUniformLocation(pgm, "u_tex0");
+	    if (loc >= 0) glUniform1i(loc, 0);
+	    loc = glGetUniformLocation(pgm, "u_tex1");
+	    if (loc >= 0) glUniform1i(loc, 1);
+
+	    glActiveTexture(GL_TEXTURE0);
+	    glBindTexture(GL_TEXTURE_2D, tex0);
+	    glActiveTexture(GL_TEXTURE1);
+	    glBindTexture(GL_TEXTURE_2D, tex1);
+	    glActiveTexture(GL_TEXTURE0);
+
+
+	    glBlendFunc(GL_ONE, GL_ONE);
+	    glDisable(GL_BLEND);
+            char str2[1024];
+	    sprintf(str2, "%i, %i, %i, %i, %i, 0",
+		    useVarColor, texCount, modulateFirstTex, extraMath, tex0);
+
+    	    doLoop((stateClock % doLoopStates) != 0, pgm, w, h, str2);
+	 }
+         break;
+         case 1: {
+            char str2[1024];
+	    glEnable(GL_BLEND);
+	    sprintf(str2, "%i, %i, %i, %i, %i, 1",
+		    useVarColor, texCount, modulateFirstTex, extraMath, tex0);
+	    doLoop((stateClock % doLoopStates) != 0, pgm, w, h, str2);
+        }
+        break;
+    }
+}
+
+
+void doTest(uint32_t w, uint32_t h) {
+    int testState = stateClock / (doLoopStates * doSingleTestStates);
+    int texCount;
+    int extraMath;
+    int testSubState;
+    const int extraMathCount = 5;
+    const int texCount0SubTestCount = 2;
+    const int texCountNSubTestCount = 8;
+
+    if ( testState < extraMathCount * texCount0SubTestCount) {
+       texCount = 0; // Only 10 tests for texCount 0
+       extraMath = (testState / texCount0SubTestCount) % extraMathCount;
+       testSubState = testState % texCount0SubTestCount;
+    } else {
+       texCount = 1 + (testState - extraMathCount * texCount0SubTestCount) / (extraMathCount * texCountNSubTestCount);
+       extraMath = (testState / texCountNSubTestCount) % extraMathCount;
+       testSubState = testState % texCountNSubTestCount;
+    }
+    if (texCount >= 3) {
+       LOGI("done\n");
+       if (fOut) {
+           fclose(fOut);
+           fOut = NULL;
+       }
+       done = true;
+       return;
+    }
+
+
+    // LOGI("doTest %d %d %d\n", texCount, extraMath, testSubState);
+
+    switch(testSubState) {
+	case 0:
+            doSingleTest(w, h, false, texCount, false, extraMath, 1, 1);
+	break;
+	case 1:
+            doSingleTest(w, h, true, texCount, false, extraMath, 1, 1);
+	break;
+	case 2:
+                doSingleTest(w, h, false, texCount, true, extraMath, 1, 1);
+	break;
+	case 3:
+                doSingleTest(w, h, true, texCount, true, extraMath, 1, 1);
+	break;
+
+	case 4:
+                doSingleTest(w, h, false, texCount, false, extraMath, 2, 2);
+	break;
+	case 5:
+                doSingleTest(w, h, true, texCount, false, extraMath, 2, 2);
+	break;
+	case 6:
+                doSingleTest(w, h, false, texCount, true, extraMath, 2, 2);
+	break;
+	case 7:
+                doSingleTest(w, h, true, texCount, true, extraMath, 2, 2);
+	break;
+    }
+}
+
+extern "C" {
+    JNIEXPORT void JNICALL Java_com_android_glperf_GLPerfLib_init(JNIEnv * env, jobject obj,  jint width, jint height);
+    JNIEXPORT void JNICALL Java_com_android_glperf_GLPerfLib_step(JNIEnv * env, jobject obj);
+};
+
+JNIEXPORT void JNICALL Java_com_android_glperf_GLPerfLib_init(JNIEnv * env, jobject obj,  jint width, jint height)
+{
+    if (!done) {
+	    w = width;
+	    h = height;
+	    stateClock = 0;
+	    done = false;
+	    setupVA();
+	    genTextures();
+	    const char* fileName = "/sdcard/glperf.csv";
+            if (fOut != NULL) {
+                 LOGI("Closing partially written output.n");
+                 fclose(fOut);
+                 fOut = NULL;
+            }
+	    LOGI("Writing to: %s\n",fileName);
+	    fOut = fopen(fileName, "w");
+	    if (fOut == NULL) {
+		LOGE("Could not open: %s\n", fileName);
+	    }
+
+	    LOGI("\nvarColor, texCount, modulate, extraMath, texSize, blend, Mpps, DC60\n");
+	    if (fOut) fprintf(fOut,"varColor, texCount, modulate, extraMath, texSize, blend, Mpps, DC60\r\n");
+    }
+}
+
+JNIEXPORT void JNICALL Java_com_android_glperf_GLPerfLib_step(JNIEnv * env, jobject obj)
+{
+    if (! done) {
+        if (stateClock > 0 && ((stateClock & 1) == 0)) {
+	    endTimer(saveBuf, w, h, 1, 100);
+        }
+        doTest(w, h);
+        stateClock++;
+    } else {
+	    glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
+    }
+}
diff --git a/opengl/tests/gl_perfapp/res/values/strings.xml b/opengl/tests/gl_perfapp/res/values/strings.xml
new file mode 100644
index 0000000..dc21075
--- /dev/null
+++ b/opengl/tests/gl_perfapp/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="glperf_activity">GLPerf</string>
+
+</resources>
+
diff --git a/opengl/tests/gl_perfapp/src/com/android/glperf/GLPerfActivity.java b/opengl/tests/gl_perfapp/src/com/android/glperf/GLPerfActivity.java
new file mode 100644
index 0000000..e3f3abf
--- /dev/null
+++ b/opengl/tests/gl_perfapp/src/com/android/glperf/GLPerfActivity.java
@@ -0,0 +1,47 @@
+/*
+ * 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.glperf;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.WindowManager;
+
+import java.io.File;
+
+
+public class GLPerfActivity extends Activity {
+
+    GLPerfView mView;
+
+    @Override protected void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+	getWindow().setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON, WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+        mView = new GLPerfView(getApplication());
+	setContentView(mView);
+    }
+
+    @Override protected void onPause() {
+        super.onPause();
+        mView.onPause();
+    }
+
+    @Override protected void onResume() {
+        super.onResume();
+        mView.onResume();
+    }
+}
diff --git a/opengl/tests/gl_perfapp/src/com/android/glperf/GLPerfLib.java b/opengl/tests/gl_perfapp/src/com/android/glperf/GLPerfLib.java
new file mode 100644
index 0000000..89a0e54
--- /dev/null
+++ b/opengl/tests/gl_perfapp/src/com/android/glperf/GLPerfLib.java
@@ -0,0 +1,33 @@
+/*
+ * 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.glperf;
+
+// Wrapper for native library
+
+public class GLPerfLib {
+
+     static {
+         System.loadLibrary("glperf");
+     }
+
+    /**
+     * @param width the current view width
+     * @param height the current view height
+     */
+     public static native void init(int width, int height);
+     public static native void step();
+}
diff --git a/opengl/tests/gl_perfapp/src/com/android/glperf/GLPerfView.java b/opengl/tests/gl_perfapp/src/com/android/glperf/GLPerfView.java
new file mode 100644
index 0000000..4ce4a4d
--- /dev/null
+++ b/opengl/tests/gl_perfapp/src/com/android/glperf/GLPerfView.java
@@ -0,0 +1,296 @@
+/*
+ * 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.glperf;
+/*
+ * 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 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.egl.EGLContext;
+import javax.microedition.khronos.egl.EGLDisplay;
+import javax.microedition.khronos.opengles.GL10;
+
+/**
+ * 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 GLPerfView extends GLSurfaceView {
+    private static String TAG = "GLPerfView";
+
+    public GLPerfView(Context context) {
+        super(context);
+        init(false, 0, 0);
+    }
+
+    public GLPerfView(Context context, boolean translucent, int depth, int stencil) {
+        super(context);
+        init(translucent, depth, stencil);
+    }
+
+    private void init(boolean translucent, int depth, int stencil) {
+        setEGLContextFactory(new ContextFactory());
+        setEGLConfigChooser( translucent ?
+              new ConfigChooser(8,8,8,8, depth, stencil) :
+              new ConfigChooser(5,6,5,0, depth, stencil));
+        setRenderer(new Renderer());
+    }
+
+    private static class ContextFactory implements GLSurfaceView.EGLContextFactory {
+        private static int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
+        public EGLContext createContext(EGL10 egl, EGLDisplay display, EGLConfig eglConfig) {
+            Log.w(TAG, "creating OpenGL ES 2.0 context");
+            checkEglError("Before eglCreateContext", egl);
+            int[] attrib_list = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE };
+            EGLContext context = egl.eglCreateContext(display, eglConfig, EGL10.EGL_NO_CONTEXT, attrib_list);
+            checkEglError("After eglCreateContext", egl);
+            return context;
+        }
+
+        public void destroyContext(EGL10 egl, EGLDisplay display, EGLContext context) {
+            egl.eglDestroyContext(display, context);
+        }
+    }
+
+    private static void checkEglError(String prompt, EGL10 egl) {
+        int error;
+        while ((error = egl.eglGetError()) != EGL10.EGL_SUCCESS) {
+            Log.e(TAG, String.format("%s: EGL error: 0x%x", prompt, error));
+        }
+    }
+
+    private static class ConfigChooser implements GLSurfaceView.EGLConfigChooser {
+        private static int EGL_OPENGL_ES2_BIT = 4;
+        private static int[] s_configAttribs2 =
+        {
+            EGL10.EGL_RED_SIZE, 4,
+            EGL10.EGL_GREEN_SIZE, 4,
+            EGL10.EGL_BLUE_SIZE, 4,
+            EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
+            EGL10.EGL_NONE
+        };
+
+        public ConfigChooser(int r, int g, int b, int a, int depth, int stencil) {
+            mRedSize = r;
+            mGreenSize = g;
+            mBlueSize = b;
+            mAlphaSize = a;
+            mDepthSize = depth;
+            mStencilSize = stencil;
+        }
+
+        public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) {
+
+            int[] num_config = new int[1];
+            egl.eglChooseConfig(display, s_configAttribs2, null, 0, num_config);
+
+            int numConfigs = num_config[0];
+
+            if (numConfigs <= 0) {
+                throw new IllegalArgumentException("No configs match configSpec");
+            }
+            EGLConfig[] configs = new EGLConfig[numConfigs];
+            egl.eglChooseConfig(display, s_configAttribs2, configs, numConfigs, num_config);
+            // printConfigs(egl, display, configs);
+            return chooseConfig(egl, display, configs);
+        }
+
+        public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display,
+                EGLConfig[] configs) {
+            EGLConfig closestConfig = null;
+            int closestDistance = 1000;
+            for(EGLConfig config : configs) {
+                int d = findConfigAttrib(egl, display, config,
+                        EGL10.EGL_DEPTH_SIZE, 0);
+                int s = findConfigAttrib(egl, display, config,
+                        EGL10.EGL_STENCIL_SIZE, 0);
+                if (d >= mDepthSize && s>= mStencilSize) {
+                    int r = findConfigAttrib(egl, display, config,
+                            EGL10.EGL_RED_SIZE, 0);
+                    int g = findConfigAttrib(egl, display, config,
+                             EGL10.EGL_GREEN_SIZE, 0);
+                    int b = findConfigAttrib(egl, display, config,
+                              EGL10.EGL_BLUE_SIZE, 0);
+                    int a = findConfigAttrib(egl, display, config,
+                            EGL10.EGL_ALPHA_SIZE, 0);
+                    int distance = Math.abs(r - mRedSize)
+                                + Math.abs(g - mGreenSize)
+                                + Math.abs(b - mBlueSize)
+                                + Math.abs(a - mAlphaSize);
+                    if (distance < closestDistance) {
+                        closestDistance = distance;
+                        closestConfig = config;
+                    }
+                }
+            }
+            return closestConfig;
+        }
+
+        private int findConfigAttrib(EGL10 egl, EGLDisplay display,
+                EGLConfig config, int attribute, int defaultValue) {
+
+            if (egl.eglGetConfigAttrib(display, config, attribute, mValue)) {
+                return mValue[0];
+            }
+            return defaultValue;
+        }
+
+        private void printConfigs(EGL10 egl, EGLDisplay display,
+            EGLConfig[] configs) {
+            int numConfigs = configs.length;
+            Log.w(TAG, String.format("%d configurations", numConfigs));
+            for (int i = 0; i < numConfigs; i++) {
+                Log.w(TAG, String.format("Configuration %d:\n", i));
+                printConfig(egl, display, configs[i]);
+            }
+        }
+
+        private void printConfig(EGL10 egl, EGLDisplay display,
+                EGLConfig config) {
+            int[] attributes = {
+                    EGL10.EGL_BUFFER_SIZE,
+                    EGL10.EGL_ALPHA_SIZE,
+                    EGL10.EGL_BLUE_SIZE,
+                    EGL10.EGL_GREEN_SIZE,
+                    EGL10.EGL_RED_SIZE,
+                    EGL10.EGL_DEPTH_SIZE,
+                    EGL10.EGL_STENCIL_SIZE,
+                    EGL10.EGL_CONFIG_CAVEAT,
+                    EGL10.EGL_CONFIG_ID,
+                    EGL10.EGL_LEVEL,
+                    EGL10.EGL_MAX_PBUFFER_HEIGHT,
+                    EGL10.EGL_MAX_PBUFFER_PIXELS,
+                    EGL10.EGL_MAX_PBUFFER_WIDTH,
+                    EGL10.EGL_NATIVE_RENDERABLE,
+                    EGL10.EGL_NATIVE_VISUAL_ID,
+                    EGL10.EGL_NATIVE_VISUAL_TYPE,
+                    0x3030, // EGL10.EGL_PRESERVED_RESOURCES,
+                    EGL10.EGL_SAMPLES,
+                    EGL10.EGL_SAMPLE_BUFFERS,
+                    EGL10.EGL_SURFACE_TYPE,
+                    EGL10.EGL_TRANSPARENT_TYPE,
+                    EGL10.EGL_TRANSPARENT_RED_VALUE,
+                    EGL10.EGL_TRANSPARENT_GREEN_VALUE,
+                    EGL10.EGL_TRANSPARENT_BLUE_VALUE,
+                    0x3039, // EGL10.EGL_BIND_TO_TEXTURE_RGB,
+                    0x303A, // EGL10.EGL_BIND_TO_TEXTURE_RGBA,
+                    0x303B, // EGL10.EGL_MIN_SWAP_INTERVAL,
+                    0x303C, // EGL10.EGL_MAX_SWAP_INTERVAL,
+                    EGL10.EGL_LUMINANCE_SIZE,
+                    EGL10.EGL_ALPHA_MASK_SIZE,
+                    EGL10.EGL_COLOR_BUFFER_TYPE,
+                    EGL10.EGL_RENDERABLE_TYPE,
+                    0x3042 // EGL10.EGL_CONFORMANT
+            };
+            String[] names = {
+                    "EGL_BUFFER_SIZE",
+                    "EGL_ALPHA_SIZE",
+                    "EGL_BLUE_SIZE",
+                    "EGL_GREEN_SIZE",
+                    "EGL_RED_SIZE",
+                    "EGL_DEPTH_SIZE",
+                    "EGL_STENCIL_SIZE",
+                    "EGL_CONFIG_CAVEAT",
+                    "EGL_CONFIG_ID",
+                    "EGL_LEVEL",
+                    "EGL_MAX_PBUFFER_HEIGHT",
+                    "EGL_MAX_PBUFFER_PIXELS",
+                    "EGL_MAX_PBUFFER_WIDTH",
+                    "EGL_NATIVE_RENDERABLE",
+                    "EGL_NATIVE_VISUAL_ID",
+                    "EGL_NATIVE_VISUAL_TYPE",
+                    "EGL_PRESERVED_RESOURCES",
+                    "EGL_SAMPLES",
+                    "EGL_SAMPLE_BUFFERS",
+                    "EGL_SURFACE_TYPE",
+                    "EGL_TRANSPARENT_TYPE",
+                    "EGL_TRANSPARENT_RED_VALUE",
+                    "EGL_TRANSPARENT_GREEN_VALUE",
+                    "EGL_TRANSPARENT_BLUE_VALUE",
+                    "EGL_BIND_TO_TEXTURE_RGB",
+                    "EGL_BIND_TO_TEXTURE_RGBA",
+                    "EGL_MIN_SWAP_INTERVAL",
+                    "EGL_MAX_SWAP_INTERVAL",
+                    "EGL_LUMINANCE_SIZE",
+                    "EGL_ALPHA_MASK_SIZE",
+                    "EGL_COLOR_BUFFER_TYPE",
+                    "EGL_RENDERABLE_TYPE",
+                    "EGL_CONFORMANT"
+            };
+            int[] value = new int[1];
+            for (int i = 0; i < attributes.length; i++) {
+                int attribute = attributes[i];
+                String name = names[i];
+                if ( egl.eglGetConfigAttrib(display, config, attribute, value)) {
+                    Log.w(TAG, String.format("  %s: %d\n", name, value[0]));
+                } else {
+                    // Log.w(TAG, String.format("  %s: failed\n", name));
+                    while (egl.eglGetError() != EGL10.EGL_SUCCESS);
+                }
+            }
+        }
+
+        // Subclasses can adjust these values:
+        protected int mRedSize;
+        protected int mGreenSize;
+        protected int mBlueSize;
+        protected int mAlphaSize;
+        protected int mDepthSize;
+        protected int mStencilSize;
+        private int[] mValue = new int[1];
+    }
+
+    private static class Renderer implements GLSurfaceView.Renderer {
+        public void onDrawFrame(GL10 gl) {
+            GLPerfLib.step();
+        }
+
+        public void onSurfaceChanged(GL10 gl, int width, int height) {
+            GLPerfLib.init(width, height);
+        }
+
+        public void onSurfaceCreated(GL10 gl, EGLConfig config) {
+            // Do nothing.
+        }
+    }
+}
+
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/packages/DefaultContainerService/res/values-cs/strings.xml b/packages/DefaultContainerService/res/values-cs/strings.xml
new file mode 100644
index 0000000..0179e85
--- /dev/null
+++ b/packages/DefaultContainerService/res/values-cs/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+**
+** Copyright 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="service_name" msgid="2260781993795858516">"Media Container Service"</string>
+</resources>
diff --git a/packages/DefaultContainerService/res/values-da/strings.xml b/packages/DefaultContainerService/res/values-da/strings.xml
new file mode 100644
index 0000000..0179e85
--- /dev/null
+++ b/packages/DefaultContainerService/res/values-da/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+**
+** Copyright 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="service_name" msgid="2260781993795858516">"Media Container Service"</string>
+</resources>
diff --git a/packages/DefaultContainerService/res/values-de/strings.xml b/packages/DefaultContainerService/res/values-de/strings.xml
new file mode 100644
index 0000000..5d12956
--- /dev/null
+++ b/packages/DefaultContainerService/res/values-de/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+**
+** Copyright 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="service_name" msgid="2260781993795858516">"Medien-Containerdienst"</string>
+</resources>
diff --git a/packages/DefaultContainerService/res/values-el/strings.xml b/packages/DefaultContainerService/res/values-el/strings.xml
new file mode 100644
index 0000000..b0b5794
--- /dev/null
+++ b/packages/DefaultContainerService/res/values-el/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+**
+** Copyright 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="service_name" msgid="2260781993795858516">"Υπηρεσία Media Container"</string>
+</resources>
diff --git a/packages/DefaultContainerService/res/values-es-rUS/strings.xml b/packages/DefaultContainerService/res/values-es-rUS/strings.xml
new file mode 100644
index 0000000..cf893de
--- /dev/null
+++ b/packages/DefaultContainerService/res/values-es-rUS/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+**
+** Copyright 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="service_name" msgid="2260781993795858516">"Servicio de contención de medios"</string>
+</resources>
diff --git a/packages/DefaultContainerService/res/values-es/strings.xml b/packages/DefaultContainerService/res/values-es/strings.xml
new file mode 100644
index 0000000..6817520
--- /dev/null
+++ b/packages/DefaultContainerService/res/values-es/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+**
+** Copyright 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="service_name" msgid="2260781993795858516">"Servicio de contenedor de medios"</string>
+</resources>
diff --git a/packages/DefaultContainerService/res/values-fr/strings.xml b/packages/DefaultContainerService/res/values-fr/strings.xml
new file mode 100644
index 0000000..3b4a90d
--- /dev/null
+++ b/packages/DefaultContainerService/res/values-fr/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+**
+** Copyright 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="service_name" msgid="2260781993795858516">"Service de support multimédia"</string>
+</resources>
diff --git a/packages/DefaultContainerService/res/values-it/strings.xml b/packages/DefaultContainerService/res/values-it/strings.xml
new file mode 100644
index 0000000..55bd6e5
--- /dev/null
+++ b/packages/DefaultContainerService/res/values-it/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+**
+** Copyright 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="service_name" msgid="2260781993795858516">"Servizio Media Container"</string>
+</resources>
diff --git a/packages/DefaultContainerService/res/values-ja/strings.xml b/packages/DefaultContainerService/res/values-ja/strings.xml
new file mode 100644
index 0000000..dc1dfea
--- /dev/null
+++ b/packages/DefaultContainerService/res/values-ja/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+**
+** Copyright 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="service_name" msgid="2260781993795858516">"メディアコンテナサービス"</string>
+</resources>
diff --git a/packages/DefaultContainerService/res/values-ko/strings.xml b/packages/DefaultContainerService/res/values-ko/strings.xml
new file mode 100644
index 0000000..0179e85
--- /dev/null
+++ b/packages/DefaultContainerService/res/values-ko/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+**
+** Copyright 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="service_name" msgid="2260781993795858516">"Media Container Service"</string>
+</resources>
diff --git a/packages/DefaultContainerService/res/values-nb/strings.xml b/packages/DefaultContainerService/res/values-nb/strings.xml
new file mode 100644
index 0000000..0179e85
--- /dev/null
+++ b/packages/DefaultContainerService/res/values-nb/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+**
+** Copyright 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="service_name" msgid="2260781993795858516">"Media Container Service"</string>
+</resources>
diff --git a/packages/DefaultContainerService/res/values-nl/strings.xml b/packages/DefaultContainerService/res/values-nl/strings.xml
new file mode 100644
index 0000000..0179e85
--- /dev/null
+++ b/packages/DefaultContainerService/res/values-nl/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+**
+** Copyright 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="service_name" msgid="2260781993795858516">"Media Container Service"</string>
+</resources>
diff --git a/packages/DefaultContainerService/res/values-pl/strings.xml b/packages/DefaultContainerService/res/values-pl/strings.xml
new file mode 100644
index 0000000..0c96f3d
--- /dev/null
+++ b/packages/DefaultContainerService/res/values-pl/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+**
+** Copyright 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="service_name" msgid="2260781993795858516">"Usługa kontenera multimediów"</string>
+</resources>
diff --git a/packages/DefaultContainerService/res/values-pt-rPT/strings.xml b/packages/DefaultContainerService/res/values-pt-rPT/strings.xml
new file mode 100644
index 0000000..0179e85
--- /dev/null
+++ b/packages/DefaultContainerService/res/values-pt-rPT/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+**
+** Copyright 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="service_name" msgid="2260781993795858516">"Media Container Service"</string>
+</resources>
diff --git a/packages/DefaultContainerService/res/values-pt/strings.xml b/packages/DefaultContainerService/res/values-pt/strings.xml
new file mode 100644
index 0000000..00b90de
--- /dev/null
+++ b/packages/DefaultContainerService/res/values-pt/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+**
+** Copyright 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="service_name" msgid="2260781993795858516">"Serviço de recipiente de mídia"</string>
+</resources>
diff --git a/packages/DefaultContainerService/res/values-ru/strings.xml b/packages/DefaultContainerService/res/values-ru/strings.xml
new file mode 100644
index 0000000..0179e85
--- /dev/null
+++ b/packages/DefaultContainerService/res/values-ru/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+**
+** Copyright 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="service_name" msgid="2260781993795858516">"Media Container Service"</string>
+</resources>
diff --git a/packages/DefaultContainerService/res/values-sv/strings.xml b/packages/DefaultContainerService/res/values-sv/strings.xml
new file mode 100644
index 0000000..b097814
--- /dev/null
+++ b/packages/DefaultContainerService/res/values-sv/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+**
+** Copyright 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="service_name" msgid="2260781993795858516">"Medietjänst"</string>
+</resources>
diff --git a/packages/DefaultContainerService/res/values-tr/strings.xml b/packages/DefaultContainerService/res/values-tr/strings.xml
new file mode 100644
index 0000000..afd870f
--- /dev/null
+++ b/packages/DefaultContainerService/res/values-tr/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+**
+** Copyright 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="service_name" msgid="2260781993795858516">"Ortam Kapsayıcı Hizmeti"</string>
+</resources>
diff --git a/packages/DefaultContainerService/res/values-zh-rCN/strings.xml b/packages/DefaultContainerService/res/values-zh-rCN/strings.xml
new file mode 100644
index 0000000..4f99d1b
--- /dev/null
+++ b/packages/DefaultContainerService/res/values-zh-rCN/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+**
+** Copyright 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="service_name" msgid="2260781993795858516">"媒体容器服务"</string>
+</resources>
diff --git a/packages/DefaultContainerService/res/values-zh-rTW/strings.xml b/packages/DefaultContainerService/res/values-zh-rTW/strings.xml
new file mode 100644
index 0000000..38870f6
--- /dev/null
+++ b/packages/DefaultContainerService/res/values-zh-rTW/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+**
+** Copyright 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="service_name" msgid="2260781993795858516">"媒體庫服務"</string>
+</resources>
diff --git a/packages/DefaultContainerService/res/values/strings.xml b/packages/DefaultContainerService/res/values/strings.xml
index 37f5b61..ffd6b59 100644
--- a/packages/DefaultContainerService/res/values/strings.xml
+++ b/packages/DefaultContainerService/res/values/strings.xml
@@ -18,6 +18,6 @@
 */
 -->
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- service name  -->
+    <!-- service name [CHAR LIMIT=25]  -->
     <string name="service_name">Package Access Helper</string>
 </resources>
diff --git a/packages/SystemUI/Android.mk b/packages/SystemUI/Android.mk
index 4c83768..910e84e 100644
--- a/packages/SystemUI/Android.mk
+++ b/packages/SystemUI/Android.mk
@@ -10,4 +10,6 @@
 LOCAL_PACKAGE_NAME := SystemUI
 LOCAL_CERTIFICATE := platform
 
+LOCAL_PROGUARD_FLAGS := -include $(LOCAL_PATH)/proguard.flags
+
 include $(BUILD_PACKAGE)
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 0fccbe7..7bc2e7d 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -13,7 +13,12 @@
         android:icon="@drawable/ic_launcher_settings">
                  
         <service
-            android:name=".statusbar.StatusBarService"
+            android:name=".statusbar.PhoneStatusBarService"
+            android:exported="false"
+            />
+
+        <service
+            android:name=".statusbar.tablet.TabletStatusBarService"
             android:exported="false"
             />
 
diff --git a/packages/SystemUI/proguard.flags b/packages/SystemUI/proguard.flags
new file mode 100644
index 0000000..9ccc5a9
--- /dev/null
+++ b/packages/SystemUI/proguard.flags
@@ -0,0 +1,4 @@
+-keep class com.android.systemui.statusbar.tablet.TabletStatusBarService {
+  public void notificationIconsClicked(android.view.View);
+  public void systemInfoClicked(android.view.View);
+}
diff --git a/packages/SystemUI/res/drawable-hdpi/dots_empty.png b/packages/SystemUI/res/drawable-hdpi/dots_empty.png
new file mode 100644
index 0000000..31e7654
--- /dev/null
+++ b/packages/SystemUI/res/drawable-hdpi/dots_empty.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/dots_full.png b/packages/SystemUI/res/drawable-hdpi/dots_full.png
new file mode 100644
index 0000000..8c5c604
--- /dev/null
+++ b/packages/SystemUI/res/drawable-hdpi/dots_full.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/notification_dragger.png b/packages/SystemUI/res/drawable-hdpi/notification_dragger.png
new file mode 100644
index 0000000..71b5507
--- /dev/null
+++ b/packages/SystemUI/res/drawable-hdpi/notification_dragger.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/status_bar_back_default.png b/packages/SystemUI/res/drawable-hdpi/status_bar_back_default.png
new file mode 100644
index 0000000..a9f9540
--- /dev/null
+++ b/packages/SystemUI/res/drawable-hdpi/status_bar_back_default.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/status_bar_back_pressed.png b/packages/SystemUI/res/drawable-hdpi/status_bar_back_pressed.png
new file mode 100644
index 0000000..b8aa190
--- /dev/null
+++ b/packages/SystemUI/res/drawable-hdpi/status_bar_back_pressed.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/status_bar_home_default.png b/packages/SystemUI/res/drawable-hdpi/status_bar_home_default.png
new file mode 100644
index 0000000..cb951dd
--- /dev/null
+++ b/packages/SystemUI/res/drawable-hdpi/status_bar_home_default.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/status_bar_home_pressed.png b/packages/SystemUI/res/drawable-hdpi/status_bar_home_pressed.png
new file mode 100644
index 0000000..a835ad5
--- /dev/null
+++ b/packages/SystemUI/res/drawable-hdpi/status_bar_home_pressed.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/status_bar_icon_tray.9.png b/packages/SystemUI/res/drawable-hdpi/status_bar_icon_tray.9.png
new file mode 100644
index 0000000..e1f041c
--- /dev/null
+++ b/packages/SystemUI/res/drawable-hdpi/status_bar_icon_tray.9.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/status_bar_menu_default.png b/packages/SystemUI/res/drawable-hdpi/status_bar_menu_default.png
new file mode 100644
index 0000000..14779cf
--- /dev/null
+++ b/packages/SystemUI/res/drawable-hdpi/status_bar_menu_default.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/status_bar_menu_pressed.png b/packages/SystemUI/res/drawable-hdpi/status_bar_menu_pressed.png
new file mode 100644
index 0000000..6c3d4c9
--- /dev/null
+++ b/packages/SystemUI/res/drawable-hdpi/status_bar_menu_pressed.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/status_bar_veto_normal.png b/packages/SystemUI/res/drawable-hdpi/status_bar_veto_normal.png
new file mode 100644
index 0000000..df532b8
--- /dev/null
+++ b/packages/SystemUI/res/drawable-hdpi/status_bar_veto_normal.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/status_bar_veto_pressed.png b/packages/SystemUI/res/drawable-hdpi/status_bar_veto_pressed.png
new file mode 100644
index 0000000..118c01b
--- /dev/null
+++ b/packages/SystemUI/res/drawable-hdpi/status_bar_veto_pressed.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/dots_empty.png b/packages/SystemUI/res/drawable-mdpi/dots_empty.png
new file mode 100644
index 0000000..22ada41
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/dots_empty.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/dots_full.png b/packages/SystemUI/res/drawable-mdpi/dots_full.png
new file mode 100644
index 0000000..2a346d6
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/dots_full.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/notification_dragger.png b/packages/SystemUI/res/drawable-mdpi/notification_dragger.png
new file mode 100644
index 0000000..fad1f32
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/notification_dragger.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/status_bar_back_default.png b/packages/SystemUI/res/drawable-mdpi/status_bar_back_default.png
new file mode 100644
index 0000000..dd64746
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/status_bar_back_default.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/status_bar_back_pressed.png b/packages/SystemUI/res/drawable-mdpi/status_bar_back_pressed.png
new file mode 100644
index 0000000..66a3677
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/status_bar_back_pressed.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/status_bar_home_default.png b/packages/SystemUI/res/drawable-mdpi/status_bar_home_default.png
new file mode 100644
index 0000000..b129210
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/status_bar_home_default.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/status_bar_home_pressed.png b/packages/SystemUI/res/drawable-mdpi/status_bar_home_pressed.png
new file mode 100644
index 0000000..dcb2447
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/status_bar_home_pressed.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/status_bar_icon_tray.9.png b/packages/SystemUI/res/drawable-mdpi/status_bar_icon_tray.9.png
new file mode 100644
index 0000000..07d00cb
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/status_bar_icon_tray.9.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/status_bar_menu_default.png b/packages/SystemUI/res/drawable-mdpi/status_bar_menu_default.png
new file mode 100644
index 0000000..bf3a755
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/status_bar_menu_default.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/status_bar_menu_pressed.png b/packages/SystemUI/res/drawable-mdpi/status_bar_menu_pressed.png
new file mode 100644
index 0000000..15e21d73
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/status_bar_menu_pressed.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/status_bar_veto_normal.png b/packages/SystemUI/res/drawable-mdpi/status_bar_veto_normal.png
new file mode 100644
index 0000000..f9a7cc2
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/status_bar_veto_normal.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/status_bar_veto_pressed.png b/packages/SystemUI/res/drawable-mdpi/status_bar_veto_pressed.png
new file mode 100644
index 0000000..4461ac8
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/status_bar_veto_pressed.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable/status_bar_back.xml b/packages/SystemUI/res/drawable/status_bar_back.xml
new file mode 100644
index 0000000..92bf147
--- /dev/null
+++ b/packages/SystemUI/res/drawable/status_bar_back.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_pressed="true" android:drawable="@drawable/status_bar_back_pressed" />
+    <item android:drawable="@drawable/status_bar_back_default" />
+</selector>
+
diff --git a/packages/SystemUI/res/drawable/status_bar_home.xml b/packages/SystemUI/res/drawable/status_bar_home.xml
new file mode 100644
index 0000000..0011711
--- /dev/null
+++ b/packages/SystemUI/res/drawable/status_bar_home.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_pressed="true" android:drawable="@drawable/status_bar_home_pressed" />
+    <item android:drawable="@drawable/status_bar_home_default" />
+</selector>
+
diff --git a/packages/SystemUI/res/drawable/status_bar_menu.xml b/packages/SystemUI/res/drawable/status_bar_menu.xml
new file mode 100644
index 0000000..aa7286e
--- /dev/null
+++ b/packages/SystemUI/res/drawable/status_bar_menu.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_pressed="true" android:drawable="@drawable/status_bar_menu_pressed" />
+    <item android:drawable="@drawable/status_bar_menu_default" />
+</selector>
+
diff --git a/packages/SystemUI/res/drawable/status_bar_veto.xml b/packages/SystemUI/res/drawable/status_bar_veto.xml
new file mode 100644
index 0000000..6e1b681
--- /dev/null
+++ b/packages/SystemUI/res/drawable/status_bar_veto.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_pressed="true" android:drawable="@drawable/status_bar_veto_pressed" />
+    <item android:drawable="@drawable/status_bar_veto_normal" />
+</selector>
+
diff --git a/packages/SystemUI/res/layout-xlarge/status_bar.xml b/packages/SystemUI/res/layout-xlarge/status_bar.xml
new file mode 100644
index 0000000..9ae2c25
--- /dev/null
+++ b/packages/SystemUI/res/layout-xlarge/status_bar.xml
@@ -0,0 +1,184 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* apps/common/assets/default/default/skins/StatusBar.xml
+**
+** 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.
+*/
+-->
+
+<!--    android:background="@drawable/status_bar_closed_default_background" -->
+<RelativeLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:systemui="http://schemas.android.com/apk/res/com.android.systemui"
+    android:background="@drawable/status_bar_background"
+    android:focusable="true"
+    android:descendantFocusability="afterDescendants"
+    >
+
+        <com.android.systemui.statusbar.tablet.NotificationIconArea
+            android:id="@+id/notificationIcons"
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:layout_alignParentLeft="true"
+            android:paddingLeft="6dip"
+            android:gravity="center_vertical"
+            android:orientation="horizontal"
+            android:clickable="true"
+            android:onClick="notificationIconsClicked"
+            android:background="@drawable/status_bar_icon_tray"
+            >
+            <view
+                class="com.android.systemui.statusbar.tablet.NotificationIconArea$MoreView"
+                android:id="@+id/more"
+                android:layout_width="wrap_content"
+                android:layout_height="match_parent"
+                android:src="@drawable/stat_notify_more"
+                android:layout_marginLeft="10dip"
+                />
+            <view
+                class="com.android.systemui.statusbar.tablet.NotificationIconArea$IconLayout"
+                android:id="@+id/icons"
+                android:layout_width="wrap_content"
+                android:layout_height="@*android:dimen/status_bar_icon_size"
+                android:layout_marginLeft="8dip"
+                />
+            <view
+                class="com.android.systemui.statusbar.tablet.NotificationIconArea$DraggerView"
+                android:id="@+id/handle"
+                android:layout_width="24dip"
+                android:layout_height="match_parent"
+                android:layout_marginLeft="8dip"
+                />
+
+        </com.android.systemui.statusbar.tablet.NotificationIconArea>
+
+        <RelativeLayout android:id="@+id/systemInfo"
+            android:layout_width="200dip"
+            android:layout_height="match_parent"
+            android:layout_centerHorizontal="true"
+            android:clickable="true"
+            android:onClick="systemInfoClicked"
+            >
+            <com.android.systemui.statusbar.Clock
+                android:id="@+id/clock"
+                android:textAppearance="@*android:style/TextAppearance.StatusBar.Icon"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_centerInParent="true"
+                android:singleLine="true"
+                android:textSize="16sp"
+                android:textStyle="bold"
+                android:padding="6dip"
+                />
+            <ImageView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_toLeftOf="@id/clock"
+                android:layout_centerVertical="true"
+                android:src="@drawable/dots_empty"
+                />
+            <ImageView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_toRightOf="@id/clock"
+                android:layout_centerVertical="true"
+                android:src="@drawable/dots_full"
+                />
+        </RelativeLayout>
+
+        <com.android.systemui.statusbar.KeyButtonView android:id="@+id/back"
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:layout_toLeftOf="@+id/menu"
+            android:paddingLeft="4dip"
+            android:paddingRight="4dip"
+            android:src="@drawable/status_bar_back"
+            systemui:keyCode="4"
+            />
+        <com.android.systemui.statusbar.KeyButtonView android:id="@+id/menu"
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:layout_toLeftOf="@+id/home"
+            android:src="@drawable/status_bar_menu"
+            android:paddingLeft="4dip"
+            android:paddingRight="4dip"
+            systemui:keyCode="82"
+            />
+        <com.android.systemui.statusbar.KeyButtonView android:id="@+id/home"
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:layout_alignParentRight="true"
+            android:paddingLeft="4dip"
+            android:paddingRight="4dip"
+            android:src="@drawable/status_bar_home"
+            systemui:keyCode="3"
+            />
+            
+<!--
+        
+    <LinearLayout android:id="@+id/ticker"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:paddingLeft="6dip"
+        android:animationCache="false"
+        android:orientation="horizontal" >
+        <ImageSwitcher android:id="@+id/tickerIcon"
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:layout_marginRight="8dip"
+            >
+            <com.android.systemui.statusbar.AnimatedImageView
+                android:layout_width="25dip"
+                android:layout_height="25dip"
+                />
+            <com.android.systemui.statusbar.AnimatedImageView
+                android:layout_width="25dip"
+                android:layout_height="25dip"
+                />
+        </ImageSwitcher>
+        <com.android.systemui.statusbar.TickerView android:id="@+id/tickerText"
+            android:layout_width="0dip"
+            android:layout_weight="1"
+            android:layout_height="wrap_content"
+            android:paddingTop="2dip"
+            android:paddingRight="10dip">
+            <TextView
+                android:textAppearance="@*android:style/TextAppearance.StatusBar.Ticker"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:singleLine="true"
+                />
+            <TextView
+                android:textAppearance="@*android:style/TextAppearance.StatusBar.Ticker"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:singleLine="true"
+                />
+        </com.android.systemui.statusbar.TickerView>
+    </LinearLayout>
+
+    <com.android.systemui.statusbar.DateView android:id="@+id/date"
+        android:textAppearance="@*android:style/TextAppearance.StatusBar.Icon"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:singleLine="true"
+        android:gravity="center_vertical|left"
+        android:paddingLeft="6px"
+        android:paddingRight="6px"
+        android:background="@drawable/status_bar_background"
+        />
+-->
+</RelativeLayout>
+
diff --git a/packages/SystemUI/res/layout-xlarge/status_bar_latest_event.xml b/packages/SystemUI/res/layout-xlarge/status_bar_latest_event.xml
new file mode 100644
index 0000000..fa492d0
--- /dev/null
+++ b/packages/SystemUI/res/layout-xlarge/status_bar_latest_event.xml
@@ -0,0 +1,36 @@
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="65sp"
+    android:orientation="vertical"
+    android:background="@android:drawable/status_bar_item_background"
+    >
+
+    <ImageButton
+        android:id="@+id/veto"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_centerVertical="true"
+        android:layout_alignParentRight="true"
+        android:src="@drawable/status_bar_veto"
+        android:background="@null"
+        android:padding="2dip"
+        />
+
+    <com.android.systemui.statusbar.LatestItemView android:id="@+id/content"
+        android:layout_alignParentTop="true"
+        android:layout_toLeftOf="@id/veto"
+        android:layout_width="match_parent"
+        android:layout_height="64sp"
+        android:focusable="true"
+        android:clickable="true"
+        android:paddingRight="6sp"
+        />
+
+    <View
+        android:layout_width="match_parent"
+        android:layout_height="1sp"
+        android:layout_alignParentBottom="true"
+        android:background="@android:drawable/divider_horizontal_dark"
+        />
+
+</RelativeLayout>
diff --git a/packages/SystemUI/res/layout-xlarge/sysbar_panel_notifications.xml b/packages/SystemUI/res/layout-xlarge/sysbar_panel_notifications.xml
new file mode 100644
index 0000000..84a718a
--- /dev/null
+++ b/packages/SystemUI/res/layout-xlarge/sysbar_panel_notifications.xml
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* apps/common/assets/default/default/skins/StatusBar.xml
+**
+** 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.
+*/
+-->
+
+<!--    android:background="@drawable/status_bar_closed_default_background" -->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_height="wrap_content"
+    android:layout_width="match_parent"
+    android:background="#FF000000"
+    android:orientation="vertical"
+    >
+
+    <TextView android:id="@+id/clear_all_button"
+        style="?android:attr/textAppearance"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="right|center_vertical"
+        android:layout_marginTop="2dip"
+        android:layout_marginBottom="1dip"
+        android:layout_marginRight="10dip"
+        android:padding="6dip"
+        android:textSize="14sp"
+        android:text="@string/status_bar_clear_all_button"
+        />
+
+    <View
+        android:layout_width="match_parent"
+        android:layout_height="1sp"
+        android:background="@android:drawable/divider_horizontal_dark"
+        />
+
+    <LinearLayout 
+        android:id="@+id/content"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:gravity="center_horizontal|bottom"
+        android:animationCache="false"
+        android:orientation="vertical"
+        android:background="@drawable/status_bar_background"
+        android:clickable="true"
+        android:focusable="true"
+        android:descendantFocusability="afterDescendants"
+        >
+    </LinearLayout>
+</LinearLayout>
diff --git a/packages/SystemUI/res/layout-xlarge/sysbar_panel_system.xml b/packages/SystemUI/res/layout-xlarge/sysbar_panel_system.xml
new file mode 100644
index 0000000..2222d08
--- /dev/null
+++ b/packages/SystemUI/res/layout-xlarge/sysbar_panel_system.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* apps/common/assets/default/default/skins/StatusBar.xml
+**
+** 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.
+*/
+-->
+
+<!--    android:background="@drawable/status_bar_closed_default_background" -->
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_height="300dip"
+    android:layout_width="400dip"
+    android:paddingLeft="8dip"
+    android:paddingRight="8dip"
+    android:background="#FF000000"
+    >
+        
+    <RelativeLayout 
+        android:id="@+id/content"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:animationCache="false"
+        android:background="@drawable/status_bar_background"
+        android:clickable="true"
+        android:focusable="true"
+        android:descendantFocusability="afterDescendants"
+        >
+
+        <TextView
+            android:id="@+id/systemPanelDummy"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_centerInParent="true"
+            android:textColor="#FFCCCCCC"
+            android:textSize="18sp"
+            />
+
+    </RelativeLayout>
+</FrameLayout>
diff --git a/packages/SystemUI/res/layout/status_bar.xml b/packages/SystemUI/res/layout/status_bar.xml
index 5fe8e79..2f1b36e8 100644
--- a/packages/SystemUI/res/layout/status_bar.xml
+++ b/packages/SystemUI/res/layout/status_bar.xml
@@ -21,6 +21,7 @@
 <!--    android:background="@drawable/status_bar_closed_default_background" -->
 <com.android.systemui.statusbar.StatusBarView
     xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:systemui="http://schemas.android.com/apk/res/com.android.systemui"
     android:background="@drawable/status_bar_background"
     android:orientation="vertical"
     android:focusable="true"
diff --git a/packages/SystemUI/res/layout/status_bar_latest_event.xml b/packages/SystemUI/res/layout/status_bar_latest_event.xml
deleted file mode 100644
index 88d9739..0000000
--- a/packages/SystemUI/res/layout/status_bar_latest_event.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="65sp"
-    android:orientation="vertical"
-    >
-
-    <com.android.systemui.statusbar.LatestItemView android:id="@+id/content"
-            android:layout_width="match_parent"
-            android:layout_height="64sp"
-            android:background="@android:drawable/status_bar_item_background"
-            android:focusable="true"
-            android:clickable="true"
-            android:paddingRight="6sp"
-            >
-    </com.android.systemui.statusbar.LatestItemView>
-
-    <View
-        android:layout_width="match_parent"
-        android:layout_height="1sp"
-        android:background="@android:drawable/divider_horizontal_bright"
-        />
-
-</LinearLayout>
-
diff --git a/packages/SystemUI/res/values-xlarge/colors.xml b/packages/SystemUI/res/values-xlarge/colors.xml
new file mode 100644
index 0000000..14161c3
--- /dev/null
+++ b/packages/SystemUI/res/values-xlarge/colors.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <drawable name="status_bar_background">#000000</drawable>
+</resources>
+
diff --git a/packages/SystemUI/res/values-xlarge/config.xml b/packages/SystemUI/res/values-xlarge/config.xml
new file mode 100644
index 0000000..4cf5d18d11
--- /dev/null
+++ b/packages/SystemUI/res/values-xlarge/config.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2010, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License"); 
+** you may not use this file except in compliance with the License. 
+** You may obtain a copy of the License at 
+**
+**     http://www.apache.org/licenses/LICENSE-2.0 
+**
+** Unless required by applicable law or agreed to in writing, software 
+** distributed under the License is distributed on an "AS IS" BASIS, 
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
+** See the License for the specific language governing permissions and 
+** limitations under the License.
+*/
+-->
+
+<!-- These resources are around just to allow their values to be customized
+     for different hardware and product builds. -->
+<resources>
+    <integer name="config_status_bar_position">1</integer>
+</resources>
+
diff --git a/packages/SystemUI/res/values-xlarge/strings.xml b/packages/SystemUI/res/values-xlarge/strings.xml
new file mode 100644
index 0000000..4aa4b47
--- /dev/null
+++ b/packages/SystemUI/res/values-xlarge/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * 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.
+ */
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- The text for the button in the notification window-shade that clears
+         all of the currently visible notifications. -->
+    <string name="status_bar_clear_all_button">Clear all</string>
+</resources>
diff --git a/packages/SystemUI/res/values/attrs.xml b/packages/SystemUI/res/values/attrs.xml
new file mode 100644
index 0000000..23bcf20
--- /dev/null
+++ b/packages/SystemUI/res/values/attrs.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<resources>
+    <declare-styleable name="KeyButtonView">
+        <attr name="keyCode" format="integer" />
+    </declare-styleable>
+</resources>
+
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 8ea46e5..ac00c69 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -20,7 +20,16 @@
 <!-- These resources are around just to allow their values to be customized
      for different hardware and product builds. -->
 <resources>
-    <!-- Control whether status bar should distinguish HSPA data icon form UMTS data icon on devices -->
+
+    <!-- Control whether status bar should distinguish HSPA data icon form UMTS
+    data icon on devices -->
     <bool name="config_hspa_data_distinguishable">false</bool>
+
+    <!-- The location of the status bar.
+        0 - top
+        1 - bottom
+    -->
+    <integer name="config_status_bar_position">0</integer>
+
 </resources>
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CloseDragHandle.java b/packages/SystemUI/src/com/android/systemui/statusbar/CloseDragHandle.java
index f45caf51..0f6723e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CloseDragHandle.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CloseDragHandle.java
@@ -23,7 +23,7 @@
 
 
 public class CloseDragHandle extends LinearLayout {
-    StatusBarService mService;
+    PhoneStatusBarService mService;
 
     public CloseDragHandle(Context context, AttributeSet attrs) {
         super(context, attrs);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index f9347b1..2c0af65 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -32,7 +32,7 @@
  * coalescing these calls so they don't stack up.  For the calls
  * are coalesced, note that they are all idempotent.
  */
-class CommandQueue extends IStatusBar.Stub {
+public class CommandQueue extends IStatusBar.Stub {
     private static final String TAG = "StatusBar.CommandQueue";
 
     private static final int MSG_MASK = 0xffff0000;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandedView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandedView.java
index 3d85f27..a2d4b95 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandedView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandedView.java
@@ -27,7 +27,7 @@
 
 
 public class ExpandedView extends LinearLayout {
-    StatusBarService mService;
+    PhoneStatusBarService mService;
     int mPrevHeight = -1;
 
     public ExpandedView(Context context, AttributeSet attrs) {
@@ -53,7 +53,7 @@
              //Slog.d(StatusBarService.TAG, "height changed old=" + mPrevHeight
              //     + " new=" + height);
              mPrevHeight = height;
-             mService.updateExpandedViewPos(StatusBarService.EXPANDED_LEAVE_ALONE);
+             mService.updateExpandedViewPos(PhoneStatusBarService.EXPANDED_LEAVE_ALONE);
          }
      }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyButtonView.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyButtonView.java
new file mode 100644
index 0000000..b0508b8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyButtonView.java
@@ -0,0 +1,122 @@
+/*
+ * 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.
+ */
+
+package com.android.systemui.statusbar;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.drawable.AnimationDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.ServiceManager;
+import android.util.AttributeSet;
+import android.util.Slog;
+import android.view.IWindowManager;
+import android.view.InputDevice;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.widget.ImageView;
+import android.widget.RemoteViews.RemoteView;
+
+import com.android.systemui.R;
+
+public class KeyButtonView extends ImageView {
+    IWindowManager mWindowManager;
+    long mDownTime;
+    boolean mSending;
+    int mCode;
+    int mRepeat;
+
+    public KeyButtonView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public KeyButtonView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs);
+
+        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.KeyButtonView,
+                defStyle, 0);
+
+        mCode = a.getInteger(R.styleable.KeyButtonView_keyCode, 0);
+        if (mCode == 0) {
+            Slog.w(StatusBarService.TAG, "KeyButtonView without key code id=0x"
+                    + Integer.toHexString(getId()));
+        }
+        
+        a.recycle();
+
+        mWindowManager = IWindowManager.Stub.asInterface(
+                ServiceManager.getService(Context.WINDOW_SERVICE));
+    }
+
+    public boolean onTouchEvent(MotionEvent ev) {
+        final int action = ev.getAction();
+        int x, y;
+
+        switch (action) {
+            case MotionEvent.ACTION_DOWN:
+                mDownTime = SystemClock.uptimeMillis();
+                mRepeat = 0;
+                mSending = true;
+                sendEvent(KeyEvent.ACTION_DOWN,
+                        KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_SOFT_KEYBOARD, mDownTime);
+                setPressed(true);
+                break;
+            case MotionEvent.ACTION_MOVE:
+                if (mSending) {
+                    x = (int)ev.getX();
+                    y = (int)ev.getY();
+                    if (x < 0 || x >= getWidth() || y < 0 || y >= getHeight()) {
+                        mSending = false;
+                        sendEvent(KeyEvent.ACTION_UP,
+                                KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_SOFT_KEYBOARD
+                                        | KeyEvent.FLAG_CANCELED);
+                        setPressed(false);
+                    }
+                }
+                break;
+            case MotionEvent.ACTION_UP:
+            case MotionEvent.ACTION_CANCEL:
+                if (mSending) {
+                    mSending = false;
+                    sendEvent(KeyEvent.ACTION_UP,
+                            KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_SOFT_KEYBOARD);
+                    setPressed(false);
+                }
+                break;
+        }
+
+        return true;
+    }
+
+    void sendEvent(int action, int flags) {
+        sendEvent(action, flags, SystemClock.uptimeMillis());
+    }
+
+    void sendEvent(int action, int flags, long when) {
+        final KeyEvent ev = new KeyEvent(mDownTime, when, action, mCode, mRepeat,
+                0, 0, 0, flags, InputDevice.SOURCE_KEYBOARD);
+        try {
+            Slog.d(StatusBarService.TAG, "injecting event " + ev);
+            mWindowManager.injectInputEventNoWait(ev);
+        } catch (RemoteException ex) {
+            // System process is dead
+        }
+    }
+}
+
+
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
index 7a82267..45abe93 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
@@ -22,6 +22,7 @@
 
 import com.android.internal.statusbar.StatusBarNotification;
 
+import java.util.Comparator;
 import java.util.ArrayList;
 
 /**
@@ -35,26 +36,47 @@
         public View row; // the outer expanded view
         public View content; // takes the click events and sends the PendingIntent
         public View expanded; // the inflated RemoteViews
+        public Entry() {}
+        public Entry(IBinder key, StatusBarNotification n, StatusBarIconView ic) {
+            this.key = key;
+            this.notification = n;
+            this.icon = ic;
+        }
     }
     private final ArrayList<Entry> mEntries = new ArrayList<Entry>();
+    private final Comparator<Entry> mEntryCmp = new Comparator<Entry>() {
+        public int compare(Entry a, Entry b) {
+            return (int)(a.notification.notification.when - b.notification.notification.when);
+        }
+    };
 
     public int size() {
         return mEntries.size();
     }
 
-    public Entry getEntryAt(int index) {
-        return mEntries.get(index);
+    public Entry get(int i) {
+        return mEntries.get(i);
     }
 
-    public int findEntry(IBinder key) {
-        final int N = mEntries.size();
-        for (int i=0; i<N; i++) {
-            Entry entry = mEntries.get(i);
-            if (entry.key == key) {
-                return i;
+    public Entry findByKey(IBinder key) {
+        for (Entry e : mEntries) {
+            if (e.key == key) {
+                return e;
             }
         }
-        return -1;
+        return null;
+    }
+
+    public int add(Entry entry) {
+        int i;
+        int N = mEntries.size();
+        for (i=0; i<N; i++) {
+            if (mEntryCmp.compare(mEntries.get(i), entry) > 0) {
+                break;
+            }
+        }
+        mEntries.add(i, entry);
+        return i;
     }
 
     public int add(IBinder key, StatusBarNotification notification, View row, View content,
@@ -66,42 +88,23 @@
         entry.content = content;
         entry.expanded = expanded;
         entry.icon = icon;
-        final int index = chooseIndex(notification.notification.when);
-        mEntries.add(index, entry);
-        return index;
+        return add(entry);
     }
 
     public Entry remove(IBinder key) {
-        final int N = mEntries.size();
-        for (int i=0; i<N; i++) {
-            Entry entry = mEntries.get(i);
-            if (entry.key == key) {
-                mEntries.remove(i);
-                return entry;
-            }
+        Entry e = findByKey(key);
+        if (e != null) {
+            mEntries.remove(e);
         }
-        return null;
-    }
-
-    private int chooseIndex(final long when) {
-        final int N = mEntries.size();
-        for (int i=0; i<N; i++) {
-            Entry entry = mEntries.get(i);
-            if (entry.notification.notification.when > when) {
-                return i;
-            }
-        }
-        return N;
+        return e;
     }
 
     /**
      * Return whether there are any visible items (i.e. items without an error).
      */
     public boolean hasVisibleItems() {
-        final int N = mEntries.size();
-        for (int i=0; i<N; i++) {
-            Entry entry = mEntries.get(i);
-            if (entry.expanded != null) { // the view successfully inflated
+        for (Entry e : mEntries) {
+            if (e.expanded != null) { // the view successfully inflated
                 return true;
             }
         }
@@ -112,11 +115,9 @@
      * Return whether there are any clearable items (that aren't errors).
      */
     public boolean hasClearableItems() {
-        final int N = mEntries.size();
-        for (int i=0; i<N; i++) {
-            Entry entry = mEntries.get(i);
-            if (entry.expanded != null) { // the view successfully inflated
-                if ((entry.notification.notification.flags & Notification.FLAG_NO_CLEAR) == 0) {
+        for (Entry e : mEntries) {
+            if (e.expanded != null) { // the view successfully inflated
+                if ((e.notification.notification.flags & Notification.FLAG_NO_CLEAR) == 0) {
                     return true;
                 }
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/PhoneStatusBarService.java b/packages/SystemUI/src/com/android/systemui/statusbar/PhoneStatusBarService.java
new file mode 100644
index 0000000..5add6de
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/PhoneStatusBarService.java
@@ -0,0 +1,1551 @@
+/*
+ * 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.systemui.statusbar;
+
+import android.app.Service;
+import android.app.ActivityManagerNative;
+import android.app.Dialog;
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.app.StatusBarManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.Message;
+import android.os.SystemClock;
+import android.util.Slog;
+import android.util.Log;
+import android.view.Display;
+import android.view.Gravity;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.view.WindowManager;
+import android.view.WindowManagerImpl;
+import android.view.animation.Animation;
+import android.view.animation.AnimationUtils;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.RemoteViews;
+import android.widget.ScrollView;
+import android.widget.TextView;
+import android.widget.FrameLayout;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Set;
+
+import com.android.internal.statusbar.IStatusBar;
+import com.android.internal.statusbar.IStatusBarService;
+import com.android.internal.statusbar.StatusBarIcon;
+import com.android.internal.statusbar.StatusBarIconList;
+import com.android.internal.statusbar.StatusBarNotification;
+
+import com.android.systemui.R;
+import com.android.systemui.statusbar.policy.StatusBarPolicy;
+
+
+
+public class PhoneStatusBarService extends StatusBarService {
+    static final String TAG = "PhoneStatusBarService";
+    static final boolean SPEW = false;
+
+    public static final String ACTION_STATUSBAR_START
+            = "com.android.internal.policy.statusbar.START";
+
+    static final int EXPANDED_LEAVE_ALONE = -10000;
+    static final int EXPANDED_FULL_OPEN = -10001;
+
+    private static final int MSG_ANIMATE = 1000;
+    private static final int MSG_ANIMATE_REVEAL = 1001;
+    private static final int MSG_SHOW_INTRUDER = 1002;
+    private static final int MSG_HIDE_INTRUDER = 1003;
+
+    // will likely move to a resource or other tunable param at some point
+    private static final int INTRUDER_ALERT_DECAY_MS = 10000;
+
+    StatusBarPolicy mIconPolicy;
+
+    int mIconSize;
+    Display mDisplay;
+
+    StatusBarView mStatusBarView;
+    int mPixelFormat;
+    H mHandler = new H();
+    Object mQueueLock = new Object();
+
+    // icons
+    LinearLayout mIcons;
+    IconMerger mNotificationIcons;
+    LinearLayout mStatusIcons;
+
+    // expanded notifications
+    Dialog mExpandedDialog;
+    ExpandedView mExpandedView;
+    WindowManager.LayoutParams mExpandedParams;
+    ScrollView mScrollView;
+    View mNotificationLinearLayout;
+    View mExpandedContents;
+    // top bar
+    TextView mNoNotificationsTitle;
+    TextView mClearButton;
+    // drag bar
+    CloseDragHandle mCloseView;
+    // ongoing
+    NotificationData mOngoing = new NotificationData();
+    TextView mOngoingTitle;
+    LinearLayout mOngoingItems;
+    // latest
+    NotificationData mLatest = new NotificationData();
+    TextView mLatestTitle;
+    LinearLayout mLatestItems;
+    // position
+    int[] mPositionTmp = new int[2];
+    boolean mExpanded;
+    boolean mExpandedVisible;
+
+    // the date view
+    DateView mDateView;
+
+    // for immersive activities
+    private View mIntruderAlertView;
+
+    // the tracker view
+    TrackingView mTrackingView;
+    WindowManager.LayoutParams mTrackingParams;
+    int mTrackingPosition; // the position of the top of the tracking view.
+    private boolean mPanelSlightlyVisible;
+
+    // ticker
+    private Ticker mTicker;
+    private View mTickerView;
+    private boolean mTicking;
+
+    // Tracking finger for opening/closing.
+    int mEdgeBorder; // corresponds to R.dimen.status_bar_edge_ignore
+    boolean mTracking;
+    VelocityTracker mVelocityTracker;
+
+    static final int ANIM_FRAME_DURATION = (1000/60);
+
+    boolean mAnimating;
+    long mCurAnimationTime;
+    float mDisplayHeight;
+    float mAnimY;
+    float mAnimVel;
+    float mAnimAccel;
+    long mAnimLastTime;
+    boolean mAnimatingReveal = false;
+    int mViewDelta;
+    int[] mAbsPos = new int[2];
+
+    // for disabling the status bar
+    int mDisabled = 0;
+
+    private class ExpandedDialog extends Dialog {
+        ExpandedDialog(Context context) {
+            super(context, com.android.internal.R.style.Theme_Light_NoTitleBar);
+        }
+
+        @Override
+        public boolean dispatchKeyEvent(KeyEvent event) {
+            boolean down = event.getAction() == KeyEvent.ACTION_DOWN;
+            switch (event.getKeyCode()) {
+            case KeyEvent.KEYCODE_BACK:
+                if (!down) {
+                    animateCollapse();
+                }
+                return true;
+            }
+            return super.dispatchKeyEvent(event);
+        }
+    }
+
+    @Override
+    public void onCreate() {
+        mDisplay = ((WindowManager)getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
+
+        super.onCreate();
+
+        addIntruderView();
+
+        // Lastly, call to the icon policy to install/update all the icons.
+        mIconPolicy = new StatusBarPolicy(this);
+    }
+
+    @Override
+    public void onDestroy() {
+        // we're never destroyed
+    }
+
+    // ================================================================================
+    // Constructing the view
+    // ================================================================================
+    protected View makeStatusBarView() {
+        final Context context = this;
+
+        Resources res = context.getResources();
+
+        mIconSize = res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_icon_size);
+
+        ExpandedView expanded = (ExpandedView)View.inflate(context,
+                R.layout.status_bar_expanded, null);
+        expanded.mService = this;
+
+        mIntruderAlertView = View.inflate(context, R.layout.intruder_alert, null);
+        mIntruderAlertView.setVisibility(View.GONE);
+        mIntruderAlertView.setClickable(true);
+
+        StatusBarView sb = (StatusBarView)View.inflate(context, R.layout.status_bar, null);
+        sb.mService = this;
+
+        // figure out which pixel-format to use for the status bar.
+        mPixelFormat = PixelFormat.TRANSLUCENT;
+        Drawable bg = sb.getBackground();
+        if (bg != null) {
+            mPixelFormat = bg.getOpacity();
+        }
+
+        mStatusBarView = sb;
+        mStatusIcons = (LinearLayout)sb.findViewById(R.id.statusIcons);
+        mNotificationIcons = (IconMerger)sb.findViewById(R.id.notificationIcons);
+        mIcons = (LinearLayout)sb.findViewById(R.id.icons);
+        mTickerView = sb.findViewById(R.id.ticker);
+        mDateView = (DateView)sb.findViewById(R.id.date);
+
+        mExpandedDialog = new ExpandedDialog(context);
+        mExpandedView = expanded;
+        mExpandedContents = expanded.findViewById(R.id.notificationLinearLayout);
+        mOngoingTitle = (TextView)expanded.findViewById(R.id.ongoingTitle);
+        mOngoingItems = (LinearLayout)expanded.findViewById(R.id.ongoingItems);
+        mLatestTitle = (TextView)expanded.findViewById(R.id.latestTitle);
+        mLatestItems = (LinearLayout)expanded.findViewById(R.id.latestItems);
+        mNoNotificationsTitle = (TextView)expanded.findViewById(R.id.noNotificationsTitle);
+        mClearButton = (TextView)expanded.findViewById(R.id.clear_all_button);
+        mClearButton.setOnClickListener(mClearButtonListener);
+        mScrollView = (ScrollView)expanded.findViewById(R.id.scroll);
+        mNotificationLinearLayout = expanded.findViewById(R.id.notificationLinearLayout);
+
+        mOngoingTitle.setVisibility(View.GONE);
+        mLatestTitle.setVisibility(View.GONE);
+
+        mTicker = new MyTicker(context, sb);
+
+        TickerView tickerView = (TickerView)sb.findViewById(R.id.tickerText);
+        tickerView.mTicker = mTicker;
+
+        mTrackingView = (TrackingView)View.inflate(context, R.layout.status_bar_tracking, null);
+        mTrackingView.mService = this;
+        mCloseView = (CloseDragHandle)mTrackingView.findViewById(R.id.close);
+        mCloseView.mService = this;
+
+        mEdgeBorder = res.getDimensionPixelSize(R.dimen.status_bar_edge_ignore);
+
+        // the more notifications icon
+        StatusBarIconView moreView = new StatusBarIconView(this, "more");
+        moreView.set(new StatusBarIcon(null, R.drawable.stat_notify_more, 0));
+        mNotificationIcons.addMoreView(moreView,
+                new LinearLayout.LayoutParams(mIconSize, mIconSize));
+
+        // set the inital view visibility
+        setAreThereNotifications();
+        mDateView.setVisibility(View.INVISIBLE);
+
+        // receive broadcasts
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
+        filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
+        filter.addAction(Intent.ACTION_SCREEN_OFF);
+        context.registerReceiver(mBroadcastReceiver, filter);
+
+        return sb;
+    }
+
+    protected int getStatusBarGravity() {
+        return Gravity.TOP | Gravity.FILL_HORIZONTAL;
+    }
+
+    private void addIntruderView() {
+        final Resources res = getResources();
+        final int height= res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height);
+
+        WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+                ViewGroup.LayoutParams.MATCH_PARENT,
+                ViewGroup.LayoutParams.WRAP_CONTENT,
+                WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL,
+                WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+                    | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
+                    | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+                    | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+                    | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
+                PixelFormat.TRANSLUCENT);
+        lp.gravity = Gravity.TOP | Gravity.FILL_HORIZONTAL;
+        lp.y += height * 1.5; // FIXME
+        lp.setTitle("IntruderAlert");
+        lp.windowAnimations = com.android.internal.R.style.Animation_StatusBar_IntruderAlert;
+
+        WindowManagerImpl.getDefault().addView(mIntruderAlertView, lp);
+    }
+
+    public void addIcon(String slot, int index, int viewIndex, StatusBarIcon icon) {
+        if (SPEW) Slog.d(TAG, "addIcon slot=" + slot + " index=" + index + " viewIndex=" + viewIndex
+                + " icon=" + icon);
+        StatusBarIconView view = new StatusBarIconView(this, slot);
+        view.set(icon);
+        mStatusIcons.addView(view, viewIndex, new LinearLayout.LayoutParams(mIconSize, mIconSize));
+    }
+
+    public void updateIcon(String slot, int index, int viewIndex,
+            StatusBarIcon old, StatusBarIcon icon) {
+        if (SPEW) Slog.d(TAG, "updateIcon slot=" + slot + " index=" + index + " viewIndex=" + viewIndex
+                + " old=" + old + " icon=" + icon);
+        StatusBarIconView view = (StatusBarIconView)mStatusIcons.getChildAt(viewIndex);
+        view.set(icon);
+    }
+
+    public void removeIcon(String slot, int index, int viewIndex) {
+        if (SPEW) Slog.d(TAG, "removeIcon slot=" + slot + " index=" + index + " viewIndex=" + viewIndex);
+        mStatusIcons.removeViewAt(viewIndex);
+    }
+
+    public void addNotification(IBinder key, StatusBarNotification notification) {
+        StatusBarIconView iconView = addNotificationViews(key, notification);
+        if (iconView == null) return;
+
+        boolean immersive = false;
+        try {
+            immersive = ActivityManagerNative.getDefault().isTopActivityImmersive();
+            Slog.d(TAG, "Top activity is " + (immersive?"immersive":"not immersive"));
+        } catch (RemoteException ex) {
+        }
+        if (immersive) {
+            if ((notification.notification.flags & Notification.FLAG_HIGH_PRIORITY) != 0) {
+                Slog.d(TAG, "Presenting high-priority notification in immersive activity");
+                // special new transient ticker mode
+                // 1. Populate mIntruderAlertView
+
+                ImageView alertIcon = (ImageView) mIntruderAlertView.findViewById(R.id.alertIcon);
+                TextView alertText = (TextView) mIntruderAlertView.findViewById(R.id.alertText);
+                alertIcon.setImageDrawable(StatusBarIconView.getIcon(
+                    alertIcon.getContext(),
+                    iconView.getStatusBarIcon()));
+                alertText.setText(notification.notification.tickerText);
+
+                View button = mIntruderAlertView.findViewById(R.id.intruder_alert_content);
+                button.setOnClickListener(
+                    new Launcher(notification.notification.contentIntent,
+                        notification.pkg, notification.tag, notification.id));
+
+                // 2. Animate mIntruderAlertView in
+                mHandler.sendEmptyMessage(MSG_SHOW_INTRUDER);
+
+                // 3. Set alarm to age the notification off (TODO)
+                mHandler.removeMessages(MSG_HIDE_INTRUDER);
+                mHandler.sendEmptyMessageDelayed(MSG_HIDE_INTRUDER, INTRUDER_ALERT_DECAY_MS);
+            }
+        } else if (notification.notification.fullScreenIntent != null) {
+            // not immersive & a full-screen alert should be shown
+            Slog.d(TAG, "Notification has fullScreenIntent and activity is not immersive;"
+                    + " sending fullScreenIntent");
+            try {
+                notification.notification.fullScreenIntent.send();
+            } catch (PendingIntent.CanceledException e) {
+            }
+        } else {
+            // usual case: status bar visible & not immersive
+
+            // show the ticker
+            tick(notification);
+        }
+
+        // Recalculate the position of the sliding windows and the titles.
+        setAreThereNotifications();
+        updateExpandedViewPos(EXPANDED_LEAVE_ALONE);
+    }
+
+    public void updateNotification(IBinder key, StatusBarNotification notification) {
+        Slog.d(TAG, "updateNotification key=" + key + " notification=" + notification);
+
+        NotificationData oldList;
+        NotificationData.Entry oldEntry = mOngoing.findByKey(key);
+        if (oldEntry != null) {
+            oldList = mOngoing;
+        } else {
+            oldEntry = mLatest.findByKey(key);
+            if (oldEntry == null) {
+                Slog.w(TAG, "updateNotification for unknown key: " + key);
+                return;
+            }
+            oldList = mLatest;
+        }
+        final StatusBarNotification oldNotification = oldEntry.notification;
+        final RemoteViews oldContentView = oldNotification.notification.contentView;
+
+        final RemoteViews contentView = notification.notification.contentView;
+
+        if (false) {
+            Slog.d(TAG, "old notification: when=" + oldNotification.notification.when
+                    + " ongoing=" + oldNotification.isOngoing()
+                    + " expanded=" + oldEntry.expanded
+                    + " contentView=" + oldContentView);
+            Slog.d(TAG, "new notification: when=" + notification.notification.when
+                    + " ongoing=" + oldNotification.isOngoing()
+                    + " contentView=" + contentView);
+        }
+
+        // Can we just reapply the RemoteViews in place?  If when didn't change, the order
+        // didn't change.
+        if (notification.notification.when == oldNotification.notification.when
+                && notification.isOngoing() == oldNotification.isOngoing()
+                && oldEntry.expanded != null
+                && contentView != null && oldContentView != null
+                && contentView.getPackage() != null
+                && oldContentView.getPackage() != null
+                && oldContentView.getPackage().equals(contentView.getPackage())
+                && oldContentView.getLayoutId() == contentView.getLayoutId()) {
+            if (SPEW) Slog.d(TAG, "reusing notification");
+            oldEntry.notification = notification;
+            try {
+                // Reapply the RemoteViews
+                contentView.reapply(this, oldEntry.content);
+                // update the contentIntent
+                final PendingIntent contentIntent = notification.notification.contentIntent;
+                if (contentIntent != null) {
+                    oldEntry.content.setOnClickListener(new Launcher(contentIntent,
+                                notification.pkg, notification.tag, notification.id));
+                }
+                // Update the icon.
+                final StatusBarIcon ic = new StatusBarIcon(notification.pkg,
+                        notification.notification.icon, notification.notification.iconLevel,
+                        notification.notification.number);
+                if (!oldEntry.icon.set(ic)) {
+                    handleNotificationError(key, notification, "Couldn't update icon: " + ic);
+                    return;
+                }
+            }
+            catch (RuntimeException e) {
+                // It failed to add cleanly.  Log, and remove the view from the panel.
+                Slog.w(TAG, "Couldn't reapply views for package " + contentView.getPackage(), e);
+                removeNotificationViews(key);
+                addNotificationViews(key, notification);
+            }
+        } else {
+            if (SPEW) Slog.d(TAG, "not reusing notification");
+            removeNotificationViews(key);
+            addNotificationViews(key, notification);
+        }
+
+        // Restart the ticker if it's still running
+        tick(notification);
+
+        // Recalculate the position of the sliding windows and the titles.
+        setAreThereNotifications();
+        updateExpandedViewPos(EXPANDED_LEAVE_ALONE);
+    }
+
+    public void removeNotification(IBinder key) {
+        if (SPEW) Slog.d(TAG, "removeNotification key=" + key);
+        StatusBarNotification old = removeNotificationViews(key);
+
+        if (old != null) {
+            // Cancel the ticker if it's still running
+            mTicker.removeEntry(old);
+
+            // Recalculate the position of the sliding windows and the titles.
+            setAreThereNotifications();
+            updateExpandedViewPos(EXPANDED_LEAVE_ALONE);
+        }
+    }
+
+    private int chooseIconIndex(boolean isOngoing, int viewIndex) {
+        final int latestSize = mLatest.size();
+        if (isOngoing) {
+            return latestSize + (mOngoing.size() - viewIndex);
+        } else {
+            return latestSize - viewIndex;
+        }
+    }
+
+    View[] makeNotificationView(StatusBarNotification notification, ViewGroup parent) {
+        Notification n = notification.notification;
+        RemoteViews remoteViews = n.contentView;
+        if (remoteViews == null) {
+            return null;
+        }
+
+        // create the row view
+        LayoutInflater inflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+        View row = inflater.inflate(R.layout.status_bar_latest_event, parent, false);
+
+        // bind the click event to the content area
+        ViewGroup content = (ViewGroup)row.findViewById(R.id.content);
+        content.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
+        content.setOnFocusChangeListener(mFocusChangeListener);
+        PendingIntent contentIntent = n.contentIntent;
+        if (contentIntent != null) {
+            content.setOnClickListener(new Launcher(contentIntent, notification.pkg,
+                        notification.tag, notification.id));
+        }
+
+        View expanded = null;
+        Exception exception = null;
+        try {
+            expanded = remoteViews.apply(this, content);
+        }
+        catch (RuntimeException e) {
+            exception = e;
+        }
+        if (expanded == null) {
+            String ident = notification.pkg + "/0x" + Integer.toHexString(notification.id);
+            Slog.e(TAG, "couldn't inflate view for notification " + ident, exception);
+            return null;
+        } else {
+            content.addView(expanded);
+            row.setDrawingCacheEnabled(true);
+        }
+
+        return new View[] { row, content, expanded };
+    }
+
+    StatusBarIconView addNotificationViews(IBinder key, StatusBarNotification notification) {
+        NotificationData list;
+        ViewGroup parent;
+        final boolean isOngoing = notification.isOngoing();
+        if (isOngoing) {
+            list = mOngoing;
+            parent = mOngoingItems;
+        } else {
+            list = mLatest;
+            parent = mLatestItems;
+        }
+        // Construct the expanded view.
+        final View[] views = makeNotificationView(notification, parent);
+        if (views == null) {
+            handleNotificationError(key, notification, "Couldn't expand RemoteViews for: "
+                    + notification);
+            return null;
+        }
+        final View row = views[0];
+        final View content = views[1];
+        final View expanded = views[2];
+        // Construct the icon.
+        final StatusBarIconView iconView = new StatusBarIconView(this,
+                notification.pkg + "/0x" + Integer.toHexString(notification.id));
+        final StatusBarIcon ic = new StatusBarIcon(notification.pkg, notification.notification.icon,
+                    notification.notification.iconLevel, notification.notification.number);
+        if (!iconView.set(ic)) {
+            handleNotificationError(key, notification, "Coulding create icon: " + ic);
+            return null;
+        }
+        // Add the expanded view.
+        final int viewIndex = list.add(key, notification, row, content, expanded, iconView);
+        parent.addView(row, viewIndex);
+        // Add the icon.
+        final int iconIndex = chooseIconIndex(isOngoing, viewIndex);
+        mNotificationIcons.addView(iconView, iconIndex,
+                new LinearLayout.LayoutParams(mIconSize, mIconSize));
+        return iconView;
+    }
+
+    StatusBarNotification removeNotificationViews(IBinder key) {
+        NotificationData.Entry entry = mOngoing.remove(key);
+        if (entry == null) {
+            entry = mLatest.remove(key);
+            if (entry == null) {
+                Slog.w(TAG, "removeNotification for unknown key: " + key);
+                return null;
+            }
+        }
+        // Remove the expanded view.
+        ((ViewGroup)entry.row.getParent()).removeView(entry.row);
+        // Remove the icon.
+        ((ViewGroup)entry.icon.getParent()).removeView(entry.icon);
+
+        return entry.notification;
+    }
+
+    private void setAreThereNotifications() {
+        boolean ongoing = mOngoing.hasVisibleItems();
+        boolean latest = mLatest.hasVisibleItems();
+
+        // (no ongoing notifications are clearable)
+        if (mLatest.hasClearableItems()) {
+            mClearButton.setVisibility(View.VISIBLE);
+        } else {
+            mClearButton.setVisibility(View.INVISIBLE);
+        }
+
+        mOngoingTitle.setVisibility(ongoing ? View.VISIBLE : View.GONE);
+        mLatestTitle.setVisibility(latest ? View.VISIBLE : View.GONE);
+
+        if (ongoing || latest) {
+            mNoNotificationsTitle.setVisibility(View.GONE);
+        } else {
+            mNoNotificationsTitle.setVisibility(View.VISIBLE);
+        }
+    }
+
+
+    /**
+     * State is one or more of the DISABLE constants from StatusBarManager.
+     */
+    public void disable(int state) {
+        final int old = mDisabled;
+        final int diff = state ^ old;
+        mDisabled = state;
+
+        if ((diff & StatusBarManager.DISABLE_EXPAND) != 0) {
+            if ((state & StatusBarManager.DISABLE_EXPAND) != 0) {
+                Slog.d(TAG, "DISABLE_EXPAND: yes");
+                animateCollapse();
+            }
+        }
+        if ((diff & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) {
+            if ((state & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) {
+                Slog.d(TAG, "DISABLE_NOTIFICATION_ICONS: yes");
+                if (mTicking) {
+                    mTicker.halt();
+                } else {
+                    setNotificationIconVisibility(false, com.android.internal.R.anim.fade_out);
+                }
+            } else {
+                Slog.d(TAG, "DISABLE_NOTIFICATION_ICONS: no");
+                if (!mExpandedVisible) {
+                    setNotificationIconVisibility(true, com.android.internal.R.anim.fade_in);
+                }
+            }
+        } else if ((diff & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) {
+            if (mTicking && (state & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) {
+                Slog.d(TAG, "DISABLE_NOTIFICATION_TICKER: yes");
+                mTicker.halt();
+            }
+        }
+    }
+
+    /**
+     * All changes to the status bar and notifications funnel through here and are batched.
+     */
+    private class H extends Handler {
+        public void handleMessage(Message m) {
+            switch (m.what) {
+                case MSG_ANIMATE:
+                    doAnimation();
+                    break;
+                case MSG_ANIMATE_REVEAL:
+                    doRevealAnimation();
+                    break;
+                case MSG_SHOW_INTRUDER:
+                    setIntruderAlertVisibility(true);
+                    break;
+                case MSG_HIDE_INTRUDER:
+                    setIntruderAlertVisibility(false);
+                    break;
+            }
+        }
+    }
+
+    View.OnFocusChangeListener mFocusChangeListener = new View.OnFocusChangeListener() {
+        public void onFocusChange(View v, boolean hasFocus) {
+            // Because 'v' is a ViewGroup, all its children will be (un)selected
+            // too, which allows marqueeing to work.
+            v.setSelected(hasFocus);
+        }
+    };
+
+    private void makeExpandedVisible() {
+        if (SPEW) Slog.d(TAG, "Make expanded visible: expanded visible=" + mExpandedVisible);
+        if (mExpandedVisible) {
+            return;
+        }
+        mExpandedVisible = true;
+        visibilityChanged(true);
+
+        updateExpandedViewPos(EXPANDED_LEAVE_ALONE);
+        mExpandedParams.flags &= ~WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+        mExpandedParams.flags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
+        mExpandedDialog.getWindow().setAttributes(mExpandedParams);
+        mExpandedView.requestFocus(View.FOCUS_FORWARD);
+        mTrackingView.setVisibility(View.VISIBLE);
+
+        if (!mTicking) {
+            setDateViewVisibility(true, com.android.internal.R.anim.fade_in);
+        }
+    }
+
+    public void animateExpand() {
+        if (SPEW) Slog.d(TAG, "Animate expand: expanded=" + mExpanded);
+        if ((mDisabled & StatusBarManager.DISABLE_EXPAND) != 0) {
+            return ;
+        }
+        if (mExpanded) {
+            return;
+        }
+
+        prepareTracking(0, true);
+        performFling(0, 2000.0f, true);
+    }
+
+    public void animateCollapse() {
+        if (SPEW) {
+            Slog.d(TAG, "animateCollapse(): mExpanded=" + mExpanded
+                    + " mExpandedVisible=" + mExpandedVisible
+                    + " mExpanded=" + mExpanded
+                    + " mAnimating=" + mAnimating
+                    + " mAnimY=" + mAnimY
+                    + " mAnimVel=" + mAnimVel);
+        }
+
+        if (!mExpandedVisible) {
+            return;
+        }
+
+        int y;
+        if (mAnimating) {
+            y = (int)mAnimY;
+        } else {
+            y = mDisplay.getHeight()-1;
+        }
+        // Let the fling think that we're open so it goes in the right direction
+        // and doesn't try to re-open the windowshade.
+        mExpanded = true;
+        prepareTracking(y, false);
+        performFling(y, -2000.0f, true);
+    }
+
+    void performExpand() {
+        if (SPEW) Slog.d(TAG, "performExpand: mExpanded=" + mExpanded);
+        if ((mDisabled & StatusBarManager.DISABLE_EXPAND) != 0) {
+            return ;
+        }
+        if (mExpanded) {
+            return;
+        }
+
+        mExpanded = true;
+        makeExpandedVisible();
+        updateExpandedViewPos(EXPANDED_FULL_OPEN);
+
+        if (false) postStartTracing();
+    }
+
+    void performCollapse() {
+        if (SPEW) Slog.d(TAG, "performCollapse: mExpanded=" + mExpanded
+                + " mExpandedVisible=" + mExpandedVisible);
+
+        if (!mExpandedVisible) {
+            return;
+        }
+        mExpandedVisible = false;
+        visibilityChanged(false);
+        mExpandedParams.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+        mExpandedParams.flags &= ~WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
+        mExpandedDialog.getWindow().setAttributes(mExpandedParams);
+        mTrackingView.setVisibility(View.GONE);
+
+        if ((mDisabled & StatusBarManager.DISABLE_NOTIFICATION_ICONS) == 0) {
+            setNotificationIconVisibility(true, com.android.internal.R.anim.fade_in);
+        }
+        setDateViewVisibility(false, com.android.internal.R.anim.fade_out);
+
+        if (!mExpanded) {
+            return;
+        }
+        mExpanded = false;
+    }
+
+    void doAnimation() {
+        if (mAnimating) {
+            if (SPEW) Slog.d(TAG, "doAnimation");
+            if (SPEW) Slog.d(TAG, "doAnimation before mAnimY=" + mAnimY);
+            incrementAnim();
+            if (SPEW) Slog.d(TAG, "doAnimation after  mAnimY=" + mAnimY);
+            if (mAnimY >= mDisplay.getHeight()-1) {
+                if (SPEW) Slog.d(TAG, "Animation completed to expanded state.");
+                mAnimating = false;
+                updateExpandedViewPos(EXPANDED_FULL_OPEN);
+                performExpand();
+            }
+            else if (mAnimY < mStatusBarView.getHeight()) {
+                if (SPEW) Slog.d(TAG, "Animation completed to collapsed state.");
+                mAnimating = false;
+                updateExpandedViewPos(0);
+                performCollapse();
+            }
+            else {
+                updateExpandedViewPos((int)mAnimY);
+                mCurAnimationTime += ANIM_FRAME_DURATION;
+                mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_ANIMATE), mCurAnimationTime);
+            }
+        }
+    }
+
+    void stopTracking() {
+        mTracking = false;
+        mVelocityTracker.recycle();
+        mVelocityTracker = null;
+    }
+
+    void incrementAnim() {
+        long now = SystemClock.uptimeMillis();
+        float t = ((float)(now - mAnimLastTime)) / 1000;            // ms -> s
+        final float y = mAnimY;
+        final float v = mAnimVel;                                   // px/s
+        final float a = mAnimAccel;                                 // px/s/s
+        mAnimY = y + (v*t) + (0.5f*a*t*t);                          // px
+        mAnimVel = v + (a*t);                                       // px/s
+        mAnimLastTime = now;                                        // ms
+        //Slog.d(TAG, "y=" + y + " v=" + v + " a=" + a + " t=" + t + " mAnimY=" + mAnimY
+        //        + " mAnimAccel=" + mAnimAccel);
+    }
+
+    void doRevealAnimation() {
+        final int h = mCloseView.getHeight() + mStatusBarView.getHeight();
+        if (mAnimatingReveal && mAnimating && mAnimY < h) {
+            incrementAnim();
+            if (mAnimY >= h) {
+                mAnimY = h;
+                updateExpandedViewPos((int)mAnimY);
+            } else {
+                updateExpandedViewPos((int)mAnimY);
+                mCurAnimationTime += ANIM_FRAME_DURATION;
+                mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_ANIMATE_REVEAL),
+                        mCurAnimationTime);
+            }
+        }
+    }
+
+    void prepareTracking(int y, boolean opening) {
+        mTracking = true;
+        mVelocityTracker = VelocityTracker.obtain();
+        if (opening) {
+            mAnimAccel = 2000.0f;
+            mAnimVel = 200;
+            mAnimY = mStatusBarView.getHeight();
+            updateExpandedViewPos((int)mAnimY);
+            mAnimating = true;
+            mAnimatingReveal = true;
+            mHandler.removeMessages(MSG_ANIMATE);
+            mHandler.removeMessages(MSG_ANIMATE_REVEAL);
+            long now = SystemClock.uptimeMillis();
+            mAnimLastTime = now;
+            mCurAnimationTime = now + ANIM_FRAME_DURATION;
+            mAnimating = true;
+            mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_ANIMATE_REVEAL),
+                    mCurAnimationTime);
+            makeExpandedVisible();
+        } else {
+            // it's open, close it?
+            if (mAnimating) {
+                mAnimating = false;
+                mHandler.removeMessages(MSG_ANIMATE);
+            }
+            updateExpandedViewPos(y + mViewDelta);
+        }
+    }
+
+    void performFling(int y, float vel, boolean always) {
+        mAnimatingReveal = false;
+        mDisplayHeight = mDisplay.getHeight();
+
+        mAnimY = y;
+        mAnimVel = vel;
+
+        //Slog.d(TAG, "starting with mAnimY=" + mAnimY + " mAnimVel=" + mAnimVel);
+
+        if (mExpanded) {
+            if (!always && (
+                    vel > 200.0f
+                    || (y > (mDisplayHeight-25) && vel > -200.0f))) {
+                // We are expanded, but they didn't move sufficiently to cause
+                // us to retract.  Animate back to the expanded position.
+                mAnimAccel = 2000.0f;
+                if (vel < 0) {
+                    mAnimVel = 0;
+                }
+            }
+            else {
+                // We are expanded and are now going to animate away.
+                mAnimAccel = -2000.0f;
+                if (vel > 0) {
+                    mAnimVel = 0;
+                }
+            }
+        } else {
+            if (always || (
+                    vel > 200.0f
+                    || (y > (mDisplayHeight/2) && vel > -200.0f))) {
+                // We are collapsed, and they moved enough to allow us to
+                // expand.  Animate in the notifications.
+                mAnimAccel = 2000.0f;
+                if (vel < 0) {
+                    mAnimVel = 0;
+                }
+            }
+            else {
+                // We are collapsed, but they didn't move sufficiently to cause
+                // us to retract.  Animate back to the collapsed position.
+                mAnimAccel = -2000.0f;
+                if (vel > 0) {
+                    mAnimVel = 0;
+                }
+            }
+        }
+        //Slog.d(TAG, "mAnimY=" + mAnimY + " mAnimVel=" + mAnimVel
+        //        + " mAnimAccel=" + mAnimAccel);
+
+        long now = SystemClock.uptimeMillis();
+        mAnimLastTime = now;
+        mCurAnimationTime = now + ANIM_FRAME_DURATION;
+        mAnimating = true;
+        mHandler.removeMessages(MSG_ANIMATE);
+        mHandler.removeMessages(MSG_ANIMATE_REVEAL);
+        mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_ANIMATE), mCurAnimationTime);
+        stopTracking();
+    }
+
+    boolean interceptTouchEvent(MotionEvent event) {
+        if (SPEW) {
+            Slog.d(TAG, "Touch: rawY=" + event.getRawY() + " event=" + event + " mDisabled="
+                + mDisabled);
+        }
+
+        if ((mDisabled & StatusBarManager.DISABLE_EXPAND) != 0) {
+            return false;
+        }
+
+        final int statusBarSize = mStatusBarView.getHeight();
+        final int hitSize = statusBarSize*2;
+        if (event.getAction() == MotionEvent.ACTION_DOWN) {
+            final int y = (int)event.getRawY();
+
+            if (!mExpanded) {
+                mViewDelta = statusBarSize - y;
+            } else {
+                mTrackingView.getLocationOnScreen(mAbsPos);
+                mViewDelta = mAbsPos[1] + mTrackingView.getHeight() - y;
+            }
+            if ((!mExpanded && y < hitSize) ||
+                    (mExpanded && y > (mDisplay.getHeight()-hitSize))) {
+
+                // We drop events at the edge of the screen to make the windowshade come
+                // down by accident less, especially when pushing open a device with a keyboard
+                // that rotates (like g1 and droid)
+                int x = (int)event.getRawX();
+                final int edgeBorder = mEdgeBorder;
+                if (x >= edgeBorder && x < mDisplay.getWidth() - edgeBorder) {
+                    prepareTracking(y, !mExpanded);// opening if we're not already fully visible
+                    mVelocityTracker.addMovement(event);
+                }
+            }
+        } else if (mTracking) {
+            mVelocityTracker.addMovement(event);
+            final int minY = statusBarSize + mCloseView.getHeight();
+            if (event.getAction() == MotionEvent.ACTION_MOVE) {
+                int y = (int)event.getRawY();
+                if (mAnimatingReveal && y < minY) {
+                    // nothing
+                } else  {
+                    mAnimatingReveal = false;
+                    updateExpandedViewPos(y + mViewDelta);
+                }
+            } else if (event.getAction() == MotionEvent.ACTION_UP) {
+                mVelocityTracker.computeCurrentVelocity(1000);
+
+                float yVel = mVelocityTracker.getYVelocity();
+                boolean negative = yVel < 0;
+
+                float xVel = mVelocityTracker.getXVelocity();
+                if (xVel < 0) {
+                    xVel = -xVel;
+                }
+                if (xVel > 150.0f) {
+                    xVel = 150.0f; // limit how much we care about the x axis
+                }
+
+                float vel = (float)Math.hypot(yVel, xVel);
+                if (negative) {
+                    vel = -vel;
+                }
+
+                performFling((int)event.getRawY(), vel, false);
+            }
+
+        }
+        return false;
+    }
+
+    private class Launcher implements View.OnClickListener {
+        private PendingIntent mIntent;
+        private String mPkg;
+        private String mTag;
+        private int mId;
+
+        Launcher(PendingIntent intent, String pkg, String tag, int id) {
+            mIntent = intent;
+            mPkg = pkg;
+            mTag = tag;
+            mId = id;
+        }
+
+        public void onClick(View v) {
+            try {
+                // The intent we are sending is for the application, which
+                // won't have permission to immediately start an activity after
+                // the user switches to home.  We know it is safe to do at this
+                // point, so make sure new activity switches are now allowed.
+                ActivityManagerNative.getDefault().resumeAppSwitches();
+            } catch (RemoteException e) {
+            }
+
+            if (mIntent != null) {
+                int[] pos = new int[2];
+                v.getLocationOnScreen(pos);
+                Intent overlay = new Intent();
+                overlay.setSourceBounds(
+                        new Rect(pos[0], pos[1], pos[0]+v.getWidth(), pos[1]+v.getHeight()));
+                try {
+                    mIntent.send(PhoneStatusBarService.this, 0, overlay);
+                } catch (PendingIntent.CanceledException e) {
+                    // the stack trace isn't very helpful here.  Just log the exception message.
+                    Slog.w(TAG, "Sending contentIntent failed: " + e);
+                }
+            }
+
+            try {
+                mBarService.onNotificationClick(mPkg, mTag, mId);
+            } catch (RemoteException ex) {
+                // system process is dead if we're here.
+            }
+
+            // close the shade if it was open
+            animateCollapse();
+
+            // If this click was on the intruder alert, hide that instead
+            mHandler.sendEmptyMessage(MSG_HIDE_INTRUDER);
+        }
+    }
+
+    private void tick(StatusBarNotification n) {
+        // Show the ticker if one is requested. Also don't do this
+        // until status bar window is attached to the window manager,
+        // because...  well, what's the point otherwise?  And trying to
+        // run a ticker without being attached will crash!
+        if (n.notification.tickerText != null && mStatusBarView.getWindowToken() != null) {
+            if (0 == (mDisabled & (StatusBarManager.DISABLE_NOTIFICATION_ICONS
+                            | StatusBarManager.DISABLE_NOTIFICATION_TICKER))) {
+                mTicker.addEntry(n);
+            }
+        }
+    }
+
+    /**
+     * Cancel this notification and tell the StatusBarManagerService / NotificationManagerService
+     * about the failure.
+     *
+     * WARNING: this will call back into us.  Don't hold any locks.
+     */
+    void handleNotificationError(IBinder key, StatusBarNotification n, String message) {
+        removeNotification(key);
+        try {
+            mBarService.onNotificationError(n.pkg, n.tag, n.id, n.uid, n.initialPid, message);
+        } catch (RemoteException ex) {
+            // The end is nigh.
+        }
+    }
+
+    private class MyTicker extends Ticker {
+        MyTicker(Context context, StatusBarView sb) {
+            super(context, sb);
+        }
+
+        @Override
+        void tickerStarting() {
+            mTicking = true;
+            mIcons.setVisibility(View.GONE);
+            mTickerView.setVisibility(View.VISIBLE);
+            mTickerView.startAnimation(loadAnim(com.android.internal.R.anim.push_up_in, null));
+            mIcons.startAnimation(loadAnim(com.android.internal.R.anim.push_up_out, null));
+            if (mExpandedVisible) {
+                setDateViewVisibility(false, com.android.internal.R.anim.push_up_out);
+            }
+        }
+
+        @Override
+        void tickerDone() {
+            mIcons.setVisibility(View.VISIBLE);
+            mTickerView.setVisibility(View.GONE);
+            mIcons.startAnimation(loadAnim(com.android.internal.R.anim.push_down_in, null));
+            mTickerView.startAnimation(loadAnim(com.android.internal.R.anim.push_down_out,
+                        mTickingDoneListener));
+            if (mExpandedVisible) {
+                setDateViewVisibility(true, com.android.internal.R.anim.push_down_in);
+            }
+        }
+
+        void tickerHalting() {
+            mIcons.setVisibility(View.VISIBLE);
+            mTickerView.setVisibility(View.GONE);
+            mIcons.startAnimation(loadAnim(com.android.internal.R.anim.fade_in, null));
+            mTickerView.startAnimation(loadAnim(com.android.internal.R.anim.fade_out,
+                        mTickingDoneListener));
+            if (mExpandedVisible) {
+                setDateViewVisibility(true, com.android.internal.R.anim.fade_in);
+            }
+        }
+    }
+
+    Animation.AnimationListener mTickingDoneListener = new Animation.AnimationListener() {;
+        public void onAnimationEnd(Animation animation) {
+            mTicking = false;
+        }
+        public void onAnimationRepeat(Animation animation) {
+        }
+        public void onAnimationStart(Animation animation) {
+        }
+    };
+
+    private Animation loadAnim(int id, Animation.AnimationListener listener) {
+        Animation anim = AnimationUtils.loadAnimation(PhoneStatusBarService.this, id);
+        if (listener != null) {
+            anim.setAnimationListener(listener);
+        }
+        return anim;
+    }
+
+    public String viewInfo(View v) {
+        return "(" + v.getLeft() + "," + v.getTop() + ")(" + v.getRight() + "," + v.getBottom()
+                + " " + v.getWidth() + "x" + v.getHeight() + ")";
+    }
+
+    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        if (checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
+                != PackageManager.PERMISSION_GRANTED) {
+            pw.println("Permission Denial: can't dump StatusBar from from pid="
+                    + Binder.getCallingPid()
+                    + ", uid=" + Binder.getCallingUid());
+            return;
+        }
+
+        synchronized (mQueueLock) {
+            pw.println("Current Status Bar state:");
+            pw.println("  mExpanded=" + mExpanded
+                    + ", mExpandedVisible=" + mExpandedVisible);
+            pw.println("  mTicking=" + mTicking);
+            pw.println("  mTracking=" + mTracking);
+            pw.println("  mAnimating=" + mAnimating
+                    + ", mAnimY=" + mAnimY + ", mAnimVel=" + mAnimVel
+                    + ", mAnimAccel=" + mAnimAccel);
+            pw.println("  mCurAnimationTime=" + mCurAnimationTime
+                    + " mAnimLastTime=" + mAnimLastTime);
+            pw.println("  mDisplayHeight=" + mDisplayHeight
+                    + " mAnimatingReveal=" + mAnimatingReveal
+                    + " mViewDelta=" + mViewDelta);
+            pw.println("  mDisplayHeight=" + mDisplayHeight);
+            pw.println("  mExpandedParams: " + mExpandedParams);
+            pw.println("  mExpandedView: " + viewInfo(mExpandedView));
+            pw.println("  mExpandedDialog: " + mExpandedDialog);
+            pw.println("  mTrackingParams: " + mTrackingParams);
+            pw.println("  mTrackingView: " + viewInfo(mTrackingView));
+            pw.println("  mOngoingTitle: " + viewInfo(mOngoingTitle));
+            pw.println("  mOngoingItems: " + viewInfo(mOngoingItems));
+            pw.println("  mLatestTitle: " + viewInfo(mLatestTitle));
+            pw.println("  mLatestItems: " + viewInfo(mLatestItems));
+            pw.println("  mNoNotificationsTitle: " + viewInfo(mNoNotificationsTitle));
+            pw.println("  mCloseView: " + viewInfo(mCloseView));
+            pw.println("  mTickerView: " + viewInfo(mTickerView));
+            pw.println("  mScrollView: " + viewInfo(mScrollView)
+                    + " scroll " + mScrollView.getScrollX() + "," + mScrollView.getScrollY());
+            pw.println("mNotificationLinearLayout: " + viewInfo(mNotificationLinearLayout));
+        }
+        /*
+        synchronized (mNotificationData) {
+            int N = mNotificationData.ongoingCount();
+            pw.println("  ongoingCount.size=" + N);
+            for (int i=0; i<N; i++) {
+                StatusBarNotification n = mNotificationData.getOngoing(i);
+                pw.println("    [" + i + "] key=" + n.key + " view=" + n.view);
+                pw.println("           data=" + n.data);
+            }
+            N = mNotificationData.latestCount();
+            pw.println("  ongoingCount.size=" + N);
+            for (int i=0; i<N; i++) {
+                StatusBarNotification n = mNotificationData.getLatest(i);
+                pw.println("    [" + i + "] key=" + n.key + " view=" + n.view);
+                pw.println("           data=" + n.data);
+            }
+        }
+        */
+
+        if (false) {
+            pw.println("see the logcat for a dump of the views we have created.");
+            // must happen on ui thread
+            mHandler.post(new Runnable() {
+                    public void run() {
+                        mStatusBarView.getLocationOnScreen(mAbsPos);
+                        Slog.d(TAG, "mStatusBarView: ----- (" + mAbsPos[0] + "," + mAbsPos[1]
+                                + ") " + mStatusBarView.getWidth() + "x"
+                                + mStatusBarView.getHeight());
+                        mStatusBarView.debug();
+
+                        mExpandedView.getLocationOnScreen(mAbsPos);
+                        Slog.d(TAG, "mExpandedView: ----- (" + mAbsPos[0] + "," + mAbsPos[1]
+                                + ") " + mExpandedView.getWidth() + "x"
+                                + mExpandedView.getHeight());
+                        mExpandedView.debug();
+
+                        mTrackingView.getLocationOnScreen(mAbsPos);
+                        Slog.d(TAG, "mTrackingView: ----- (" + mAbsPos[0] + "," + mAbsPos[1]
+                                + ") " + mTrackingView.getWidth() + "x"
+                                + mTrackingView.getHeight());
+                        mTrackingView.debug();
+                    }
+                });
+        }
+    }
+
+    void onBarViewAttached() {
+        WindowManager.LayoutParams lp;
+        int pixelFormat;
+        Drawable bg;
+
+        /// ---------- Tracking View --------------
+        pixelFormat = PixelFormat.RGBX_8888;
+        bg = mTrackingView.getBackground();
+        if (bg != null) {
+            pixelFormat = bg.getOpacity();
+        }
+
+        lp = new WindowManager.LayoutParams(
+                ViewGroup.LayoutParams.MATCH_PARENT,
+                ViewGroup.LayoutParams.MATCH_PARENT,
+                WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL,
+                WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+                | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
+                | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
+                pixelFormat);
+//        lp.token = mStatusBarView.getWindowToken();
+        lp.gravity = Gravity.TOP | Gravity.FILL_HORIZONTAL;
+        lp.setTitle("TrackingView");
+        lp.y = mTrackingPosition;
+        mTrackingParams = lp;
+
+        WindowManagerImpl.getDefault().addView(mTrackingView, lp);
+    }
+
+    void onTrackingViewAttached() {
+        WindowManager.LayoutParams lp;
+        int pixelFormat;
+        Drawable bg;
+
+        /// ---------- Expanded View --------------
+        pixelFormat = PixelFormat.TRANSLUCENT;
+
+        final int disph = mDisplay.getHeight();
+        lp = mExpandedDialog.getWindow().getAttributes();
+        lp.width = ViewGroup.LayoutParams.MATCH_PARENT;
+        lp.height = getExpandedHeight();
+        lp.x = 0;
+        mTrackingPosition = lp.y = -disph; // sufficiently large negative
+        lp.type = WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL;
+        lp.flags = WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+                | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
+                | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+                | WindowManager.LayoutParams.FLAG_DITHER
+                | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+        lp.format = pixelFormat;
+        lp.gravity = Gravity.TOP | Gravity.FILL_HORIZONTAL;
+        lp.setTitle("StatusBarExpanded");
+        mExpandedDialog.getWindow().setAttributes(lp);
+        mExpandedDialog.getWindow().setFormat(pixelFormat);
+        mExpandedParams = lp;
+
+        mExpandedDialog.getWindow().requestFeature(Window.FEATURE_NO_TITLE);
+        mExpandedDialog.setContentView(mExpandedView,
+                new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
+                                           ViewGroup.LayoutParams.MATCH_PARENT));
+        mExpandedDialog.getWindow().setBackgroundDrawable(null);
+        mExpandedDialog.show();
+        FrameLayout hack = (FrameLayout)mExpandedView.getParent();
+    }
+
+    void setDateViewVisibility(boolean visible, int anim) {
+        mDateView.setUpdates(visible);
+        mDateView.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
+        mDateView.startAnimation(loadAnim(anim, null));
+    }
+
+    void setNotificationIconVisibility(boolean visible, int anim) {
+        int old = mNotificationIcons.getVisibility();
+        int v = visible ? View.VISIBLE : View.INVISIBLE;
+        if (old != v) {
+            mNotificationIcons.setVisibility(v);
+            mNotificationIcons.startAnimation(loadAnim(anim, null));
+        }
+    }
+
+    void updateExpandedViewPos(int expandedPosition) {
+        if (SPEW) {
+            Slog.d(TAG, "updateExpandedViewPos before expandedPosition=" + expandedPosition
+                    + " mTrackingParams.y=" + mTrackingParams.y
+                    + " mTrackingPosition=" + mTrackingPosition);
+        }
+
+        int h = mStatusBarView.getHeight();
+        int disph = mDisplay.getHeight();
+
+        // If the expanded view is not visible, make sure they're still off screen.
+        // Maybe the view was resized.
+        if (!mExpandedVisible) {
+            if (mTrackingView != null) {
+                mTrackingPosition = -disph;
+                if (mTrackingParams != null) {
+                    mTrackingParams.y = mTrackingPosition;
+                    WindowManagerImpl.getDefault().updateViewLayout(mTrackingView, mTrackingParams);
+                }
+            }
+            if (mExpandedParams != null) {
+                mExpandedParams.y = -disph;
+                mExpandedDialog.getWindow().setAttributes(mExpandedParams);
+            }
+            return;
+        }
+
+        // tracking view...
+        int pos;
+        if (expandedPosition == EXPANDED_FULL_OPEN) {
+            pos = h;
+        }
+        else if (expandedPosition == EXPANDED_LEAVE_ALONE) {
+            pos = mTrackingPosition;
+        }
+        else {
+            if (expandedPosition <= disph) {
+                pos = expandedPosition;
+            } else {
+                pos = disph;
+            }
+            pos -= disph-h;
+        }
+        mTrackingPosition = mTrackingParams.y = pos;
+        mTrackingParams.height = disph-h;
+        WindowManagerImpl.getDefault().updateViewLayout(mTrackingView, mTrackingParams);
+
+        if (mExpandedParams != null) {
+            mCloseView.getLocationInWindow(mPositionTmp);
+            final int closePos = mPositionTmp[1];
+
+            mExpandedContents.getLocationInWindow(mPositionTmp);
+            final int contentsBottom = mPositionTmp[1] + mExpandedContents.getHeight();
+
+            mExpandedParams.y = pos + mTrackingView.getHeight()
+                    - (mTrackingParams.height-closePos) - contentsBottom;
+            int max = h;
+            if (mExpandedParams.y > max) {
+                mExpandedParams.y = max;
+            }
+            int min = mTrackingPosition;
+            if (mExpandedParams.y < min) {
+                mExpandedParams.y = min;
+            }
+
+            boolean visible = (mTrackingPosition + mTrackingView.getHeight()) > h;
+            if (!visible) {
+                // if the contents aren't visible, move the expanded view way off screen
+                // because the window itself extends below the content view.
+                mExpandedParams.y = -disph;
+            }
+            mExpandedDialog.getWindow().setAttributes(mExpandedParams);
+
+            // As long as this isn't just a repositioning that's not supposed to affect
+            // the user's perception of what's showing, call to say that the visibility
+            // has changed. (Otherwise, someone else will call to do that).
+            if (expandedPosition != EXPANDED_LEAVE_ALONE) {
+                if (SPEW) Slog.d(TAG, "updateExpandedViewPos visibilityChanged(" + visible + ")");
+                visibilityChanged(visible);
+            }
+        }
+
+        if (SPEW) {
+            Slog.d(TAG, "updateExpandedViewPos after  expandedPosition=" + expandedPosition
+                    + " mTrackingParams.y=" + mTrackingParams.y
+                    + " mTrackingPosition=" + mTrackingPosition
+                    + " mExpandedParams.y=" + mExpandedParams.y
+                    + " mExpandedParams.height=" + mExpandedParams.height);
+        }
+    }
+
+    int getExpandedHeight() {
+        return mDisplay.getHeight() - mStatusBarView.getHeight() - mCloseView.getHeight();
+    }
+
+    void updateExpandedHeight() {
+        if (mExpandedView != null) {
+            mExpandedParams.height = getExpandedHeight();
+            mExpandedDialog.getWindow().setAttributes(mExpandedParams);
+        }
+    }
+
+    /**
+     * The LEDs are turned o)ff when the notification panel is shown, even just a little bit.
+     * This was added last-minute and is inconsistent with the way the rest of the notifications
+     * are handled, because the notification isn't really cancelled.  The lights are just
+     * turned off.  If any other notifications happen, the lights will turn back on.  Steve says
+     * this is what he wants. (see bug 1131461)
+     */
+    void visibilityChanged(boolean visible) {
+        if (mPanelSlightlyVisible != visible) {
+            mPanelSlightlyVisible = visible;
+            try {
+                mBarService.onPanelRevealed();
+            } catch (RemoteException ex) {
+                // Won't fail unless the world has ended.
+            }
+        }
+    }
+
+    void performDisableActions(int net) {
+        int old = mDisabled;
+        int diff = net ^ old;
+        mDisabled = net;
+
+        // act accordingly
+        if ((diff & StatusBarManager.DISABLE_EXPAND) != 0) {
+            if ((net & StatusBarManager.DISABLE_EXPAND) != 0) {
+                Slog.d(TAG, "DISABLE_EXPAND: yes");
+                animateCollapse();
+            }
+        }
+        if ((diff & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) {
+            if ((net & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) {
+                Slog.d(TAG, "DISABLE_NOTIFICATION_ICONS: yes");
+                if (mTicking) {
+                    mNotificationIcons.setVisibility(View.INVISIBLE);
+                    mTicker.halt();
+                } else {
+                    setNotificationIconVisibility(false, com.android.internal.R.anim.fade_out);
+                }
+            } else {
+                Slog.d(TAG, "DISABLE_NOTIFICATION_ICONS: no");
+                if (!mExpandedVisible) {
+                    setNotificationIconVisibility(true, com.android.internal.R.anim.fade_in);
+                }
+            }
+        } else if ((diff & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) {
+            if (mTicking && (net & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) {
+                mTicker.halt();
+            }
+        }
+    }
+
+    private View.OnClickListener mClearButtonListener = new View.OnClickListener() {
+        public void onClick(View v) {
+            try {
+                mBarService.onClearAllNotifications();
+            } catch (RemoteException ex) {
+                // system process is dead if we're here.
+            }
+            animateCollapse();
+        }
+    };
+
+    private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)
+                    || Intent.ACTION_SCREEN_OFF.equals(action)) {
+                //collapse();
+            }
+            else if (Intent.ACTION_CONFIGURATION_CHANGED.equals(action)) {
+                updateResources();
+            }
+        }
+    };
+
+    private void setIntruderAlertVisibility(boolean vis) {
+        mIntruderAlertView.setVisibility(vis ? View.VISIBLE : View.GONE);
+    }
+
+    /**
+     * Reload some of our resources when the configuration changes.
+     *
+     * We don't reload everything when the configuration changes -- we probably
+     * should, but getting that smooth is tough.  Someday we'll fix that.  In the
+     * meantime, just update the things that we know change.
+     */
+    void updateResources() {
+        Resources res = getResources();
+
+        mClearButton.setText(getText(R.string.status_bar_clear_all_button));
+        mOngoingTitle.setText(getText(R.string.status_bar_ongoing_events_title));
+        mLatestTitle.setText(getText(R.string.status_bar_latest_events_title));
+        mNoNotificationsTitle.setText(getText(R.string.status_bar_no_notifications_title));
+
+        mEdgeBorder = res.getDimensionPixelSize(R.dimen.status_bar_edge_ignore);
+
+        if (false) Slog.v(TAG, "updateResources");
+    }
+
+    //
+    // tracing
+    //
+
+    void postStartTracing() {
+        mHandler.postDelayed(mStartTracing, 3000);
+    }
+
+    void vibrate() {
+        android.os.Vibrator vib = (android.os.Vibrator)getSystemService(Context.VIBRATOR_SERVICE);
+        vib.vibrate(250);
+    }
+
+    Runnable mStartTracing = new Runnable() {
+        public void run() {
+            vibrate();
+            SystemClock.sleep(250);
+            Slog.d(TAG, "startTracing");
+            android.os.Debug.startMethodTracing("/data/statusbar-traces/trace");
+            mHandler.postDelayed(mStopTracing, 10000);
+        }
+    };
+
+    Runnable mStopTracing = new Runnable() {
+        public void run() {
+            android.os.Debug.stopMethodTracing();
+            Slog.d(TAG, "stopTracing");
+            vibrate();
+        }
+    };
+}
+
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarPolicy.java
index 4f39ee4..e828f68 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarPolicy.java
@@ -1033,6 +1033,7 @@
                     iconId = sWifiSignalImages[mLastWifiSignalLevel];
                 }
 
+                mService.setIcon("wifi", iconId, 0);
                 // Show the icon since wi-fi is connected
                 mService.setIconVisibility("wifi", true);
 
@@ -1041,11 +1042,11 @@
                 mIsWifiConnected = false;
                 iconId = sWifiSignalImages[0];
 
+                mService.setIcon("wifi", iconId, 0);
                 // Hide the icon since we're not connected
                 mService.setIconVisibility("wifi", false);
             }
 
-            mService.setIcon("wifi", iconId, 0);
         } else if (action.equals(WifiManager.RSSI_CHANGED_ACTION)) {
             int iconId;
             final int newRssi = intent.getIntExtra(WifiManager.EXTRA_NEW_RSSI, -200);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarService.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarService.java
index 07bcce7..a64c3e7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarService.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarService.java
@@ -17,188 +17,53 @@
 package com.android.systemui.statusbar;
 
 import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.graphics.PixelFormat;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Slog;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.view.WindowManagerImpl;
+
+import java.util.ArrayList;
+
 import com.android.internal.statusbar.IStatusBar;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.statusbar.StatusBarIcon;
 import com.android.internal.statusbar.StatusBarIconList;
 import com.android.internal.statusbar.StatusBarNotification;
 
-import android.app.ActivityManagerNative;
-import android.app.Dialog;
-import android.app.Notification;
-import android.app.PendingIntent;
-import android.app.Service;
-import android.app.StatusBarManager;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.PackageManager;
-import android.content.res.Resources;
-import android.graphics.PixelFormat;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.net.Uri;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.os.Binder;
-import android.os.Handler;
-import android.os.Message;
-import android.os.ServiceManager;
-import android.os.SystemClock;
-import android.util.Slog;
-import android.util.Log;
-import android.view.Display;
-import android.view.Gravity;
-import android.view.KeyEvent;
-import android.view.LayoutInflater;
-import android.view.MotionEvent;
-import android.view.VelocityTracker;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.Window;
-import android.view.WindowManager;
-import android.view.WindowManagerImpl;
-import android.view.animation.Animation;
-import android.view.animation.AnimationUtils;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.RemoteViews;
-import android.widget.ScrollView;
-import android.widget.TextView;
-import android.widget.FrameLayout;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Set;
-
 import com.android.systemui.R;
-import com.android.systemui.statusbar.policy.StatusBarPolicy;
 
-
-
-public class StatusBarService extends Service implements CommandQueue.Callbacks {
+public abstract class StatusBarService extends Service implements CommandQueue.Callbacks {
     static final String TAG = "StatusBarService";
-    static final boolean SPEW = false;
 
-    public static final String ACTION_STATUSBAR_START
-            = "com.android.internal.policy.statusbar.START";
+    protected CommandQueue mCommandQueue;
+    protected IStatusBarService mBarService;
 
-    static final int EXPANDED_LEAVE_ALONE = -10000;
-    static final int EXPANDED_FULL_OPEN = -10001;
+    // Up-call methods
+    protected abstract View makeStatusBarView();
+    protected abstract int getStatusBarGravity();
 
-    private static final int MSG_ANIMATE = 1000;
-    private static final int MSG_ANIMATE_REVEAL = 1001;
-    private static final int MSG_SHOW_INTRUDER = 1002;
-    private static final int MSG_HIDE_INTRUDER = 1003;
-
-    // will likely move to a resource or other tunable param at some point
-    private static final int INTRUDER_ALERT_DECAY_MS = 10000;
-
-    StatusBarPolicy mIconPolicy;
-
-    CommandQueue mCommandQueue;
-    IStatusBarService mBarService;
-
-    int mIconSize;
-    Display mDisplay;
-    StatusBarView mStatusBarView;
-    int mPixelFormat;
-    H mHandler = new H();
-    Object mQueueLock = new Object();
-
-    // icons
-    LinearLayout mIcons;
-    IconMerger mNotificationIcons;
-    LinearLayout mStatusIcons;
-
-    // expanded notifications
-    Dialog mExpandedDialog;
-    ExpandedView mExpandedView;
-    WindowManager.LayoutParams mExpandedParams;
-    ScrollView mScrollView;
-    View mNotificationLinearLayout;
-    View mExpandedContents;
-    // top bar
-    TextView mNoNotificationsTitle;
-    TextView mClearButton;
-    // drag bar
-    CloseDragHandle mCloseView;
-    // ongoing
-    NotificationData mOngoing = new NotificationData();
-    TextView mOngoingTitle;
-    LinearLayout mOngoingItems;
-    // latest
-    NotificationData mLatest = new NotificationData();
-    TextView mLatestTitle;
-    LinearLayout mLatestItems;
-    // position
-    int[] mPositionTmp = new int[2];
-    boolean mExpanded;
-    boolean mExpandedVisible;
-
-    // the date view
-    DateView mDateView;
-
-    // the tracker view
-    TrackingView mTrackingView;
-    WindowManager.LayoutParams mTrackingParams;
-    int mTrackingPosition; // the position of the top of the tracking view.
-    private boolean mPanelSlightlyVisible;
-
-    // ticker
-    private Ticker mTicker;
-    private View mTickerView;
-    private boolean mTicking;
-
-    // Tracking finger for opening/closing.
-    int mEdgeBorder; // corresponds to R.dimen.status_bar_edge_ignore
-    boolean mTracking;
-    VelocityTracker mVelocityTracker;
-
-    static final int ANIM_FRAME_DURATION = (1000/60);
-
-    boolean mAnimating;
-    long mCurAnimationTime;
-    float mDisplayHeight;
-    float mAnimY;
-    float mAnimVel;
-    float mAnimAccel;
-    long mAnimLastTime;
-    boolean mAnimatingReveal = false;
-    int mViewDelta;
-    int[] mAbsPos = new int[2];
-
-    // for disabling the status bar
-    int mDisabled = 0;
-
-    private class ExpandedDialog extends Dialog {
-        ExpandedDialog(Context context) {
-            super(context, com.android.internal.R.style.Theme_Light_NoTitleBar);
-        }
-
-        @Override
-        public boolean dispatchKeyEvent(KeyEvent event) {
-            boolean down = event.getAction() == KeyEvent.ACTION_DOWN;
-            switch (event.getKeyCode()) {
-            case KeyEvent.KEYCODE_BACK:
-                if (!down) {
-                    animateCollapse();
-                }
-                return true;
-            }
-            return super.dispatchKeyEvent(event);
-        }
+    /**
+     * Nobody binds to us.
+     */
+    @Override
+    public IBinder onBind(Intent intent) {
+        return null;
     }
 
-
     @Override
     public void onCreate() {
         // First set up our views and stuff.
-        mDisplay = ((WindowManager)getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
-        makeStatusBarView(this);
+        View sb = makeStatusBarView();
 
         // Connect in to the status bar manager service
         StatusBarIconList iconList = new StatusBarIconList();
@@ -236,1371 +101,22 @@
         }
 
         // Put up the view
-        addStatusBarView();
-
-        // Lastly, call to the icon policy to install/update all the icons.
-        mIconPolicy = new StatusBarPolicy(this);
-    }
-
-    @Override
-    public void onDestroy() {
-        // we're never destroyed
-    }
-
-    // for immersive activities
-    private View mIntruderAlertView;
-
-    /**
-     * Nobody binds to us.
-     */
-    @Override
-    public IBinder onBind(Intent intent) {
-        return null;
-    }
-
-    // ================================================================================
-    // Constructing the view
-    // ================================================================================
-    private void makeStatusBarView(Context context) {
-        Resources res = context.getResources();
-
-        mIconSize = res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_icon_size);
-
-        ExpandedView expanded = (ExpandedView)View.inflate(context,
-                R.layout.status_bar_expanded, null);
-        expanded.mService = this;
-
-        mIntruderAlertView = View.inflate(context, R.layout.intruder_alert, null);
-        mIntruderAlertView.setVisibility(View.GONE);
-        mIntruderAlertView.setClickable(true);
-
-        StatusBarView sb = (StatusBarView)View.inflate(context, R.layout.status_bar, null);
-        sb.mService = this;
-
-        // figure out which pixel-format to use for the status bar.
-        mPixelFormat = PixelFormat.TRANSLUCENT;
-        Drawable bg = sb.getBackground();
-        if (bg != null) {
-            mPixelFormat = bg.getOpacity();
-        }
-
-        mStatusBarView = sb;
-        mStatusIcons = (LinearLayout)sb.findViewById(R.id.statusIcons);
-        mNotificationIcons = (IconMerger)sb.findViewById(R.id.notificationIcons);
-        mIcons = (LinearLayout)sb.findViewById(R.id.icons);
-        mTickerView = sb.findViewById(R.id.ticker);
-        mDateView = (DateView)sb.findViewById(R.id.date);
-
-        mExpandedDialog = new ExpandedDialog(context);
-        mExpandedView = expanded;
-        mExpandedContents = expanded.findViewById(R.id.notificationLinearLayout);
-        mOngoingTitle = (TextView)expanded.findViewById(R.id.ongoingTitle);
-        mOngoingItems = (LinearLayout)expanded.findViewById(R.id.ongoingItems);
-        mLatestTitle = (TextView)expanded.findViewById(R.id.latestTitle);
-        mLatestItems = (LinearLayout)expanded.findViewById(R.id.latestItems);
-        mNoNotificationsTitle = (TextView)expanded.findViewById(R.id.noNotificationsTitle);
-        mClearButton = (TextView)expanded.findViewById(R.id.clear_all_button);
-        mClearButton.setOnClickListener(mClearButtonListener);
-        mScrollView = (ScrollView)expanded.findViewById(R.id.scroll);
-        mNotificationLinearLayout = expanded.findViewById(R.id.notificationLinearLayout);
-
-        mOngoingTitle.setVisibility(View.GONE);
-        mLatestTitle.setVisibility(View.GONE);
-
-        mTicker = new MyTicker(context, sb);
-
-        TickerView tickerView = (TickerView)sb.findViewById(R.id.tickerText);
-        tickerView.mTicker = mTicker;
-
-        mTrackingView = (TrackingView)View.inflate(context, R.layout.status_bar_tracking, null);
-        mTrackingView.mService = this;
-        mCloseView = (CloseDragHandle)mTrackingView.findViewById(R.id.close);
-        mCloseView.mService = this;
-
-        mEdgeBorder = res.getDimensionPixelSize(R.dimen.status_bar_edge_ignore);
-
-        // the more notifications icon
-        StatusBarIconView moreView = new StatusBarIconView(this, "more");
-        moreView.set(new StatusBarIcon(null, R.drawable.stat_notify_more, 0));
-        mNotificationIcons.addMoreView(moreView,
-                new LinearLayout.LayoutParams(mIconSize, mIconSize));
-
-        // set the inital view visibility
-        setAreThereNotifications();
-        mDateView.setVisibility(View.INVISIBLE);
-
-        // receive broadcasts
-        IntentFilter filter = new IntentFilter();
-        filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
-        filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
-        filter.addAction(Intent.ACTION_SCREEN_OFF);
-        context.registerReceiver(mBroadcastReceiver, filter);
-    }
-
-    protected void addStatusBarView() {
-        Resources res = getResources();
+        final Resources res = getResources();
         final int height= res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height);
 
-        final StatusBarView view = mStatusBarView;
-        WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+        final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
                 ViewGroup.LayoutParams.MATCH_PARENT,
                 height,
                 WindowManager.LayoutParams.TYPE_STATUS_BAR,
                 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                     | WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING,
                 PixelFormat.RGBX_8888);
-        lp.gravity = Gravity.TOP | Gravity.FILL_HORIZONTAL;
+        lp.gravity = getStatusBarGravity();
         lp.setTitle("StatusBar");
         // TODO lp.windowAnimations = R.style.Animation_StatusBar;
+        WindowManagerImpl.getDefault().addView(sb, lp);
 
-        WindowManagerImpl.getDefault().addView(view, lp);
-
-        lp = new WindowManager.LayoutParams(
-                ViewGroup.LayoutParams.MATCH_PARENT,
-                ViewGroup.LayoutParams.WRAP_CONTENT,
-                WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL,
-                WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
-                    | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
-                    | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
-                    | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
-                    | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
-                PixelFormat.TRANSLUCENT);
-        lp.gravity = Gravity.TOP | Gravity.FILL_HORIZONTAL;
-        lp.y += height * 1.5; // FIXME
-        lp.setTitle("IntruderAlert");
-        lp.windowAnimations = com.android.internal.R.style.Animation_StatusBar_IntruderAlert;
-
-        WindowManagerImpl.getDefault().addView(mIntruderAlertView, lp);
+        Slog.d(TAG, "Added status bar view w/ gravity 0x" + Integer.toHexString(lp.gravity));
     }
-
-    public void addIcon(String slot, int index, int viewIndex, StatusBarIcon icon) {
-        if (SPEW) Slog.d(TAG, "addIcon slot=" + slot + " index=" + index + " viewIndex=" + viewIndex
-                + " icon=" + icon);
-        StatusBarIconView view = new StatusBarIconView(this, slot);
-        view.set(icon);
-        mStatusIcons.addView(view, viewIndex, new LinearLayout.LayoutParams(mIconSize, mIconSize));
-    }
-
-    public void updateIcon(String slot, int index, int viewIndex,
-            StatusBarIcon old, StatusBarIcon icon) {
-        if (SPEW) Slog.d(TAG, "updateIcon slot=" + slot + " index=" + index + " viewIndex=" + viewIndex
-                + " old=" + old + " icon=" + icon);
-        StatusBarIconView view = (StatusBarIconView)mStatusIcons.getChildAt(viewIndex);
-        view.set(icon);
-    }
-
-    public void removeIcon(String slot, int index, int viewIndex) {
-        if (SPEW) Slog.d(TAG, "removeIcon slot=" + slot + " index=" + index + " viewIndex=" + viewIndex);
-        mStatusIcons.removeViewAt(viewIndex);
-    }
-
-    public void addNotification(IBinder key, StatusBarNotification notification) {
-        StatusBarIconView iconView = addNotificationViews(key, notification);
-        if (iconView == null) return;
-
-        boolean immersive = false;
-        try {
-            immersive = ActivityManagerNative.getDefault().isTopActivityImmersive();
-            Slog.d(TAG, "Top activity is " + (immersive?"immersive":"not immersive"));
-        } catch (RemoteException ex) {
-        }
-        if (immersive) {
-            if ((notification.notification.flags & Notification.FLAG_HIGH_PRIORITY) != 0) {
-                Slog.d(TAG, "Presenting high-priority notification in immersive activity");
-                // @@@ special new transient ticker mode
-                // 1. Populate mIntruderAlertView
-
-                ImageView alertIcon = (ImageView) mIntruderAlertView.findViewById(R.id.alertIcon);
-                TextView alertText = (TextView) mIntruderAlertView.findViewById(R.id.alertText);
-                alertIcon.setImageDrawable(StatusBarIconView.getIcon(
-                    alertIcon.getContext(),
-                    iconView.getStatusBarIcon()));
-                alertText.setText(notification.notification.tickerText);
-
-                View button = mIntruderAlertView.findViewById(R.id.intruder_alert_content);
-                button.setOnClickListener(
-                    new Launcher(notification.notification.contentIntent,
-                        notification.pkg, notification.tag, notification.id));
-
-                // 2. Animate mIntruderAlertView in
-                mHandler.sendEmptyMessage(MSG_SHOW_INTRUDER);
-
-                // 3. Set alarm to age the notification off (TODO)
-                mHandler.removeMessages(MSG_HIDE_INTRUDER);
-                mHandler.sendEmptyMessageDelayed(MSG_HIDE_INTRUDER, INTRUDER_ALERT_DECAY_MS);
-            }
-        } else if (notification.notification.fullScreenIntent != null) {
-            // not immersive & a full-screen alert should be shown
-            Slog.d(TAG, "Notification has fullScreenIntent and activity is not immersive;"
-                    + " sending fullScreenIntent");
-            try {
-                notification.notification.fullScreenIntent.send();
-            } catch (PendingIntent.CanceledException e) {
-            }
-        } else {
-            // usual case: status bar visible & not immersive
-
-            // show the ticker
-            tick(notification);
-        }
-
-        // Recalculate the position of the sliding windows and the titles.
-        setAreThereNotifications();
-        updateExpandedViewPos(EXPANDED_LEAVE_ALONE);
-    }
-
-    public void updateNotification(IBinder key, StatusBarNotification notification) {
-        Slog.d(TAG, "updateNotification key=" + key + " notification=" + notification);
-
-        NotificationData oldList;
-        int oldIndex = mOngoing.findEntry(key);
-        if (oldIndex >= 0) {
-            oldList = mOngoing;
-        } else {
-            oldIndex = mLatest.findEntry(key);
-            if (oldIndex < 0) {
-                Slog.w(TAG, "updateNotification for unknown key: " + key);
-                return;
-            }
-            oldList = mLatest;
-        }
-        final NotificationData.Entry oldEntry = oldList.getEntryAt(oldIndex);
-        final StatusBarNotification oldNotification = oldEntry.notification;
-        final RemoteViews oldContentView = oldNotification.notification.contentView;
-
-        final RemoteViews contentView = notification.notification.contentView;
-
-        if (false) {
-            Slog.d(TAG, "old notification: when=" + oldNotification.notification.when
-                    + " ongoing=" + oldNotification.isOngoing()
-                    + " expanded=" + oldEntry.expanded
-                    + " contentView=" + oldContentView);
-            Slog.d(TAG, "new notification: when=" + notification.notification.when
-                    + " ongoing=" + oldNotification.isOngoing()
-                    + " contentView=" + contentView);
-        }
-
-        // Can we just reapply the RemoteViews in place?  If when didn't change, the order
-        // didn't change.
-        if (notification.notification.when == oldNotification.notification.when
-                && notification.isOngoing() == oldNotification.isOngoing()
-                && oldEntry.expanded != null
-                && contentView != null && oldContentView != null
-                && contentView.getPackage() != null
-                && oldContentView.getPackage() != null
-                && oldContentView.getPackage().equals(contentView.getPackage())
-                && oldContentView.getLayoutId() == contentView.getLayoutId()) {
-            if (SPEW) Slog.d(TAG, "reusing notification");
-            oldEntry.notification = notification;
-            try {
-                // Reapply the RemoteViews
-                contentView.reapply(this, oldEntry.content);
-                // update the contentIntent
-                final PendingIntent contentIntent = notification.notification.contentIntent;
-                if (contentIntent != null) {
-                    oldEntry.content.setOnClickListener(new Launcher(contentIntent,
-                                notification.pkg, notification.tag, notification.id));
-                }
-                // Update the icon.
-                final StatusBarIcon ic = new StatusBarIcon(notification.pkg,
-                        notification.notification.icon, notification.notification.iconLevel,
-                        notification.notification.number);
-                if (!oldEntry.icon.set(ic)) {
-                    handleNotificationError(key, notification, "Couldn't update icon: " + ic);
-                    return;
-                }
-            }
-            catch (RuntimeException e) {
-                // It failed to add cleanly.  Log, and remove the view from the panel.
-                Slog.w(TAG, "Couldn't reapply views for package " + contentView.getPackage(), e);
-                removeNotificationViews(key);
-                addNotificationViews(key, notification);
-            }
-        } else {
-            if (SPEW) Slog.d(TAG, "not reusing notification");
-            removeNotificationViews(key);
-            addNotificationViews(key, notification);
-        }
-
-        // Restart the ticker if it's still running
-        tick(notification);
-
-        // Recalculate the position of the sliding windows and the titles.
-        setAreThereNotifications();
-        updateExpandedViewPos(EXPANDED_LEAVE_ALONE);
-    }
-
-    public void removeNotification(IBinder key) {
-        if (SPEW) Slog.d(TAG, "removeNotification key=" + key);
-        StatusBarNotification old = removeNotificationViews(key);
-
-        if (old != null) {
-            // Cancel the ticker if it's still running
-            mTicker.removeEntry(old);
-
-            // Recalculate the position of the sliding windows and the titles.
-            setAreThereNotifications();
-            updateExpandedViewPos(EXPANDED_LEAVE_ALONE);
-        }
-    }
-
-    private int chooseIconIndex(boolean isOngoing, int viewIndex) {
-        final int latestSize = mLatest.size();
-        if (isOngoing) {
-            return latestSize + (mOngoing.size() - viewIndex);
-        } else {
-            return latestSize - viewIndex;
-        }
-    }
-
-    View[] makeNotificationView(StatusBarNotification notification, ViewGroup parent) {
-        Notification n = notification.notification;
-        RemoteViews remoteViews = n.contentView;
-        if (remoteViews == null) {
-            return null;
-        }
-
-        // create the row view
-        LayoutInflater inflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
-        View row = inflater.inflate(R.layout.status_bar_latest_event, parent, false);
-
-        // bind the click event to the content area
-        ViewGroup content = (ViewGroup)row.findViewById(R.id.content);
-        content.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
-        content.setOnFocusChangeListener(mFocusChangeListener);
-        PendingIntent contentIntent = n.contentIntent;
-        if (contentIntent != null) {
-            content.setOnClickListener(new Launcher(contentIntent, notification.pkg,
-                        notification.tag, notification.id));
-        }
-
-        View expanded = null;
-        Exception exception = null;
-        try {
-            expanded = remoteViews.apply(this, content);
-        }
-        catch (RuntimeException e) {
-            exception = e;
-        }
-        if (expanded == null) {
-            String ident = notification.pkg + "/0x" + Integer.toHexString(notification.id);
-            Slog.e(TAG, "couldn't inflate view for notification " + ident, exception);
-            return null;
-        } else {
-            content.addView(expanded);
-            row.setDrawingCacheEnabled(true);
-        }
-
-        return new View[] { row, content, expanded };
-    }
-
-    StatusBarIconView addNotificationViews(IBinder key, StatusBarNotification notification) {
-        NotificationData list;
-        ViewGroup parent;
-        final boolean isOngoing = notification.isOngoing();
-        if (isOngoing) {
-            list = mOngoing;
-            parent = mOngoingItems;
-        } else {
-            list = mLatest;
-            parent = mLatestItems;
-        }
-        // Construct the expanded view.
-        final View[] views = makeNotificationView(notification, parent);
-        if (views == null) {
-            handleNotificationError(key, notification, "Couldn't expand RemoteViews for: "
-                    + notification);
-            return null;
-        }
-        final View row = views[0];
-        final View content = views[1];
-        final View expanded = views[2];
-        // Construct the icon.
-        final StatusBarIconView iconView = new StatusBarIconView(this,
-                notification.pkg + "/0x" + Integer.toHexString(notification.id));
-        final StatusBarIcon ic = new StatusBarIcon(notification.pkg, notification.notification.icon,
-                    notification.notification.iconLevel, notification.notification.number);
-        if (!iconView.set(ic)) {
-            handleNotificationError(key, notification, "Coulding create icon: " + ic);
-            return null;
-        }
-        // Add the expanded view.
-        final int viewIndex = list.add(key, notification, row, content, expanded, iconView);
-        parent.addView(row, viewIndex);
-        // Add the icon.
-        final int iconIndex = chooseIconIndex(isOngoing, viewIndex);
-        mNotificationIcons.addView(iconView, iconIndex,
-                new LinearLayout.LayoutParams(mIconSize, mIconSize));
-        return iconView;
-    }
-
-    StatusBarNotification removeNotificationViews(IBinder key) {
-        NotificationData.Entry entry = mOngoing.remove(key);
-        if (entry == null) {
-            entry = mLatest.remove(key);
-            if (entry == null) {
-                Slog.w(TAG, "removeNotification for unknown key: " + key);
-                return null;
-            }
-        }
-        // Remove the expanded view.
-        ((ViewGroup)entry.row.getParent()).removeView(entry.row);
-        // Remove the icon.
-        ((ViewGroup)entry.icon.getParent()).removeView(entry.icon);
-
-        return entry.notification;
-    }
-
-    private void setAreThereNotifications() {
-        boolean ongoing = mOngoing.hasVisibleItems();
-        boolean latest = mLatest.hasVisibleItems();
-
-        // (no ongoing notifications are clearable)
-        if (mLatest.hasClearableItems()) {
-            mClearButton.setVisibility(View.VISIBLE);
-        } else {
-            mClearButton.setVisibility(View.INVISIBLE);
-        }
-
-        mOngoingTitle.setVisibility(ongoing ? View.VISIBLE : View.GONE);
-        mLatestTitle.setVisibility(latest ? View.VISIBLE : View.GONE);
-
-        if (ongoing || latest) {
-            mNoNotificationsTitle.setVisibility(View.GONE);
-        } else {
-            mNoNotificationsTitle.setVisibility(View.VISIBLE);
-        }
-    }
-
-
-    /**
-     * State is one or more of the DISABLE constants from StatusBarManager.
-     */
-    public void disable(int state) {
-        final int old = mDisabled;
-        final int diff = state ^ old;
-        mDisabled = state;
-
-        if ((diff & StatusBarManager.DISABLE_EXPAND) != 0) {
-            if ((state & StatusBarManager.DISABLE_EXPAND) != 0) {
-                Slog.d(TAG, "DISABLE_EXPAND: yes");
-                animateCollapse();
-            }
-        }
-        if ((diff & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) {
-            if ((state & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) {
-                Slog.d(TAG, "DISABLE_NOTIFICATION_ICONS: yes");
-                if (mTicking) {
-                    mTicker.halt();
-                } else {
-                    setNotificationIconVisibility(false, com.android.internal.R.anim.fade_out);
-                }
-            } else {
-                Slog.d(TAG, "DISABLE_NOTIFICATION_ICONS: no");
-                if (!mExpandedVisible) {
-                    setNotificationIconVisibility(true, com.android.internal.R.anim.fade_in);
-                }
-            }
-        } else if ((diff & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) {
-            if (mTicking && (state & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) {
-                Slog.d(TAG, "DISABLE_NOTIFICATION_TICKER: yes");
-                mTicker.halt();
-            }
-        }
-    }
-
-    /**
-     * All changes to the status bar and notifications funnel through here and are batched.
-     */
-    private class H extends Handler {
-        public void handleMessage(Message m) {
-            switch (m.what) {
-                case MSG_ANIMATE:
-                    doAnimation();
-                    break;
-                case MSG_ANIMATE_REVEAL:
-                    doRevealAnimation();
-                    break;
-                case MSG_SHOW_INTRUDER:
-                    setIntruderAlertVisibility(true);
-                    break;
-                case MSG_HIDE_INTRUDER:
-                    setIntruderAlertVisibility(false);
-                    break;
-            }
-        }
-    }
-
-    View.OnFocusChangeListener mFocusChangeListener = new View.OnFocusChangeListener() {
-        public void onFocusChange(View v, boolean hasFocus) {
-            // Because 'v' is a ViewGroup, all its children will be (un)selected
-            // too, which allows marqueeing to work.
-            v.setSelected(hasFocus);
-        }
-    };
-
-    private void makeExpandedVisible() {
-        if (SPEW) Slog.d(TAG, "Make expanded visible: expanded visible=" + mExpandedVisible);
-        if (mExpandedVisible) {
-            return;
-        }
-        mExpandedVisible = true;
-        visibilityChanged(true);
-
-        updateExpandedViewPos(EXPANDED_LEAVE_ALONE);
-        mExpandedParams.flags &= ~WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
-        mExpandedParams.flags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
-        mExpandedDialog.getWindow().setAttributes(mExpandedParams);
-        mExpandedView.requestFocus(View.FOCUS_FORWARD);
-        mTrackingView.setVisibility(View.VISIBLE);
-
-        if (!mTicking) {
-            setDateViewVisibility(true, com.android.internal.R.anim.fade_in);
-        }
-    }
-
-    public void animateExpand() {
-        if (SPEW) Slog.d(TAG, "Animate expand: expanded=" + mExpanded);
-        if ((mDisabled & StatusBarManager.DISABLE_EXPAND) != 0) {
-            return ;
-        }
-        if (mExpanded) {
-            return;
-        }
-
-        prepareTracking(0, true);
-        performFling(0, 2000.0f, true);
-    }
-
-    public void animateCollapse() {
-        if (SPEW) {
-            Slog.d(TAG, "animateCollapse(): mExpanded=" + mExpanded
-                    + " mExpandedVisible=" + mExpandedVisible
-                    + " mExpanded=" + mExpanded
-                    + " mAnimating=" + mAnimating
-                    + " mAnimY=" + mAnimY
-                    + " mAnimVel=" + mAnimVel);
-        }
-
-        if (!mExpandedVisible) {
-            return;
-        }
-
-        int y;
-        if (mAnimating) {
-            y = (int)mAnimY;
-        } else {
-            y = mDisplay.getHeight()-1;
-        }
-        // Let the fling think that we're open so it goes in the right direction
-        // and doesn't try to re-open the windowshade.
-        mExpanded = true;
-        prepareTracking(y, false);
-        performFling(y, -2000.0f, true);
-    }
-
-    void performExpand() {
-        if (SPEW) Slog.d(TAG, "performExpand: mExpanded=" + mExpanded);
-        if ((mDisabled & StatusBarManager.DISABLE_EXPAND) != 0) {
-            return ;
-        }
-        if (mExpanded) {
-            return;
-        }
-
-        mExpanded = true;
-        makeExpandedVisible();
-        updateExpandedViewPos(EXPANDED_FULL_OPEN);
-
-        if (false) postStartTracing();
-    }
-
-    void performCollapse() {
-        if (SPEW) Slog.d(TAG, "performCollapse: mExpanded=" + mExpanded
-                + " mExpandedVisible=" + mExpandedVisible);
-
-        if (!mExpandedVisible) {
-            return;
-        }
-        mExpandedVisible = false;
-        visibilityChanged(false);
-        mExpandedParams.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
-        mExpandedParams.flags &= ~WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
-        mExpandedDialog.getWindow().setAttributes(mExpandedParams);
-        mTrackingView.setVisibility(View.GONE);
-
-        if ((mDisabled & StatusBarManager.DISABLE_NOTIFICATION_ICONS) == 0) {
-            setNotificationIconVisibility(true, com.android.internal.R.anim.fade_in);
-        }
-        setDateViewVisibility(false, com.android.internal.R.anim.fade_out);
-
-        if (!mExpanded) {
-            return;
-        }
-        mExpanded = false;
-    }
-
-    void doAnimation() {
-        if (mAnimating) {
-            if (SPEW) Slog.d(TAG, "doAnimation");
-            if (SPEW) Slog.d(TAG, "doAnimation before mAnimY=" + mAnimY);
-            incrementAnim();
-            if (SPEW) Slog.d(TAG, "doAnimation after  mAnimY=" + mAnimY);
-            if (mAnimY >= mDisplay.getHeight()-1) {
-                if (SPEW) Slog.d(TAG, "Animation completed to expanded state.");
-                mAnimating = false;
-                updateExpandedViewPos(EXPANDED_FULL_OPEN);
-                performExpand();
-            }
-            else if (mAnimY < mStatusBarView.getHeight()) {
-                if (SPEW) Slog.d(TAG, "Animation completed to collapsed state.");
-                mAnimating = false;
-                updateExpandedViewPos(0);
-                performCollapse();
-            }
-            else {
-                updateExpandedViewPos((int)mAnimY);
-                mCurAnimationTime += ANIM_FRAME_DURATION;
-                mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_ANIMATE), mCurAnimationTime);
-            }
-        }
-    }
-
-    void stopTracking() {
-        mTracking = false;
-        mVelocityTracker.recycle();
-        mVelocityTracker = null;
-    }
-
-    void incrementAnim() {
-        long now = SystemClock.uptimeMillis();
-        float t = ((float)(now - mAnimLastTime)) / 1000;            // ms -> s
-        final float y = mAnimY;
-        final float v = mAnimVel;                                   // px/s
-        final float a = mAnimAccel;                                 // px/s/s
-        mAnimY = y + (v*t) + (0.5f*a*t*t);                          // px
-        mAnimVel = v + (a*t);                                       // px/s
-        mAnimLastTime = now;                                        // ms
-        //Slog.d(TAG, "y=" + y + " v=" + v + " a=" + a + " t=" + t + " mAnimY=" + mAnimY
-        //        + " mAnimAccel=" + mAnimAccel);
-    }
-
-    void doRevealAnimation() {
-        final int h = mCloseView.getHeight() + mStatusBarView.getHeight();
-        if (mAnimatingReveal && mAnimating && mAnimY < h) {
-            incrementAnim();
-            if (mAnimY >= h) {
-                mAnimY = h;
-                updateExpandedViewPos((int)mAnimY);
-            } else {
-                updateExpandedViewPos((int)mAnimY);
-                mCurAnimationTime += ANIM_FRAME_DURATION;
-                mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_ANIMATE_REVEAL),
-                        mCurAnimationTime);
-            }
-        }
-    }
-
-    void prepareTracking(int y, boolean opening) {
-        mTracking = true;
-        mVelocityTracker = VelocityTracker.obtain();
-        if (opening) {
-            mAnimAccel = 2000.0f;
-            mAnimVel = 200;
-            mAnimY = mStatusBarView.getHeight();
-            updateExpandedViewPos((int)mAnimY);
-            mAnimating = true;
-            mAnimatingReveal = true;
-            mHandler.removeMessages(MSG_ANIMATE);
-            mHandler.removeMessages(MSG_ANIMATE_REVEAL);
-            long now = SystemClock.uptimeMillis();
-            mAnimLastTime = now;
-            mCurAnimationTime = now + ANIM_FRAME_DURATION;
-            mAnimating = true;
-            mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_ANIMATE_REVEAL),
-                    mCurAnimationTime);
-            makeExpandedVisible();
-        } else {
-            // it's open, close it?
-            if (mAnimating) {
-                mAnimating = false;
-                mHandler.removeMessages(MSG_ANIMATE);
-            }
-            updateExpandedViewPos(y + mViewDelta);
-        }
-    }
-
-    void performFling(int y, float vel, boolean always) {
-        mAnimatingReveal = false;
-        mDisplayHeight = mDisplay.getHeight();
-
-        mAnimY = y;
-        mAnimVel = vel;
-
-        //Slog.d(TAG, "starting with mAnimY=" + mAnimY + " mAnimVel=" + mAnimVel);
-
-        if (mExpanded) {
-            if (!always && (
-                    vel > 200.0f
-                    || (y > (mDisplayHeight-25) && vel > -200.0f))) {
-                // We are expanded, but they didn't move sufficiently to cause
-                // us to retract.  Animate back to the expanded position.
-                mAnimAccel = 2000.0f;
-                if (vel < 0) {
-                    mAnimVel = 0;
-                }
-            }
-            else {
-                // We are expanded and are now going to animate away.
-                mAnimAccel = -2000.0f;
-                if (vel > 0) {
-                    mAnimVel = 0;
-                }
-            }
-        } else {
-            if (always || (
-                    vel > 200.0f
-                    || (y > (mDisplayHeight/2) && vel > -200.0f))) {
-                // We are collapsed, and they moved enough to allow us to
-                // expand.  Animate in the notifications.
-                mAnimAccel = 2000.0f;
-                if (vel < 0) {
-                    mAnimVel = 0;
-                }
-            }
-            else {
-                // We are collapsed, but they didn't move sufficiently to cause
-                // us to retract.  Animate back to the collapsed position.
-                mAnimAccel = -2000.0f;
-                if (vel > 0) {
-                    mAnimVel = 0;
-                }
-            }
-        }
-        //Slog.d(TAG, "mAnimY=" + mAnimY + " mAnimVel=" + mAnimVel
-        //        + " mAnimAccel=" + mAnimAccel);
-
-        long now = SystemClock.uptimeMillis();
-        mAnimLastTime = now;
-        mCurAnimationTime = now + ANIM_FRAME_DURATION;
-        mAnimating = true;
-        mHandler.removeMessages(MSG_ANIMATE);
-        mHandler.removeMessages(MSG_ANIMATE_REVEAL);
-        mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_ANIMATE), mCurAnimationTime);
-        stopTracking();
-    }
-
-    boolean interceptTouchEvent(MotionEvent event) {
-        if (SPEW) {
-            Slog.d(TAG, "Touch: rawY=" + event.getRawY() + " event=" + event + " mDisabled="
-                + mDisabled);
-        }
-
-        if ((mDisabled & StatusBarManager.DISABLE_EXPAND) != 0) {
-            return false;
-        }
-
-        final int statusBarSize = mStatusBarView.getHeight();
-        final int hitSize = statusBarSize*2;
-        if (event.getAction() == MotionEvent.ACTION_DOWN) {
-            final int y = (int)event.getRawY();
-
-            if (!mExpanded) {
-                mViewDelta = statusBarSize - y;
-            } else {
-                mTrackingView.getLocationOnScreen(mAbsPos);
-                mViewDelta = mAbsPos[1] + mTrackingView.getHeight() - y;
-            }
-            if ((!mExpanded && y < hitSize) ||
-                    (mExpanded && y > (mDisplay.getHeight()-hitSize))) {
-
-                // We drop events at the edge of the screen to make the windowshade come
-                // down by accident less, especially when pushing open a device with a keyboard
-                // that rotates (like g1 and droid)
-                int x = (int)event.getRawX();
-                final int edgeBorder = mEdgeBorder;
-                if (x >= edgeBorder && x < mDisplay.getWidth() - edgeBorder) {
-                    prepareTracking(y, !mExpanded);// opening if we're not already fully visible
-                    mVelocityTracker.addMovement(event);
-                }
-            }
-        } else if (mTracking) {
-            mVelocityTracker.addMovement(event);
-            final int minY = statusBarSize + mCloseView.getHeight();
-            if (event.getAction() == MotionEvent.ACTION_MOVE) {
-                int y = (int)event.getRawY();
-                if (mAnimatingReveal && y < minY) {
-                    // nothing
-                } else  {
-                    mAnimatingReveal = false;
-                    updateExpandedViewPos(y + mViewDelta);
-                }
-            } else if (event.getAction() == MotionEvent.ACTION_UP) {
-                mVelocityTracker.computeCurrentVelocity(1000);
-
-                float yVel = mVelocityTracker.getYVelocity();
-                boolean negative = yVel < 0;
-
-                float xVel = mVelocityTracker.getXVelocity();
-                if (xVel < 0) {
-                    xVel = -xVel;
-                }
-                if (xVel > 150.0f) {
-                    xVel = 150.0f; // limit how much we care about the x axis
-                }
-
-                float vel = (float)Math.hypot(yVel, xVel);
-                if (negative) {
-                    vel = -vel;
-                }
-
-                performFling((int)event.getRawY(), vel, false);
-            }
-
-        }
-        return false;
-    }
-
-    private class Launcher implements View.OnClickListener {
-        private PendingIntent mIntent;
-        private String mPkg;
-        private String mTag;
-        private int mId;
-
-        Launcher(PendingIntent intent, String pkg, String tag, int id) {
-            mIntent = intent;
-            mPkg = pkg;
-            mTag = tag;
-            mId = id;
-        }
-
-        public void onClick(View v) {
-            try {
-                // The intent we are sending is for the application, which
-                // won't have permission to immediately start an activity after
-                // the user switches to home.  We know it is safe to do at this
-                // point, so make sure new activity switches are now allowed.
-                ActivityManagerNative.getDefault().resumeAppSwitches();
-            } catch (RemoteException e) {
-            }
-
-            if (mIntent != null) {
-                int[] pos = new int[2];
-                v.getLocationOnScreen(pos);
-                Intent overlay = new Intent();
-                overlay.setSourceBounds(
-                        new Rect(pos[0], pos[1], pos[0]+v.getWidth(), pos[1]+v.getHeight()));
-                try {
-                    mIntent.send(StatusBarService.this, 0, overlay);
-                } catch (PendingIntent.CanceledException e) {
-                    // the stack trace isn't very helpful here.  Just log the exception message.
-                    Slog.w(TAG, "Sending contentIntent failed: " + e);
-                }
-            }
-
-            try {
-                mBarService.onNotificationClick(mPkg, mTag, mId);
-            } catch (RemoteException ex) {
-                // system process is dead if we're here.
-            }
-
-            // close the shade if it was open
-            animateCollapse();
-
-            // If this click was on the intruder alert, hide that instead
-            mHandler.sendEmptyMessage(MSG_HIDE_INTRUDER);
-        }
-    }
-
-    private void tick(StatusBarNotification n) {
-        // Show the ticker if one is requested. Also don't do this
-        // until status bar window is attached to the window manager,
-        // because...  well, what's the point otherwise?  And trying to
-        // run a ticker without being attached will crash!
-        if (n.notification.tickerText != null && mStatusBarView.getWindowToken() != null) {
-            if (0 == (mDisabled & (StatusBarManager.DISABLE_NOTIFICATION_ICONS
-                            | StatusBarManager.DISABLE_NOTIFICATION_TICKER))) {
-                mTicker.addEntry(n);
-            }
-        }
-    }
-
-    /**
-     * Cancel this notification and tell the StatusBarManagerService / NotificationManagerService
-     * about the failure.
-     *
-     * WARNING: this will call back into us.  Don't hold any locks.
-     */
-    void handleNotificationError(IBinder key, StatusBarNotification n, String message) {
-        removeNotification(key);
-        try {
-            mBarService.onNotificationError(n.pkg, n.tag, n.id, n.uid, n.initialPid, message);
-        } catch (RemoteException ex) {
-            // The end is nigh.
-        }
-    }
-
-    private class MyTicker extends Ticker {
-        MyTicker(Context context, StatusBarView sb) {
-            super(context, sb);
-        }
-
-        @Override
-        void tickerStarting() {
-            mTicking = true;
-            mIcons.setVisibility(View.GONE);
-            mTickerView.setVisibility(View.VISIBLE);
-            mTickerView.startAnimation(loadAnim(com.android.internal.R.anim.push_up_in, null));
-            mIcons.startAnimation(loadAnim(com.android.internal.R.anim.push_up_out, null));
-            if (mExpandedVisible) {
-                setDateViewVisibility(false, com.android.internal.R.anim.push_up_out);
-            }
-        }
-
-        @Override
-        void tickerDone() {
-            mIcons.setVisibility(View.VISIBLE);
-            mTickerView.setVisibility(View.GONE);
-            mIcons.startAnimation(loadAnim(com.android.internal.R.anim.push_down_in, null));
-            mTickerView.startAnimation(loadAnim(com.android.internal.R.anim.push_down_out,
-                        mTickingDoneListener));
-            if (mExpandedVisible) {
-                setDateViewVisibility(true, com.android.internal.R.anim.push_down_in);
-            }
-        }
-
-        void tickerHalting() {
-            mIcons.setVisibility(View.VISIBLE);
-            mTickerView.setVisibility(View.GONE);
-            mIcons.startAnimation(loadAnim(com.android.internal.R.anim.fade_in, null));
-            mTickerView.startAnimation(loadAnim(com.android.internal.R.anim.fade_out,
-                        mTickingDoneListener));
-            if (mExpandedVisible) {
-                setDateViewVisibility(true, com.android.internal.R.anim.fade_in);
-            }
-        }
-    }
-
-    Animation.AnimationListener mTickingDoneListener = new Animation.AnimationListener() {;
-        public void onAnimationEnd(Animation animation) {
-            mTicking = false;
-        }
-        public void onAnimationRepeat(Animation animation) {
-        }
-        public void onAnimationStart(Animation animation) {
-        }
-    };
-
-    private Animation loadAnim(int id, Animation.AnimationListener listener) {
-        Animation anim = AnimationUtils.loadAnimation(StatusBarService.this, id);
-        if (listener != null) {
-            anim.setAnimationListener(listener);
-        }
-        return anim;
-    }
-
-    public String viewInfo(View v) {
-        return "(" + v.getLeft() + "," + v.getTop() + ")(" + v.getRight() + "," + v.getBottom()
-                + " " + v.getWidth() + "x" + v.getHeight() + ")";
-    }
-
-    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        if (checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
-                != PackageManager.PERMISSION_GRANTED) {
-            pw.println("Permission Denial: can't dump StatusBar from from pid="
-                    + Binder.getCallingPid()
-                    + ", uid=" + Binder.getCallingUid());
-            return;
-        }
-
-        synchronized (mQueueLock) {
-            pw.println("Current Status Bar state:");
-            pw.println("  mExpanded=" + mExpanded
-                    + ", mExpandedVisible=" + mExpandedVisible);
-            pw.println("  mTicking=" + mTicking);
-            pw.println("  mTracking=" + mTracking);
-            pw.println("  mAnimating=" + mAnimating
-                    + ", mAnimY=" + mAnimY + ", mAnimVel=" + mAnimVel
-                    + ", mAnimAccel=" + mAnimAccel);
-            pw.println("  mCurAnimationTime=" + mCurAnimationTime
-                    + " mAnimLastTime=" + mAnimLastTime);
-            pw.println("  mDisplayHeight=" + mDisplayHeight
-                    + " mAnimatingReveal=" + mAnimatingReveal
-                    + " mViewDelta=" + mViewDelta);
-            pw.println("  mDisplayHeight=" + mDisplayHeight);
-            pw.println("  mExpandedParams: " + mExpandedParams);
-            pw.println("  mExpandedView: " + viewInfo(mExpandedView));
-            pw.println("  mExpandedDialog: " + mExpandedDialog);
-            pw.println("  mTrackingParams: " + mTrackingParams);
-            pw.println("  mTrackingView: " + viewInfo(mTrackingView));
-            pw.println("  mOngoingTitle: " + viewInfo(mOngoingTitle));
-            pw.println("  mOngoingItems: " + viewInfo(mOngoingItems));
-            pw.println("  mLatestTitle: " + viewInfo(mLatestTitle));
-            pw.println("  mLatestItems: " + viewInfo(mLatestItems));
-            pw.println("  mNoNotificationsTitle: " + viewInfo(mNoNotificationsTitle));
-            pw.println("  mCloseView: " + viewInfo(mCloseView));
-            pw.println("  mTickerView: " + viewInfo(mTickerView));
-            pw.println("  mScrollView: " + viewInfo(mScrollView)
-                    + " scroll " + mScrollView.getScrollX() + "," + mScrollView.getScrollY());
-            pw.println("mNotificationLinearLayout: " + viewInfo(mNotificationLinearLayout));
-        }
-        /*
-        synchronized (mNotificationData) {
-            int N = mNotificationData.ongoingCount();
-            pw.println("  ongoingCount.size=" + N);
-            for (int i=0; i<N; i++) {
-                StatusBarNotification n = mNotificationData.getOngoing(i);
-                pw.println("    [" + i + "] key=" + n.key + " view=" + n.view);
-                pw.println("           data=" + n.data);
-            }
-            N = mNotificationData.latestCount();
-            pw.println("  ongoingCount.size=" + N);
-            for (int i=0; i<N; i++) {
-                StatusBarNotification n = mNotificationData.getLatest(i);
-                pw.println("    [" + i + "] key=" + n.key + " view=" + n.view);
-                pw.println("           data=" + n.data);
-            }
-        }
-        */
-
-        if (false) {
-            pw.println("see the logcat for a dump of the views we have created.");
-            // must happen on ui thread
-            mHandler.post(new Runnable() {
-                    public void run() {
-                        mStatusBarView.getLocationOnScreen(mAbsPos);
-                        Slog.d(TAG, "mStatusBarView: ----- (" + mAbsPos[0] + "," + mAbsPos[1]
-                                + ") " + mStatusBarView.getWidth() + "x"
-                                + mStatusBarView.getHeight());
-                        mStatusBarView.debug();
-
-                        mExpandedView.getLocationOnScreen(mAbsPos);
-                        Slog.d(TAG, "mExpandedView: ----- (" + mAbsPos[0] + "," + mAbsPos[1]
-                                + ") " + mExpandedView.getWidth() + "x"
-                                + mExpandedView.getHeight());
-                        mExpandedView.debug();
-
-                        mTrackingView.getLocationOnScreen(mAbsPos);
-                        Slog.d(TAG, "mTrackingView: ----- (" + mAbsPos[0] + "," + mAbsPos[1]
-                                + ") " + mTrackingView.getWidth() + "x"
-                                + mTrackingView.getHeight());
-                        mTrackingView.debug();
-                    }
-                });
-        }
-    }
-
-    void onBarViewAttached() {
-        WindowManager.LayoutParams lp;
-        int pixelFormat;
-        Drawable bg;
-
-        /// ---------- Tracking View --------------
-        pixelFormat = PixelFormat.RGBX_8888;
-        bg = mTrackingView.getBackground();
-        if (bg != null) {
-            pixelFormat = bg.getOpacity();
-        }
-
-        lp = new WindowManager.LayoutParams(
-                ViewGroup.LayoutParams.MATCH_PARENT,
-                ViewGroup.LayoutParams.MATCH_PARENT,
-                WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL,
-                WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
-                | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
-                | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
-                pixelFormat);
-//        lp.token = mStatusBarView.getWindowToken();
-        lp.gravity = Gravity.TOP | Gravity.FILL_HORIZONTAL;
-        lp.setTitle("TrackingView");
-        lp.y = mTrackingPosition;
-        mTrackingParams = lp;
-
-        WindowManagerImpl.getDefault().addView(mTrackingView, lp);
-    }
-
-    void onTrackingViewAttached() {
-        WindowManager.LayoutParams lp;
-        int pixelFormat;
-        Drawable bg;
-
-        /// ---------- Expanded View --------------
-        pixelFormat = PixelFormat.TRANSLUCENT;
-
-        final int disph = mDisplay.getHeight();
-        lp = mExpandedDialog.getWindow().getAttributes();
-        lp.width = ViewGroup.LayoutParams.MATCH_PARENT;
-        lp.height = getExpandedHeight();
-        lp.x = 0;
-        mTrackingPosition = lp.y = -disph; // sufficiently large negative
-        lp.type = WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL;
-        lp.flags = WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
-                | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
-                | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
-                | WindowManager.LayoutParams.FLAG_DITHER
-                | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
-        lp.format = pixelFormat;
-        lp.gravity = Gravity.TOP | Gravity.FILL_HORIZONTAL;
-        lp.setTitle("StatusBarExpanded");
-        mExpandedDialog.getWindow().setAttributes(lp);
-        mExpandedDialog.getWindow().setFormat(pixelFormat);
-        mExpandedParams = lp;
-
-        mExpandedDialog.getWindow().requestFeature(Window.FEATURE_NO_TITLE);
-        mExpandedDialog.setContentView(mExpandedView,
-                new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
-                                           ViewGroup.LayoutParams.MATCH_PARENT));
-        mExpandedDialog.getWindow().setBackgroundDrawable(null);
-        mExpandedDialog.show();
-        FrameLayout hack = (FrameLayout)mExpandedView.getParent();
-    }
-
-    void setDateViewVisibility(boolean visible, int anim) {
-        mDateView.setUpdates(visible);
-        mDateView.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
-        mDateView.startAnimation(loadAnim(anim, null));
-    }
-
-    void setNotificationIconVisibility(boolean visible, int anim) {
-        int old = mNotificationIcons.getVisibility();
-        int v = visible ? View.VISIBLE : View.INVISIBLE;
-        if (old != v) {
-            mNotificationIcons.setVisibility(v);
-            mNotificationIcons.startAnimation(loadAnim(anim, null));
-        }
-    }
-
-    void updateExpandedViewPos(int expandedPosition) {
-        if (SPEW) {
-            Slog.d(TAG, "updateExpandedViewPos before expandedPosition=" + expandedPosition
-                    + " mTrackingParams.y=" + mTrackingParams.y
-                    + " mTrackingPosition=" + mTrackingPosition);
-        }
-
-        int h = mStatusBarView.getHeight();
-        int disph = mDisplay.getHeight();
-
-        // If the expanded view is not visible, make sure they're still off screen.
-        // Maybe the view was resized.
-        if (!mExpandedVisible) {
-            if (mTrackingView != null) {
-                mTrackingPosition = -disph;
-                if (mTrackingParams != null) {
-                    mTrackingParams.y = mTrackingPosition;
-                    WindowManagerImpl.getDefault().updateViewLayout(mTrackingView, mTrackingParams);
-                }
-            }
-            if (mExpandedParams != null) {
-                mExpandedParams.y = -disph;
-                mExpandedDialog.getWindow().setAttributes(mExpandedParams);
-            }
-            return;
-        }
-
-        // tracking view...
-        int pos;
-        if (expandedPosition == EXPANDED_FULL_OPEN) {
-            pos = h;
-        }
-        else if (expandedPosition == EXPANDED_LEAVE_ALONE) {
-            pos = mTrackingPosition;
-        }
-        else {
-            if (expandedPosition <= disph) {
-                pos = expandedPosition;
-            } else {
-                pos = disph;
-            }
-            pos -= disph-h;
-        }
-        mTrackingPosition = mTrackingParams.y = pos;
-        mTrackingParams.height = disph-h;
-        WindowManagerImpl.getDefault().updateViewLayout(mTrackingView, mTrackingParams);
-
-        if (mExpandedParams != null) {
-            mCloseView.getLocationInWindow(mPositionTmp);
-            final int closePos = mPositionTmp[1];
-
-            mExpandedContents.getLocationInWindow(mPositionTmp);
-            final int contentsBottom = mPositionTmp[1] + mExpandedContents.getHeight();
-
-            mExpandedParams.y = pos + mTrackingView.getHeight()
-                    - (mTrackingParams.height-closePos) - contentsBottom;
-            int max = h;
-            if (mExpandedParams.y > max) {
-                mExpandedParams.y = max;
-            }
-            int min = mTrackingPosition;
-            if (mExpandedParams.y < min) {
-                mExpandedParams.y = min;
-            }
-
-            boolean visible = (mTrackingPosition + mTrackingView.getHeight()) > h;
-            if (!visible) {
-                // if the contents aren't visible, move the expanded view way off screen
-                // because the window itself extends below the content view.
-                mExpandedParams.y = -disph;
-            }
-            mExpandedDialog.getWindow().setAttributes(mExpandedParams);
-
-            // As long as this isn't just a repositioning that's not supposed to affect
-            // the user's perception of what's showing, call to say that the visibility
-            // has changed. (Otherwise, someone else will call to do that).
-            if (expandedPosition != EXPANDED_LEAVE_ALONE) {
-                if (SPEW) Slog.d(TAG, "updateExpandedViewPos visibilityChanged(" + visible + ")");
-                visibilityChanged(visible);
-            }
-        }
-
-        if (SPEW) {
-            Slog.d(TAG, "updateExpandedViewPos after  expandedPosition=" + expandedPosition
-                    + " mTrackingParams.y=" + mTrackingParams.y
-                    + " mTrackingPosition=" + mTrackingPosition
-                    + " mExpandedParams.y=" + mExpandedParams.y
-                    + " mExpandedParams.height=" + mExpandedParams.height);
-        }
-    }
-
-    int getExpandedHeight() {
-        return mDisplay.getHeight() - mStatusBarView.getHeight() - mCloseView.getHeight();
-    }
-
-    void updateExpandedHeight() {
-        if (mExpandedView != null) {
-            mExpandedParams.height = getExpandedHeight();
-            mExpandedDialog.getWindow().setAttributes(mExpandedParams);
-        }
-    }
-
-    /**
-     * The LEDs are turned o)ff when the notification panel is shown, even just a little bit.
-     * This was added last-minute and is inconsistent with the way the rest of the notifications
-     * are handled, because the notification isn't really cancelled.  The lights are just
-     * turned off.  If any other notifications happen, the lights will turn back on.  Steve says
-     * this is what he wants. (see bug 1131461)
-     */
-    void visibilityChanged(boolean visible) {
-        if (mPanelSlightlyVisible != visible) {
-            mPanelSlightlyVisible = visible;
-            try {
-                mBarService.onPanelRevealed();
-            } catch (RemoteException ex) {
-                // Won't fail unless the world has ended.
-            }
-        }
-    }
-
-    void performDisableActions(int net) {
-        int old = mDisabled;
-        int diff = net ^ old;
-        mDisabled = net;
-
-        // act accordingly
-        if ((diff & StatusBarManager.DISABLE_EXPAND) != 0) {
-            if ((net & StatusBarManager.DISABLE_EXPAND) != 0) {
-                Slog.d(TAG, "DISABLE_EXPAND: yes");
-                animateCollapse();
-            }
-        }
-        if ((diff & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) {
-            if ((net & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) {
-                Slog.d(TAG, "DISABLE_NOTIFICATION_ICONS: yes");
-                if (mTicking) {
-                    mNotificationIcons.setVisibility(View.INVISIBLE);
-                    mTicker.halt();
-                } else {
-                    setNotificationIconVisibility(false, com.android.internal.R.anim.fade_out);
-                }
-            } else {
-                Slog.d(TAG, "DISABLE_NOTIFICATION_ICONS: no");
-                if (!mExpandedVisible) {
-                    setNotificationIconVisibility(true, com.android.internal.R.anim.fade_in);
-                }
-            }
-        } else if ((diff & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) {
-            if (mTicking && (net & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) {
-                mTicker.halt();
-            }
-        }
-    }
-
-    private View.OnClickListener mClearButtonListener = new View.OnClickListener() {
-        public void onClick(View v) {
-            try {
-                mBarService.onClearAllNotifications();
-            } catch (RemoteException ex) {
-                // system process is dead if we're here.
-            }
-            animateCollapse();
-        }
-    };
-
-    private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
-        public void onReceive(Context context, Intent intent) {
-            String action = intent.getAction();
-            if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)
-                    || Intent.ACTION_SCREEN_OFF.equals(action)) {
-                //collapse();
-            }
-            else if (Intent.ACTION_CONFIGURATION_CHANGED.equals(action)) {
-                updateResources();
-            }
-        }
-    };
-
-    private void setIntruderAlertVisibility(boolean vis) {
-        mIntruderAlertView.setVisibility(vis ? View.VISIBLE : View.GONE);
-    }
-
-    /**
-     * Reload some of our resources when the configuration changes.
-     *
-     * We don't reload everything when the configuration changes -- we probably
-     * should, but getting that smooth is tough.  Someday we'll fix that.  In the
-     * meantime, just update the things that we know change.
-     */
-    void updateResources() {
-        Resources res = getResources();
-
-        mClearButton.setText(getText(R.string.status_bar_clear_all_button));
-        mOngoingTitle.setText(getText(R.string.status_bar_ongoing_events_title));
-        mLatestTitle.setText(getText(R.string.status_bar_latest_events_title));
-        mNoNotificationsTitle.setText(getText(R.string.status_bar_no_notifications_title));
-
-        mEdgeBorder = res.getDimensionPixelSize(R.dimen.status_bar_edge_ignore);
-
-        if (false) Slog.v(TAG, "updateResources");
-    }
-
-    //
-    // tracing
-    //
-
-    void postStartTracing() {
-        mHandler.postDelayed(mStartTracing, 3000);
-    }
-
-    void vibrate() {
-        android.os.Vibrator vib = (android.os.Vibrator)getSystemService(Context.VIBRATOR_SERVICE);
-        vib.vibrate(250);
-    }
-
-    Runnable mStartTracing = new Runnable() {
-        public void run() {
-            vibrate();
-            SystemClock.sleep(250);
-            Slog.d(TAG, "startTracing");
-            android.os.Debug.startMethodTracing("/data/statusbar-traces/trace");
-            mHandler.postDelayed(mStopTracing, 10000);
-        }
-    };
-
-    Runnable mStopTracing = new Runnable() {
-        public void run() {
-            android.os.Debug.stopMethodTracing();
-            Slog.d(TAG, "stopTracing");
-            vibrate();
-        }
-    };
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarView.java
index 1e140b9..20fc41f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarView.java
@@ -19,6 +19,7 @@
 import android.content.Context;
 import android.content.res.Configuration;
 import android.graphics.Canvas;
+import android.graphics.Rect;
 import android.os.SystemClock;
 import android.util.AttributeSet;
 import android.view.MotionEvent;
@@ -34,7 +35,7 @@
 
     static final int DIM_ANIM_TIME = 400;
     
-    StatusBarService mService;
+    PhoneStatusBarService mService;
     boolean mTracking;
     int mStartX, mStartY;
     ViewGroup mNotificationIcons;
@@ -46,6 +47,9 @@
     int mStartAlpha = 0, mEndAlpha = 0;
     long mEndTime = 0;
 
+    Rect mButtonBounds = new Rect();
+    boolean mCapturingEvents = true;
+
     public StatusBarView(Context context, AttributeSet attrs) {
         super(context, attrs);
     }
@@ -94,7 +98,7 @@
     @Override
     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
         super.onSizeChanged(w, h, oldw, oldh);
-        mService.updateExpandedViewPos(StatusBarService.EXPANDED_LEAVE_ALONE);
+        mService.updateExpandedViewPos(PhoneStatusBarService.EXPANDED_LEAVE_ALONE);
     }
 
     @Override
@@ -177,6 +181,9 @@
      */
     @Override
     public boolean onTouchEvent(MotionEvent event) {
+        if (!mCapturingEvents) {
+            return false;
+        }
         if (event.getAction() != MotionEvent.ACTION_DOWN) {
             mService.interceptTouchEvent(event);
         }
@@ -185,6 +192,13 @@
 
     @Override
     public boolean onInterceptTouchEvent(MotionEvent event) {
+        if (event.getAction() == MotionEvent.ACTION_DOWN) {
+            if (mButtonBounds.contains((int)event.getX(), (int)event.getY())) {
+                mCapturingEvents = false;
+                return false;
+            }
+        }
+        mCapturingEvents = true;
         return mService.interceptTouchEvent(event)
                 ? true : super.onInterceptTouchEvent(event);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/TrackingView.java b/packages/SystemUI/src/com/android/systemui/statusbar/TrackingView.java
index 9108eee..c59eb6a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/TrackingView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/TrackingView.java
@@ -26,7 +26,7 @@
 
 public class TrackingView extends LinearLayout {
     final Display mDisplay;
-    StatusBarService mService;
+    PhoneStatusBarService mService;
     boolean mTracking;
     int mStartX, mStartY;
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/NotificationIconArea.java b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/NotificationIconArea.java
new file mode 100644
index 0000000..3c7b130
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/NotificationIconArea.java
@@ -0,0 +1,64 @@
+/*
+ * 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.systemui.statusbar.tablet;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.os.Handler;
+import android.util.AttributeSet;
+import android.util.Slog;
+import android.view.View;
+import android.widget.LinearLayout;
+import android.widget.ImageView;
+
+import com.android.systemui.R;
+
+
+public class NotificationIconArea extends LinearLayout {
+    private static final String TAG = "NotificationIconArea";
+
+    MoreView mMoreView;
+    IconLayout mIconLayout;
+    DraggerView mDraggerView;
+
+    public NotificationIconArea(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        mMoreView = (MoreView) findViewById(R.id.more);
+        mIconLayout = (IconLayout)findViewById(R.id.icons);
+        mDraggerView = (DraggerView) findViewById(R.id.handle);
+    }
+
+    static class MoreView extends ImageView {
+        public MoreView(Context context, AttributeSet attrs) {
+            super(context, attrs);
+        }
+    }
+
+    static class IconLayout extends LinearLayout {
+        public IconLayout(Context context, AttributeSet attrs) {
+            super(context, attrs);
+        }
+    }
+
+    static class DraggerView extends View {
+        public DraggerView(Context context, AttributeSet attrs) {
+            super(context, attrs);
+        }
+    }
+}
+
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBarService.java b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBarService.java
new file mode 100644
index 0000000..b1c4ee3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBarService.java
@@ -0,0 +1,514 @@
+/*
+ * 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.systemui.statusbar.tablet;
+
+import android.app.Notification;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.graphics.PixelFormat;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.util.Slog;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.widget.RemoteViews;
+import android.app.ActivityManagerNative;
+import android.app.PendingIntent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.graphics.Rect;
+import android.os.RemoteException;
+import android.view.WindowManagerImpl;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.internal.statusbar.StatusBarIcon;
+import com.android.internal.statusbar.StatusBarIconList;
+import com.android.internal.statusbar.StatusBarNotification;
+
+import com.android.systemui.statusbar.*;
+import com.android.systemui.R;
+
+public class TabletStatusBarService extends StatusBarService {
+    public static final boolean DEBUG = false;
+    public static final String TAG = "TabletStatusBar";
+
+    View mStatusBarView;
+    NotificationIconArea mNotificationIconArea;
+
+    int mIconSize;
+
+    H mHandler = new H();
+
+    View mNotificationPanel;
+    View mSystemPanel;
+
+    ViewGroup mPile;
+    TextView mClearButton;
+
+    NotificationIconArea.IconLayout mIconLayout;
+
+    private NotificationData mNotns = new NotificationData();
+    
+    protected void addPanelWindows() {
+        mNotificationPanel = View.inflate(this, R.layout.sysbar_panel_notifications, null);
+        mSystemPanel = View.inflate(this, R.layout.sysbar_panel_system, null);
+
+        mNotificationPanel.setVisibility(View.GONE);
+        mSystemPanel.setVisibility(View.GONE);
+
+        final Resources res = getResources();
+        final int barHeight= res.getDimensionPixelSize(
+            com.android.internal.R.dimen.status_bar_height);
+
+        WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+                400, // ViewGroup.LayoutParams.WRAP_CONTENT,
+                ViewGroup.LayoutParams.WRAP_CONTENT,
+                WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL,
+                WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+                    | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
+                    | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+                    | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+                    | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
+                PixelFormat.TRANSLUCENT);
+        lp.gravity = Gravity.BOTTOM | Gravity.LEFT;
+        lp.setTitle("NotificationPanel");
+        lp.windowAnimations = com.android.internal.R.style.Animation_SlidingCard;
+
+        WindowManagerImpl.getDefault().addView(mNotificationPanel, lp);
+
+        lp = new WindowManager.LayoutParams(
+                400, // ViewGroup.LayoutParams.WRAP_CONTENT,
+                200, // ViewGroup.LayoutParams.WRAP_CONTENT,
+                WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL,
+                WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+                    | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
+                    | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+                    | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+                    | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
+                PixelFormat.TRANSLUCENT);
+        lp.gravity = Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL;
+        lp.setTitle("SystemPanel");
+        lp.windowAnimations = com.android.internal.R.style.Animation_SlidingCard;
+
+        WindowManagerImpl.getDefault().addView(mSystemPanel, lp);
+
+        // Lorem ipsum, Dolores
+        TextView tv = ((TextView) mSystemPanel.findViewById(R.id.systemPanelDummy));
+        if (tv != null) tv.setText("System status: great");
+
+        mPile = (ViewGroup)mNotificationPanel.findViewById(R.id.content);
+        mPile.removeAllViews();
+
+        mClearButton = (TextView)mNotificationPanel.findViewById(R.id.clear_all_button);
+        mClearButton.setOnClickListener(mClearButtonListener);
+    }
+
+    @Override
+    public void onCreate() {
+        super.onCreate(); // will add the main bar view
+
+        addPanelWindows();
+    }
+
+    protected View makeStatusBarView() {
+        Resources res = getResources();
+
+        mIconSize = res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_icon_size);
+
+        final View sb = View.inflate(this, R.layout.status_bar, null);
+        mStatusBarView = sb;
+
+        // the more notifications icon
+        mNotificationIconArea = (NotificationIconArea)sb.findViewById(R.id.notificationIcons);
+
+        // where the icons go
+        mIconLayout = (NotificationIconArea.IconLayout) sb.findViewById(R.id.icons);
+
+        return sb;
+    }
+
+    protected int getStatusBarGravity() {
+        return Gravity.BOTTOM | Gravity.FILL_HORIZONTAL;
+    }
+
+    private class H extends Handler {
+        public static final int MSG_OPEN_NOTIFICATION_PANEL = 1000;
+        public static final int MSG_CLOSE_NOTIFICATION_PANEL = 1001;
+        public static final int MSG_OPEN_SYSTEM_PANEL = 1010;
+        public static final int MSG_CLOSE_SYSTEM_PANEL = 1011;
+        public void handleMessage(Message m) {
+            switch (m.what) {
+                case MSG_OPEN_NOTIFICATION_PANEL:
+                    if (DEBUG) Slog.d(TAG, "opening notifications panel");
+                    mNotificationPanel.setVisibility(View.VISIBLE);
+                    break;
+                case MSG_CLOSE_NOTIFICATION_PANEL:
+                    if (DEBUG) Slog.d(TAG, "closing notifications panel");
+                    mNotificationPanel.setVisibility(View.GONE);
+                    break;
+                case MSG_OPEN_SYSTEM_PANEL:
+                    if (DEBUG) Slog.d(TAG, "opening system panel");
+                    mSystemPanel.setVisibility(View.VISIBLE);
+                    break;
+                case MSG_CLOSE_SYSTEM_PANEL:
+                    if (DEBUG) Slog.d(TAG, "closing system panel");
+                    mSystemPanel.setVisibility(View.GONE);
+                    break;
+            }
+        }
+    }
+
+    public void addIcon(String slot, int index, int viewIndex, StatusBarIcon icon) {
+        // TODO
+    }
+
+    public void updateIcon(String slot, int index, int viewIndex,
+            StatusBarIcon old, StatusBarIcon icon) {
+        // TODO
+    }
+
+    public void removeIcon(String slot, int index, int viewIndex) {
+        // TODO
+    }
+
+    public void addNotification(IBinder key, StatusBarNotification notification) {
+        if (DEBUG) Slog.d(TAG, "addNotification(" + key + " -> " + notification + ")");
+        addNotificationViews(key, notification);
+
+        // TODO: kicker; immersive mode
+    }
+
+    public void updateNotification(IBinder key, StatusBarNotification notification) {
+        if (DEBUG) Slog.d(TAG, "updateNotification(" + key + " -> " + notification + ") // TODO");
+        
+        final NotificationData.Entry oldEntry = mNotns.findByKey(key);
+        if (oldEntry == null) {
+            Slog.w(TAG, "updateNotification for unknown key: " + key);
+            return;
+        }
+
+        final StatusBarNotification oldNotification = oldEntry.notification;
+        final RemoteViews oldContentView = oldNotification.notification.contentView;
+
+        final RemoteViews contentView = notification.notification.contentView;
+
+        if (false) {
+            Slog.d(TAG, "old notification: when=" + oldNotification.notification.when
+                    + " ongoing=" + oldNotification.isOngoing()
+                    + " expanded=" + oldEntry.expanded
+                    + " contentView=" + oldContentView);
+            Slog.d(TAG, "new notification: when=" + notification.notification.when
+                    + " ongoing=" + oldNotification.isOngoing()
+                    + " contentView=" + contentView);
+        }
+
+        // Can we just reapply the RemoteViews in place?  If when didn't change, the order
+        // didn't change.
+        if (notification.notification.when == oldNotification.notification.when
+                && notification.isOngoing() == oldNotification.isOngoing()
+                && oldEntry.expanded != null
+                && contentView != null
+                && oldContentView != null
+                && contentView.getPackage() != null
+                && oldContentView.getPackage() != null
+                && oldContentView.getPackage().equals(contentView.getPackage())
+                && oldContentView.getLayoutId() == contentView.getLayoutId()) {
+            if (DEBUG) Slog.d(TAG, "reusing notification for key: " + key);
+            oldEntry.notification = notification;
+            try {
+                // Reapply the RemoteViews
+                contentView.reapply(this, oldEntry.content);
+                // update the contentIntent
+                final PendingIntent contentIntent = notification.notification.contentIntent;
+                if (contentIntent != null) {
+                    oldEntry.content.setOnClickListener(new NotificationClicker(contentIntent,
+                                notification.pkg, notification.tag, notification.id));
+                }
+                // Update the icon.
+                final StatusBarIcon ic = new StatusBarIcon(notification.pkg,
+                        notification.notification.icon, notification.notification.iconLevel,
+                        notification.notification.number);
+                if (!oldEntry.icon.set(ic)) {
+                    handleNotificationError(key, notification, "Couldn't update icon: " + ic);
+                    return;
+                }
+            }
+            catch (RuntimeException e) {
+                // It failed to add cleanly.  Log, and remove the view from the panel.
+                Slog.w(TAG, "Couldn't reapply views for package " + contentView.getPackage(), e);
+                removeNotificationViews(key);
+                addNotificationViews(key, notification);
+            }
+        } else {
+            if (DEBUG) Slog.d(TAG, "not reusing notification for key: " + key);
+            removeNotificationViews(key);
+            addNotificationViews(key, notification);
+        }
+        // TODO: kicker; immersive mode
+    }
+
+    public void removeNotification(IBinder key) {
+        if (DEBUG) Slog.d(TAG, "removeNotification(" + key + ") // TODO");
+        removeNotificationViews(key);
+    }
+
+    public void disable(int state) {
+        // TODO
+    }
+
+    public void animateExpand() {
+        mHandler.removeMessages(H.MSG_OPEN_NOTIFICATION_PANEL);
+        mHandler.sendEmptyMessage(H.MSG_OPEN_NOTIFICATION_PANEL);
+    }
+
+    public void animateCollapse() {
+        mHandler.removeMessages(H.MSG_CLOSE_NOTIFICATION_PANEL);
+        mHandler.sendEmptyMessage(H.MSG_CLOSE_NOTIFICATION_PANEL);
+        mHandler.removeMessages(H.MSG_CLOSE_SYSTEM_PANEL);
+        mHandler.sendEmptyMessage(H.MSG_CLOSE_SYSTEM_PANEL);
+    }
+
+    public void notificationIconsClicked(View v) {
+        if (DEBUG) Slog.d(TAG, "clicked notification icons");
+        mHandler.removeMessages(H.MSG_CLOSE_SYSTEM_PANEL);
+        mHandler.sendEmptyMessage(H.MSG_CLOSE_SYSTEM_PANEL);
+
+        int msg = (mNotificationPanel.getVisibility() == View.GONE) 
+            ? H.MSG_OPEN_NOTIFICATION_PANEL
+            : H.MSG_CLOSE_NOTIFICATION_PANEL;
+        mHandler.removeMessages(msg);
+        mHandler.sendEmptyMessage(msg);
+    }
+
+    public void systemInfoClicked(View v) {
+        if (DEBUG) Slog.d(TAG, "clicked system info");
+        mHandler.removeMessages(H.MSG_CLOSE_NOTIFICATION_PANEL);
+        mHandler.sendEmptyMessage(H.MSG_CLOSE_NOTIFICATION_PANEL);
+
+        int msg = (mSystemPanel.getVisibility() == View.GONE) 
+            ? H.MSG_OPEN_SYSTEM_PANEL
+            : H.MSG_CLOSE_SYSTEM_PANEL;
+        mHandler.removeMessages(msg);
+        mHandler.sendEmptyMessage(msg);
+    }
+
+    /**
+     * Cancel this notification and tell the status bar service about the failure. Hold no locks.
+     */
+    void handleNotificationError(IBinder key, StatusBarNotification n, String message) {
+        removeNotification(key);
+        try {
+            mBarService.onNotificationError(n.pkg, n.tag, n.id, n.uid, n.initialPid, message);
+        } catch (RemoteException ex) {
+            // The end is nigh.
+        }
+    }
+
+    private View.OnClickListener mClearButtonListener = new View.OnClickListener() {
+        public void onClick(View v) {
+            try {
+                mBarService.onClearAllNotifications();
+            } catch (RemoteException ex) {
+                // system process is dead if we're here.
+            }
+            animateCollapse();
+        }
+    };
+
+    private class NotificationClicker implements View.OnClickListener {
+        private PendingIntent mIntent;
+        private String mPkg;
+        private String mTag;
+        private int mId;
+
+        NotificationClicker(PendingIntent intent, String pkg, String tag, int id) {
+            mIntent = intent;
+            mPkg = pkg;
+            mTag = tag;
+            mId = id;
+        }
+
+        public void onClick(View v) {
+            try {
+                // The intent we are sending is for the application, which
+                // won't have permission to immediately start an activity after
+                // the user switches to home.  We know it is safe to do at this
+                // point, so make sure new activity switches are now allowed.
+                ActivityManagerNative.getDefault().resumeAppSwitches();
+            } catch (RemoteException e) {
+            }
+
+            if (mIntent != null) {
+                int[] pos = new int[2];
+                v.getLocationOnScreen(pos);
+                Intent overlay = new Intent();
+                overlay.setSourceBounds(
+                        new Rect(pos[0], pos[1], pos[0]+v.getWidth(), pos[1]+v.getHeight()));
+                try {
+                    mIntent.send(TabletStatusBarService.this, 0, overlay);
+                } catch (PendingIntent.CanceledException e) {
+                    // the stack trace isn't very helpful here.  Just log the exception message.
+                    Slog.w(TAG, "Sending contentIntent failed: " + e);
+                }
+            }
+
+            try {
+                mBarService.onNotificationClick(mPkg, mTag, mId);
+            } catch (RemoteException ex) {
+                // system process is dead if we're here.
+            }
+
+            // close the shade if it was open
+            animateCollapse();
+
+            // If this click was on the intruder alert, hide that instead
+//            mHandler.sendEmptyMessage(MSG_HIDE_INTRUDER);
+        }
+    }
+
+    StatusBarNotification removeNotificationViews(IBinder key) {
+        NotificationData.Entry entry = mNotns.remove(key);
+        if (entry == null) {
+            Slog.w(TAG, "removeNotification for unknown key: " + key);
+            return null;
+        }
+        // Remove the expanded view.
+        ViewGroup rowParent = (ViewGroup)entry.row.getParent();
+        if (rowParent != null) rowParent.removeView(entry.row);
+        // Remove the icon.
+//        ViewGroup iconParent = (ViewGroup)entry.icon.getParent();
+//        if (iconParent != null) iconParent.removeView(entry.icon);
+        refreshIcons();
+
+        return entry.notification;
+    }
+
+    StatusBarIconView addNotificationViews(IBinder key, StatusBarNotification notification) {
+        NotificationData list = mNotns;
+        ViewGroup parent = mPile;
+        // Construct the icon.
+        final StatusBarIconView iconView = new StatusBarIconView(this,
+                notification.pkg + "/0x" + Integer.toHexString(notification.id));
+        iconView.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
+
+        final StatusBarIcon ic = new StatusBarIcon(notification.pkg,
+                    notification.notification.icon,
+                    notification.notification.iconLevel,
+                    notification.notification.number);
+        if (!iconView.set(ic)) {
+            handleNotificationError(key, notification, "Couldn't attach StatusBarIcon: " + ic);
+            return null;
+        }
+        // Construct the expanded view.
+        NotificationData.Entry entry = new NotificationData.Entry(key, notification, iconView);
+        if (!inflateViews(entry, parent)) {
+            handleNotificationError(key, notification, "Couldn't expand RemoteViews for: "
+                    + notification);
+            return null;
+        }
+        // Add the expanded view.
+        final int viewIndex = list.add(entry);
+        if (parent != null) parent.addView(entry.row, viewIndex);
+        // Add the icon.
+//        final int iconIndex = 0; // XXX: sort into ongoing and regular buckets
+//        mIconLayout.addView(iconView, iconIndex,
+//                new LinearLayout.LayoutParams(mIconSize, mIconSize));
+
+        refreshIcons();
+
+        return iconView;
+    }
+
+    private void refreshIcons() {
+        // XXX: need to implement a new limited linear layout class
+        // to avoid removing & readding everything
+        mIconLayout.removeAllViews();
+        int N = mNotns.size();
+        LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(mIconSize, mIconSize);
+        for (int i=0; i<4; i++) {
+            if (i>=N) break;
+            mIconLayout.addView(mNotns.get(N-i-1).icon, i, params);
+        }
+    }
+
+    private boolean inflateViews(NotificationData.Entry entry, ViewGroup parent) {
+        StatusBarNotification sbn = entry.notification;
+        RemoteViews remoteViews = sbn.notification.contentView;
+        if (remoteViews == null) {
+            return false;
+        }
+
+        // create the row view
+        LayoutInflater inflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+        View row = inflater.inflate(R.layout.status_bar_latest_event, parent, false);
+        View vetoButton = row.findViewById(R.id.veto);
+        final String _pkg = sbn.pkg;
+        final String _tag = sbn.tag;
+        final int _id = sbn.id;
+        vetoButton.setOnClickListener(new View.OnClickListener() { 
+                public void onClick(View v) {
+                    try {
+                        mBarService.onNotificationClear(_pkg, _tag, _id);
+                    } catch (RemoteException ex) {
+                        // system process is dead if we're here.
+                    }
+//                    animateCollapse();
+                }
+            });
+
+        // bind the click event to the content area
+        ViewGroup content = (ViewGroup)row.findViewById(R.id.content);
+        // XXX: update to allow controls within notification views
+        content.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
+//        content.setOnFocusChangeListener(mFocusChangeListener);
+        PendingIntent contentIntent = sbn.notification.contentIntent;
+        if (contentIntent != null) {
+            content.setOnClickListener(new NotificationClicker(contentIntent,
+                        sbn.pkg, sbn.tag, sbn.id));
+        }
+
+        View expanded = null;
+        Exception exception = null;
+        try {
+            expanded = remoteViews.apply(this, content);
+        }
+        catch (RuntimeException e) {
+            exception = e;
+        }
+        if (expanded == null) {
+            String ident = sbn.pkg + "/0x" + Integer.toHexString(sbn.id);
+            Slog.e(TAG, "couldn't inflate view for notification " + ident, exception);
+            return false;
+        } else {
+            content.addView(expanded);
+            row.setDrawingCacheEnabled(true);
+        }
+
+        entry.row = row;
+        entry.content = content;
+        entry.expanded = expanded;
+
+        return true;
+    }
+}
diff --git a/packages/TtsService/jni/Android.mk b/packages/TtsService/jni/Android.mk
index b41759a..5dc0c30 100755
--- a/packages/TtsService/jni/Android.mk
+++ b/packages/TtsService/jni/Android.mk
@@ -5,6 +5,7 @@
 	android_tts_SynthProxy.cpp
 
 LOCAL_C_INCLUDES += \
+	frameworks/base/native/include \
 	$(JNI_H_INCLUDE)
 
 LOCAL_SHARED_LIBRARIES := \
diff --git a/packages/TtsService/jni/android_tts_SynthProxy.cpp b/packages/TtsService/jni/android_tts_SynthProxy.cpp
index 1d69361..8dc88db 100644
--- a/packages/TtsService/jni/android_tts_SynthProxy.cpp
+++ b/packages/TtsService/jni/android_tts_SynthProxy.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2009 Google Inc.
+ * Copyright (C) 2009-2010 Google Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -23,7 +23,7 @@
 #include <nativehelper/jni.h>
 #include <nativehelper/JNIHelp.h>
 #include <android_runtime/AndroidRuntime.h>
-#include <tts/TtsEngine.h>
+#include <android/tts.h>
 #include <media/AudioTrack.h>
 #include <math.h>
 
@@ -154,7 +154,7 @@
 class SynthProxyJniStorage {
     public :
         jobject                   tts_ref;
-        TtsEngine*                mNativeSynthInterface;
+        android_tts_engine_t*       mEngine;
         void*                     mEngineLibHandle;
         AudioTrack*               mAudioOut;
         int8_t                    mPlayState;
@@ -168,7 +168,7 @@
 
         SynthProxyJniStorage() {
             tts_ref = NULL;
-            mNativeSynthInterface = NULL;
+            mEngine = NULL;
             mEngineLibHandle = NULL;
             mAudioOut = NULL;
             mPlayState =  SYNTHPLAYSTATE_IS_STOPPED;
@@ -184,9 +184,9 @@
         ~SynthProxyJniStorage() {
             //LOGV("entering ~SynthProxyJniStorage()");
             killAudio();
-            if (mNativeSynthInterface) {
-                mNativeSynthInterface->shutdown();
-                mNativeSynthInterface = NULL;
+            if (mEngine) {
+                mEngine->funcs->shutdown(mEngine);
+                mEngine = NULL;
             }
             if (mEngineLibHandle) {
                 //LOGE("~SynthProxyJniStorage(): before close library");
@@ -273,28 +273,45 @@
  * Callback from TTS engine.
  * Directly speaks using AudioTrack or write to file
  */
-static tts_callback_status ttsSynthDoneCB(void *& userdata, uint32_t rate,
-                           uint32_t format, int channel,
-                           int8_t *&wav, size_t &bufferSize, tts_synth_status status) {
+extern "C" android_tts_callback_status_t
+__ttsSynthDoneCB(void ** pUserdata, uint32_t rate,
+               android_tts_audio_format_t format, int channel,
+               int8_t **pWav, size_t *pBufferSize,
+               android_tts_synth_status_t status) 
+{
     //LOGV("ttsSynthDoneCallback: %d bytes", bufferSize);
+    AudioSystem::audio_format  encoding;
 
-    if (userdata == NULL){
+    if (*pUserdata == NULL){
         LOGE("userdata == NULL");
-        return TTS_CALLBACK_HALT;
+        return ANDROID_TTS_CALLBACK_HALT;
     }
-    afterSynthData_t* pForAfter = (afterSynthData_t*)userdata;
+    switch (format) {
+    case ANDROID_TTS_AUDIO_FORMAT_PCM_8_BIT:
+        encoding = AudioSystem::PCM_8_BIT;
+        break;
+    case ANDROID_TTS_AUDIO_FORMAT_PCM_16_BIT:
+        encoding = AudioSystem::PCM_16_BIT;
+        break;
+    default:
+        LOGE("Can't play, bad format");
+        return ANDROID_TTS_CALLBACK_HALT;
+    }
+    afterSynthData_t* pForAfter = (afterSynthData_t*) *pUserdata;
     SynthProxyJniStorage* pJniData = (SynthProxyJniStorage*)(pForAfter->jniStorage);
 
     if (pForAfter->usageMode == USAGEMODE_PLAY_IMMEDIATELY){
         //LOGV("Direct speech");
 
-        if (wav == NULL) {
+        if (*pWav == NULL) {
             delete pForAfter;
+            pForAfter = NULL;
             LOGV("Null: speech has completed");
+            return ANDROID_TTS_CALLBACK_HALT;
         }
 
-        if (bufferSize > 0) {
-            prepAudioTrack(pJniData, pForAfter->streamType, rate, (AudioSystem::audio_format)format, channel);
+        if (*pBufferSize > 0) {
+            prepAudioTrack(pJniData, pForAfter->streamType, rate, encoding, channel);
             if (pJniData->mAudioOut) {
                 pJniData->mPlayLock.lock();
                 if(pJniData->mAudioOut->stopped()
@@ -303,28 +320,31 @@
                 }
                 pJniData->mPlayLock.unlock();
                 if (bUseFilter) {
-                    applyFilter((int16_t*)wav, bufferSize/2);
+                    applyFilter((int16_t*)*pWav, *pBufferSize/2);
                 }
-                pJniData->mAudioOut->write(wav, bufferSize);
-                memset(wav, 0, bufferSize);
+                pJniData->mAudioOut->write(*pWav, *pBufferSize);
+                memset(*pWav, 0, *pBufferSize);
                 //LOGV("AudioTrack wrote: %d bytes", bufferSize);
             } else {
                 LOGE("Can't play, null audiotrack");
+                delete pForAfter;
+                pForAfter = NULL;
+                return ANDROID_TTS_CALLBACK_HALT;
             }
         }
     } else  if (pForAfter->usageMode == USAGEMODE_WRITE_TO_FILE) {
         //LOGV("Save to file");
-        if (wav == NULL) {
+        if (*pWav == NULL) {
             delete pForAfter;
             LOGV("Null: speech has completed");
-            return TTS_CALLBACK_HALT;
+            return ANDROID_TTS_CALLBACK_HALT;
         }
-        if (bufferSize > 0){
+        if (*pBufferSize > 0){
             if (bUseFilter) {
-                applyFilter((int16_t*)wav, bufferSize/2);
+                applyFilter((int16_t*)*pWav, *pBufferSize/2);
             }
-            fwrite(wav, 1, bufferSize, pForAfter->outputFile);
-            memset(wav, 0, bufferSize);
+            fwrite(*pWav, 1, *pBufferSize, pForAfter->outputFile);
+            memset(*pWav, 0, *pBufferSize);
         }
     }
     // Future update:
@@ -332,7 +352,7 @@
     //      javaTTSFields.synthProxyMethodPost methode to notify
     //      playback has completed if the synthesis is done or if a marker has been reached.
 
-    if (status == TTS_SYNTH_DONE) {
+    if (status == ANDROID_TTS_SYNTH_DONE) {
         // this struct was allocated in the original android_tts_SynthProxy_speak call,
         // all processing matching this call is now done.
         LOGV("Speech synthesis done.");
@@ -342,16 +362,16 @@
             delete pForAfter;
             pForAfter = NULL;
         }
-        return TTS_CALLBACK_HALT;
+        return ANDROID_TTS_CALLBACK_HALT;
     }
 
     // we don't update the wav (output) parameter as we'll let the next callback
     // write at the same location, we've consumed the data already, but we need
     // to update bufferSize to let the TTS engine know how much it can write the
     // next time it calls this function.
-    bufferSize = pJniData->mBufferSize;
+    *pBufferSize = pJniData->mBufferSize;
 
-    return TTS_CALLBACK_CONTINUE;
+    return ANDROID_TTS_CALLBACK_CONTINUE;
 }
 
 
@@ -360,7 +380,7 @@
 android_tts_SynthProxy_setLowShelf(JNIEnv *env, jobject thiz, jboolean applyFilter,
         jfloat filterGain, jfloat attenuationInDb, jfloat freqInHz, jfloat slope)
 {
-    int result = TTS_SUCCESS;
+    int result = ANDROID_TTS_SUCCESS;
 
     bUseFilter = applyFilter;
     if (applyFilter) {
@@ -373,7 +393,7 @@
             initializeEQ();
         } else {
             LOGE("Invalid slope, can't be null");
-            result = TTS_FAILURE;
+            result = ANDROID_TTS_FAILURE;
         }
     }
 
@@ -385,7 +405,7 @@
 android_tts_SynthProxy_native_setup(JNIEnv *env, jobject thiz,
         jobject weak_this, jstring nativeSoLib, jstring engConfig)
 {
-    int result = TTS_FAILURE;
+    int result = ANDROID_TTS_FAILURE;
 
     bUseFilter = false;
 
@@ -402,18 +422,28 @@
     if (engine_lib_handle == NULL) {
        LOGE("android_tts_SynthProxy_native_setup(): engine_lib_handle == NULL");
     } else {
-        TtsEngine *(*get_TtsEngine)() =
-            reinterpret_cast<TtsEngine* (*)()>(dlsym(engine_lib_handle, "getTtsEngine"));
+        android_tts_engine_t * (*get_TtsEngine)() =
+            reinterpret_cast<android_tts_engine_t* (*)()>(dlsym(engine_lib_handle, "android_getTtsEngine"));
 
-        pJniStorage->mNativeSynthInterface = (*get_TtsEngine)();
-        pJniStorage->mEngineLibHandle = engine_lib_handle;
-
-        if (pJniStorage->mNativeSynthInterface) {
-            Mutex::Autolock l(engineMutex);
-            pJniStorage->mNativeSynthInterface->init(ttsSynthDoneCB, engConfigString);
+        // Support obsolete/legacy binary modules
+        if (get_TtsEngine == NULL) {
+            get_TtsEngine =
+                reinterpret_cast<android_tts_engine_t* (*)()>(dlsym(engine_lib_handle, "getTtsEngine"));
         }
 
-        result = TTS_SUCCESS;
+        pJniStorage->mEngine = (*get_TtsEngine)();
+        pJniStorage->mEngineLibHandle = engine_lib_handle;
+
+        android_tts_engine_t *engine = pJniStorage->mEngine;
+        if (engine) {
+            Mutex::Autolock l(engineMutex);
+            engine->funcs->init(
+                engine,
+                __ttsSynthDoneCB,
+                engConfigString);
+        }
+
+        result = ANDROID_TTS_SUCCESS;
     }
 
     // we use a weak reference so the SynthProxy object can be garbage collected.
@@ -462,7 +492,7 @@
 android_tts_SynthProxy_isLanguageAvailable(JNIEnv *env, jobject thiz, jint jniData,
         jstring language, jstring country, jstring variant)
 {
-    int result = TTS_LANG_NOT_SUPPORTED;
+    int result = ANDROID_TTS_LANG_NOT_SUPPORTED;
 
     if (jniData == 0) {
         LOGE("android_tts_SynthProxy_isLanguageAvailable(): invalid JNI data");
@@ -474,8 +504,10 @@
     const char *countryNativeString = env->GetStringUTFChars(country, 0);
     const char *variantNativeString = env->GetStringUTFChars(variant, 0);
 
-    if (pSynthData->mNativeSynthInterface) {
-        result = pSynthData->mNativeSynthInterface->isLanguageAvailable(langNativeString,
+    android_tts_engine_t *engine = pSynthData->mEngine;
+
+    if (engine) {
+        result = engine->funcs->isLanguageAvailable(engine,langNativeString,
                 countryNativeString, variantNativeString);
     }
     env->ReleaseStringUTFChars(language, langNativeString);
@@ -487,7 +519,7 @@
 static int
 android_tts_SynthProxy_setConfig(JNIEnv *env, jobject thiz, jint jniData, jstring engineConfig)
 {
-    int result = TTS_FAILURE;
+    int result = ANDROID_TTS_FAILURE;
 
     if (jniData == 0) {
         LOGE("android_tts_SynthProxy_setConfig(): invalid JNI data");
@@ -498,9 +530,10 @@
 
     SynthProxyJniStorage* pSynthData = (SynthProxyJniStorage*)jniData;
     const char *engineConfigNativeString = env->GetStringUTFChars(engineConfig, 0);
+    android_tts_engine_t *engine = pSynthData->mEngine;
 
-    if (pSynthData->mNativeSynthInterface) {
-        result = pSynthData->mNativeSynthInterface->setProperty(ANDROID_TTS_ENGINE_PROPERTY_CONFIG,
+    if (engine) {
+        result = engine->funcs->setProperty(engine,ANDROID_TTS_ENGINE_PROPERTY_CONFIG,
                 engineConfigNativeString, strlen(engineConfigNativeString));
     }
     env->ReleaseStringUTFChars(engineConfig, engineConfigNativeString);
@@ -512,7 +545,7 @@
 android_tts_SynthProxy_setLanguage(JNIEnv *env, jobject thiz, jint jniData,
         jstring language, jstring country, jstring variant)
 {
-    int result = TTS_LANG_NOT_SUPPORTED;
+    int result = ANDROID_TTS_LANG_NOT_SUPPORTED;
 
     if (jniData == 0) {
         LOGE("android_tts_SynthProxy_setLanguage(): invalid JNI data");
@@ -525,9 +558,10 @@
     const char *langNativeString = env->GetStringUTFChars(language, 0);
     const char *countryNativeString = env->GetStringUTFChars(country, 0);
     const char *variantNativeString = env->GetStringUTFChars(variant, 0);
+    android_tts_engine_t *engine = pSynthData->mEngine;
 
-    if (pSynthData->mNativeSynthInterface) {
-        result = pSynthData->mNativeSynthInterface->setLanguage(langNativeString,
+    if (engine) {
+        result = engine->funcs->setLanguage(engine, langNativeString,
                 countryNativeString, variantNativeString);
     }
     env->ReleaseStringUTFChars(language, langNativeString);
@@ -541,7 +575,7 @@
 android_tts_SynthProxy_loadLanguage(JNIEnv *env, jobject thiz, jint jniData,
         jstring language, jstring country, jstring variant)
 {
-    int result = TTS_LANG_NOT_SUPPORTED;
+    int result = ANDROID_TTS_LANG_NOT_SUPPORTED;
 
     if (jniData == 0) {
         LOGE("android_tts_SynthProxy_loadLanguage(): invalid JNI data");
@@ -552,9 +586,10 @@
     const char *langNativeString = env->GetStringUTFChars(language, 0);
     const char *countryNativeString = env->GetStringUTFChars(country, 0);
     const char *variantNativeString = env->GetStringUTFChars(variant, 0);
+    android_tts_engine_t *engine = pSynthData->mEngine;
 
-    if (pSynthData->mNativeSynthInterface) {
-        result = pSynthData->mNativeSynthInterface->loadLanguage(langNativeString,
+    if (engine) {
+        result = engine->funcs->loadLanguage(engine, langNativeString,
                 countryNativeString, variantNativeString);
     }
     env->ReleaseStringUTFChars(language, langNativeString);
@@ -569,7 +604,7 @@
 android_tts_SynthProxy_setSpeechRate(JNIEnv *env, jobject thiz, jint jniData,
         jint speechRate)
 {
-    int result = TTS_FAILURE;
+    int result = ANDROID_TTS_FAILURE;
 
     if (jniData == 0) {
         LOGE("android_tts_SynthProxy_setSpeechRate(): invalid JNI data");
@@ -584,9 +619,10 @@
 
     SynthProxyJniStorage* pSynthData = (SynthProxyJniStorage*)jniData;
     LOGI("setting speech rate to %d", speechRate);
+    android_tts_engine_t *engine = pSynthData->mEngine;
 
-    if (pSynthData->mNativeSynthInterface) {
-        result = pSynthData->mNativeSynthInterface->setProperty("rate", buffer, bufSize);
+    if (engine) {
+        result = engine->funcs->setProperty(engine, "rate", buffer, bufSize);
     }
 
     return result;
@@ -597,7 +633,7 @@
 android_tts_SynthProxy_setPitch(JNIEnv *env, jobject thiz, jint jniData,
         jint pitch)
 {
-    int result = TTS_FAILURE;
+    int result = ANDROID_TTS_FAILURE;
 
     if (jniData == 0) {
         LOGE("android_tts_SynthProxy_setPitch(): invalid JNI data");
@@ -612,9 +648,10 @@
 
     SynthProxyJniStorage* pSynthData = (SynthProxyJniStorage*)jniData;
     LOGI("setting pitch to %d", pitch);
+    android_tts_engine_t *engine = pSynthData->mEngine;
 
-    if (pSynthData->mNativeSynthInterface) {
-        result = pSynthData->mNativeSynthInterface->setProperty("pitch", buffer, bufSize);
+    if (engine) {
+        result = engine->funcs->setProperty(engine, "pitch", buffer, bufSize);
     }
 
     return result;
@@ -625,7 +662,7 @@
 android_tts_SynthProxy_synthesizeToFile(JNIEnv *env, jobject thiz, jint jniData,
         jstring textJavaString, jstring filenameJavaString)
 {
-    int result = TTS_FAILURE;
+    int result = ANDROID_TTS_FAILURE;
 
     if (jniData == 0) {
         LOGE("android_tts_SynthProxy_synthesizeToFile(): invalid JNI data");
@@ -633,7 +670,7 @@
     }
 
     SynthProxyJniStorage* pSynthData = (SynthProxyJniStorage*)jniData;
-    if (!pSynthData->mNativeSynthInterface) {
+    if (!pSynthData->mEngine) {
         LOGE("android_tts_SynthProxy_synthesizeToFile(): invalid engine handle");
         return result;
     }
@@ -643,12 +680,22 @@
     Mutex::Autolock l(engineMutex);
 
     // Retrieve audio parameters before writing the file header
-    AudioSystem::audio_format encoding = DEFAULT_TTS_FORMAT;
+    AudioSystem::audio_format encoding;
     uint32_t rate = DEFAULT_TTS_RATE;
     int channels = DEFAULT_TTS_NB_CHANNELS;
-    pSynthData->mNativeSynthInterface->setAudioFormat(encoding, rate, channels);
+    android_tts_engine_t *engine = pSynthData->mEngine;
+    android_tts_audio_format_t  format = ANDROID_TTS_AUDIO_FORMAT_DEFAULT;
 
-    if ((encoding != AudioSystem::PCM_16_BIT) && (encoding != AudioSystem::PCM_8_BIT)) {
+    engine->funcs->setAudioFormat(engine, &format, &rate, &channels);
+
+    switch (format) {
+    case ANDROID_TTS_AUDIO_FORMAT_PCM_16_BIT:
+        encoding = AudioSystem::PCM_16_BIT;
+        break;
+    case ANDROID_TTS_AUDIO_FORMAT_PCM_8_BIT:
+        encoding = AudioSystem::PCM_8_BIT;
+        break;
+    default:
         LOGE("android_tts_SynthProxy_synthesizeToFile(): engine uses invalid format");
         return result;
     }
@@ -677,7 +724,8 @@
     unsigned int unique_identifier;
 
     memset(pSynthData->mBuffer, 0, pSynthData->mBufferSize);
-    result = pSynthData->mNativeSynthInterface->synthesizeText(textNativeString,
+
+    result = engine->funcs->synthesizeText(engine, textNativeString,
             pSynthData->mBuffer, pSynthData->mBufferSize, (void *)pForAfter);
 
     long filelen = ftell(pForAfter->outputFile);
@@ -737,7 +785,7 @@
 android_tts_SynthProxy_speak(JNIEnv *env, jobject thiz, jint jniData,
         jstring textJavaString, jint javaStreamType)
 {
-    int result = TTS_FAILURE;
+    int result = ANDROID_TTS_FAILURE;
 
     if (jniData == 0) {
         LOGE("android_tts_SynthProxy_speak(): invalid JNI data");
@@ -759,10 +807,12 @@
     pForAfter->usageMode  = USAGEMODE_PLAY_IMMEDIATELY;
     pForAfter->streamType = (AudioSystem::stream_type) javaStreamType;
 
-    if (pSynthData->mNativeSynthInterface) {
+    if (pSynthData->mEngine) {
         const char *textNativeString = env->GetStringUTFChars(textJavaString, 0);
         memset(pSynthData->mBuffer, 0, pSynthData->mBufferSize);
-        result = pSynthData->mNativeSynthInterface->synthesizeText(textNativeString,
+        android_tts_engine_t *engine = pSynthData->mEngine;
+
+        result = engine->funcs->synthesizeText(engine, textNativeString,
                 pSynthData->mBuffer, pSynthData->mBufferSize, (void *)pForAfter);
         env->ReleaseStringUTFChars(textJavaString, textNativeString);
     }
@@ -774,7 +824,7 @@
 static int
 android_tts_SynthProxy_stop(JNIEnv *env, jobject thiz, jint jniData)
 {
-    int result = TTS_FAILURE;
+    int result = ANDROID_TTS_FAILURE;
 
     if (jniData == 0) {
         LOGE("android_tts_SynthProxy_stop(): invalid JNI data");
@@ -790,8 +840,9 @@
     }
     pSynthData->mPlayLock.unlock();
 
-    if (pSynthData->mNativeSynthInterface) {
-        result = pSynthData->mNativeSynthInterface->stop();
+    android_tts_engine_t *engine = pSynthData->mEngine;
+    if (engine) {
+        result = engine->funcs->stop(engine);
     }
 
     return result;
@@ -801,7 +852,7 @@
 static int
 android_tts_SynthProxy_stopSync(JNIEnv *env, jobject thiz, jint jniData)
 {
-    int result = TTS_FAILURE;
+    int result = ANDROID_TTS_FAILURE;
 
     if (jniData == 0) {
         LOGE("android_tts_SynthProxy_stop(): invalid JNI data");
@@ -829,7 +880,7 @@
 
     SynthProxyJniStorage* pSynthData = (SynthProxyJniStorage*)jniData;
 
-    if (pSynthData->mNativeSynthInterface) {
+    if (pSynthData->mEngine) {
         size_t bufSize = 100;
         char lang[bufSize];
         char country[bufSize];
@@ -839,7 +890,9 @@
         memset(variant, 0, bufSize);
         jobjectArray retLocale = (jobjectArray)env->NewObjectArray(3,
                 env->FindClass("java/lang/String"), env->NewStringUTF(""));
-        pSynthData->mNativeSynthInterface->getLanguage(lang, country, variant);
+
+        android_tts_engine_t *engine = pSynthData->mEngine;
+        engine->funcs->getLanguage(engine, lang, country, variant);
         env->SetObjectArrayElement(retLocale, 0, env->NewStringUTF(lang));
         env->SetObjectArrayElement(retLocale, 1, env->NewStringUTF(country));
         env->SetObjectArrayElement(retLocale, 2, env->NewStringUTF(variant));
@@ -864,8 +917,9 @@
     char buf[bufSize];
     memset(buf, 0, bufSize);
     // TODO check return codes
-    if (pSynthData->mNativeSynthInterface) {
-        pSynthData->mNativeSynthInterface->getProperty("rate", buf, &bufSize);
+    android_tts_engine_t *engine = pSynthData->mEngine;
+    if (engine) {
+        engine->funcs->getProperty(engine,"rate", buf, &bufSize);
     }
     return atoi(buf);
 }
diff --git a/policy/src/com/android/internal/policy/impl/LockPatternKeyguardView.java b/policy/src/com/android/internal/policy/impl/LockPatternKeyguardView.java
index 27706ef..8693294 100644
--- a/policy/src/com/android/internal/policy/impl/LockPatternKeyguardView.java
+++ b/policy/src/com/android/internal/policy/impl/LockPatternKeyguardView.java
@@ -667,6 +667,7 @@
                 case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC:
                 case DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC:
                 case DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC:
+                case DevicePolicyManager.PASSWORD_QUALITY_COMPLEX:
                     currentMode = UnlockMode.Password;
                     break;
                 case DevicePolicyManager.PASSWORD_QUALITY_SOMETHING:
@@ -687,8 +688,17 @@
 
     private void showTimeoutDialog() {
         int timeoutInSeconds = (int) LockPatternUtils.FAILED_ATTEMPT_TIMEOUT_MS / 1000;
+        int messageId = R.string.lockscreen_too_many_failed_attempts_dialog_message;;
+        if(getUnlockMode() == UnlockMode.Password) {
+            if(mLockPatternUtils.getKeyguardStoredPasswordQuality() ==
+                DevicePolicyManager.PASSWORD_QUALITY_NUMERIC) {
+                messageId = R.string.lockscreen_too_many_failed_pin_attempts_dialog_message;
+            } else {
+                messageId = R.string.lockscreen_too_many_failed_password_attempts_dialog_message;
+            }
+        }
         String message = mContext.getString(
-                R.string.lockscreen_too_many_failed_attempts_dialog_message,
+                messageId,
                 mUpdateMonitor.getFailedAttempts(),
                 timeoutInSeconds);
         final AlertDialog dialog = new AlertDialog.Builder(mContext)
diff --git a/policy/src/com/android/internal/policy/impl/LockScreen.java b/policy/src/com/android/internal/policy/impl/LockScreen.java
index a5ef1fa..b3707b0 100644
--- a/policy/src/com/android/internal/policy/impl/LockScreen.java
+++ b/policy/src/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/policy/src/com/android/internal/policy/impl/PasswordUnlockScreen.java b/policy/src/com/android/internal/policy/impl/PasswordUnlockScreen.java
index 39f2917..60cd56c 100644
--- a/policy/src/com/android/internal/policy/impl/PasswordUnlockScreen.java
+++ b/policy/src/com/android/internal/policy/impl/PasswordUnlockScreen.java
@@ -53,6 +53,8 @@
     private final KeyguardUpdateMonitor mUpdateMonitor;
     private final KeyguardScreenCallback mCallback;
 
+    private boolean mIsAlpha;
+
     private EditText mPasswordEntry;
     private Button mEmergencyCallButton;
     private LockPatternUtils mLockPatternUtils;
@@ -87,8 +89,9 @@
         }
 
         final int quality = lockPatternUtils.getKeyguardStoredPasswordQuality();
-        final boolean isAlpha = DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC == quality
-                || DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC == quality;
+        mIsAlpha = DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC == quality
+                || DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC == quality
+                || DevicePolicyManager.PASSWORD_QUALITY_COMPLEX == quality;
 
         mKeyboardView = (PasswordEntryKeyboardView) findViewById(R.id.keyboard);
         mPasswordEntry = (EditText) findViewById(R.id.passwordEntry);
@@ -99,7 +102,7 @@
         mTitle = (TextView) findViewById(R.id.enter_password_label);
 
         mKeyboardHelper = new PasswordEntryKeyboardHelper(context, mKeyboardView, this);
-        mKeyboardHelper.setKeyboardMode(isAlpha ? PasswordEntryKeyboardHelper.KEYBOARD_MODE_ALPHA
+        mKeyboardHelper.setKeyboardMode(mIsAlpha ? PasswordEntryKeyboardHelper.KEYBOARD_MODE_ALPHA
                 : PasswordEntryKeyboardHelper.KEYBOARD_MODE_NUMERIC);
 
         mKeyboardView.setVisibility(mCreationHardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_NO
@@ -108,10 +111,11 @@
 
         // This allows keyboards with overlapping qwerty/numeric keys to choose just the
         // numeric keys.
-        if (isAlpha) {
+        if (mIsAlpha) {
             mPasswordEntry.setKeyListener(TextKeyListener.getInstance());
         } else {
             mPasswordEntry.setKeyListener(DigitsKeyListener.getInstance());
+            mTitle.setText(R.string.keyguard_password_enter_pin_password_code);
         }
 
         mKeyboardHelper.setVibratePattern(mLockPatternUtils.isTactileFeedbackEnabled() ?
@@ -138,6 +142,7 @@
     public void onResume() {
         // start fresh
         mPasswordEntry.setText("");
+        resetStatusInfo();
         mPasswordEntry.requestFocus();
         mLockPatternUtils.updateEmergencyCallButtonState(mEmergencyCallButton);
 
@@ -174,6 +179,9 @@
                 long deadline = mLockPatternUtils.setLockoutAttemptDeadline();
                 handleAttemptLockout(deadline);
             }
+            mTitle.setText(R.string.lockscreen_password_wrong);
+        } else if (entry.length() > 0) {
+            mTitle.setText(R.string.lockscreen_password_wrong);
         }
         mPasswordEntry.setText("");
     }
@@ -197,8 +205,8 @@
             @Override
             public void onFinish() {
                 mPasswordEntry.setEnabled(true);
-                mTitle.setText(R.string.keyguard_password_enter_password_code);
                 mKeyboardView.setEnabled(true);
+                resetStatusInfo();
             }
         }.start();
     }
@@ -264,4 +272,12 @@
 
     }
 
+    private void resetStatusInfo() {
+        if(mIsAlpha) {
+            mTitle.setText(R.string.keyguard_password_enter_password_code);
+        } else {
+            mTitle.setText(R.string.keyguard_password_enter_pin_password_code);
+        }
+    }
+
 }
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindow.java b/policy/src/com/android/internal/policy/impl/PhoneWindow.java
index b9232c8..2299852 100644
--- a/policy/src/com/android/internal/policy/impl/PhoneWindow.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindow.java
@@ -24,11 +24,15 @@
 
 import com.android.internal.view.BaseSurfaceHolder;
 import com.android.internal.view.RootViewSurfaceTaker;
+import com.android.internal.view.StandaloneActionMode;
 import com.android.internal.view.menu.ContextMenuBuilder;
 import com.android.internal.view.menu.MenuBuilder;
 import com.android.internal.view.menu.MenuDialogHelper;
+import com.android.internal.view.menu.MenuPopupHelper;
 import com.android.internal.view.menu.MenuView;
 import com.android.internal.view.menu.SubMenuBuilder;
+import com.android.internal.widget.ActionBarContextView;
+import com.android.internal.widget.ActionBarView;
 
 import android.app.KeyguardManager;
 import android.app.SearchManager;
@@ -53,6 +57,7 @@
 import android.util.EventLog;
 import android.util.Log;
 import android.util.SparseArray;
+import android.view.ActionMode;
 import android.view.Gravity;
 import android.view.HapticFeedbackConstants;
 import android.view.InputQueue;
@@ -66,6 +71,7 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewManager;
+import android.view.ViewStub;
 import android.view.VolumePanel;
 import android.view.Window;
 import android.view.WindowManager;
@@ -95,7 +101,7 @@
      * Simple callback used by the context menu and its submenus. The options
      * menu submenus do not use this (their behavior is more complex).
      */
-    ContextMenuCallback mContextMenuCallback = new ContextMenuCallback(FEATURE_CONTEXT_MENU);
+    DialogMenuCallback mContextMenuCallback = new DialogMenuCallback(FEATURE_CONTEXT_MENU);
 
     // This is the top-level view of the window, containing the window decor.
     private DecorView mDecor;
@@ -114,6 +120,8 @@
     private LayoutInflater mLayoutInflater;
 
     private TextView mTitleView;
+    
+    private ActionBarView mActionBar;
 
     private DrawableFeatureState[] mDrawables;
 
@@ -191,9 +199,6 @@
             /* Custom title feature is enabled and the user is trying to enable another feature */
             throw new AndroidRuntimeException("You cannot combine custom titles with other title features");
         }
-        if (featureId == FEATURE_OPENGL) {
-            getAttributes().memoryType = WindowManager.LayoutParams.MEMORY_TYPE_GPU;
-        }
         return super.requestFeature(featureId);
     }
 
@@ -276,6 +281,8 @@
     public void setTitle(CharSequence title) {
         if (mTitleView != null) {
             mTitleView.setText(title);
+        } else if (mActionBar != null) {
+            mActionBar.setWindowTitle(title);
         }
         mTitle = title;
     }
@@ -301,7 +308,7 @@
         // Already prepared (isPrepared will be reset to false later)
         if (st.isPrepared)
             return true;
-
+        
         if ((mPreparedPanel != null) && (mPreparedPanel != st)) {
             // Another Panel is prepared and possibly open, so close it
             closePanel(mPreparedPanel, false);
@@ -315,9 +322,11 @@
 
         if (st.createdPanelView == null) {
             // Init the panel state's menu--return false if init failed
-            if (st.menu == null) {
-                if (!initializePanelMenu(st) || (st.menu == null)) {
-                    return false;
+            if (st.menu == null || st.refreshMenuContent) {
+                if (st.menu == null) {
+                    if (!initializePanelMenu(st) || (st.menu == null)) {
+                        return false;
+                    }
                 }
                 // Call callback, and return if it doesn't want to display menu
                 if ((cb == null) || !cb.onCreatePanelMenu(st.featureId, st.menu)) {
@@ -326,6 +335,12 @@
 
                     return false;
                 }
+                
+                st.refreshMenuContent = false;
+
+                if (mActionBar != null) {
+                    mActionBar.setMenu(st.menu);
+                }
             }
 
             // Callback and return if the callback does not want to show the menu
@@ -374,11 +389,9 @@
                 clearMenuViews(st);
             }
         }
-
     }
 
     private static void clearMenuViews(PanelFeatureState st) {
-
         // This can be called on config changes, so we should make sure
         // the views will be reconstructed based on the new orientation, etc.
 
@@ -545,6 +558,26 @@
         }
     }
 
+    @Override
+    public void invalidatePanelMenu(int featureId) {
+        PanelFeatureState st = getPanelState(featureId, true);
+        if (st.menu != null) {
+            st.menu.clear();
+        }
+        st.refreshMenuContent = true;
+        st.refreshDecorView = true;
+        
+        // Prepare the options panel if we have an action bar
+        if ((featureId == FEATURE_ACTION_BAR || featureId == FEATURE_OPTIONS_PANEL)
+                && mActionBar != null) {
+            st = getPanelState(Window.FEATURE_OPTIONS_PANEL, false);
+            if (st != null) {
+                st.isPrepared = false;
+                preparePanel(st, null);
+            }
+        }
+    }
+    
     /**
      * Called when the panel key is pushed down.
      * @param featureId The feature ID of the relevant panel (defaults to FEATURE_OPTIONS_PANEL}.
@@ -777,8 +810,20 @@
             return true;
         }
 
-        // The window manager will give us a valid window token
-        new MenuDialogHelper(subMenu).show(null);
+        final Menu parentMenu = subMenu.getRootMenu();
+        final PanelFeatureState panel = findMenuPanel(parentMenu);
+
+        /*
+         * Use the panel open state to determine whether this is coming from an open panel
+         * or an action button. If it's an open panel we want to use MenuDialogHelper.
+         * If it's closed we want to grab the relevant view and create a popup anchored to it.
+         */
+        if (panel.isOpen) {
+            // The window manager will give us a valid window token
+            new MenuDialogHelper(subMenu).show(null);
+        } else {
+            new MenuPopupHelper(getContext(), subMenu).show();
+        }
 
         return true;
     }
@@ -1334,8 +1379,12 @@
             }
 
             case KeyEvent.KEYCODE_MENU: {
-                onKeyUpPanel(featureId < 0 ? FEATURE_OPTIONS_PANEL : featureId,
-                        event);
+                if (mActionBar != null && mActionBar.isOverflowReserved()) {
+                    mActionBar.showOverflowMenu();
+                } else {
+                    onKeyUpPanel(featureId < 0 ? FEATURE_OPTIONS_PANEL : featureId,
+                            event);
+                }
                 return true;
             }
 
@@ -1592,6 +1641,9 @@
         private boolean mWatchingForMenu;
         private int mDownY;
 
+        private ActionMode mActionMode;
+        private ActionBarContextView mActionModeView;
+
         public DecorView(Context context, int featureId) {
             super(context);
             mFeatureId = featureId;
@@ -1879,6 +1931,53 @@
             return mContextMenuHelper != null;
         }
 
+        @Override
+        public ActionMode startActionModeForChild(View originalView,
+                ActionMode.Callback callback) {
+            // originalView can be used here to be sure that we don't obscure
+            // relevant content with the context mode UI.
+            return startActionMode(callback);
+        }
+
+        @Override
+        public ActionMode startActionMode(ActionMode.Callback callback) {
+            if (mActionMode != null) {
+                mActionMode.finish();
+            }
+
+            ActionMode mode = getCallback().onStartActionMode(callback);
+            if (mode != null) {
+                mActionMode = mode;
+            } else {
+                if (mActionModeView == null) {
+                    if (hasFeature(FEATURE_ACTION_MODE_OVERLAY)) {
+                        mActionModeView = new ActionBarContextView(mContext);
+                        FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
+                                MATCH_PARENT, WRAP_CONTENT);
+                        addView(mActionModeView, params);
+                    } else {
+                        ViewStub stub = (ViewStub) findViewById(
+                                com.android.internal.R.id.action_mode_bar_stub);
+                        mActionModeView = (ActionBarContextView) stub.inflate();
+                    }
+                }
+
+                if (mActionModeView != null) {
+                    mode = new StandaloneActionMode(getContext(), mActionModeView,
+                            new ActionModeCallbackWrapper(callback));
+                    if (callback.onCreateActionMode(mode, mode.getMenu())) {
+                        mode.invalidate();
+                        mActionModeView.initForMode(mode);
+                        mActionModeView.setVisibility(View.VISIBLE);
+                        mActionMode = mode;
+                    } else {
+                        mActionMode = null;
+                    }
+                }
+            }
+            return mActionMode;
+        }
+
         public void startChanging() {
             mChanging = true;
         }
@@ -2058,6 +2157,35 @@
             if (keepOn) PhoneWindow.this.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
             else PhoneWindow.this.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
         }
+
+        /**
+         * Clears out internal reference when the action mode is destroyed.
+         */
+        private class ActionModeCallbackWrapper implements ActionMode.Callback {
+            private ActionMode.Callback mWrapped;
+
+            public ActionModeCallbackWrapper(ActionMode.Callback wrapped) {
+                mWrapped = wrapped;
+            }
+
+            public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+                return mWrapped.onCreateActionMode(mode, menu);
+            }
+
+            public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
+                return mWrapped.onPrepareActionMode(mode, menu);
+            }
+
+            public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
+                return mWrapped.onActionItemClicked(mode, item);
+            }
+
+            public void onDestroyActionMode(ActionMode mode) {
+                mWrapped.onDestroyActionMode(mode);
+                mActionModeView.removeAllViews();
+                mActionMode = null;
+            }
+        }
     }
 
     protected DecorView generateDecor() {
@@ -2106,6 +2234,13 @@
 
         if (a.getBoolean(com.android.internal.R.styleable.Window_windowNoTitle, false)) {
             requestFeature(FEATURE_NO_TITLE);
+        } else if (a.getBoolean(com.android.internal.R.styleable.Window_windowActionBar, false)) {
+            // Don't allow an action bar if there is no title.
+            requestFeature(FEATURE_ACTION_BAR);
+        }
+
+        if (a.getBoolean(com.android.internal.R.styleable.Window_windowActionModeOverlay, false)) {
+            requestFeature(FEATURE_ACTION_MODE_OVERLAY);
         }
 
         if (a.getBoolean(com.android.internal.R.styleable.Window_windowFullscreen, false)) {
@@ -2189,6 +2324,8 @@
             // If the window is floating, we need a dialog layout
             if (mIsFloating) {
                 layoutResource = com.android.internal.R.layout.dialog_title;
+            } else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) {
+                layoutResource = com.android.internal.R.layout.screen_action_bar;
             } else {
                 layoutResource = com.android.internal.R.layout.screen_title;
             }
@@ -2273,6 +2410,11 @@
                 } else {
                     mTitleView.setText(mTitle);
                 }
+            } else {
+                mActionBar = (ActionBarView) findViewById(com.android.internal.R.id.action_bar);
+                if (mActionBar != null && mActionBar.getTitle() == null) {
+                    mActionBar.setWindowTitle(mTitle);
+                }
             }
         }
     }
@@ -2626,6 +2768,8 @@
 
         boolean refreshDecorView;
 
+        boolean refreshMenuContent;
+        
         boolean wasLastOpen;
         
         boolean wasLastExpanded;
@@ -2748,11 +2892,11 @@
      * <li> Calls back to the callback's onMenuItemSelected when an item is
      * selected.
      */
-    private final class ContextMenuCallback implements MenuBuilder.Callback {
+    private final class DialogMenuCallback implements MenuBuilder.Callback {
         private int mFeatureId;
         private MenuDialogHelper mSubMenuHelper;
 
-        public ContextMenuCallback(int featureId) {
+        public DialogMenuCallback(int featureId) {
             mFeatureId = featureId;
         }
 
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
index 83d9c47..cb7fe06 100755
--- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
@@ -192,6 +192,7 @@
     
     boolean mSafeMode;
     WindowState mStatusBar = null;
+    boolean mStatusBarCanHide;
     final ArrayList<WindowState> mStatusBarPanels = new ArrayList<WindowState>();
     WindowState mKeyguard = null;
     KeyguardViewMediator mKeyguardMediator;
@@ -546,6 +547,9 @@
                 com.android.internal.R.array.config_safeModeDisabledVibePattern);
         mSafeModeEnabledVibePattern = getLongIntArray(mContext.getResources(),
                 com.android.internal.R.array.config_safeModeEnabledVibePattern);
+
+        // Note: the Configuration is not stable here, so we cannot load mStatusBarCanHide from
+        // config_statusBarCanHide because the latter depends on the screen size
     }
 
     public void updateSettings() {
@@ -954,6 +958,11 @@
                     return WindowManagerImpl.ADD_MULTIPLE_SINGLETON;
                 }
                 mStatusBar = win;
+
+                // The Configuration will be stable by now, so we can load this
+                mStatusBarCanHide = mContext.getResources().getBoolean(
+                        com.android.internal.R.bool.config_statusBarCanHide);
+
                 break;
             case TYPE_STATUS_BAR_PANEL:
                 mContext.enforceCallingOrSelfPermission(
@@ -1212,7 +1221,7 @@
     public void getContentInsetHintLw(WindowManager.LayoutParams attrs, Rect contentInset) {
         final int fl = attrs.flags;
         
-        if ((fl &
+        if (mStatusBarCanHide && (fl &
                 (FLAG_LAYOUT_IN_SCREEN | FLAG_FULLSCREEN | FLAG_LAYOUT_INSET_DECOR))
                 == (FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR)) {
             contentInset.set(mCurLeft, mCurTop, mW - mCurRight, mH - mCurBottom);
@@ -1245,10 +1254,17 @@
             if (mStatusBar.isVisibleLw()) {
                 // If the status bar is hidden, we don't want to cause
                 // windows behind it to scroll.
-                mDockTop = mContentTop = mCurTop = mStatusBar.getFrameLw().bottom;
-                if (DEBUG_LAYOUT) Log.v(TAG, "Status bar: mDockBottom="
-                        + mDockBottom + " mContentBottom="
-                        + mContentBottom + " mCurBottom=" + mCurBottom);
+                final Rect r = mStatusBar.getFrameLw();
+                if (mDockTop == r.top) mDockTop = r.bottom;
+                else if (mDockBottom == r.bottom) mDockBottom = r.top;
+                mContentTop = mCurTop = mDockTop;
+                mContentBottom = mCurBottom = mDockBottom;
+                if (DEBUG_LAYOUT) Log.v(TAG, "Status bar: mDockTop=" + mDockTop
+                        + " mContentTop=" + mContentTop
+                        + " mCurTop=" + mCurTop
+                        + " mDockBottom=" + mDockBottom
+                        + " mContentBottom=" + mContentBottom
+                        + " mCurBottom=" + mCurBottom);
             }
         }
     }
@@ -1333,7 +1349,7 @@
             attrs.gravity = Gravity.BOTTOM;
             mDockLayer = win.getSurfaceLayer();
         } else {
-            if ((fl &
+            if (mStatusBarCanHide && (fl &
                     (FLAG_LAYOUT_IN_SCREEN | FLAG_FULLSCREEN | FLAG_LAYOUT_INSET_DECOR))
                     == (FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR)) {
                 // This is the case for a normal activity window: we want it
@@ -1365,7 +1381,7 @@
                     vf.right = mCurRight;
                     vf.bottom = mCurBottom;
                 }
-            } else if ((fl & FLAG_LAYOUT_IN_SCREEN) != 0) {
+            } else if (mStatusBarCanHide && (fl & FLAG_LAYOUT_IN_SCREEN) != 0) {
                 // A window that has requested to fill the entire screen just
                 // gets everything, period.
                 pf.left = df.left = cf.left = 0;
@@ -1509,9 +1525,13 @@
                 boolean hideStatusBar =
                     (lp.flags & WindowManager.LayoutParams.FLAG_FULLSCREEN) != 0;
                 if (hideStatusBar) {
-                    if (DEBUG_LAYOUT) Log.v(TAG, "Hiding status bar");
-                    if (mStatusBar.hideLw(true)) changes |= FINISH_LAYOUT_REDO_LAYOUT;
-                    hiding = true;
+                    if (mStatusBarCanHide) {
+                        if (DEBUG_LAYOUT) Log.v(TAG, "Hiding status bar");
+                        if (mStatusBar.hideLw(true)) changes |= FINISH_LAYOUT_REDO_LAYOUT;
+                        hiding = true;
+                    } else if (localLOGV) {
+                        Log.v(TAG, "Preventing status bar from hiding by policy");
+                    }
                 } else {
                     if (DEBUG_LAYOUT) Log.v(TAG, "Showing status bar");
                     if (mStatusBar.showLw(true)) changes |= FINISH_LAYOUT_REDO_LAYOUT;
diff --git a/preloaded-classes b/preloaded-classes
index 1d5fbc08..33dba37 100644
--- a/preloaded-classes
+++ b/preloaded-classes
@@ -563,8 +563,6 @@
 android.widget.AdapterView
 android.widget.ArrayAdapter
 android.widget.AutoCompleteTextView
-android.widget.AutoCompleteTextView$DropDownItemClickListener
-android.widget.AutoCompleteTextView$DropDownListView
 android.widget.BaseAdapter
 android.widget.BaseExpandableListAdapter
 android.widget.CheckBox
@@ -582,6 +580,7 @@
 android.widget.ImageView
 android.widget.ImageView$ScaleType
 android.widget.LinearLayout
+android.widget.ListPopupWindow
 android.widget.ListView
 android.widget.ListView$SavedState
 android.widget.MediaController
@@ -685,7 +684,6 @@
 com.android.internal.view.menu.MenuBuilder
 com.android.internal.view.menu.MenuItemImpl
 com.android.internal.view.menu.SubMenuBuilder
-com.android.internal.widget.ContactHeaderWidget
 com.android.internal.widget.DialogTitle
 com.android.internal.widget.EditableInputConnection
 com.android.internal.widget.LinearLayoutWithDefaultTouchRecepient
@@ -727,7 +725,6 @@
 java.io.File
 java.io.FileDescriptor
 java.io.FileInputStream
-java.io.FileInputStream$RepositioningLock
 java.io.FileNotFoundException
 java.io.FileOutputStream
 java.io.FilterInputStream
@@ -1131,7 +1128,6 @@
 org.apache.harmony.nio.FileChannelFactory
 org.apache.harmony.nio.internal.DirectBuffer
 org.apache.harmony.nio.internal.FileChannelImpl
-org.apache.harmony.nio.internal.FileChannelImpl$RepositioningLock
 org.apache.harmony.nio.internal.FileLockImpl
 org.apache.harmony.nio.internal.LockManager
 org.apache.harmony.nio.internal.LockManager$1
diff --git a/services/audioflinger/AudioPolicyManagerBase.cpp b/services/audioflinger/AudioPolicyManagerBase.cpp
index 1d87c0d..4614c8d 100644
--- a/services/audioflinger/AudioPolicyManagerBase.cpp
+++ b/services/audioflinger/AudioPolicyManagerBase.cpp
@@ -1002,8 +1002,8 @@
 #ifdef AUDIO_POLICY_TEST
     Thread(false),
 #endif //AUDIO_POLICY_TEST
-    mPhoneState(AudioSystem::MODE_NORMAL), mRingerMode(0), mMusicStopTime(0),
-    mLimitRingtoneVolume(false), mTotalEffectsCpuLoad(0), mTotalEffectsMemory(0)
+    mPhoneState(AudioSystem::MODE_NORMAL), mRingerMode(0), mMusicStopTime(0), mLimitRingtoneVolume(false),
+    mLastVoiceVolume(-1.0f), mTotalEffectsCpuLoad(0), mTotalEffectsMemory(0)
 {
     mpClientInterface = clientInterface;
 
@@ -1829,29 +1829,38 @@
     }
 
     float volume = computeVolume(stream, index, output, device);
-    // do not set volume if the float value did not change
-    if (volume != mOutputs.valueFor(output)->mCurVolume[stream] || force) {
+    // We actually change the volume if:
+    // - the float value returned by computeVolume() changed
+    // - the force flag is set
+    if (volume != mOutputs.valueFor(output)->mCurVolume[stream] ||
+            force) {
         mOutputs.valueFor(output)->mCurVolume[stream] = volume;
         LOGV("setStreamVolume() for output %d stream %d, volume %f, delay %d", output, stream, volume, delayMs);
         if (stream == AudioSystem::VOICE_CALL ||
             stream == AudioSystem::DTMF ||
             stream == AudioSystem::BLUETOOTH_SCO) {
-            float voiceVolume = -1.0;
             // offset value to reflect actual hardware volume that never reaches 0
             // 1% corresponds roughly to first step in VOICE_CALL stream volume setting (see AudioService.java)
             volume = 0.01 + 0.99 * volume;
-            if (stream == AudioSystem::VOICE_CALL) {
-                voiceVolume = (float)index/(float)mStreams[stream].mIndexMax;
-            } else if (stream == AudioSystem::BLUETOOTH_SCO) {
-                voiceVolume = 1.0;
-            }
-            if (voiceVolume >= 0 && output == mHardwareOutput) {
-                mpClientInterface->setVoiceVolume(voiceVolume, delayMs);
-            }
         }
         mpClientInterface->setStreamVolume((AudioSystem::stream_type)stream, volume, output, delayMs);
     }
 
+    if (stream == AudioSystem::VOICE_CALL ||
+        stream == AudioSystem::BLUETOOTH_SCO) {
+        float voiceVolume;
+        // Force voice volume to max for bluetooth SCO as volume is managed by the headset
+        if (stream == AudioSystem::VOICE_CALL) {
+            voiceVolume = (float)index/(float)mStreams[stream].mIndexMax;
+        } else {
+            voiceVolume = 1.0;
+        }
+        if (voiceVolume != mLastVoiceVolume && output == mHardwareOutput) {
+            mpClientInterface->setVoiceVolume(voiceVolume, delayMs);
+            mLastVoiceVolume = voiceVolume;
+        }
+    }
+
     return NO_ERROR;
 }
 
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/AppWidgetService.java b/services/java/com/android/server/AppWidgetService.java
index 3ed6c12..30597320 100644
--- a/services/java/com/android/server/AppWidgetService.java
+++ b/services/java/com/android/server/AppWidgetService.java
@@ -425,6 +425,23 @@
         }
     }
 
+    public void notifyAppWidgetViewDataChanged(int[] appWidgetIds, RemoteViews views, int viewId) {
+        if (appWidgetIds == null) {
+            return;
+        }
+        if (appWidgetIds.length == 0) {
+            return;
+        }
+        final int N = appWidgetIds.length;
+
+        synchronized (mAppWidgetIds) {
+            for (int i=0; i<N; i++) {
+                AppWidgetId id = lookupAppWidgetIdLocked(appWidgetIds[i]);
+                notifyAppWidgetViewDataChangedInstanceLocked(id, views, viewId);
+            }
+        }
+    }
+
     public void updateAppWidgetProvider(ComponentName provider, RemoteViews views) {
         synchronized (mAppWidgetIds) {
             Provider p = lookupProviderLocked(provider);
@@ -462,6 +479,27 @@
         }
     }
 
+    void notifyAppWidgetViewDataChangedInstanceLocked(AppWidgetId id, RemoteViews views, int viewId) {
+        // allow for stale appWidgetIds and other badness
+        // lookup also checks that the calling process can access the appWidgetId
+        // drop unbound appWidgetIds (shouldn't be possible under normal circumstances)
+        if (id != null && id.provider != null && !id.provider.zombie && !id.host.zombie) {
+            id.views = views;
+
+            // is anyone listening?
+            if (id.host.callbacks != null) {
+                try {
+                    // the lock is held, but this is a oneway call
+                    id.host.callbacks.viewDataChanged(id.appWidgetId, views, viewId);
+                } catch (RemoteException e) {
+                    // It failed; remove the callback. No need to prune because
+                    // we know that this host is still referenced by this instance.
+                    id.host.callbacks = null;
+                }
+            }
+        }
+    }
+
     public int[] startListening(IAppWidgetHost callbacks, String packageName, int hostId,
             List<RemoteViews> updatedViews) {
         int callingUid = enforceCallingUid(packageName);
@@ -741,6 +779,9 @@
             }
             info.label = activityInfo.loadLabel(mPackageManager).toString();
             info.icon = ri.getIconResource();
+            info.previewImage = sa.getResourceId(
+            		com.android.internal.R.styleable.AppWidgetProviderInfo_previewImage, 0);
+
             sa.recycle();
         } catch (Exception e) {
             // Ok to catch Exception here, because anything going wrong because
diff --git a/services/java/com/android/server/ClipboardService.java b/services/java/com/android/server/ClipboardService.java
index aa8cded..4e4fc0c 100644
--- a/services/java/com/android/server/ClipboardService.java
+++ b/services/java/com/android/server/ClipboardService.java
@@ -16,42 +16,77 @@
 
 package com.android.server;
 
-import android.text.IClipboard;
+import android.content.ClippedData;
+import android.content.IClipboard;
+import android.content.IOnPrimaryClipChangedListener;
 import android.content.Context;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
 
 /**
  * Implementation of the clipboard for copy and paste.
  */
 public class ClipboardService extends IClipboard.Stub {
-    private CharSequence mClipboard = "";
+    private ClippedData mPrimaryClip;
+    private final RemoteCallbackList<IOnPrimaryClipChangedListener> mPrimaryClipListeners
+            = new RemoteCallbackList<IOnPrimaryClipChangedListener>();
 
     /**
      * Instantiates the clipboard.
      */
     public ClipboardService(Context context) { }
 
-    // javadoc from interface
-    public void setClipboardText(CharSequence text) {
+    public void setPrimaryClip(ClippedData clip) {
         synchronized (this) {
-            if (text == null) {
-                text = "";
+            if (clip != null && clip.getItemCount() <= 0) {
+                throw new IllegalArgumentException("No items");
             }
+            mPrimaryClip = clip;
+            final int n = mPrimaryClipListeners.beginBroadcast();
+            for (int i = 0; i < n; i++) {
+                try {
+                    mPrimaryClipListeners.getBroadcastItem(i).dispatchPrimaryClipChanged();
+                } catch (RemoteException e) {
+
+                    // The RemoteCallbackList will take care of removing
+                    // the dead object for us.
+                }
+            }
+            mPrimaryClipListeners.finishBroadcast();
+        }
+    }
     
-            mClipboard = text;
-        }
-    }
-
-    // javadoc from interface
-    public CharSequence getClipboardText() {
+    public ClippedData getPrimaryClip() {
         synchronized (this) {
-            return mClipboard;
+            return mPrimaryClip;
         }
     }
 
-    // javadoc from interface
+    public boolean hasPrimaryClip() {
+        synchronized (this) {
+            return mPrimaryClip != null;
+        }
+    }
+
+    public void addPrimaryClipChangedListener(IOnPrimaryClipChangedListener listener) {
+        synchronized (this) {
+            mPrimaryClipListeners.register(listener);
+        }
+    }
+
+    public void removePrimaryClipChangedListener(IOnPrimaryClipChangedListener listener) {
+        synchronized (this) {
+            mPrimaryClipListeners.unregister(listener);
+        }
+    }
+
     public boolean hasClipboardText() {
         synchronized (this) {
-            return mClipboard.length() > 0;
+            if (mPrimaryClip != null) {
+                CharSequence text = mPrimaryClip.getItem(0).getText();
+                return text != null && text.length() > 0;
+            }
+            return false;
         }
     }
 }
diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java
index 81b8d40..cc1566b 100644
--- a/services/java/com/android/server/ConnectivityService.java
+++ b/services/java/com/android/server/ConnectivityService.java
@@ -26,13 +26,16 @@
 import android.net.IConnectivityManager;
 import android.net.MobileDataStateTracker;
 import android.net.NetworkInfo;
+import android.net.NetworkProperties;
 import android.net.NetworkStateTracker;
 import android.net.wifi.WifiStateTracker;
+import android.net.NetworkUtils;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
+import android.os.PowerManager;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemProperties;
@@ -46,8 +49,13 @@
 import com.android.server.connectivity.Tethering;
 
 import java.io.FileDescriptor;
+import java.io.FileWriter;
+import java.io.IOException;
 import java.io.PrintWriter;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.List;
 
 /**
@@ -64,7 +72,6 @@
     private static final String NETWORK_RESTORE_DELAY_PROP_NAME =
             "android.telephony.apn-restore";
 
-
     private Tethering mTethering;
     private boolean mTetheringConfigValid = false;
 
@@ -81,6 +88,8 @@
      */
     private List mNetRequestersPids[];
 
+    private WifiWatchdogService mWifiWatchdogService;
+
     // priority order of the nettrackers
     // (excluding dynamically set mNetworkPreference)
     // TODO - move mNetworkTypePreference into this
@@ -104,6 +113,11 @@
     private boolean mSystemReady;
     private Intent mInitialBroadcast;
 
+    private PowerManager.WakeLock mNetTransitionWakeLock;
+    private String mNetTransitionWakeLockCausedBy = "";
+    private int mNetTransitionWakeLockSerialNumber;
+    private int mNetTransitionWakeLockTimeout;
+
     private static class NetworkAttributes {
         /**
          * Class for holding settings read from resources.
@@ -194,6 +208,12 @@
         }
 
         mContext = context;
+
+        PowerManager powerManager = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE);
+        mNetTransitionWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
+        mNetTransitionWakeLockTimeout = mContext.getResources().getInteger(
+                com.android.internal.R.integer.config_networkTransitionTimeout);
+
         mNetTrackers = new NetworkStateTracker[
                 ConnectivityManager.MAX_NETWORK_TYPE+1];
         mHandler = new MyHandler();
@@ -300,6 +320,9 @@
                 mNetTrackers[ConnectivityManager.TYPE_WIFI] = wst;
                 wst.startMonitoring();
 
+                //TODO: as part of WWS refactor, create only when needed
+                mWifiWatchdogService = new WifiWatchdogService(context, wst);
+
                 break;
             case ConnectivityManager.TYPE_MOBILE:
                 mNetTrackers[netType] = new MobileDataStateTracker(context, mHandler,
@@ -582,7 +605,7 @@
                         !network.isTeardownRequested()) {
                     if (ni.isConnected() == true) {
                         // add the pid-specific dns
-                        handleDnsConfigurationChange();
+                        handleDnsConfigurationChange(networkType);
                         if (DBG) Slog.d(TAG, "special network already active");
                         return Phone.APN_ALREADY_ACTIVE;
                     }
@@ -597,15 +620,7 @@
                 network.reconnect();
                 return Phone.APN_REQUEST_STARTED;
             } else {
-                synchronized(this) {
-                    mFeatureUsers.add(f);
-                }
-                mHandler.sendMessageDelayed(mHandler.obtainMessage(
-                        NetworkStateTracker.EVENT_RESTORE_DEFAULT_NETWORK,
-                        f), getRestoreDefaultNetworkDelay());
-
-                return network.startUsingNetworkFeature(feature,
-                        getCallingPid(), getCallingUid());
+                return -1;
             }
         }
         return Phone.APN_TYPE_NOT_AVAILABLE;
@@ -724,8 +739,7 @@
             tracker.teardown();
             return 1;
         } else {
-            // do it the old fashioned way
-            return tracker.stopUsingNetworkFeature(feature, pid, uid);
+            return -1;
         }
     }
 
@@ -736,6 +750,7 @@
      * specified host is to be routed
      * @param hostAddress the IP address of the host to which the route is
      * desired
+     * todo - deprecate (only v4!)
      * @return {@code true} on success, {@code false} on failure
      */
     public boolean requestRouteToHost(int networkType, int hostAddress) {
@@ -752,7 +767,39 @@
             }
             return false;
         }
-        return tracker.requestRouteToHost(hostAddress);
+        try {
+            InetAddress addr = InetAddress.getByAddress(NetworkUtils.v4IntToArray(hostAddress));
+            return addHostRoute(tracker, addr);
+        } catch (UnknownHostException e) {}
+        return false;
+    }
+
+    /**
+     * Ensure that a network route exists to deliver traffic to the specified
+     * host via the mobile data network.
+     * @param hostAddress the IP address of the host to which the route is desired,
+     * in network byte order.
+     * TODO - deprecate
+     * @return {@code true} on success, {@code false} on failure
+     */
+    private boolean addHostRoute(NetworkStateTracker nt, InetAddress hostAddress) {
+        if (nt.getNetworkInfo().getType() == ConnectivityManager.TYPE_WIFI) {
+            return false;
+        }
+
+        NetworkProperties p = nt.getNetworkProperties();
+        if (p == null) return false;
+        String interfaceName = p.getInterfaceName();
+
+        if (DBG) {
+            Slog.d(TAG, "Requested host route to " + hostAddress + "(" + interfaceName + ")");
+        }
+        if (interfaceName != null) {
+            return NetworkUtils.addHostRoute(interfaceName, hostAddress) == 0;
+        } else {
+            if (DBG) Slog.e(TAG, "addHostRoute failed due to null interface name");
+            return false;
+        }
     }
 
     /**
@@ -859,6 +906,12 @@
                 "ConnectivityService");
     }
 
+    private void enforceConnectivityInternalPermission() {
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.CONNECTIVITY_INTERNAL,
+                "ConnectivityService");
+    }
+
     /**
      * Handle a {@code DISCONNECTED} event. If this pertains to the non-active
      * network, we ignore it. If it is for the active network, we send out a
@@ -914,7 +967,7 @@
             }
         }
         // do this before we broadcast the change
-        handleConnectivityChange();
+        handleConnectivityChange(prevNetType);
 
         sendStickyBroadcast(intent);
         /*
@@ -1070,9 +1123,6 @@
             }
         }
 
-        // do this before we broadcast the change
-        handleConnectivityChange();
-
         sendStickyBroadcast(intent);
         /*
          * If the failover network is already connected, then immediately send
@@ -1134,16 +1184,24 @@
                         Slog.e(TAG, "Network declined teardown request");
                         return;
                     }
-                    if (isFailover) {
-                        otherNet.releaseWakeLock();
-                    }
+                }
+            }
+            synchronized (ConnectivityService.this) {
+                // have a new default network, release the transition wakelock in a second
+                // if it's held.  The second pause is to allow apps to reconnect over the
+                // new network
+                if (mNetTransitionWakeLock.isHeld()) {
+                    mHandler.sendMessageDelayed(mHandler.obtainMessage(
+                            NetworkStateTracker.EVENT_CLEAR_NET_TRANSITION_WAKELOCK,
+                            mNetTransitionWakeLockSerialNumber, 0),
+                            1000);
                 }
             }
             mActiveDefaultNetwork = type;
         }
         thisNet.setTeardownRequested(false);
-        thisNet.updateNetworkSettings();
-        handleConnectivityChange();
+        updateNetworkSettings(thisNet);
+        handleConnectivityChange(type);
         sendConnectedBroadcast(info);
     }
 
@@ -1170,42 +1228,172 @@
     }
 
     /**
-     * After any kind of change in the connectivity state of any network,
-     * make sure that anything that depends on the connectivity state of
-     * more than one network is set up correctly. We're mainly concerned
-     * with making sure that the list of DNS servers is set up  according
-     * to which networks are connected, and ensuring that the right routing
-     * table entries exist.
+     * After a change in the connectivity state of a network. We're mainly
+     * concerned with making sure that the list of DNS servers is set up
+     * according to which networks are connected, and ensuring that the
+     * right routing table entries exist.
      */
-    private void handleConnectivityChange() {
+    private void handleConnectivityChange(int netType) {
         /*
          * If a non-default network is enabled, add the host routes that
-         * will allow it's DNS servers to be accessed.  Only
-         * If both mobile and wifi are enabled, add the host routes that
-         * will allow MMS traffic to pass on the mobile network. But
-         * remove the default route for the mobile network, so that there
-         * will be only one default route, to ensure that all traffic
-         * except MMS will travel via Wi-Fi.
+         * will allow it's DNS servers to be accessed.
          */
-        handleDnsConfigurationChange();
+        handleDnsConfigurationChange(netType);
 
-        for (int netType : mPriorityList) {
-            if (mNetTrackers[netType].getNetworkInfo().isConnected()) {
-                if (mNetAttributes[netType].isDefault()) {
-                    mNetTrackers[netType].addDefaultRoute();
-                } else {
-                    mNetTrackers[netType].addPrivateDnsRoutes();
-                }
+        if (mNetTrackers[netType].getNetworkInfo().isConnected()) {
+            if (mNetAttributes[netType].isDefault()) {
+                addDefaultRoute(mNetTrackers[netType]);
             } else {
-                if (mNetAttributes[netType].isDefault()) {
-                    mNetTrackers[netType].removeDefaultRoute();
-                } else {
-                    mNetTrackers[netType].removePrivateDnsRoutes();
-                }
+                addPrivateDnsRoutes(mNetTrackers[netType]);
+            }
+        } else {
+            if (mNetAttributes[netType].isDefault()) {
+                removeDefaultRoute(mNetTrackers[netType]);
+            } else {
+                removePrivateDnsRoutes(mNetTrackers[netType]);
             }
         }
     }
 
+    private void addPrivateDnsRoutes(NetworkStateTracker nt) {
+        boolean privateDnsRouteSet = nt.isPrivateDnsRouteSet();
+        NetworkProperties p = nt.getNetworkProperties();
+        if (p == null) return;
+        String interfaceName = p.getInterfaceName();
+
+        if (DBG) {
+            Slog.d(TAG, "addPrivateDnsRoutes for " + nt +
+                    "(" + interfaceName + ") - mPrivateDnsRouteSet = " + privateDnsRouteSet);
+        }
+        if (interfaceName != null && !privateDnsRouteSet) {
+            Collection<InetAddress> dnsList = p.getDnses();
+            for (InetAddress dns : dnsList) {
+                if (DBG) Slog.d(TAG, "  adding " + dns);
+                NetworkUtils.addHostRoute(interfaceName, dns);
+            }
+            nt.privateDnsRouteSet(true);
+        }
+    }
+
+    private void removePrivateDnsRoutes(NetworkStateTracker nt) {
+        // TODO - we should do this explicitly but the NetUtils api doesnt
+        // support this yet - must remove all.  No worse than before
+        NetworkProperties p = nt.getNetworkProperties();
+        if (p == null) return;
+        String interfaceName = p.getInterfaceName();
+        boolean privateDnsRouteSet = nt.isPrivateDnsRouteSet();
+        if (interfaceName != null && privateDnsRouteSet) {
+            if (DBG) {
+                Slog.d(TAG, "removePrivateDnsRoutes for " + nt.getNetworkInfo().getTypeName() +
+                        " (" + interfaceName + ")");
+            }
+            NetworkUtils.removeHostRoutes(interfaceName);
+            nt.privateDnsRouteSet(false);
+        }
+    }
+
+
+    private void addDefaultRoute(NetworkStateTracker nt) {
+        NetworkProperties p = nt.getNetworkProperties();
+        if (p == null) return;
+        String interfaceName = p.getInterfaceName();
+        InetAddress defaultGatewayAddr = p.getGateway();
+
+        if ((interfaceName != null) && (defaultGatewayAddr != null )) {
+            if ((NetworkUtils.setDefaultRoute(interfaceName, defaultGatewayAddr) >= 0) && DBG) {
+                NetworkInfo networkInfo = nt.getNetworkInfo();
+                Slog.d(TAG, "addDefaultRoute for " + networkInfo.getTypeName() +
+                        " (" + interfaceName + "), GatewayAddr=" + defaultGatewayAddr);
+            }
+        }
+    }
+
+
+    public void removeDefaultRoute(NetworkStateTracker nt) {
+        NetworkProperties p = nt.getNetworkProperties();
+        if (p == null) return;
+        String interfaceName = p.getInterfaceName();
+
+        if (interfaceName != null) {
+            if ((NetworkUtils.removeDefaultRoute(interfaceName) >= 0) && DBG) {
+                NetworkInfo networkInfo = nt.getNetworkInfo();
+                Slog.d(TAG, "removeDefaultRoute for " + networkInfo.getTypeName() + " (" +
+                        interfaceName + ")");
+            }
+        }
+    }
+
+   /**
+     * Reads the network specific TCP buffer sizes from SystemProperties
+     * net.tcp.buffersize.[default|wifi|umts|edge|gprs] and set them for system
+     * wide use
+     */
+   public void updateNetworkSettings(NetworkStateTracker nt) {
+        String key = nt.getTcpBufferSizesPropName();
+        String bufferSizes = SystemProperties.get(key);
+
+        if (bufferSizes.length() == 0) {
+            Slog.e(TAG, key + " not found in system properties. Using defaults");
+
+            // Setting to default values so we won't be stuck to previous values
+            key = "net.tcp.buffersize.default";
+            bufferSizes = SystemProperties.get(key);
+        }
+
+        // Set values in kernel
+        if (bufferSizes.length() != 0) {
+            if (DBG) {
+                Slog.v(TAG, "Setting TCP values: [" + bufferSizes
+                        + "] which comes from [" + key + "]");
+            }
+            setBufferSize(bufferSizes);
+        }
+    }
+
+   /**
+     * Writes TCP buffer sizes to /sys/kernel/ipv4/tcp_[r/w]mem_[min/def/max]
+     * which maps to /proc/sys/net/ipv4/tcp_rmem and tcpwmem
+     *
+     * @param bufferSizes in the format of "readMin, readInitial, readMax,
+     *        writeMin, writeInitial, writeMax"
+     */
+    private void setBufferSize(String bufferSizes) {
+        try {
+            String[] values = bufferSizes.split(",");
+
+            if (values.length == 6) {
+              final String prefix = "/sys/kernel/ipv4/tcp_";
+                stringToFile(prefix + "rmem_min", values[0]);
+                stringToFile(prefix + "rmem_def", values[1]);
+                stringToFile(prefix + "rmem_max", values[2]);
+                stringToFile(prefix + "wmem_min", values[3]);
+                stringToFile(prefix + "wmem_def", values[4]);
+                stringToFile(prefix + "wmem_max", values[5]);
+            } else {
+                Slog.e(TAG, "Invalid buffersize string: " + bufferSizes);
+            }
+        } catch (IOException e) {
+            Slog.e(TAG, "Can't set tcp buffer sizes:" + e);
+        }
+    }
+
+   /**
+     * Writes string to file. Basically same as "echo -n $string > $filename"
+     *
+     * @param filename
+     * @param string
+     * @throws IOException
+     */
+    private void stringToFile(String filename, String string) throws IOException {
+        FileWriter out = new FileWriter(filename);
+        try {
+            out.write(string);
+        } finally {
+            out.close();
+        }
+    }
+
+
     /**
      * Adjust the per-process dns entries (net.dns<x>.<pid>) based
      * on the highest priority active net which this process requested.
@@ -1221,12 +1409,14 @@
             NetworkStateTracker nt = mNetTrackers[i];
             if (nt.getNetworkInfo().isConnected() &&
                     !nt.isTeardownRequested()) {
+                NetworkProperties p = nt.getNetworkProperties();
+                if (p == null) continue;
                 List pids = mNetRequestersPids[i];
                 for (int j=0; j<pids.size(); j++) {
                     Integer pid = (Integer)pids.get(j);
                     if (pid.intValue() == myPid) {
-                        String[] dnsList = nt.getNameServers();
-                        writePidDns(dnsList, myPid);
+                        Collection<InetAddress> dnses = p.getDnses();
+                        writePidDns(dnses, myPid);
                         if (doBump) {
                             bumpDns();
                         }
@@ -1248,12 +1438,10 @@
         }
     }
 
-    private void writePidDns(String[] dnsList, int pid) {
+    private void writePidDns(Collection <InetAddress> dnses, int pid) {
         int j = 1;
-        for (String dns : dnsList) {
-            if (dns != null && !TextUtils.equals(dns, "0.0.0.0")) {
-                SystemProperties.set("net.dns" + j++ + "." + pid, dns);
-            }
+        for (InetAddress dns : dnses) {
+            SystemProperties.set("net.dns" + j++ + "." + pid, dns.getHostAddress());
         }
     }
 
@@ -1272,42 +1460,37 @@
         SystemProperties.set("net.dnschange", "" + (n+1));
     }
 
-    private void handleDnsConfigurationChange() {
+    private void handleDnsConfigurationChange(int netType) {
         // add default net's dns entries
-        for (int x = mPriorityList.length-1; x>= 0; x--) {
-            int netType = mPriorityList[x];
-            NetworkStateTracker nt = mNetTrackers[netType];
-            if (nt != null && nt.getNetworkInfo().isConnected() &&
-                    !nt.isTeardownRequested()) {
-                String[] dnsList = nt.getNameServers();
-                if (mNetAttributes[netType].isDefault()) {
-                    int j = 1;
-                    for (String dns : dnsList) {
-                        if (dns != null && !TextUtils.equals(dns, "0.0.0.0")) {
-                            if (DBG) {
-                                Slog.d(TAG, "adding dns " + dns + " for " +
-                                        nt.getNetworkInfo().getTypeName());
-                            }
-                            SystemProperties.set("net.dns" + j++, dns);
-                        }
+        NetworkStateTracker nt = mNetTrackers[netType];
+        if (nt != null && nt.getNetworkInfo().isConnected() && !nt.isTeardownRequested()) {
+            NetworkProperties p = nt.getNetworkProperties();
+            if (p == null) return;
+            Collection<InetAddress> dnses = p.getDnses();
+            if (mNetAttributes[netType].isDefault()) {
+                int j = 1;
+                for (InetAddress dns : dnses) {
+                    if (DBG) {
+                        Slog.d(TAG, "adding dns " + dns + " for " +
+                                nt.getNetworkInfo().getTypeName());
                     }
-                    for (int k=j ; k<mNumDnsEntries; k++) {
-                        if (DBG) Slog.d(TAG, "erasing net.dns" + k);
-                        SystemProperties.set("net.dns" + k, "");
-                    }
-                    mNumDnsEntries = j;
-                } else {
-                    // set per-pid dns for attached secondary nets
-                    List pids = mNetRequestersPids[netType];
-                    for (int y=0; y< pids.size(); y++) {
-                        Integer pid = (Integer)pids.get(y);
-                        writePidDns(dnsList, pid.intValue());
-                    }
+                    SystemProperties.set("net.dns" + j++, dns.getHostAddress());
+                }
+                for (int k=j ; k<mNumDnsEntries; k++) {
+                    if (DBG) Slog.d(TAG, "erasing net.dns" + k);
+                    SystemProperties.set("net.dns" + k, "");
+                }
+                mNumDnsEntries = j;
+            } else {
+                // set per-pid dns for attached secondary nets
+                List pids = mNetRequestersPids[netType];
+                for (int y=0; y< pids.size(); y++) {
+                    Integer pid = (Integer)pids.get(y);
+                    writePidDns(dnses, pid.intValue());
                 }
             }
+            bumpDns();
         }
-
-        bumpDns();
     }
 
     private int getRestoreDefaultNetworkDelay() {
@@ -1362,6 +1545,13 @@
         }
         pw.println();
 
+        synchronized (this) {
+            pw.println("NetworkTranstionWakeLock is currently " +
+                    (mNetTransitionWakeLock.isHeld() ? "" : "not ") + "held.");
+            pw.println("It was last requested for "+mNetTransitionWakeLockCausedBy);
+        }
+        pw.println();
+
         mTethering.dump(fd, pw, args);
     }
 
@@ -1437,11 +1627,14 @@
                 case NetworkStateTracker.EVENT_NOTIFICATION_CHANGED:
                     handleNotificationChange(msg.arg1 == 1, msg.arg2,
                             (Notification) msg.obj);
-
-                case NetworkStateTracker.EVENT_CONFIGURATION_CHANGED:
-                    handleDnsConfigurationChange();
                     break;
 
+                case NetworkStateTracker.EVENT_CONFIGURATION_CHANGED:
+                    // TODO - make this handle ip/proxy/gateway/dns changes
+                    info = (NetworkInfo) msg.obj;
+                    type = info.getType();
+                    handleDnsConfigurationChange(type);
+                    break;
                 case NetworkStateTracker.EVENT_ROAMING_CHANGED:
                     // fill me in
                     break;
@@ -1453,6 +1646,20 @@
                     FeatureUser u = (FeatureUser)msg.obj;
                     u.expire();
                     break;
+                case NetworkStateTracker.EVENT_CLEAR_NET_TRANSITION_WAKELOCK:
+                    String causedBy = null;
+                    synchronized (ConnectivityService.this) {
+                        if (msg.arg1 == mNetTransitionWakeLockSerialNumber &&
+                                mNetTransitionWakeLock.isHeld()) {
+                            mNetTransitionWakeLock.release();
+                            causedBy = mNetTransitionWakeLockCausedBy;
+                        }
+                    }
+                    if (causedBy != null) {
+                        Slog.d(TAG, "NetTransition Wakelock for " +
+                                causedBy + " released by timeout");
+                    }
+                    break;
             }
         }
     }
@@ -1536,4 +1743,23 @@
                 Settings.Secure.TETHER_SUPPORTED, defaultVal) != 0);
         return tetherEnabledInSettings && mTetheringConfigValid;
     }
+
+    // An API NetworkStateTrackers can call when they lose their network.
+    // This will automatically be cleared after X seconds or a network becomes CONNECTED,
+    // whichever happens first.  The timer is started by the first caller and not
+    // restarted by subsequent callers.
+    public void requestNetworkTransitionWakelock(String forWhom) {
+        enforceConnectivityInternalPermission();
+        synchronized (this) {
+            if (mNetTransitionWakeLock.isHeld()) return;
+            mNetTransitionWakeLockSerialNumber++;
+            mNetTransitionWakeLock.acquire();
+            mNetTransitionWakeLockCausedBy = forWhom;
+        }
+        mHandler.sendMessageDelayed(mHandler.obtainMessage(
+                NetworkStateTracker.EVENT_CLEAR_NET_TRANSITION_WAKELOCK,
+                mNetTransitionWakeLockSerialNumber, 0),
+                mNetTransitionWakeLockTimeout);
+        return;
+    }
 }
diff --git a/services/java/com/android/server/CountryDetectorService.java b/services/java/com/android/server/CountryDetectorService.java
new file mode 100644
index 0000000..3081ebe
--- /dev/null
+++ b/services/java/com/android/server/CountryDetectorService.java
@@ -0,0 +1,204 @@
+/*
+ * 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 java.util.HashMap;
+
+import com.android.server.location.ComprehensiveCountryDetector;
+
+import android.content.Context;
+import android.location.Country;
+import android.location.CountryListener;
+import android.location.ICountryDetector;
+import android.location.ICountryListener;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Process;
+import android.os.RemoteException;
+import android.util.Slog;
+
+/**
+ * This class detects the country that the user is in through
+ * {@link ComprehensiveCountryDetector}.
+ *
+ * @hide
+ */
+public class CountryDetectorService extends ICountryDetector.Stub implements Runnable {
+
+    /**
+     * The class represents the remote listener, it will also removes itself
+     * from listener list when the remote process was died.
+     */
+    private final class Receiver implements IBinder.DeathRecipient {
+        private final ICountryListener mListener;
+        private final IBinder mKey;
+
+        public Receiver(ICountryListener listener) {
+            mListener = listener;
+            mKey = listener.asBinder();
+        }
+
+        public void binderDied() {
+            removeListener(mKey);
+        }
+
+        @Override
+        public boolean equals(Object otherObj) {
+            if (otherObj instanceof Receiver) {
+                return mKey.equals(((Receiver) otherObj).mKey);
+            }
+            return false;
+        }
+
+        @Override
+        public int hashCode() {
+            return mKey.hashCode();
+        }
+
+        public ICountryListener getListener() {
+            return mListener;
+        }
+    }
+
+    private final static String TAG = "CountryDetectorService";
+
+    private final HashMap<IBinder, Receiver> mReceivers;
+    private final Context mContext;
+    private ComprehensiveCountryDetector mCountryDetector;
+    private boolean mSystemReady;
+    private Handler mHandler;
+    private CountryListener mLocationBasedDetectorListener;
+
+    public CountryDetectorService(Context context) {
+        super();
+        mReceivers = new HashMap<IBinder, Receiver>();
+        mContext = context;
+    }
+
+    @Override
+    public Country detectCountry() throws RemoteException {
+        if (!mSystemReady) {
+            throw new RemoteException();
+        }
+        return mCountryDetector.detectCountry();
+    }
+
+    /**
+     * Add the ICountryListener into the listener list.
+     */
+    @Override
+    public void addCountryListener(ICountryListener listener) throws RemoteException {
+        if (!mSystemReady) {
+            throw new RemoteException();
+        }
+        addListener(listener);
+    }
+
+    /**
+     * Remove the ICountryListener from the listener list.
+     */
+    @Override
+    public void removeCountryListener(ICountryListener listener) throws RemoteException {
+        if (!mSystemReady) {
+            throw new RemoteException();
+        }
+        removeListener(listener.asBinder());
+    }
+
+    private void addListener(ICountryListener listener) {
+        synchronized (mReceivers) {
+            Receiver r = new Receiver(listener);
+            try {
+                listener.asBinder().linkToDeath(r, 0);
+                mReceivers.put(listener.asBinder(), r);
+                if (mReceivers.size() == 1) {
+                    Slog.d(TAG, "The first listener is added");
+                    setCountryListener(mLocationBasedDetectorListener);
+                }
+            } catch (RemoteException e) {
+                Slog.e(TAG, "linkToDeath failed:", e);
+            }
+        }
+    }
+
+    private void removeListener(IBinder key) {
+        synchronized (mReceivers) {
+            mReceivers.remove(key);
+            if (mReceivers.isEmpty()) {
+                setCountryListener(null);
+                Slog.d(TAG, "No listener is left");
+            }
+        }
+    }
+
+
+    protected void notifyReceivers(Country country) {
+        synchronized(mReceivers) {
+            for (Receiver receiver : mReceivers.values()) {
+                try {
+                    receiver.getListener().onCountryDetected(country);
+                } catch (RemoteException e) {
+                    // TODO: Shall we remove the receiver?
+                    Slog.e(TAG, "notifyReceivers failed:", e);
+                }
+            }
+        }
+    }
+
+    void systemReady() {
+        // Shall we wait for the initialization finish.
+        Thread thread = new Thread(this, "CountryDetectorService");
+        thread.start();
+    }
+
+    private void initialize() {
+        mCountryDetector = new ComprehensiveCountryDetector(mContext);
+        mLocationBasedDetectorListener = new CountryListener() {
+            public void onCountryDetected(final Country country) {
+                mHandler.post(new Runnable() {
+                    public void run() {
+                        notifyReceivers(country);
+                    }
+                });
+            }
+        };
+    }
+
+    public void run() {
+        Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
+        Looper.prepare();
+        mHandler = new Handler();
+        initialize();
+        mSystemReady = true;
+        Looper.loop();
+    }
+
+    protected void setCountryListener(final CountryListener listener) {
+        mHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                mCountryDetector.setCountryListener(listener);
+            }
+        });
+    }
+
+    // For testing
+    boolean isSystemReady() {
+        return mSystemReady;
+    }
+}
diff --git a/services/java/com/android/server/DemoDataSet.java b/services/java/com/android/server/DemoDataSet.java
deleted file mode 100644
index 277985f..0000000
--- a/services/java/com/android/server/DemoDataSet.java
+++ /dev/null
@@ -1,140 +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 com.android.server;
-
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.Intent;
-import android.content.res.AssetManager;
-import android.net.Uri;
-import android.os.Environment;
-import android.provider.Contacts;
-import android.provider.Settings;
-import android.provider.MediaStore.Images;
-import android.util.Config;
-import android.util.Slog;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.InputStream;
-import java.io.OutputStream;
-
-public class DemoDataSet
-{
-    private final static String LOG_TAG = "DemoDataSet";
-
-    private ContentResolver mContentResolver;
-
-    public final void add(Context context)
-    {
-        mContentResolver = context.getContentResolver();
-
-        // Remove all the old data
-        mContentResolver.delete(Contacts.People.CONTENT_URI, null, null);
-
-        // Add the new data
-        addDefaultData();
-        
-        // Add images from /android/images
-        addDefaultImages();
-    }
-
-    private final void addDefaultImages()
-    {
-        File rootDirectory = Environment.getRootDirectory();
-        String [] files
-            = new File(rootDirectory, "images").list();
-        int count = files.length;
-
-        if (count == 0) {
-            Slog.i(LOG_TAG, "addDefaultImages: no images found!");
-            return;
-        }
-
-        for (int i = 0; i < count; i++)
-        {
-            String name = files[i];
-            String path = rootDirectory + "/" + name;
-            
-            try {
-                Images.Media.insertImage(mContentResolver, path, name, null);
-            } catch (FileNotFoundException e) {
-                Slog.e(LOG_TAG, "Failed to import image " + path, e);
-            }
-        }
-    }
-    
-    private final void addDefaultData()
-    {
-        Slog.i(LOG_TAG, "Adding default data...");
-
-//       addImage("Violet", "images/violet.png");
-//       addImage("Corky", "images/corky.png");
-
-        // PENDING: should this be done here?!?!
-        Intent intent = new Intent(
-                Intent.ACTION_CALL, Uri.fromParts("voicemail", "", null));
-        addShortcut("1", intent);
-    }
-
-    private final Uri addImage(String name, Uri file)
-    {
-        ContentValues imagev = new ContentValues();
-        imagev.put("name", name);
-
-        Uri url = null;
-
-        AssetManager ass = AssetManager.getSystem();
-        InputStream in = null;
-        OutputStream out = null;
-        
-        try
-        {
-            in = ass.open(file.toString());
-
-            url = mContentResolver.insert(Images.Media.INTERNAL_CONTENT_URI, imagev);
-            out = mContentResolver.openOutputStream(url);
-
-            final int size = 8 * 1024;
-            byte[] buf = new byte[size];
-
-            int count = 0;
-            do
-            {
-                count = in.read(buf, 0, size);
-                if (count > 0) {
-                    out.write(buf, 0, count);
-                }
-            } while (count > 0);
-        }
-        catch (Exception e)
-        {
-            Slog.e(LOG_TAG, "Failed to insert image '" + file + "'", e);
-            url = null;
-        }
-
-        return url;
-    }
-
-    private final Uri addShortcut(String shortcut, Intent intent)
-    {
-        if (Config.LOGV) Slog.v(LOG_TAG, "addShortcut: shortcut=" + shortcut + ", intent=" + intent);
-        return Settings.Bookmarks.add(mContentResolver, intent, null, null,
-                                      shortcut != null ? shortcut.charAt(0) : 0, 0);
-    }
-}
diff --git a/services/java/com/android/server/DevicePolicyManagerService.java b/services/java/com/android/server/DevicePolicyManagerService.java
index 19d146d..0c3a0e6 100644
--- a/services/java/com/android/server/DevicePolicyManagerService.java
+++ b/services/java/com/android/server/DevicePolicyManagerService.java
@@ -33,6 +33,7 @@
 import android.app.admin.IDevicePolicyManager;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
@@ -46,6 +47,8 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemClock;
+import android.net.Proxy;
+import android.provider.Settings;
 import android.util.Slog;
 import android.util.PrintWriterPrinter;
 import android.util.Printer;
@@ -58,46 +61,66 @@
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.PrintWriter;
+import java.net.InetSocketAddress;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
+import java.util.Set;
 
 /**
  * Implementation of the device policy APIs.
  */
 public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
     static final String TAG = "DevicePolicyManagerService";
-    
+
     final Context mContext;
     final MyPackageMonitor mMonitor;
 
     IPowerManager mIPowerManager;
-    
+
     int mActivePasswordQuality = DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
     int mActivePasswordLength = 0;
+    int mActivePasswordUpperCase = 0;
+    int mActivePasswordLowerCase = 0;
+    int mActivePasswordLetters = 0;
+    int mActivePasswordNumeric = 0;
+    int mActivePasswordSymbols = 0;
+    int mActivePasswordNonLetter = 0;
     int mFailedPasswordAttempts = 0;
-    
+
     int mPasswordOwner = -1;
-    
+
     final HashMap<ComponentName, ActiveAdmin> mAdminMap
             = new HashMap<ComponentName, ActiveAdmin>();
     final ArrayList<ActiveAdmin> mAdminList
             = new ArrayList<ActiveAdmin>();
-    
+
     static class ActiveAdmin {
         final DeviceAdminInfo info;
-        
+
         int passwordQuality = DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
         int minimumPasswordLength = 0;
+        int passwordHistoryLength = 0;
+        int minimumPasswordUpperCase = 0;
+        int minimumPasswordLowerCase = 0;
+        int minimumPasswordLetters = 1;
+        int minimumPasswordNumeric = 1;
+        int minimumPasswordSymbols = 1;
+        int minimumPasswordNonLetter = 0;
         long maximumTimeToUnlock = 0;
         int maximumFailedPasswordsForWipe = 0;
-        
+
+        // TODO: review implementation decisions with frameworks team
+        boolean specifiesGlobalProxy = false;
+        String globalProxySpec = null;
+        String globalProxyExclusionList = null;
+
         ActiveAdmin(DeviceAdminInfo _info) {
             info = _info;
         }
-        
+
         int getUid() { return info.getActivityInfo().applicationInfo.uid; }
-        
+
         void writeToXml(XmlSerializer out)
                 throws IllegalArgumentException, IllegalStateException, IOException {
             out.startTag(null, "policies");
@@ -110,7 +133,42 @@
                 if (minimumPasswordLength > 0) {
                     out.startTag(null, "min-password-length");
                     out.attribute(null, "value", Integer.toString(minimumPasswordLength));
-                    out.endTag(null, "mn-password-length");
+                    out.endTag(null, "min-password-length");
+                }
+                if(passwordHistoryLength > 0) {
+                    out.startTag(null, "password-history-length");
+                    out.attribute(null, "value", Integer.toString(passwordHistoryLength));
+                    out.endTag(null, "password-history-length");
+                }
+                if (minimumPasswordUpperCase > 0) {
+                    out.startTag(null, "min-password-uppercase");
+                    out.attribute(null, "value", Integer.toString(minimumPasswordUpperCase));
+                    out.endTag(null, "min-password-uppercase");
+                }
+                if (minimumPasswordLowerCase > 0) {
+                    out.startTag(null, "min-password-lowercase");
+                    out.attribute(null, "value", Integer.toString(minimumPasswordLowerCase));
+                    out.endTag(null, "min-password-lowercase");
+                }
+                if (minimumPasswordLetters > 0) {
+                    out.startTag(null, "min-password-letters");
+                    out.attribute(null, "value", Integer.toString(minimumPasswordLetters));
+                    out.endTag(null, "min-password-letters");
+                }
+                if (minimumPasswordNumeric > 0) {
+                    out.startTag(null, "min-password-numeric");
+                    out.attribute(null, "value", Integer.toString(minimumPasswordNumeric));
+                    out.endTag(null, "min-password-numeric");
+                }
+                if (minimumPasswordSymbols > 0) {
+                    out.startTag(null, "min-password-symbols");
+                    out.attribute(null, "value", Integer.toString(minimumPasswordSymbols));
+                    out.endTag(null, "min-password-symbols");
+                }
+                if (minimumPasswordNonLetter > 0) {
+                    out.startTag(null, "min-password-nonletter");
+                    out.attribute(null, "value", Integer.toString(minimumPasswordNonLetter));
+                    out.endTag(null, "min-password-nonletter");
                 }
             }
             if (maximumTimeToUnlock != DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) {
@@ -123,8 +181,23 @@
                 out.attribute(null, "value", Integer.toString(maximumFailedPasswordsForWipe));
                 out.endTag(null, "max-failed-password-wipe");
             }
+            if (specifiesGlobalProxy) {
+                out.startTag(null, "specifies-global-proxy");
+                out.attribute(null, "value", Boolean.toString(specifiesGlobalProxy));
+                out.endTag(null, "specifies_global_proxy");
+                if (globalProxySpec != null) {
+                    out.startTag(null, "global-proxy-spec");
+                    out.attribute(null, "value", globalProxySpec);
+                    out.endTag(null, "global-proxy-spec");
+                }
+                if (globalProxyExclusionList != null) {
+                    out.startTag(null, "global-proxy-exclusion-list");
+                    out.attribute(null, "value", globalProxyExclusionList);
+                    out.endTag(null, "global-proxy-exclusion-list");
+                }
+            }
         }
-        
+
         void readFromXml(XmlPullParser parser)
                 throws XmlPullParserException, IOException {
             int outerDepth = parser.getDepth();
@@ -143,19 +216,49 @@
                 } else if ("min-password-length".equals(tag)) {
                     minimumPasswordLength = Integer.parseInt(
                             parser.getAttributeValue(null, "value"));
+                } else if ("password-history-length".equals(tag)) {
+                    passwordHistoryLength = Integer.parseInt(
+                            parser.getAttributeValue(null, "value"));
+                } else if ("min-password-uppercase".equals(tag)) {
+                    minimumPasswordUpperCase = Integer.parseInt(
+                            parser.getAttributeValue(null, "value"));
+                } else if ("min-password-lowercase".equals(tag)) {
+                    minimumPasswordLowerCase = Integer.parseInt(
+                            parser.getAttributeValue(null, "value"));
+                } else if ("min-password-letters".equals(tag)) {
+                    minimumPasswordLetters = Integer.parseInt(
+                            parser.getAttributeValue(null, "value"));
+                } else if ("min-password-numeric".equals(tag)) {
+                    minimumPasswordNumeric = Integer.parseInt(
+                            parser.getAttributeValue(null, "value"));
+                } else if ("min-password-symbols".equals(tag)) {
+                    minimumPasswordSymbols = Integer.parseInt(
+                            parser.getAttributeValue(null, "value"));
+                } else if ("min-password-nonletter".equals(tag)) {
+                    minimumPasswordNonLetter = Integer.parseInt(
+                            parser.getAttributeValue(null, "value"));
                 } else if ("max-time-to-unlock".equals(tag)) {
                     maximumTimeToUnlock = Long.parseLong(
                             parser.getAttributeValue(null, "value"));
                 } else if ("max-failed-password-wipe".equals(tag)) {
                     maximumFailedPasswordsForWipe = Integer.parseInt(
                             parser.getAttributeValue(null, "value"));
+                } else if ("specifies-global-proxy".equals(tag)) {
+                    specifiesGlobalProxy = Boolean.getBoolean(
+                            parser.getAttributeValue(null, "value"));
+                } else if ("global-proxy-spec".equals(tag)) {
+                    globalProxySpec =
+                        parser.getAttributeValue(null, "value");
+                } else if ("global-proxy-exclusion-list".equals(tag)) {
+                    globalProxyExclusionList =
+                        parser.getAttributeValue(null, "value");
                 } else {
                     Slog.w(TAG, "Unknown admin tag: " + tag);
                 }
                 XmlUtils.skipCurrentTag(parser);
             }
         }
-        
+
         void dump(String prefix, PrintWriter pw) {
             pw.print(prefix); pw.print("uid="); pw.println(getUid());
             pw.print(prefix); pw.println("policies:");
@@ -166,23 +269,47 @@
                 }
             }
             pw.print(prefix); pw.print("passwordQuality=0x");
-                    pw.print(Integer.toHexString(passwordQuality));
-                    pw.print(" minimumPasswordLength=");
+                    pw.println(Integer.toHexString(passwordQuality));
+            pw.print(prefix); pw.print("minimumPasswordLength=");
                     pw.println(minimumPasswordLength);
+            pw.print(prefix); pw.print("passwordHistoryLength=");
+                    pw.println(passwordHistoryLength);
+            pw.print(prefix); pw.print("minimumPasswordUpperCase=");
+                    pw.println(minimumPasswordUpperCase);
+            pw.print(prefix); pw.print("minimumPasswordLowerCase=");
+                    pw.println(minimumPasswordLowerCase);
+            pw.print(prefix); pw.print("minimumPasswordLetters=");
+                    pw.println(minimumPasswordLetters);
+            pw.print(prefix); pw.print("minimumPasswordNumeric=");
+                    pw.println(minimumPasswordNumeric);
+            pw.print(prefix); pw.print("minimumPasswordSymbols=");
+                    pw.println(minimumPasswordSymbols);
+            pw.print(prefix); pw.print("minimumPasswordNonLetter=");
+                    pw.println(minimumPasswordNonLetter);
             pw.print(prefix); pw.print("maximumTimeToUnlock=");
                     pw.println(maximumTimeToUnlock);
             pw.print(prefix); pw.print("maximumFailedPasswordsForWipe=");
                     pw.println(maximumFailedPasswordsForWipe);
+            pw.print(prefix); pw.print("specifiesGlobalProxy=");
+                    pw.println(specifiesGlobalProxy);
+            if (globalProxySpec != null) {
+                pw.print(prefix); pw.print("globalProxySpec=");
+                        pw.println(globalProxySpec);
+            }
+            if (globalProxyExclusionList != null) {
+                pw.print(prefix); pw.print("globalProxyEclusionList=");
+                        pw.println(globalProxyExclusionList);
+            }
         }
     }
-    
+
     class MyPackageMonitor extends PackageMonitor {
         public void onSomePackagesChanged() {
             synchronized (DevicePolicyManagerService.this) {
                 boolean removed = false;
                 for (int i=mAdminList.size()-1; i>=0; i--) {
                     ActiveAdmin aa = mAdminList.get(i);
-                    int change = isPackageDisappearing(aa.info.getPackageName()); 
+                    int change = isPackageDisappearing(aa.info.getPackageName());
                     if (change == PACKAGE_PERMANENT_CHANGE
                             || change == PACKAGE_TEMPORARY_CHANGE) {
                         Slog.w(TAG, "Admin unexpectedly uninstalled: "
@@ -207,7 +334,7 @@
             }
         }
     }
-    
+
     /**
      * Instantiates the service.
      */
@@ -224,7 +351,7 @@
         }
         return mIPowerManager;
     }
-    
+
     ActiveAdmin getActiveAdminUncheckedLocked(ComponentName who) {
         ActiveAdmin admin = mAdminMap.get(who);
         if (admin != null
@@ -234,7 +361,7 @@
         }
         return null;
     }
-    
+
     ActiveAdmin getActiveAdminForCallerLocked(ComponentName who, int reqPolicy)
             throws SecurityException {
         final int callingUid = Binder.getCallingUid();
@@ -265,13 +392,13 @@
                     + Binder.getCallingUid() + " for policy #" + reqPolicy);
         }
     }
-    
+
     void sendAdminCommandLocked(ActiveAdmin admin, String action) {
         Intent intent = new Intent(action);
         intent.setComponent(admin.info.getComponent());
         mContext.sendBroadcast(intent);
     }
-    
+
     void sendAdminCommandLocked(String action, int reqPolicy) {
         final int N = mAdminList.size();
         if (N > 0) {
@@ -283,19 +410,24 @@
             }
         }
     }
-    
+
     void removeActiveAdminLocked(ComponentName adminReceiver) {
         ActiveAdmin admin = getActiveAdminUncheckedLocked(adminReceiver);
         if (admin != null) {
+            boolean doProxyCleanup =
+                admin.info.usesPolicy(DeviceAdminInfo.USES_POLICY_SETS_GLOBAL_PROXY);
             sendAdminCommandLocked(admin,
                     DeviceAdminReceiver.ACTION_DEVICE_ADMIN_DISABLED);
             // XXX need to wait for it to complete.
             mAdminList.remove(admin);
             mAdminMap.remove(adminReceiver);
             validatePasswordOwnerLocked();
+            if (doProxyCleanup) {
+                resetGlobalProxy();
+            }
         }
     }
-    
+
     public DeviceAdminInfo findAdmin(ComponentName adminName) {
         Intent resolveIntent = new Intent();
         resolveIntent.setComponent(adminName);
@@ -304,7 +436,7 @@
         if (infos == null || infos.size() <= 0) {
             throw new IllegalArgumentException("Unknown admin: " + adminName);
         }
-        
+
         try {
             return new DeviceAdminInfo(mContext, infos.get(0));
         } catch (XmlPullParserException e) {
@@ -315,7 +447,7 @@
             return null;
         }
     }
-    
+
     private static JournaledFile makeJournaledFile() {
         final String base = "/data/system/device_policies.xml";
         return new JournaledFile(new File(base), new File(base + ".tmp"));
@@ -331,7 +463,7 @@
             out.startDocument(null, true);
 
             out.startTag(null, "policies");
-            
+
             final int N = mAdminList.size();
             for (int i=0; i<N; i++) {
                 ActiveAdmin ap = mAdminList.get(i);
@@ -342,26 +474,36 @@
                     out.endTag(null, "admin");
                 }
             }
-            
+
             if (mPasswordOwner >= 0) {
                 out.startTag(null, "password-owner");
                 out.attribute(null, "value", Integer.toString(mPasswordOwner));
                 out.endTag(null, "password-owner");
             }
-            
+
             if (mFailedPasswordAttempts != 0) {
                 out.startTag(null, "failed-password-attempts");
                 out.attribute(null, "value", Integer.toString(mFailedPasswordAttempts));
                 out.endTag(null, "failed-password-attempts");
             }
-            
-            if (mActivePasswordQuality != 0 || mActivePasswordLength != 0) {
+
+            if (mActivePasswordQuality != 0 || mActivePasswordLength != 0
+                    || mActivePasswordUpperCase != 0 || mActivePasswordLowerCase != 0
+                    || mActivePasswordLetters != 0 || mActivePasswordNumeric != 0
+                    || mActivePasswordSymbols != 0 || mActivePasswordNonLetter != 0) {
                 out.startTag(null, "active-password");
                 out.attribute(null, "quality", Integer.toString(mActivePasswordQuality));
                 out.attribute(null, "length", Integer.toString(mActivePasswordLength));
+                out.attribute(null, "uppercase", Integer.toString(mActivePasswordUpperCase));
+                out.attribute(null, "lowercase", Integer.toString(mActivePasswordLowerCase));
+                out.attribute(null, "letters", Integer.toString(mActivePasswordLetters));
+                out.attribute(null, "numeric", Integer
+                        .toString(mActivePasswordNumeric));
+                out.attribute(null, "symbols", Integer.toString(mActivePasswordSymbols));
+                out.attribute(null, "nonletter", Integer.toString(mActivePasswordNonLetter));
                 out.endTag(null, "active-password");
             }
-            
+
             out.endTag(null, "policies");
 
             out.endDocument();
@@ -439,6 +581,18 @@
                             parser.getAttributeValue(null, "quality"));
                     mActivePasswordLength = Integer.parseInt(
                             parser.getAttributeValue(null, "length"));
+                    mActivePasswordUpperCase = Integer.parseInt(
+                            parser.getAttributeValue(null, "uppercase"));
+                    mActivePasswordLowerCase = Integer.parseInt(
+                            parser.getAttributeValue(null, "lowercase"));
+                    mActivePasswordLetters = Integer.parseInt(
+                            parser.getAttributeValue(null, "letters"));
+                    mActivePasswordNumeric = Integer.parseInt(
+                            parser.getAttributeValue(null, "numeric"));
+                    mActivePasswordSymbols = Integer.parseInt(
+                            parser.getAttributeValue(null, "symbols"));
+                    mActivePasswordNonLetter = Integer.parseInt(
+                            parser.getAttributeValue(null, "nonletter"));
                     XmlUtils.skipCurrentTag(parser);
                 } else {
                     Slog.w(TAG, "Unknown tag: " + tag);
@@ -476,10 +630,16 @@
                     + Integer.toHexString(utils.getActivePasswordQuality()));
             mActivePasswordQuality = DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
             mActivePasswordLength = 0;
+            mActivePasswordUpperCase = 0;
+            mActivePasswordLowerCase = 0;
+            mActivePasswordLetters = 0;
+            mActivePasswordNumeric = 0;
+            mActivePasswordSymbols = 0;
+            mActivePasswordNonLetter = 0;
         }
-        
+
         validatePasswordOwnerLocked();
-        
+
         long timeMs = getMaximumTimeToLock(null);
         if (timeMs <= 0) {
             timeMs = Integer.MAX_VALUE;
@@ -498,12 +658,13 @@
             case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC:
             case DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC:
             case DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC:
+            case DevicePolicyManager.PASSWORD_QUALITY_COMPLEX:
                 return;
         }
         throw new IllegalArgumentException("Invalid quality constant: 0x"
                 + Integer.toHexString(quality));
     }
-    
+
     void validatePasswordOwnerLocked() {
         if (mPasswordOwner >= 0) {
             boolean haveOwner = false;
@@ -520,17 +681,17 @@
             }
         }
     }
-    
+
     public void systemReady() {
         synchronized (this) {
             loadSettingsLocked();
         }
     }
-    
+
     public void setActiveAdmin(ComponentName adminReceiver) {
         mContext.enforceCallingOrSelfPermission(
                 android.Manifest.permission.BIND_DEVICE_ADMIN, null);
-        
+
         DeviceAdminInfo info = findAdmin(adminReceiver);
         if (info == null) {
             throw new IllegalArgumentException("Bad admin: " + adminReceiver);
@@ -552,13 +713,13 @@
             }
         }
     }
-    
+
     public boolean isAdminActive(ComponentName adminReceiver) {
         synchronized (this) {
             return getActiveAdminUncheckedLocked(adminReceiver) != null;
         }
     }
-    
+
     public List<ComponentName> getActiveAdmins() {
         synchronized (this) {
             final int N = mAdminList.size();
@@ -572,7 +733,7 @@
             return res;
         }
     }
-    
+
     public boolean packageHasActiveAdmins(String packageName) {
         synchronized (this) {
             final int N = mAdminList.size();
@@ -584,7 +745,7 @@
             return false;
         }
     }
-    
+
     public void removeActiveAdmin(ComponentName adminReceiver) {
         synchronized (this) {
             ActiveAdmin admin = getActiveAdminUncheckedLocked(adminReceiver);
@@ -603,10 +764,10 @@
             }
         }
     }
-    
+
     public void setPasswordQuality(ComponentName who, int quality) {
         validateQualityConstant(quality);
-        
+
         synchronized (this) {
             if (who == null) {
                 throw new NullPointerException("ComponentName is null");
@@ -619,16 +780,16 @@
             }
         }
     }
-    
+
     public int getPasswordQuality(ComponentName who) {
         synchronized (this) {
             int mode = DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
-            
+
             if (who != null) {
                 ActiveAdmin admin = getActiveAdminUncheckedLocked(who);
                 return admin != null ? admin.passwordQuality : mode;
             }
-            
+
             final int N = mAdminList.size();
             for  (int i=0; i<N; i++) {
                 ActiveAdmin admin = mAdminList.get(i);
@@ -639,7 +800,7 @@
             return mode;
         }
     }
-    
+
     public void setPasswordMinimumLength(ComponentName who, int length) {
         synchronized (this) {
             if (who == null) {
@@ -653,16 +814,16 @@
             }
         }
     }
-    
+
     public int getPasswordMinimumLength(ComponentName who) {
         synchronized (this) {
             int length = 0;
-            
+
             if (who != null) {
                 ActiveAdmin admin = getActiveAdminUncheckedLocked(who);
                 return admin != null ? admin.minimumPasswordLength : length;
             }
-            
+
             final int N = mAdminList.size();
             for  (int i=0; i<N; i++) {
                 ActiveAdmin admin = mAdminList.get(i);
@@ -673,18 +834,267 @@
             return length;
         }
     }
-    
+
+    public void setPasswordHistoryLength(ComponentName who, int length) {
+        synchronized (this) {
+            if (who == null) {
+                throw new NullPointerException("ComponentName is null");
+            }
+            ActiveAdmin ap = getActiveAdminForCallerLocked(who,
+                    DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD);
+            if (ap.passwordHistoryLength != length) {
+                ap.passwordHistoryLength = length;
+                saveSettingsLocked();
+            }
+        }
+    }
+
+    public int getPasswordHistoryLength(ComponentName who) {
+        synchronized (this) {
+            int length = 0;
+
+            if (who != null) {
+                ActiveAdmin admin = getActiveAdminUncheckedLocked(who);
+                return admin != null ? admin.passwordHistoryLength : length;
+            }
+
+            final int N = mAdminList.size();
+            for (int i = 0; i < N; i++) {
+                ActiveAdmin admin = mAdminList.get(i);
+                if (length < admin.passwordHistoryLength) {
+                    length = admin.passwordHistoryLength;
+                }
+            }
+            return length;
+        }
+    }
+
+    public void setPasswordMinimumUpperCase(ComponentName who, int length) {
+        synchronized (this) {
+            if (who == null) {
+                throw new NullPointerException("ComponentName is null");
+            }
+            ActiveAdmin ap = getActiveAdminForCallerLocked(who,
+                    DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD);
+            if (ap.minimumPasswordUpperCase != length) {
+                ap.minimumPasswordUpperCase = length;
+                saveSettingsLocked();
+            }
+        }
+    }
+
+    public int getPasswordMinimumUpperCase(ComponentName who) {
+        synchronized (this) {
+            int length = 0;
+
+            if (who != null) {
+                ActiveAdmin admin = getActiveAdminUncheckedLocked(who);
+                return admin != null ? admin.minimumPasswordUpperCase : length;
+            }
+
+            final int N = mAdminList.size();
+            for (int i=0; i<N; i++) {
+                ActiveAdmin admin = mAdminList.get(i);
+                if (length < admin.minimumPasswordUpperCase) {
+                    length = admin.minimumPasswordUpperCase;
+                }
+            }
+            return length;
+        }
+    }
+
+    public void setPasswordMinimumLowerCase(ComponentName who, int length) {
+        synchronized (this) {
+            if (who == null) {
+                throw new NullPointerException("ComponentName is null");
+            }
+            ActiveAdmin ap = getActiveAdminForCallerLocked(who,
+                    DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD);
+            if (ap.minimumPasswordLowerCase != length) {
+                ap.minimumPasswordLowerCase = length;
+                saveSettingsLocked();
+            }
+        }
+    }
+
+    public int getPasswordMinimumLowerCase(ComponentName who) {
+        synchronized (this) {
+            int length = 0;
+
+            if (who != null) {
+                ActiveAdmin admin = getActiveAdminUncheckedLocked(who);
+                return admin != null ? admin.minimumPasswordLowerCase : length;
+            }
+
+            final int N = mAdminList.size();
+            for (int i=0; i<N; i++) {
+                ActiveAdmin admin = mAdminList.get(i);
+                if (length < admin.minimumPasswordLowerCase) {
+                    length = admin.minimumPasswordLowerCase;
+                }
+            }
+            return length;
+        }
+    }
+
+    public void setPasswordMinimumLetters(ComponentName who, int length) {
+        synchronized (this) {
+            if (who == null) {
+                throw new NullPointerException("ComponentName is null");
+            }
+            ActiveAdmin ap = getActiveAdminForCallerLocked(who,
+                    DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD);
+            if (ap.minimumPasswordLetters != length) {
+                ap.minimumPasswordLetters = length;
+                saveSettingsLocked();
+            }
+        }
+    }
+
+    public int getPasswordMinimumLetters(ComponentName who) {
+        synchronized (this) {
+            int length = 0;
+
+            if (who != null) {
+                ActiveAdmin admin = getActiveAdminUncheckedLocked(who);
+                return admin != null ? admin.minimumPasswordLetters : length;
+            }
+
+            final int N = mAdminList.size();
+            for (int i=0; i<N; i++) {
+                ActiveAdmin admin = mAdminList.get(i);
+                if (length < admin.minimumPasswordLetters) {
+                    length = admin.minimumPasswordLetters;
+                }
+            }
+            return length;
+        }
+    }
+
+    public void setPasswordMinimumNumeric(ComponentName who, int length) {
+        synchronized (this) {
+            if (who == null) {
+                throw new NullPointerException("ComponentName is null");
+            }
+            ActiveAdmin ap = getActiveAdminForCallerLocked(who,
+                    DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD);
+            if (ap.minimumPasswordNumeric != length) {
+                ap.minimumPasswordNumeric = length;
+                saveSettingsLocked();
+            }
+        }
+    }
+
+    public int getPasswordMinimumNumeric(ComponentName who) {
+        synchronized (this) {
+            int length = 0;
+
+            if (who != null) {
+                ActiveAdmin admin = getActiveAdminUncheckedLocked(who);
+                return admin != null ? admin.minimumPasswordNumeric : length;
+            }
+
+            final int N = mAdminList.size();
+            for (int i = 0; i < N; i++) {
+                ActiveAdmin admin = mAdminList.get(i);
+                if (length < admin.minimumPasswordNumeric) {
+                    length = admin.minimumPasswordNumeric;
+                }
+            }
+            return length;
+        }
+    }
+
+    public void setPasswordMinimumSymbols(ComponentName who, int length) {
+        synchronized (this) {
+            if (who == null) {
+                throw new NullPointerException("ComponentName is null");
+            }
+            ActiveAdmin ap = getActiveAdminForCallerLocked(who,
+                    DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD);
+            if (ap.minimumPasswordSymbols != length) {
+                ap.minimumPasswordSymbols = length;
+                saveSettingsLocked();
+            }
+        }
+    }
+
+    public int getPasswordMinimumSymbols(ComponentName who) {
+        synchronized (this) {
+            int length = 0;
+
+            if (who != null) {
+                ActiveAdmin admin = getActiveAdminUncheckedLocked(who);
+                return admin != null ? admin.minimumPasswordSymbols : length;
+            }
+
+            final int N = mAdminList.size();
+            for  (int i=0; i<N; i++) {
+                ActiveAdmin admin = mAdminList.get(i);
+                if (length < admin.minimumPasswordSymbols) {
+                    length = admin.minimumPasswordSymbols;
+                }
+            }
+            return length;
+        }
+    }
+
+    public void setPasswordMinimumNonLetter(ComponentName who, int length) {
+        synchronized (this) {
+            if (who == null) {
+                throw new NullPointerException("ComponentName is null");
+            }
+            ActiveAdmin ap = getActiveAdminForCallerLocked(who,
+                    DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD);
+            if (ap.minimumPasswordNonLetter != length) {
+                ap.minimumPasswordNonLetter = length;
+                saveSettingsLocked();
+            }
+        }
+    }
+
+    public int getPasswordMinimumNonLetter(ComponentName who) {
+        synchronized (this) {
+            int length = 0;
+
+            if (who != null) {
+                ActiveAdmin admin = getActiveAdminUncheckedLocked(who);
+                return admin != null ? admin.minimumPasswordNonLetter : length;
+            }
+
+            final int N = mAdminList.size();
+            for (int i=0; i<N; i++) {
+                ActiveAdmin admin = mAdminList.get(i);
+                if (length < admin.minimumPasswordNonLetter) {
+                    length = admin.minimumPasswordNonLetter;
+                }
+            }
+            return length;
+        }
+    }
+
     public boolean isActivePasswordSufficient() {
         synchronized (this) {
             // This API can only be called by an active device admin,
             // so try to retrieve it to check that the caller is one.
             getActiveAdminForCallerLocked(null,
                     DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD);
-            return mActivePasswordQuality >= getPasswordQuality(null)
-                    && mActivePasswordLength >= getPasswordMinimumLength(null);
+            if (mActivePasswordQuality < getPasswordQuality(null)
+                    || mActivePasswordLength < getPasswordMinimumLength(null)) {
+                return false;
+            }
+            if(mActivePasswordQuality != DevicePolicyManager.PASSWORD_QUALITY_COMPLEX) {
+                return true;
+            }
+            return mActivePasswordUpperCase >= getPasswordMinimumUpperCase(null)
+                    && mActivePasswordLowerCase >= getPasswordMinimumLowerCase(null)
+                    && mActivePasswordLetters >= getPasswordMinimumLetters(null)
+                    && mActivePasswordNumeric >= getPasswordMinimumNumeric(null)
+                    && mActivePasswordSymbols >= getPasswordMinimumSymbols(null)
+                    && mActivePasswordNonLetter >= getPasswordMinimumNonLetter(null);
         }
     }
-    
+
     public int getCurrentFailedPasswordAttempts() {
         synchronized (this) {
             // This API can only be called by an active device admin,
@@ -694,7 +1104,7 @@
             return mFailedPasswordAttempts;
         }
     }
-    
+
     public void setMaximumFailedPasswordsForWipe(ComponentName who, int num) {
         synchronized (this) {
             // This API can only be called by an active device admin,
@@ -709,16 +1119,16 @@
             }
         }
     }
-    
+
     public int getMaximumFailedPasswordsForWipe(ComponentName who) {
         synchronized (this) {
             int count = 0;
-            
+
             if (who != null) {
                 ActiveAdmin admin = getActiveAdminUncheckedLocked(who);
                 return admin != null ? admin.maximumFailedPasswordsForWipe : count;
             }
-            
+
             final int N = mAdminList.size();
             for  (int i=0; i<N; i++) {
                 ActiveAdmin admin = mAdminList.get(i);
@@ -732,7 +1142,7 @@
             return count;
         }
     }
-    
+
     public boolean resetPassword(String password, int flags) {
         int quality;
         synchronized (this) {
@@ -743,14 +1153,15 @@
             quality = getPasswordQuality(null);
             if (quality != DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) {
                 int realQuality = LockPatternUtils.computePasswordQuality(password);
-                if (realQuality < quality) {
+                if (realQuality < quality
+                        && quality != DevicePolicyManager.PASSWORD_QUALITY_COMPLEX) {
                     Slog.w(TAG, "resetPassword: password quality 0x"
                             + Integer.toHexString(quality)
                             + " does not meet required quality 0x"
                             + Integer.toHexString(quality));
                     return false;
                 }
-                quality = realQuality;
+                quality = Math.max(realQuality, quality);
             }
             int length = getPasswordMinimumLength(null);
             if (password.length() < length) {
@@ -758,14 +1169,86 @@
                         + " does not meet required length " + length);
                 return false;
             }
+            if (quality == DevicePolicyManager.PASSWORD_QUALITY_COMPLEX) {
+                int letters = 0;
+                int uppercase = 0;
+                int lowercase = 0;
+                int numbers = 0;
+                int symbols = 0;
+                int nonletter = 0;
+                for (int i = 0; i < password.length(); i++) {
+                    char c = password.charAt(i);
+                    if (c >= 'A' && c <= 'Z') {
+                        letters++;
+                        uppercase++;
+                    } else if (c >= 'a' && c <= 'z') {
+                        letters++;
+                        lowercase++;
+                    } else if (c >= '0' && c <= '9') {
+                        numbers++;
+                        nonletter++;
+                    } else {
+                        symbols++;
+                        nonletter++;
+                    }
+                }
+                int neededLetters = getPasswordMinimumLetters(null);
+                if(letters < neededLetters) {
+                    Slog.w(TAG, "resetPassword: number of letters " + letters
+                            + " does not meet required number of letters " + neededLetters);
+                    return false;
+                }
+                int neededNumbers = getPasswordMinimumNumeric(null);
+                if (numbers < neededNumbers) {
+                    Slog
+                            .w(TAG, "resetPassword: number of numerical digits " + numbers
+                                    + " does not meet required number of numerical digits "
+                                    + neededNumbers);
+                    return false;
+                }
+                int neededLowerCase = getPasswordMinimumLowerCase(null);
+                if (lowercase < neededLowerCase) {
+                    Slog.w(TAG, "resetPassword: number of lowercase letters " + lowercase
+                            + " does not meet required number of lowercase letters "
+                            + neededLowerCase);
+                    return false;
+                }
+                int neededUpperCase = getPasswordMinimumUpperCase(null);
+                if (uppercase < neededUpperCase) {
+                    Slog.w(TAG, "resetPassword: number of uppercase letters " + uppercase
+                            + " does not meet required number of uppercase letters "
+                            + neededUpperCase);
+                    return false;
+                }
+                int neededSymbols = getPasswordMinimumSymbols(null);
+                if (symbols < neededSymbols) {
+                    Slog.w(TAG, "resetPassword: number of special symbols " + symbols
+                            + " does not meet required number of special symbols " + neededSymbols);
+                    return false;
+                }
+                int neededNonLetter = getPasswordMinimumNonLetter(null);
+                if (nonletter < neededNonLetter) {
+                    Slog.w(TAG, "resetPassword: number of non-letter characters " + nonletter
+                            + " does not meet required number of non-letter characters "
+                            + neededNonLetter);
+                    return false;
+                }
+            }
+
+            LockPatternUtils utils = new LockPatternUtils(mContext);
+            if(utils.checkPasswordHistory(password)) {
+                Slog.w(TAG, "resetPassword: password is the same as one of the last "
+                        + getPasswordHistoryLength(null) + " passwords");
+                return false;
+            }
         }
-        
+
         int callingUid = Binder.getCallingUid();
         if (mPasswordOwner >= 0 && mPasswordOwner != callingUid) {
             Slog.w(TAG, "resetPassword: already set by another uid and not entered by user");
             return false;
         }
-        
+
         // Don't do this with the lock held, because it is going to call
         // back in to the service.
         long ident = Binder.clearCallingIdentity();
@@ -783,10 +1266,10 @@
         } finally {
             Binder.restoreCallingIdentity(ident);
         }
-        
+
         return true;
     }
-    
+
     public void setMaximumTimeToLock(ComponentName who, long timeMs) {
         synchronized (this) {
             if (who == null) {
@@ -796,16 +1279,16 @@
                     DeviceAdminInfo.USES_POLICY_FORCE_LOCK);
             if (ap.maximumTimeToUnlock != timeMs) {
                 ap.maximumTimeToUnlock = timeMs;
-                
+
                 long ident = Binder.clearCallingIdentity();
                 try {
                     saveSettingsLocked();
-                    
+
                     timeMs = getMaximumTimeToLock(null);
                     if (timeMs <= 0) {
                         timeMs = Integer.MAX_VALUE;
                     }
-                    
+
                     try {
                         getIPowerManager().setMaximumScreenOffTimeount((int)timeMs);
                     } catch (RemoteException e) {
@@ -817,16 +1300,16 @@
             }
         }
     }
-    
+
     public long getMaximumTimeToLock(ComponentName who) {
         synchronized (this) {
             long time = 0;
-            
+
             if (who != null) {
                 ActiveAdmin admin = getActiveAdminUncheckedLocked(who);
                 return admin != null ? admin.maximumTimeToUnlock : time;
             }
-            
+
             final int N = mAdminList.size();
             for  (int i=0; i<N; i++) {
                 ActiveAdmin admin = mAdminList.get(i);
@@ -840,7 +1323,7 @@
             return time;
         }
     }
-    
+
     public void lockNow() {
         synchronized (this) {
             // This API can only be called by an active device admin,
@@ -857,7 +1340,7 @@
             }
         }
     }
-    
+
     void wipeDataLocked(int flags) {
         try {
             RecoverySystem.rebootWipeUserData(mContext);
@@ -865,7 +1348,7 @@
             Slog.w(TAG, "Failed requesting data wipe", e);
         }
     }
-    
+
     public void wipeData(int flags) {
         synchronized (this) {
             // This API can only be called by an active device admin,
@@ -880,11 +1363,11 @@
             }
         }
     }
-    
+
     public void getRemoveWarning(ComponentName comp, final RemoteCallback result) {
         mContext.enforceCallingOrSelfPermission(
                 android.Manifest.permission.BIND_DEVICE_ADMIN, null);
-        
+
         synchronized (this) {
             ActiveAdmin admin = getActiveAdminUncheckedLocked(comp);
             if (admin == null) {
@@ -907,20 +1390,30 @@
             }, null, Activity.RESULT_OK, null, null);
         }
     }
-    
-    public void setActivePasswordState(int quality, int length) {
+
+    public void setActivePasswordState(int quality, int length, int letters, int uppercase,
+            int lowercase, int numbers, int symbols, int nonletter) {
         mContext.enforceCallingOrSelfPermission(
                 android.Manifest.permission.BIND_DEVICE_ADMIN, null);
-        
+
         validateQualityConstant(quality);
-        
+
         synchronized (this) {
             if (mActivePasswordQuality != quality || mActivePasswordLength != length
-                    || mFailedPasswordAttempts != 0) {
+                    || mFailedPasswordAttempts != 0 || mActivePasswordLetters != letters
+                    || mActivePasswordUpperCase != uppercase
+                    || mActivePasswordLowerCase != lowercase || mActivePasswordNumeric != numbers
+                    || mActivePasswordSymbols != symbols || mActivePasswordNonLetter != nonletter) {
                 long ident = Binder.clearCallingIdentity();
                 try {
                     mActivePasswordQuality = quality;
                     mActivePasswordLength = length;
+                    mActivePasswordLetters = letters;
+                    mActivePasswordLowerCase = lowercase;
+                    mActivePasswordUpperCase = uppercase;
+                    mActivePasswordNumeric = numbers;
+                    mActivePasswordSymbols = symbols;
+                    mActivePasswordNonLetter = nonletter;
                     mFailedPasswordAttempts = 0;
                     saveSettingsLocked();
                     sendAdminCommandLocked(DeviceAdminReceiver.ACTION_PASSWORD_CHANGED,
@@ -931,11 +1424,11 @@
             }
         }
     }
-    
+
     public void reportFailedPasswordAttempt() {
         mContext.enforceCallingOrSelfPermission(
                 android.Manifest.permission.BIND_DEVICE_ADMIN, null);
-        
+
         synchronized (this) {
             long ident = Binder.clearCallingIdentity();
             try {
@@ -952,11 +1445,11 @@
             }
         }
     }
-    
+
     public void reportSuccessfulPasswordAttempt() {
         mContext.enforceCallingOrSelfPermission(
                 android.Manifest.permission.BIND_DEVICE_ADMIN, null);
-        
+
         synchronized (this) {
             if (mFailedPasswordAttempts != 0 || mPasswordOwner >= 0) {
                 long ident = Binder.clearCallingIdentity();
@@ -972,7 +1465,95 @@
             }
         }
     }
-    
+
+    public ComponentName setGlobalProxy(ComponentName who, String proxySpec,
+            String exclusionList) {
+        synchronized(this) {
+            if (who == null) {
+                throw new NullPointerException("ComponentName is null");
+            }
+
+            ActiveAdmin admin = getActiveAdminForCallerLocked(who,
+                    DeviceAdminInfo.USES_POLICY_SETS_GLOBAL_PROXY);
+
+            // Scan through active admins and find if anyone has already
+            // set the global proxy.
+            final int N = mAdminList.size();
+            Set<ComponentName> compSet = mAdminMap.keySet();
+            for  (ComponentName component : compSet) {
+                ActiveAdmin ap = mAdminMap.get(component);
+                if ((ap.specifiesGlobalProxy) && (!component.equals(who))) {
+                    // Another admin already sets the global proxy
+                    // Return it to the caller.
+                    return component;
+                }
+            }
+            if (proxySpec == null) {
+                admin.specifiesGlobalProxy = false;
+                admin.globalProxySpec = null;
+                admin.globalProxyExclusionList = null;
+            } else {
+
+                admin.specifiesGlobalProxy = true;
+                admin.globalProxySpec = proxySpec;
+                admin.globalProxyExclusionList = exclusionList;
+            }
+
+            // Reset the global proxy accordingly
+            // Do this using system permissions, as apps cannot write to secure settings
+            long origId = Binder.clearCallingIdentity();
+            resetGlobalProxy();
+            Binder.restoreCallingIdentity(origId);
+            return null;
+        }
+    }
+
+    public ComponentName getGlobalProxyAdmin() {
+        synchronized(this) {
+            // Scan through active admins and find if anyone has already
+            // set the global proxy.
+            final int N = mAdminList.size();
+            for (int i = 0; i < N; i++) {
+                ActiveAdmin ap = mAdminList.get(i);
+                if (ap.specifiesGlobalProxy) {
+                    // Device admin sets the global proxy
+                    // Return it to the caller.
+                    return ap.info.getComponent();
+                }
+            }
+        }
+        // No device admin sets the global proxy.
+        return null;
+    }
+
+    private void resetGlobalProxy() {
+        final int N = mAdminList.size();
+        for (int i = 0; i < N; i++) {
+            ActiveAdmin ap = mAdminList.get(i);
+            if (ap.specifiesGlobalProxy) {
+                saveGlobalProxy(ap.globalProxySpec, ap.globalProxyExclusionList);
+                return;
+            }
+        }
+        // No device admins defining global proxies - reset global proxy settings to none
+        saveGlobalProxy(null, null);
+    }
+
+    private void saveGlobalProxy(String proxySpec, String exclusionList) {
+        if (exclusionList == null) {
+            exclusionList = "";
+        }
+        if (proxySpec == null) {
+            proxySpec = "";
+        }
+        // Remove white spaces
+        proxySpec = proxySpec.trim();
+        exclusionList = exclusionList.trim();
+        ContentResolver res = mContext.getContentResolver();
+        Settings.Secure.putString(res, Settings.Secure.HTTP_PROXY, proxySpec);
+        Settings.Secure.putString(res, Settings.Secure.HTTP_PROXY_EXCLUSION_LIST, exclusionList);
+    }
+
     @Override
     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
@@ -983,12 +1564,12 @@
                     + ", uid=" + Binder.getCallingUid());
             return;
         }
-        
+
         final Printer p = new PrintWriterPrinter(pw);
-        
+
         synchronized (this) {
             p.println("Current Device Policy Manager state:");
-            
+
             p.println("  Enabled Device Admins:");
             final int N = mAdminList.size();
             for (int i=0; i<N; i++) {
@@ -999,11 +1580,17 @@
                     ap.dump("    ", pw);
                 }
             }
-            
+
             pw.println(" ");
             pw.print("  mActivePasswordQuality=0x");
                     pw.println(Integer.toHexString(mActivePasswordQuality));
             pw.print("  mActivePasswordLength="); pw.println(mActivePasswordLength);
+            pw.print("  mActivePasswordUpperCase="); pw.println(mActivePasswordUpperCase);
+            pw.print("  mActivePasswordLowerCase="); pw.println(mActivePasswordLowerCase);
+            pw.print("  mActivePasswordLetters="); pw.println(mActivePasswordLetters);
+            pw.print("  mActivePasswordNumeric="); pw.println(mActivePasswordNumeric);
+            pw.print("  mActivePasswordSymbols="); pw.println(mActivePasswordSymbols);
+            pw.print("  mActivePasswordNonLetter="); pw.println(mActivePasswordNonLetter);
             pw.print("  mFailedPasswordAttempts="); pw.println(mFailedPasswordAttempts);
             pw.print("  mPasswordOwner="); pw.println(mPasswordOwner);
         }
diff --git a/services/java/com/android/server/InputMethodManagerService.java b/services/java/com/android/server/InputMethodManagerService.java
index 4d35bec..36b3a5e 100644
--- a/services/java/com/android/server/InputMethodManagerService.java
+++ b/services/java/com/android/server/InputMethodManagerService.java
@@ -469,8 +469,10 @@
                 mContext.getContentResolver(),
                 Settings.Secure.ENABLED_INPUT_METHODS);
         Slog.i(TAG, "Enabled input methods: " + enabledStr);
-        if (enabledStr == null) {
-            Slog.i(TAG, "Enabled input methods has not been set, enabling all");
+        final String defaultIme = Settings.Secure.getString(mContext
+                .getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
+        if (enabledStr == null || TextUtils.isEmpty(defaultIme)) {
+            Slog.i(TAG, "Enabled input methods or default IME has not been set, enabling all");
             InputMethodInfo defIm = null;
             StringBuilder sb = new StringBuilder(256);
             final int N = mMethodList.size();
@@ -978,7 +980,7 @@
     void setInputMethodLocked(String id) {
         InputMethodInfo info = mMethodMap.get(id);
         if (info == null) {
-            throw new IllegalArgumentException("Unknown id: " + mCurMethodId);
+            throw new IllegalArgumentException("Unknown id: " + id);
         }
 
         if (id.equals(mCurMethodId)) {
@@ -1476,7 +1478,7 @@
 
         String defaultIme = Settings.Secure.getString(mContext
                 .getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
-        if (!map.containsKey(defaultIme)) {
+        if (!TextUtils.isEmpty(defaultIme) && !map.containsKey(defaultIme)) {
             if (chooseNewDefaultIMELocked()) {
                 updateFromSettingsLocked();
             }
diff --git a/services/java/com/android/server/LocationManagerService.java b/services/java/com/android/server/LocationManagerService.java
index 3bcf427..5697181 100644
--- a/services/java/com/android/server/LocationManagerService.java
+++ b/services/java/com/android/server/LocationManagerService.java
@@ -465,10 +465,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);
@@ -476,7 +477,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/MountService.java b/services/java/com/android/server/MountService.java
index d7b92ec..ef5e9cc 100644
--- a/services/java/com/android/server/MountService.java
+++ b/services/java/com/android/server/MountService.java
@@ -127,6 +127,8 @@
     private boolean                               mBooted = false;
     private boolean                               mReady = false;
     private boolean                               mSendUmsConnectedOnBoot = false;
+    // true if we should fake MEDIA_MOUNTED state for external storage
+    private boolean                               mEmulateExternalStorage = false;
 
     /**
      * Private hash of currently mounted secure containers.
@@ -319,7 +321,9 @@
                             String path = Environment.getExternalStorageDirectory().getPath();
                             String state = getVolumeState(path);
 
-                            if (state.equals(Environment.MEDIA_UNMOUNTED)) {
+                            if (mEmulateExternalStorage) {
+                                notifyVolumeStateChange(null, path, VolumeState.NoMedia, VolumeState.Mounted);
+                            } else if (state.equals(Environment.MEDIA_UNMOUNTED)) {
                                 int rc = doMountVolume(path);
                                 if (rc != StorageResultCode.OperationSucceeded) {
                                     Slog.e(TAG, String.format("Boot-time mount failed (%d)", rc));
@@ -390,11 +394,13 @@
             Slog.w(TAG, String.format("Duplicate state transition (%s -> %s)", mLegacyState, state));
             return;
         }
-        // Update state on PackageManager
-        if (Environment.MEDIA_UNMOUNTED.equals(state)) {
-            mPms.updateExternalMediaStatus(false, false);
-        } else if (Environment.MEDIA_MOUNTED.equals(state)) {
-            mPms.updateExternalMediaStatus(true, false);
+        // Update state on PackageManager, but only of real events
+        if (!mEmulateExternalStorage) {
+            if (Environment.MEDIA_UNMOUNTED.equals(state)) {
+                mPms.updateExternalMediaStatus(false, false);
+            } else if (Environment.MEDIA_MOUNTED.equals(state)) {
+                mPms.updateExternalMediaStatus(true, false);
+            }
         }
         String oldState = mLegacyState;
         mLegacyState = state;
@@ -894,6 +900,13 @@
     public MountService(Context context) {
         mContext = context;
 
+        mEmulateExternalStorage = context.getResources().getBoolean(
+                com.android.internal.R.bool.config_emulateExternalStorage);
+        if (mEmulateExternalStorage) {
+            Slog.d(TAG, "using emulated external storage");
+            mLegacyState = Environment.MEDIA_MOUNTED;
+        }
+
         // XXX: This will go away soon in favor of IMountServiceObserver
         mPms = (PackageManagerService) ServiceManager.getService("package");
 
diff --git a/services/java/com/android/server/NotificationManagerService.java b/services/java/com/android/server/NotificationManagerService.java
index 63325dd..67796c6 100755
--- a/services/java/com/android/server/NotificationManagerService.java
+++ b/services/java/com/android/server/NotificationManagerService.java
@@ -61,6 +61,7 @@
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityManager;
 import android.widget.Toast;
+import android.widget.Toast;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -281,6 +282,10 @@
                     Notification.FLAG_FOREGROUND_SERVICE);
         }
 
+        public void onNotificationClear(String pkg, String tag, int id) {
+            cancelNotification(pkg, tag, id, 0, 0); // maybe add some flags?
+        }
+
         public void onPanelRevealed() {
             synchronized (mNotificationList) {
                 // sound
diff --git a/services/java/com/android/server/PackageManagerService.java b/services/java/com/android/server/PackageManagerService.java
index 0ff33d1..8a876a2 100644
--- a/services/java/com/android/server/PackageManagerService.java
+++ b/services/java/com/android/server/PackageManagerService.java
@@ -9594,7 +9594,8 @@
     * Update media status on PackageManager.
     */
    public void updateExternalMediaStatus(final boolean mediaStatus, final boolean reportStatus) {
-       if (Binder.getCallingUid() != Process.SYSTEM_UID) {
+       int callingUid = Binder.getCallingUid();
+       if (callingUid != 0 && callingUid != Process.SYSTEM_UID) {
            throw new SecurityException("Media status can only be updated by the system");
        }
        synchronized (mPackages) {
diff --git a/services/java/com/android/server/SamplingProfilerService.java b/services/java/com/android/server/SamplingProfilerService.java
new file mode 100644
index 0000000..26af7f7
--- /dev/null
+++ b/services/java/com/android/server/SamplingProfilerService.java
@@ -0,0 +1,118 @@
+/*
+ * 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.content.ContentResolver;
+import android.os.DropBoxManager;
+import android.os.FileObserver;
+import android.os.Binder;
+
+import android.util.Slog;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.os.SystemProperties;
+import android.provider.Settings;
+import com.android.internal.os.SamplingProfilerIntegration;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+public class SamplingProfilerService extends Binder {
+
+    private static final String TAG = "SamplingProfilerService";
+    private static final boolean LOCAL_LOGV = false;
+    public static final String SNAPSHOT_DIR = SamplingProfilerIntegration.SNAPSHOT_DIR;
+
+    private FileObserver snapshotObserver;
+
+    public SamplingProfilerService(Context context) {
+        registerSettingObserver(context);
+        startWorking(context);
+    }
+
+    private void startWorking(Context context) {
+        if (LOCAL_LOGV) Slog.v(TAG, "starting SamplingProfilerService!");
+
+        final DropBoxManager dropbox =
+                (DropBoxManager) context.getSystemService(Context.DROPBOX_SERVICE);
+
+        // before FileObserver is ready, there could have already been some snapshots
+        // in the directory, we don't want to miss them
+        File[] snapshotFiles = new File(SNAPSHOT_DIR).listFiles();
+        for (int i = 0; snapshotFiles != null && i < snapshotFiles.length; i++) {
+            handleSnapshotFile(snapshotFiles[i], dropbox);
+        }
+
+        // detect new snapshot and put it in dropbox
+        // delete it afterwards no matter what happened before
+        // Note: needs listening at event ATTRIB rather than CLOSE_WRITE, because we set the
+        // readability of snapshot files after writing them!
+        snapshotObserver = new FileObserver(SNAPSHOT_DIR, FileObserver.ATTRIB) {
+            @Override
+            public void onEvent(int event, String path) {
+                handleSnapshotFile(new File(SNAPSHOT_DIR, path), dropbox);
+            }
+        };
+        snapshotObserver.startWatching();
+
+        if (LOCAL_LOGV) Slog.v(TAG, "SamplingProfilerService activated");
+    }
+
+    private void handleSnapshotFile(File file, DropBoxManager dropbox) {
+        try {
+            dropbox.addFile(TAG, file, 0);
+            if (LOCAL_LOGV) Slog.v(TAG, file.getPath() + " added to dropbox");
+        } catch (IOException e) {
+            Slog.e(TAG, "Can't add " + file.getPath() + " to dropbox", e);
+        } finally {
+            file.delete();
+        }
+    }
+
+    private void registerSettingObserver(Context context) {
+        ContentResolver contentResolver = context.getContentResolver();
+        contentResolver.registerContentObserver(
+                Settings.Secure.getUriFor(Settings.Secure.SAMPLING_PROFILER_HZ),
+                false, new SamplingProfilerSettingsObserver(contentResolver));
+    }
+
+    @Override
+    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println("SamplingProfilerService:");
+        pw.println("Watching directory: " + SNAPSHOT_DIR);
+    }
+
+    private class SamplingProfilerSettingsObserver extends ContentObserver {
+        private ContentResolver mContentResolver;
+        public SamplingProfilerSettingsObserver(ContentResolver contentResolver) {
+            super(null);
+            mContentResolver = contentResolver;
+            onChange(false);
+        }
+        @Override
+        public void onChange(boolean selfChange) {
+            Integer samplingProfilerHz = Settings.Secure.getInt(
+                    mContentResolver, Settings.Secure.SAMPLING_PROFILER_HZ, 0);
+            // setting this secure property will start or stop sampling profiler,
+            // as well as adjust the frequency of taking snapshots.
+            SystemProperties.set("persist.sys.profiler_hz", samplingProfilerHz.toString());
+        }
+    }
+}
+
diff --git a/services/java/com/android/server/StatusBarManagerService.java b/services/java/com/android/server/StatusBarManagerService.java
index 4177432..717c309 100644
--- a/services/java/com/android/server/StatusBarManagerService.java
+++ b/services/java/com/android/server/StatusBarManagerService.java
@@ -84,6 +84,7 @@
         void onSetDisabled(int status);
         void onClearAll();
         void onNotificationClick(String pkg, String tag, int id);
+        void onNotificationClear(String pkg, String tag, int id);
         void onPanelRevealed();
         void onNotificationError(String pkg, String tag, int id,
                 int uid, int initialPid, String message);
@@ -302,6 +303,12 @@
         mNotificationCallbacks.onNotificationError(pkg, tag, id, uid, initialPid, message);
     }
 
+    public void onNotificationClear(String pkg, String tag, int id) {
+        enforceStatusBarService();
+
+        mNotificationCallbacks.onNotificationClear(pkg, tag, id);
+    }
+
     public void onClearAllNotifications() {
         enforceStatusBarService();
 
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index ab869bb..2412b7d 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -23,26 +23,26 @@
 import dalvik.system.VMRuntime;
 import dalvik.system.Zygote;
 
+import android.accounts.AccountManagerService;
 import android.app.ActivityManagerNative;
 import android.bluetooth.BluetoothAdapter;
-import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.ContentService;
 import android.content.Context;
-import android.content.Intent;
 import android.content.pm.IPackageManager;
 import android.database.ContentObserver;
-import android.database.Cursor;
 import android.media.AudioService;
-import android.os.*;
-import android.provider.Contacts.People;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.os.SystemProperties;
 import android.provider.Settings;
 import android.server.BluetoothA2dpService;
 import android.server.BluetoothService;
 import android.server.search.SearchManagerService;
 import android.util.EventLog;
 import android.util.Slog;
-import android.accounts.AccountManagerService;
 
 import java.io.File;
 import java.util.Timer;
@@ -50,11 +50,8 @@
 
 class ServerThread extends Thread {
     private static final String TAG = "SystemServer";
-    private final static boolean INCLUDE_DEMO = false;
 
-    private static final int LOG_BOOT_PROGRESS_SYSTEM_RUN = 3010;
-
-    private ContentResolver mContentResolver;
+    ContentResolver mContentResolver;
 
     private class AdbSettingsObserver extends ContentObserver {
         public AdbSettingsObserver() {
@@ -209,6 +206,7 @@
         NotificationManagerService notification = null;
         WallpaperManagerService wallpaper = null;
         LocationManagerService location = null;
+        CountryDetectorService countryDetector = null;
 
         if (factoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL) {
             try {
@@ -319,6 +317,14 @@
             }
 
             try {
+                Slog.i(TAG, "Country Detector");
+                countryDetector = new CountryDetectorService(context);
+                ServiceManager.addService(Context.COUNTRY_DETECTOR, countryDetector);
+            } catch (Throwable e) {
+                Slog.e(TAG, "Failure starting Country Detector", e);
+            }
+
+            try {
                 Slog.i(TAG, "Search Service");
                 ServiceManager.addService(Context.SEARCH_SERVICE,
                         new SearchManagerService(context));
@@ -326,11 +332,6 @@
                 Slog.e(TAG, "Failure starting Search Service", e);
             }
 
-            if (INCLUDE_DEMO) {
-                Slog.i(TAG, "Installing demo data...");
-                (new DemoThread(context)).start();
-            }
-
             try {
                 Slog.i(TAG, "DropBox Service");
                 ServiceManager.addService(Context.DROPBOX_SERVICE,
@@ -415,6 +416,18 @@
             } catch (Throwable e) {
                 Slog.e(TAG, "Failure starting DiskStats Service", e);
             }
+
+            try {
+                // need to add this service even if SamplingProfilerIntegration.isEnabled()
+                // is false, because it is this service that detects system property change and
+                // turns on SamplingProfilerIntegration. Plus, when sampling profiler doesn't work,
+                // there is little overhead for running this service.
+                Slog.i(TAG, "SamplingProfiler Service");
+                ServiceManager.addService("samplingprofiler",
+                            new SamplingProfilerService(context));
+            } catch (Throwable e) {
+                Slog.e(TAG, "Failure starting SamplingProfiler Service", e);
+            }
         }
 
         // make sure the ADB_ENABLED setting value matches the secure property value
@@ -475,6 +488,7 @@
         final InputMethodManagerService immF = imm;
         final RecognitionManagerService recognitionF = recognition;
         final LocationManagerService locationF = location;
+        final CountryDetectorService countryDetectorF = countryDetector;
 
         // We now tell the activity manager it is okay to run third party
         // code.  It will call back into us once it has gotten to the state
@@ -502,6 +516,7 @@
                 if (wallpaperF != null) wallpaperF.systemReady();
                 if (immF != null) immF.systemReady();
                 if (locationF != null) locationF.systemReady();
+                if (countryDetectorF != null) countryDetectorF.systemReady();
                 if (throttleF != null) throttleF.systemReady();
             }
         });
@@ -511,37 +526,7 @@
     }
 }
 
-class DemoThread extends Thread
-{
-    DemoThread(Context context)
-    {
-        mContext = context;
-    }
-
-    @Override
-    public void run()
-    {
-        try {
-            Cursor c = mContext.getContentResolver().query(People.CONTENT_URI, null, null, null, null);
-            boolean hasData = c != null && c.moveToFirst();
-            if (c != null) {
-                c.deactivate();
-            }
-            if (!hasData) {
-                DemoDataSet dataset = new DemoDataSet();
-                dataset.add(mContext);
-            }
-        } catch (Throwable e) {
-            Slog.e("SystemServer", "Failure installing demo data", e);
-        }
-
-    }
-
-    Context mContext;
-}
-
-public class SystemServer
-{
+public class SystemServer {
     private static final String TAG = "SystemServer";
 
     public static final int FACTORY_TEST_OFF = 0;
@@ -565,7 +550,7 @@
             timer.schedule(new TimerTask() {
                 @Override
                 public void run() {
-                    SamplingProfilerIntegration.writeSnapshot("system_server");
+                    SamplingProfilerIntegration.writeSnapshot("system_server", null);
                 }
             }, SNAPSHOT_INTERVAL, SNAPSHOT_INTERVAL);
         }
@@ -573,7 +558,7 @@
         // The system server has to run all of the time, so it needs to be
         // as efficient as possible with its memory usage.
         VMRuntime.getRuntime().setTargetHeapUtilization(0.8f);
-        
+
         System.loadLibrary("android_servers");
         init1(args);
     }
diff --git a/services/java/com/android/server/TelephonyRegistry.java b/services/java/com/android/server/TelephonyRegistry.java
index 664dfa5..73234df 100644
--- a/services/java/com/android/server/TelephonyRegistry.java
+++ b/services/java/com/android/server/TelephonyRegistry.java
@@ -19,6 +19,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
+import android.net.NetworkProperties;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.IBinder;
@@ -34,6 +35,7 @@
 import java.util.ArrayList;
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.net.NetworkInterface;
 
 import com.android.internal.app.IBatteryStats;
 import com.android.internal.telephony.ITelephonyRegistry;
@@ -88,9 +90,9 @@
 
     private String mDataConnectionApn = "";
 
-    private String[] mDataConnectionApnTypes = null;
+    private ArrayList<String> mConnectedApns;
 
-    private String mDataConnectionInterfaceName = "";
+    private NetworkProperties mDataConnectionProperties;
 
     private Bundle mCellLocation = new Bundle();
 
@@ -120,6 +122,7 @@
         }
         mContext = context;
         mBatteryStats = BatteryStatsService.getService();
+        mConnectedApns = new ArrayList<String>();
     }
 
     public void listen(String pkgForDebug, IPhoneStateListener callback, int events,
@@ -232,19 +235,20 @@
         if (!checkNotifyPermission("notifyCallState()")) {
             return;
         }
+        ArrayList<IBinder> removeList = new ArrayList<IBinder>();
         synchronized (mRecords) {
             mCallState = state;
             mCallIncomingNumber = incomingNumber;
-            for (int i = mRecords.size() - 1; i >= 0; i--) {
-                Record r = mRecords.get(i);
+            for (Record r : mRecords) {
                 if ((r.events & PhoneStateListener.LISTEN_CALL_STATE) != 0) {
                     try {
                         r.callback.onCallStateChanged(state, incomingNumber);
                     } catch (RemoteException ex) {
-                        remove(r.binder);
+                        removeList.add(r.binder);
                     }
                 }
             }
+            for (IBinder b : removeList) remove(b);
         }
         broadcastCallStateChanged(state, incomingNumber);
     }
@@ -255,8 +259,7 @@
         }
         synchronized (mRecords) {
             mServiceState = state;
-            for (int i = mRecords.size() - 1; i >= 0; i--) {
-                Record r = mRecords.get(i);
+            for (Record r : mRecords) {
                 if ((r.events & PhoneStateListener.LISTEN_SERVICE_STATE) != 0) {
                     sendServiceState(r, state);
                 }
@@ -269,10 +272,10 @@
         if (!checkNotifyPermission("notifySignalStrength()")) {
             return;
         }
+        ArrayList<IBinder> removeList = new ArrayList<IBinder>();
         synchronized (mRecords) {
             mSignalStrength = signalStrength;
-            for (int i = mRecords.size() - 1; i >= 0; i--) {
-                Record r = mRecords.get(i);
+            for (Record r : mRecords) {
                 if ((r.events & PhoneStateListener.LISTEN_SIGNAL_STRENGTHS) != 0) {
                     sendSignalStrength(r, signalStrength);
                 }
@@ -282,10 +285,11 @@
                         r.callback.onSignalStrengthChanged((gsmSignalStrength == 99 ? -1
                                 : gsmSignalStrength));
                     } catch (RemoteException ex) {
-                        remove(r.binder);
+                        removeList.add(r.binder);
                     }
                 }
             }
+            for (IBinder b : removeList) remove(b);
         }
         broadcastSignalStrengthChanged(signalStrength);
     }
@@ -294,18 +298,19 @@
         if (!checkNotifyPermission("notifyMessageWaitingChanged()")) {
             return;
         }
+        ArrayList<IBinder> removeList = new ArrayList<IBinder>();
         synchronized (mRecords) {
             mMessageWaiting = mwi;
-            for (int i = mRecords.size() - 1; i >= 0; i--) {
-                Record r = mRecords.get(i);
+            for (Record r : mRecords) {
                 if ((r.events & PhoneStateListener.LISTEN_MESSAGE_WAITING_INDICATOR) != 0) {
                     try {
                         r.callback.onMessageWaitingIndicatorChanged(mwi);
                     } catch (RemoteException ex) {
-                        remove(r.binder);
+                        removeList.add(r.binder);
                     }
                 }
             }
+            for (IBinder b : removeList) remove(b);
         }
     }
 
@@ -313,18 +318,19 @@
         if (!checkNotifyPermission("notifyCallForwardingChanged()")) {
             return;
         }
+        ArrayList<IBinder> removeList = new ArrayList<IBinder>();
         synchronized (mRecords) {
             mCallForwarding = cfi;
-            for (int i = mRecords.size() - 1; i >= 0; i--) {
-                Record r = mRecords.get(i);
+            for (Record r : mRecords) {
                 if ((r.events & PhoneStateListener.LISTEN_CALL_FORWARDING_INDICATOR) != 0) {
                     try {
                         r.callback.onCallForwardingIndicatorChanged(cfi);
                     } catch (RemoteException ex) {
-                        remove(r.binder);
+                        removeList.add(r.binder);
                     }
                 }
             }
+            for (IBinder b : removeList) remove(b);
         }
     }
 
@@ -332,56 +338,81 @@
         if (!checkNotifyPermission("notifyDataActivity()" )) {
             return;
         }
+        ArrayList<IBinder> removeList = new ArrayList<IBinder>();
         synchronized (mRecords) {
             mDataActivity = state;
-            for (int i = mRecords.size() - 1; i >= 0; i--) {
-                Record r = mRecords.get(i);
+            for (Record r : mRecords) {
                 if ((r.events & PhoneStateListener.LISTEN_DATA_ACTIVITY) != 0) {
                     try {
                         r.callback.onDataActivity(state);
                     } catch (RemoteException ex) {
-                        remove(r.binder);
+                        removeList.add(r.binder);
                     }
                 }
             }
+            for (IBinder b : removeList) remove(b);
         }
     }
 
     public void notifyDataConnection(int state, boolean isDataConnectivityPossible,
-            String reason, String apn, String[] apnTypes, String interfaceName, int networkType) {
+            String reason, String apn, String apnType, NetworkProperties networkProperties,
+            int networkType) {
         if (!checkNotifyPermission("notifyDataConnection()" )) {
             return;
         }
         synchronized (mRecords) {
-            mDataConnectionState = state;
+            boolean modified = false;
+            if (state == TelephonyManager.DATA_CONNECTED) {
+                if (!mConnectedApns.contains(apnType)) {
+                    mConnectedApns.add(apnType);
+                    if (mDataConnectionState != state) {
+                        mDataConnectionState = state;
+                        modified = true;
+                    }
+                }
+            } else {
+                mConnectedApns.remove(apnType);
+                if (mConnectedApns.isEmpty()) {
+                    mDataConnectionState = state;
+                    modified = true;
+                } else {
+                    // leave mDataConnectionState as is and
+                    // send out the new status for the APN in question.
+                }
+            }
             mDataConnectionPossible = isDataConnectivityPossible;
             mDataConnectionReason = reason;
             mDataConnectionApn = apn;
-            mDataConnectionApnTypes = apnTypes;
-            mDataConnectionInterfaceName = interfaceName;
-            mDataConnectionNetworkType = networkType;
-            for (int i = mRecords.size() - 1; i >= 0; i--) {
-                Record r = mRecords.get(i);
-                if ((r.events & PhoneStateListener.LISTEN_DATA_CONNECTION_STATE) != 0) {
-                    try {
-                        r.callback.onDataConnectionStateChanged(state, networkType);
-                    } catch (RemoteException ex) {
-                        remove(r.binder);
+            mDataConnectionProperties = networkProperties;
+            if (mDataConnectionNetworkType != networkType) {
+                mDataConnectionNetworkType = networkType;
+                modified = true;
+            }
+            if (modified) {
+                ArrayList<IBinder> removeList = new ArrayList<IBinder>();
+                for (Record r : mRecords) {
+                    if ((r.events & PhoneStateListener.LISTEN_DATA_CONNECTION_STATE) != 0) {
+                        try {
+                            r.callback.onDataConnectionStateChanged(state, networkType);
+                        } catch (RemoteException ex) {
+                            removeList.add(r.binder);
+                        }
                     }
                 }
+                for (IBinder b : removeList) remove(b);
             }
         }
         broadcastDataConnectionStateChanged(state, isDataConnectivityPossible, reason, apn,
-                apnTypes, interfaceName);
+                apnType, networkProperties);
     }
 
-    public void notifyDataConnectionFailed(String reason) {
+    public void notifyDataConnectionFailed(String reason, String apnType) {
         if (!checkNotifyPermission("notifyDataConnectionFailed()")) {
             return;
         }
         /*
-         * This is commented out because there is on onDataConnectionFailed callback
-         * on PhoneStateListener. There should be
+         * This is commented out because there is no onDataConnectionFailed callback
+         * in PhoneStateListener. There should be.
         synchronized (mRecords) {
             mDataConnectionFailedReason = reason;
             final int N = mRecords.size();
@@ -393,7 +424,7 @@
             }
         }
         */
-        broadcastDataConnectionFailed(reason);
+        broadcastDataConnectionFailed(reason, apnType);
     }
 
     public void notifyCellLocation(Bundle cellLocation) {
@@ -402,8 +433,7 @@
         }
         synchronized (mRecords) {
             mCellLocation = cellLocation;
-            for (int i = mRecords.size() - 1; i >= 0; i--) {
-                Record r = mRecords.get(i);
+            for (Record r : mRecords) {
                 if ((r.events & PhoneStateListener.LISTEN_CELL_LOCATION) != 0) {
                     sendCellLocation(r, cellLocation);
                 }
@@ -414,7 +444,7 @@
     /**
      * Copy the service state object so they can't mess it up in the local calls
      */
-    public void sendServiceState(Record r, ServiceState state) {
+    private void sendServiceState(Record r, ServiceState state) {
         try {
             r.callback.onServiceStateChanged(new ServiceState(state));
         } catch (RemoteException ex) {
@@ -460,11 +490,10 @@
             pw.println("  mDataConnectionPossible=" + mDataConnectionPossible);
             pw.println("  mDataConnectionReason=" + mDataConnectionReason);
             pw.println("  mDataConnectionApn=" + mDataConnectionApn);
-            pw.println("  mDataConnectionInterfaceName=" + mDataConnectionInterfaceName);
+            pw.println("  mDataConnectionProperties=" + mDataConnectionProperties);
             pw.println("  mCellLocation=" + mCellLocation);
             pw.println("registrations: count=" + recordCount);
-            for (int i = 0; i < recordCount; i++) {
-                Record r = mRecords.get(i);
+            for (Record r : mRecords) {
                 pw.println("  " + r.pkgForDebug + " 0x" + Integer.toHexString(r.events));
             }
         }
@@ -535,7 +564,7 @@
 
     private void broadcastDataConnectionStateChanged(int state,
             boolean isDataConnectivityPossible,
-            String reason, String apn, String[] apnTypes, String interfaceName) {
+            String reason, String apn, String apnType, NetworkProperties networkProperties) {
         // Note: not reporting to the battery stats service here, because the
         // status bar takes care of that after taking into account all of the
         // required info.
@@ -548,23 +577,23 @@
         if (reason != null) {
             intent.putExtra(Phone.STATE_CHANGE_REASON_KEY, reason);
         }
-        intent.putExtra(Phone.DATA_APN_KEY, apn);
-        String types = new String("");
-        if (apnTypes.length > 0) {
-            types = apnTypes[0];
-            for (int i = 1; i < apnTypes.length; i++) {
-                types = types+","+apnTypes[i];
+        if (networkProperties != null) {
+            intent.putExtra(Phone.DATA_NETWORK_PROPERTIES_KEY, networkProperties);
+            NetworkInterface iface = networkProperties.getInterface();
+            if (iface != null) {
+                intent.putExtra(Phone.DATA_IFACE_NAME_KEY, iface.getName());
             }
         }
-        intent.putExtra(Phone.DATA_APN_TYPES_KEY, types);
-        intent.putExtra(Phone.DATA_IFACE_NAME_KEY, interfaceName);
+        intent.putExtra(Phone.DATA_APN_KEY, apn);
+        intent.putExtra(Phone.DATA_APN_TYPE_KEY, apnType);
         mContext.sendStickyBroadcast(intent);
     }
 
-    private void broadcastDataConnectionFailed(String reason) {
+    private void broadcastDataConnectionFailed(String reason, String apnType) {
         Intent intent = new Intent(TelephonyIntents.ACTION_DATA_CONNECTION_FAILED);
         intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
         intent.putExtra(Phone.FAILURE_REASON_KEY, reason);
+        intent.putExtra(Phone.DATA_APN_TYPE_KEY, apnType);
         mContext.sendStickyBroadcast(intent);
     }
 
diff --git a/services/java/com/android/server/ThrottleService.java b/services/java/com/android/server/ThrottleService.java
index a93a6ee..d841cb3 100644
--- a/services/java/com/android/server/ThrottleService.java
+++ b/services/java/com/android/server/ThrottleService.java
@@ -60,6 +60,8 @@
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.util.Calendar;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
 import java.util.GregorianCalendar;
 import java.util.Properties;
 import java.util.Random;
@@ -83,8 +85,8 @@
     private static final long TESTING_THRESHOLD = 1 * 1024 * 1024;
 
     private int mPolicyPollPeriodSec;
-    private long mPolicyThreshold;
-    private int mPolicyThrottleValue;
+    private AtomicLong mPolicyThreshold;
+    private AtomicInteger mPolicyThrottleValue;
     private int mPolicyResetDay; // 1-28
     private int mPolicyNotificationsAllowedMask;
 
@@ -114,7 +116,7 @@
     private InterfaceObserver mInterfaceObserver;
     private SettingsObserver mSettingsObserver;
 
-    private int mThrottleIndex; // 0 for none, 1 for first throttle val, 2 for next, etc
+    private AtomicInteger mThrottleIndex; // 0 for none, 1 for first throttle val, 2 for next, etc
     private static final int THROTTLE_INDEX_UNINITIALIZED = -1;
     private static final int THROTTLE_INDEX_UNTHROTTLED   =  0;
 
@@ -126,6 +128,10 @@
         if (VDBG) Slog.v(TAG, "Starting ThrottleService");
         mContext = context;
 
+        mPolicyThreshold = new AtomicLong();
+        mPolicyThrottleValue = new AtomicInteger();
+        mThrottleIndex = new AtomicInteger();
+
         mNtpActive = false;
 
         mIface = mContext.getResources().getString(R.string.config_datause_iface);
@@ -214,7 +220,7 @@
     }
 
     private long ntpToWallTime(long ntpTime) {
-        long bestNow = getBestTime();
+        long bestNow = getBestTime(true); // do it quickly
         long localNow = System.currentTimeMillis();
         return localNow + (ntpTime - bestNow);
     }
@@ -222,40 +228,42 @@
     // TODO - fetch for the iface
     // return time in the local, system wall time, correcting for the use of ntp
 
-    public synchronized long getResetTime(String iface) {
+    public long getResetTime(String iface) {
         enforceAccessPermission();
         long resetTime = 0;
         if (mRecorder != null) {
-            resetTime = ntpToWallTime(mRecorder.getPeriodEnd());
+            resetTime = mRecorder.getPeriodEnd();
         }
+        resetTime = ntpToWallTime(resetTime);
         return resetTime;
     }
 
     // TODO - fetch for the iface
     // return time in the local, system wall time, correcting for the use of ntp
-    public synchronized long getPeriodStartTime(String iface) {
-        enforceAccessPermission();
+    public long getPeriodStartTime(String iface) {
         long startTime = 0;
+        enforceAccessPermission();
         if (mRecorder != null) {
-            startTime = ntpToWallTime(mRecorder.getPeriodStart());
+            startTime = mRecorder.getPeriodStart();
         }
+        startTime = ntpToWallTime(startTime);
         return startTime;
     }
     //TODO - a better name?  getCliffByteCountThreshold?
     // TODO - fetch for the iface
-    public synchronized long getCliffThreshold(String iface, int cliff) {
+    public long getCliffThreshold(String iface, int cliff) {
         enforceAccessPermission();
         if (cliff == 1) {
-            return mPolicyThreshold;
+           return mPolicyThreshold.get();
         }
         return 0;
     }
     // TODO - a better name? getThrottleRate?
     // TODO - fetch for the iface
-    public synchronized int getCliffLevel(String iface, int cliff) {
+    public int getCliffLevel(String iface, int cliff) {
         enforceAccessPermission();
         if (cliff == 1) {
-            return mPolicyThrottleValue;
+            return mPolicyThrottleValue.get();
         }
         return 0;
     }
@@ -267,10 +275,9 @@
     }
 
     // TODO - fetch for the iface
-    public synchronized long getByteCount(String iface, int dir, int period, int ago) {
+    public long getByteCount(String iface, int dir, int period, int ago) {
         enforceAccessPermission();
-        if ((period == ThrottleManager.PERIOD_CYCLE) &&
-                (mRecorder != null)) {
+        if ((period == ThrottleManager.PERIOD_CYCLE) && (mRecorder != null)) {
             if (dir == ThrottleManager.DIRECTION_TX) return mRecorder.getPeriodTx(ago);
             if (dir == ThrottleManager.DIRECTION_RX) return mRecorder.getPeriodRx(ago);
         }
@@ -279,10 +286,10 @@
 
     // TODO - a better name - getCurrentThrottleRate?
     // TODO - fetch for the iface
-    public synchronized int getThrottle(String iface) {
+    public int getThrottle(String iface) {
         enforceAccessPermission();
-        if (mThrottleIndex == 1) {
-            return mPolicyThrottleValue;
+        if (mThrottleIndex.get() == 1) {
+            return mPolicyThrottleValue.get();
         }
         return 0;
     }
@@ -305,22 +312,6 @@
                 }
             }, new IntentFilter(ACTION_RESET));
 
-        // use a new thread as we don't want to stall the system for file writes
-        mThread = new HandlerThread(TAG);
-        mThread.start();
-        mHandler = new MyHandler(mThread.getLooper());
-        mHandler.obtainMessage(EVENT_REBOOT_RECOVERY).sendToTarget();
-
-        mInterfaceObserver = new InterfaceObserver(mHandler, EVENT_IFACE_UP, mIface);
-        try {
-            mNMService.registerObserver(mInterfaceObserver);
-        } catch (RemoteException e) {
-            Slog.e(TAG, "Could not register InterfaceObserver " + e);
-        }
-
-        mSettingsObserver = new SettingsObserver(mHandler, EVENT_POLICY_CHANGED);
-        mSettingsObserver.observe(mContext);
-
         FileInputStream stream = null;
         try {
             Properties properties = new Properties();
@@ -337,6 +328,22 @@
                 } catch (Exception e) {}
             }
         }
+
+        // use a new thread as we don't want to stall the system for file writes
+        mThread = new HandlerThread(TAG);
+        mThread.start();
+        mHandler = new MyHandler(mThread.getLooper());
+        mHandler.obtainMessage(EVENT_REBOOT_RECOVERY).sendToTarget();
+
+        mInterfaceObserver = new InterfaceObserver(mHandler, EVENT_IFACE_UP, mIface);
+        try {
+            mNMService.registerObserver(mInterfaceObserver);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Could not register InterfaceObserver " + e);
+        }
+
+        mSettingsObserver = new SettingsObserver(mHandler, EVENT_POLICY_CHANGED);
+        mSettingsObserver.observe(mContext);
     }
 
 
@@ -375,7 +382,7 @@
             // check for sim change TODO
             // reregister for notification of policy change
 
-            mThrottleIndex = THROTTLE_INDEX_UNINITIALIZED;
+            mThrottleIndex.set(THROTTLE_INDEX_UNINITIALIZED);
 
             mRecorder = new DataRecorder(mContext, ThrottleService.this);
 
@@ -403,15 +410,16 @@
                     R.integer.config_datause_threshold_bytes);
             int defaultValue = mContext.getResources().getInteger(
                     R.integer.config_datause_throttle_kbitsps);
-            synchronized (ThrottleService.this) {
-                mPolicyThreshold = Settings.Secure.getLong(mContext.getContentResolver(),
-                        Settings.Secure.THROTTLE_THRESHOLD_BYTES, defaultThreshold);
-                mPolicyThrottleValue = Settings.Secure.getInt(mContext.getContentResolver(),
-                        Settings.Secure.THROTTLE_VALUE_KBITSPS, defaultValue);
-                if (testing) {
-                    mPolicyPollPeriodSec = TESTING_POLLING_PERIOD_SEC;
-                    mPolicyThreshold = TESTING_THRESHOLD;
-                }
+            long threshold = Settings.Secure.getLong(mContext.getContentResolver(),
+                    Settings.Secure.THROTTLE_THRESHOLD_BYTES, defaultThreshold);
+            int value = Settings.Secure.getInt(mContext.getContentResolver(),
+                    Settings.Secure.THROTTLE_VALUE_KBITSPS, defaultValue);
+
+            mPolicyThreshold.set(threshold);
+            mPolicyThrottleValue.set(value);
+            if (testing) {
+                mPolicyPollPeriodSec = TESTING_POLLING_PERIOD_SEC;
+                mPolicyThreshold.set(TESTING_THRESHOLD);
             }
 
             mPolicyResetDay = Settings.Secure.getInt(mContext.getContentResolver(),
@@ -423,10 +431,8 @@
                 Settings.Secure.putInt(mContext.getContentResolver(),
                 Settings.Secure.THROTTLE_RESET_DAY, mPolicyResetDay);
             }
-            synchronized (ThrottleService.this) {
-                if (mIface == null) {
-                    mPolicyThreshold = 0;
-                }
+            if (mIface == null) {
+                mPolicyThreshold.set(0);
             }
 
             int defaultNotificationType = mContext.getResources().getInteger(
@@ -437,15 +443,16 @@
             mMaxNtpCacheAgeSec = Settings.Secure.getInt(mContext.getContentResolver(),
                     Settings.Secure.THROTTLE_MAX_NTP_CACHE_AGE_SEC, MAX_NTP_CACHE_AGE_SEC);
 
-            if (VDBG || (mPolicyThreshold != 0)) {
+            if (VDBG || (mPolicyThreshold.get() != 0)) {
                 Slog.d(TAG, "onPolicyChanged testing=" + testing +", period=" +
-                        mPolicyPollPeriodSec + ", threshold=" + mPolicyThreshold + ", value=" +
-                        mPolicyThrottleValue + ", resetDay=" + mPolicyResetDay + ", noteType=" +
-                        mPolicyNotificationsAllowedMask + ", maxNtpCacheAge=" + mMaxNtpCacheAgeSec);
+                        mPolicyPollPeriodSec + ", threshold=" + mPolicyThreshold.get() +
+                        ", value=" + mPolicyThrottleValue.get() + ", resetDay=" + mPolicyResetDay +
+                        ", noteType=" + mPolicyNotificationsAllowedMask + ", maxNtpCacheAge=" +
+                        mMaxNtpCacheAgeSec);
             }
 
             // force updates
-            mThrottleIndex = THROTTLE_INDEX_UNINITIALIZED;
+            mThrottleIndex.set(THROTTLE_INDEX_UNINITIALIZED);
 
             onResetAlarm();
 
@@ -487,7 +494,7 @@
             long periodRx = mRecorder.getPeriodRx(0);
             long periodTx = mRecorder.getPeriodTx(0);
             long total = periodRx + periodTx;
-            if (VDBG || (mPolicyThreshold != 0)) {
+            if (VDBG || (mPolicyThreshold.get() != 0)) {
                 Slog.d(TAG, "onPollAlarm - roaming =" + roaming +
                         ", read =" + incRead + ", written =" + incWrite + ", new total =" + total);
             }
@@ -510,11 +517,11 @@
         private void onIfaceUp() {
             // if we were throttled before, be sure and set it again - the iface went down
             // (and may have disappeared all together) and these settings were lost
-            if (mThrottleIndex == 1) {
+            if (mThrottleIndex.get() == 1) {
                 try {
                     mNMService.setInterfaceThrottle(mIface, -1, -1);
                     mNMService.setInterfaceThrottle(mIface,
-                            mPolicyThrottleValue, mPolicyThrottleValue);
+                            mPolicyThrottleValue.get(), mPolicyThrottleValue.get());
                 } catch (Exception e) {
                     Slog.e(TAG, "error setting Throttle: " + e);
                 }
@@ -523,7 +530,8 @@
 
         private void checkThrottleAndPostNotification(long currentTotal) {
             // is throttling enabled?
-            if (mPolicyThreshold == 0) {
+            long threshold = mPolicyThreshold.get();
+            if (threshold == 0) {
                 clearThrottleAndNotification();
                 return;
             }
@@ -535,15 +543,13 @@
             }
 
             // check if we need to throttle
-            if (currentTotal > mPolicyThreshold) {
-                if (mThrottleIndex != 1) {
-                    synchronized (ThrottleService.this) {
-                        mThrottleIndex = 1;
-                    }
-                    if (DBG) Slog.d(TAG, "Threshold " + mPolicyThreshold + " exceeded!");
+            if (currentTotal > threshold) {
+                if (mThrottleIndex.get() != 1) {
+                    mThrottleIndex.set(1);
+                    if (DBG) Slog.d(TAG, "Threshold " + threshold + " exceeded!");
                     try {
                         mNMService.setInterfaceThrottle(mIface,
-                                mPolicyThrottleValue, mPolicyThrottleValue);
+                                mPolicyThrottleValue.get(), mPolicyThrottleValue.get());
                     } catch (Exception e) {
                         Slog.e(TAG, "error setting Throttle: " + e);
                     }
@@ -556,7 +562,8 @@
                             Notification.FLAG_ONGOING_EVENT);
 
                     Intent broadcast = new Intent(ThrottleManager.THROTTLE_ACTION);
-                    broadcast.putExtra(ThrottleManager.EXTRA_THROTTLE_LEVEL, mPolicyThrottleValue);
+                    broadcast.putExtra(ThrottleManager.EXTRA_THROTTLE_LEVEL,
+                            mPolicyThrottleValue.get());
                     mContext.sendStickyBroadcast(broadcast);
 
                 } // else already up!
@@ -579,8 +586,8 @@
                     long periodLength = end - start;
                     long now = System.currentTimeMillis();
                     long timeUsed = now - start;
-                    long warningThreshold = 2*mPolicyThreshold*timeUsed/(timeUsed+periodLength);
-                    if ((currentTotal > warningThreshold) && (currentTotal > mPolicyThreshold/4)) {
+                    long warningThreshold = 2*threshold*timeUsed/(timeUsed+periodLength);
+                    if ((currentTotal > warningThreshold) && (currentTotal > threshold/4)) {
                         if (mWarningNotificationSent == false) {
                             mWarningNotificationSent = true;
                             mNotificationManager.cancel(R.drawable.stat_sys_throttled);
@@ -625,11 +632,9 @@
         }
 
 
-        private synchronized void clearThrottleAndNotification() {
-            if (mThrottleIndex != THROTTLE_INDEX_UNTHROTTLED) {
-                synchronized (ThrottleService.this) {
-                    mThrottleIndex = THROTTLE_INDEX_UNTHROTTLED;
-                }
+        private void clearThrottleAndNotification() {
+            if (mThrottleIndex.get() != THROTTLE_INDEX_UNTHROTTLED) {
+                mThrottleIndex.set(THROTTLE_INDEX_UNTHROTTLED);
                 try {
                     mNMService.setInterfaceThrottle(mIface, -1, -1);
                 } catch (Exception e) {
@@ -687,12 +692,12 @@
         }
 
         private void onResetAlarm() {
-            if (VDBG || (mPolicyThreshold != 0)) {
+            if (VDBG || (mPolicyThreshold.get() != 0)) {
                 Slog.d(TAG, "onResetAlarm - last period had " + mRecorder.getPeriodRx(0) +
                         " bytes read and " + mRecorder.getPeriodTx(0) + " written");
             }
 
-            long now = getBestTime();
+            long now = getBestTime(false);
 
             if (mNtpActive || (mNtpServer == null)) {
                 Calendar end = calculatePeriodEnd(now);
@@ -719,20 +724,23 @@
 
         // will try to get the ntp time and switch to it if found.
         // will also cache the time so we don't fetch it repeatedly.
-        getBestTime();
+        getBestTime(false);
     }
 
     private static final int MAX_NTP_CACHE_AGE_SEC = 60 * 60 * 24; // 1 day
-    private static final int MAX_NTP_FETCH_WAIT = 10 * 1000;
+    private static final int MAX_NTP_FETCH_WAIT = 20 * 1000;
     private int mMaxNtpCacheAgeSec = MAX_NTP_CACHE_AGE_SEC;
     private long cachedNtp;
     private long cachedNtpTimestamp;
 
-    private long getBestTime() {
+    // if the request is tied to UI and ANR's are a danger, request a fast result
+    // the regular polling should have updated the cached time recently using the
+    // slower method (!fast)
+    private long getBestTime(boolean fast) {
         if (mNtpServer != null) {
             if (mNtpActive) {
                 long ntpAge = SystemClock.elapsedRealtime() - cachedNtpTimestamp;
-                if (ntpAge < mMaxNtpCacheAgeSec * 1000) {
+                if (ntpAge < mMaxNtpCacheAgeSec * 1000 || fast) {
                     if (VDBG) Slog.v(TAG, "using cached time");
                     return cachedNtp + ntpAge;
                 }
@@ -1025,39 +1033,57 @@
                 if (DBG) Slog.d(TAG, "data file empty");
                 return;
             }
-            synchronized (mParent) {
-                String[] parsed = data.split(":");
-                int parsedUsed = 0;
-                if (parsed.length < 6) {
-                    Slog.e(TAG, "reading data file with insufficient length - ignoring");
-                    return;
-                }
+            String[] parsed = data.split(":");
+            int parsedUsed = 0;
+            if (parsed.length < 6) {
+                Slog.e(TAG, "reading data file with insufficient length - ignoring");
+                return;
+            }
 
+            int periodCount;
+            long[] periodRxData;
+            long[] periodTxData;
+            int currentPeriod;
+            Calendar periodStart;
+            Calendar periodEnd;
+            try {
                 if (Integer.parseInt(parsed[parsedUsed++]) != DATA_FILE_VERSION) {
                     Slog.e(TAG, "reading data file with bad version - ignoring");
                     return;
                 }
 
-                mPeriodCount = Integer.parseInt(parsed[parsedUsed++]);
-                if (parsed.length != 5 + (2 * mPeriodCount)) {
+                periodCount = Integer.parseInt(parsed[parsedUsed++]);
+                if (parsed.length != 5 + (2 * periodCount)) {
                     Slog.e(TAG, "reading data file with bad length (" + parsed.length +
-                            " != " + (5+(2*mPeriodCount)) + ") - ignoring");
+                            " != " + (5 + (2 * periodCount)) + ") - ignoring");
                     return;
                 }
+                periodRxData = new long[periodCount];
+                for (int i = 0; i < periodCount; i++) {
+                    periodRxData[i] = Long.parseLong(parsed[parsedUsed++]);
+                }
+                periodTxData = new long[periodCount];
+                for (int i = 0; i < periodCount; i++) {
+                    periodTxData[i] = Long.parseLong(parsed[parsedUsed++]);
+                }
 
-                mPeriodRxData = new long[mPeriodCount];
-                for(int i = 0; i < mPeriodCount; i++) {
-                    mPeriodRxData[i] = Long.parseLong(parsed[parsedUsed++]);
-                }
-                mPeriodTxData = new long[mPeriodCount];
-                for(int i = 0; i < mPeriodCount; i++) {
-                    mPeriodTxData[i] = Long.parseLong(parsed[parsedUsed++]);
-                }
-                mCurrentPeriod = Integer.parseInt(parsed[parsedUsed++]);
-                mPeriodStart = new GregorianCalendar();
-                mPeriodStart.setTimeInMillis(Long.parseLong(parsed[parsedUsed++]));
-                mPeriodEnd = new GregorianCalendar();
-                mPeriodEnd.setTimeInMillis(Long.parseLong(parsed[parsedUsed++]));
+                currentPeriod = Integer.parseInt(parsed[parsedUsed++]);
+
+                periodStart = new GregorianCalendar();
+                periodStart.setTimeInMillis(Long.parseLong(parsed[parsedUsed++]));
+                periodEnd = new GregorianCalendar();
+                periodEnd.setTimeInMillis(Long.parseLong(parsed[parsedUsed++]));
+            } catch (Exception e) {
+                Slog.e(TAG, "Error parsing data file - ignoring");
+                return;
+            }
+            synchronized (mParent) {
+                mPeriodCount = periodCount;
+                mPeriodRxData = periodRxData;
+                mPeriodTxData = periodTxData;
+                mCurrentPeriod = currentPeriod;
+                mPeriodStart = periodStart;
+                mPeriodEnd = periodEnd;
             }
         }
 
@@ -1091,15 +1117,15 @@
         }
         pw.println();
 
-        pw.println("The threshold is " + mPolicyThreshold +
+        pw.println("The threshold is " + mPolicyThreshold.get() +
                 ", after which you experince throttling to " +
-                mPolicyThrottleValue + "kbps");
+                mPolicyThrottleValue.get() + "kbps");
         pw.println("Current period is " +
                 (mRecorder.getPeriodEnd() - mRecorder.getPeriodStart())/1000 + " seconds long " +
                 "and ends in " + (getResetTime(mIface) - System.currentTimeMillis()) / 1000 +
                 " seconds.");
         pw.println("Polling every " + mPolicyPollPeriodSec + " seconds");
-        pw.println("Current Throttle Index is " + mThrottleIndex);
+        pw.println("Current Throttle Index is " + mThrottleIndex.get());
         pw.println("Max NTP Cache Age is " + mMaxNtpCacheAgeSec);
 
         for (int i = 0; i < mRecorder.getPeriodCount(); i++) {
diff --git a/services/java/com/android/server/UsbObserver.java b/services/java/com/android/server/UsbObserver.java
index d08fe9b..546e5f8 100644
--- a/services/java/com/android/server/UsbObserver.java
+++ b/services/java/com/android/server/UsbObserver.java
@@ -24,6 +24,7 @@
 import android.os.Handler;
 import android.os.Message;
 import android.os.UEventObserver;
+import android.provider.Mtp;
 import android.provider.Settings;
 import android.util.Log;
 import android.util.Slog;
@@ -147,8 +148,43 @@
         }
     }
 
+    private native void monitorUsbHostBus();
+
+    // called from JNI in monitorUsbHostBus()
+    private void usbCameraAdded(int deviceID) {
+        Intent intent = new Intent(Usb.ACTION_USB_CAMERA_ATTACHED,
+                                Mtp.Device.getContentUri(deviceID));
+        Log.d(TAG, "usbCameraAdded, sending " + intent);
+        mContext.sendBroadcast(intent);
+    }
+
+    // called from JNI in monitorUsbHostBus()
+    private void usbCameraRemoved(int deviceID) {
+        Intent intent = new Intent(Usb.ACTION_USB_CAMERA_DETACHED,
+                                Mtp.Device.getContentUri(deviceID));
+        Log.d(TAG, "usbCameraRemoved, sending " + intent);
+        mContext.sendBroadcast(intent);
+    }
+
+    private void initHostSupport() {
+        // Create a thread to call into native code to wait for USB host events.
+        // This thread will call us back on usbCameraAdded and usbCameraRemoved.
+        Runnable runnable = new Runnable() {
+            public void run() {
+                monitorUsbHostBus();
+            }
+        };
+        new Thread(null, runnable, "UsbObserver host thread").start();
+    }
+
     void systemReady() {
         synchronized (this) {
+            if (mContext.getResources().getBoolean(
+                    com.android.internal.R.bool.config_hasUsbHostSupport)) {
+                // start monitoring for connected USB devices
+                initHostSupport();
+            }
+
             update();
             mSystemReady = true;
         }
diff --git a/services/java/com/android/server/WifiService.java b/services/java/com/android/server/WifiService.java
index 509c789..15080b2 100644
--- a/services/java/com/android/server/WifiService.java
+++ b/services/java/com/android/server/WifiService.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2008 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.
@@ -16,18 +16,6 @@
 
 package com.android.server;
 
-import static android.net.wifi.WifiManager.WIFI_STATE_DISABLED;
-import static android.net.wifi.WifiManager.WIFI_STATE_DISABLING;
-import static android.net.wifi.WifiManager.WIFI_STATE_ENABLED;
-import static android.net.wifi.WifiManager.WIFI_STATE_ENABLING;
-import static android.net.wifi.WifiManager.WIFI_STATE_UNKNOWN;
-
-import static android.net.wifi.WifiManager.WIFI_AP_STATE_DISABLED;
-import static android.net.wifi.WifiManager.WIFI_AP_STATE_DISABLING;
-import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLED;
-import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLING;
-import static android.net.wifi.WifiManager.WIFI_AP_STATE_FAILED;
-
 import android.app.AlarmManager;
 import android.app.PendingIntent;
 import android.bluetooth.BluetoothA2dp;
@@ -41,7 +29,6 @@
 import android.net.wifi.IWifiManager;
 import android.net.wifi.WifiInfo;
 import android.net.wifi.WifiManager;
-import android.net.wifi.WifiNative;
 import android.net.wifi.WifiStateTracker;
 import android.net.wifi.ScanResult;
 import android.net.wifi.WifiConfiguration;
@@ -51,56 +38,43 @@
 import android.net.InterfaceConfiguration;
 import android.net.NetworkStateTracker;
 import android.net.DhcpInfo;
-import android.net.NetworkUtils;
 import android.os.Binder;
-import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.IBinder;
 import android.os.INetworkManagementService;
-import android.os.Looper;
-import android.os.Message;
-import android.os.PowerManager;
-import android.os.Process;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.provider.Settings;
 import android.util.Slog;
-import android.text.TextUtils;
 
 import java.util.ArrayList;
-import java.util.BitSet;
-import java.util.HashMap;
-import java.util.LinkedHashMap;
 import java.util.List;
-import java.util.Map;
 import java.util.Set;
-import java.util.regex.Pattern;
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
-import java.net.UnknownHostException;
 
 import com.android.internal.app.IBatteryStats;
-import android.app.backup.IBackupManager;
 import com.android.server.am.BatteryStatsService;
 import com.android.internal.R;
 
 /**
  * WifiService handles remote WiFi operation requests by implementing
- * the IWifiManager interface. It also creates a WifiMonitor to listen
- * for Wifi-related events.
+ * the IWifiManager interface.
  *
  * @hide
  */
+//TODO: Clean up multiple locks and implement WifiService
+// as a SM to track soft AP/client/adhoc bring up based
+// on device idle state, airplane mode and boot.
+
 public class WifiService extends IWifiManager.Stub {
     private static final String TAG = "WifiService";
-    private static final boolean DBG = false;
-    private static final Pattern scanResultPattern = Pattern.compile("\t+");
+    private static final boolean DBG = true;
+
     private final WifiStateTracker mWifiStateTracker;
-    /* TODO: fetch a configurable interface */
-    private static final String SOFTAP_IFACE = "wl0.1";
 
     private Context mContext;
-    private int mWifiApState;
 
     private AlarmManager mAlarmManager;
     private PendingIntent mIdleIntent;
@@ -109,10 +83,8 @@
     private boolean mDeviceIdle;
     private int mPluggedType;
 
-    private enum DriverAction {DRIVER_UNLOAD, NO_DRIVER_UNLOAD};
-
     // true if the user enabled Wifi while in airplane mode
-    private boolean mAirplaneModeOverwridden;
+    private AtomicBoolean mAirplaneModeOverwridden = new AtomicBoolean(false);
 
     private final LockList mLocks = new LockList();
     // some wifi lock statistics
@@ -128,9 +100,7 @@
 
     private final IBatteryStats mBatteryStats;
 
-    private INetworkManagementService nwService;
     ConnectivityManager mCm;
-    private WifiWatchdogService mWifiWatchdogService = null;
     private String[] mWifiRegexs;
 
     /**
@@ -142,60 +112,6 @@
      */
     private static final long DEFAULT_IDLE_MILLIS = 15 * 60 * 1000; /* 15 minutes */
 
-    private static final String WAKELOCK_TAG = "WifiService";
-
-    /**
-     * The maximum amount of time to hold the wake lock after a disconnect
-     * caused by stopping the driver. Establishing an EDGE connection has been
-     * observed to take about 5 seconds under normal circumstances. This
-     * provides a bit of extra margin.
-     * <p>
-     * See {@link android.provider.Settings.Secure#WIFI_MOBILE_DATA_TRANSITION_WAKELOCK_TIMEOUT_MS}.
-     * This is the default value if a Settings.Secure value is not present.
-     */
-    private static final int DEFAULT_WAKELOCK_TIMEOUT = 8000;
-
-    // Wake lock used by driver-stop operation
-    private static PowerManager.WakeLock sDriverStopWakeLock;
-    // Wake lock used by other operations
-    private static PowerManager.WakeLock sWakeLock;
-
-    private static final int MESSAGE_ENABLE_WIFI        = 0;
-    private static final int MESSAGE_DISABLE_WIFI       = 1;
-    private static final int MESSAGE_STOP_WIFI          = 2;
-    private static final int MESSAGE_START_WIFI         = 3;
-    private static final int MESSAGE_RELEASE_WAKELOCK   = 4;
-    private static final int MESSAGE_UPDATE_STATE       = 5;
-    private static final int MESSAGE_START_ACCESS_POINT = 6;
-    private static final int MESSAGE_STOP_ACCESS_POINT  = 7;
-    private static final int MESSAGE_SET_CHANNELS       = 8;
-
-
-    private final  WifiHandler mWifiHandler;
-
-    /*
-     * Cache of scan results objects (size is somewhat arbitrary)
-     */
-    private static final int SCAN_RESULT_CACHE_SIZE = 80;
-    private final LinkedHashMap<String, ScanResult> mScanResultCache;
-
-    /*
-     * Character buffer used to parse scan results (optimization)
-     */
-    private static final int SCAN_RESULT_BUFFER_SIZE = 512;
-    private boolean mNeedReconfig;
-
-    /*
-     * Last UID that asked to enable WIFI.
-     */
-    private int mLastEnableUid = Process.myUid();
-
-    /*
-     * Last UID that asked to enable WIFI AP.
-     */
-    private int mLastApEnableUid = Process.myUid();
-
-
     /**
      * Number of allowed radio frequency channels in various regulatory domains.
      * This list is sufficient for 802.11b/g networks (2.4GHz range).
@@ -205,47 +121,27 @@
     private static final String ACTION_DEVICE_IDLE =
             "com.android.server.WifiManager.action.DEVICE_IDLE";
 
+    private boolean mIsReceiverRegistered = false;
+
     WifiService(Context context, WifiStateTracker tracker) {
         mContext = context;
         mWifiStateTracker = tracker;
         mWifiStateTracker.enableRssiPolling(true);
         mBatteryStats = BatteryStatsService.getService();
 
-        IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
-        nwService = INetworkManagementService.Stub.asInterface(b);
-
-        mScanResultCache = new LinkedHashMap<String, ScanResult>(
-            SCAN_RESULT_CACHE_SIZE, 0.75f, true) {
-                /*
-                 * Limit the cache size by SCAN_RESULT_CACHE_SIZE
-                 * elements
-                 */
-                public boolean removeEldestEntry(Map.Entry eldest) {
-                    return SCAN_RESULT_CACHE_SIZE < this.size();
-                }
-            };
-
-        HandlerThread wifiThread = new HandlerThread("WifiService");
-        wifiThread.start();
-        mWifiHandler = new WifiHandler(wifiThread.getLooper());
-
-        mWifiStateTracker.setWifiState(WIFI_STATE_DISABLED);
-        mWifiApState = WIFI_AP_STATE_DISABLED;
-
         mAlarmManager = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE);
         Intent idleIntent = new Intent(ACTION_DEVICE_IDLE, null);
         mIdleIntent = PendingIntent.getBroadcast(mContext, IDLE_REQUEST, idleIntent, 0);
 
-        PowerManager powerManager = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE);
-        sWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_TAG);
-        sDriverStopWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_TAG);
+        HandlerThread wifiThread = new HandlerThread("WifiService");
+        wifiThread.start();
 
         mContext.registerReceiver(
                 new BroadcastReceiver() {
                     @Override
                     public void onReceive(Context context, Intent intent) {
                         // clear our flag indicating the user has overwridden airplane mode
-                        mAirplaneModeOverwridden = false;
+                        mAirplaneModeOverwridden.set(false);
                         // on airplane disable, restore Wifi if the saved state indicates so
                         if (!isAirplaneModeOn() && testAndClearWifiSavedState()) {
                             persistWifiEnabled(true);
@@ -260,11 +156,11 @@
                 @Override
                 public void onReceive(Context context, Intent intent) {
 
-                  ArrayList<String> available = intent.getStringArrayListExtra(
-                          ConnectivityManager.EXTRA_AVAILABLE_TETHER);
-                  ArrayList<String> active = intent.getStringArrayListExtra(
-                          ConnectivityManager.EXTRA_ACTIVE_TETHER);
-                  updateTetherState(available, active);
+                    ArrayList<String> available = intent.getStringArrayListExtra(
+                            ConnectivityManager.EXTRA_AVAILABLE_TETHER);
+                    ArrayList<String> active = intent.getStringArrayListExtra(
+                            ConnectivityManager.EXTRA_ACTIVE_TETHER);
+                    updateTetherState(available, active);
 
                 }
             },new IntentFilter(ConnectivityManager.ACTION_TETHER_STATE_CHANGED));
@@ -293,7 +189,10 @@
         IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
         INetworkManagementService service = INetworkManagementService.Stub.asInterface(b);
 
-        mCm = (ConnectivityManager)mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
+        if (mCm == null) {
+            mCm = (ConnectivityManager)mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
+        }
+
         mWifiRegexs = mCm.getTetherableWifiRegexs();
 
         for (String intf : available) {
@@ -313,17 +212,14 @@
                         }
                     } catch (Exception e) {
                         Slog.e(TAG, "Error configuring interface " + intf + ", :" + e);
-                        try {
-                            nwService.stopAccessPoint();
-                        } catch (Exception ee) {
-                            Slog.e(TAG, "Could not stop AP, :" + ee);
-                        }
-                        setWifiApEnabledState(WIFI_AP_STATE_FAILED, 0, DriverAction.DRIVER_UNLOAD);
+                        setWifiApEnabled(null, false);
                         return;
                     }
 
                     if(mCm.tether(intf) != ConnectivityManager.TETHER_ERROR_NO_ERROR) {
-                        Slog.e(TAG, "Error tethering "+intf);
+                        Slog.e(TAG, "Error tethering on " + intf);
+                        setWifiApEnabled(null, false);
+                        return;
                     }
                     break;
                 }
@@ -365,12 +261,11 @@
 
     /**
      * see {@link android.net.wifi.WifiManager#pingSupplicant()}
-     * @return {@code true} if the operation succeeds
+     * @return {@code true} if the operation succeeds, {@code false} otherwise
      */
     public boolean pingSupplicant() {
-        enforceChangePermission();
-
-        return mWifiStateTracker.ping();
+        enforceAccessPermission();
+        return mWifiStateTracker.pingSupplicant();
     }
 
     /**
@@ -379,170 +274,7 @@
      */
     public boolean startScan(boolean forceActive) {
         enforceChangePermission();
-
-        switch (mWifiStateTracker.getSupplicantState()) {
-            case DISCONNECTED:
-            case INACTIVE:
-            case SCANNING:
-            case DORMANT:
-                break;
-            default:
-                mWifiStateTracker.setScanResultHandling(
-                        WifiStateTracker.SUPPL_SCAN_HANDLING_LIST_ONLY);
-                break;
-        }
-        return mWifiStateTracker.scan(forceActive);
-    }
-
-    /**
-     * see {@link android.net.wifi.WifiManager#setWifiEnabled(boolean)}
-     * @param enable {@code true} to enable, {@code false} to disable.
-     * @return {@code true} if the enable/disable operation was
-     *         started or is already in the queue.
-     */
-    public boolean setWifiEnabled(boolean enable) {
-        enforceChangePermission();
-        if (mWifiHandler == null) return false;
-
-        synchronized (mWifiHandler) {
-            // caller may not have WAKE_LOCK permission - it's not required here
-            long ident = Binder.clearCallingIdentity();
-            sWakeLock.acquire();
-            Binder.restoreCallingIdentity(ident);
-
-            mLastEnableUid = Binder.getCallingUid();
-            // set a flag if the user is enabling Wifi while in airplane mode
-            mAirplaneModeOverwridden = (enable && isAirplaneModeOn() && isAirplaneToggleable());
-            sendEnableMessage(enable, true, Binder.getCallingUid());
-        }
-
-        return true;
-    }
-
-    /**
-     * Enables/disables Wi-Fi synchronously.
-     * @param enable {@code true} to turn Wi-Fi on, {@code false} to turn it off.
-     * @param persist {@code true} if the setting should be persisted.
-     * @param uid The UID of the process making the request.
-     * @return {@code true} if the operation succeeds (or if the existing state
-     *         is the same as the requested state)
-     */
-    private boolean setWifiEnabledBlocking(boolean enable, boolean persist, int uid) {
-        final int eventualWifiState = enable ? WIFI_STATE_ENABLED : WIFI_STATE_DISABLED;
-        final int wifiState = mWifiStateTracker.getWifiState();
-
-        if (wifiState == eventualWifiState) {
-            return true;
-        }
-        if (enable && isAirplaneModeOn() && !mAirplaneModeOverwridden) {
-            return false;
-        }
-
-        /**
-         * Multiple calls to unregisterReceiver() cause exception and a system crash.
-         * This can happen if a supplicant is lost (or firmware crash occurs) and user indicates
-         * disable wifi at the same time.
-         * Avoid doing a disable when the current Wifi state is UNKNOWN
-         * TODO: Handle driver load fail and supplicant lost as seperate states
-         */
-        if ((wifiState == WIFI_STATE_UNKNOWN) && !enable) {
-            return false;
-        }
-
-        /**
-         * Fail Wifi if AP is enabled
-         * TODO: Deprecate WIFI_STATE_UNKNOWN and rename it
-         * WIFI_STATE_FAILED
-         */
-        if ((mWifiApState == WIFI_AP_STATE_ENABLED) && enable) {
-            setWifiEnabledState(WIFI_STATE_UNKNOWN, uid);
-            return false;
-        }
-
-        setWifiEnabledState(enable ? WIFI_STATE_ENABLING : WIFI_STATE_DISABLING, uid);
-
-        if (enable) {
-            if (!mWifiStateTracker.loadDriver()) {
-                Slog.e(TAG, "Failed to load Wi-Fi driver.");
-                setWifiEnabledState(WIFI_STATE_UNKNOWN, uid);
-                return false;
-            }
-            if (!mWifiStateTracker.startSupplicant()) {
-                mWifiStateTracker.unloadDriver();
-                Slog.e(TAG, "Failed to start supplicant daemon.");
-                setWifiEnabledState(WIFI_STATE_UNKNOWN, uid);
-                return false;
-            }
-
-            registerForBroadcasts();
-            mWifiStateTracker.startEventLoop();
-
-        } else {
-
-            mContext.unregisterReceiver(mReceiver);
-           // Remove notification (it will no-op if it isn't visible)
-            mWifiStateTracker.setNotificationVisible(false, 0, false, 0);
-
-            boolean failedToStopSupplicantOrUnloadDriver = false;
-
-            if (!mWifiStateTracker.stopSupplicant()) {
-                Slog.e(TAG, "Failed to stop supplicant daemon.");
-                setWifiEnabledState(WIFI_STATE_UNKNOWN, uid);
-                failedToStopSupplicantOrUnloadDriver = true;
-            }
-
-            /**
-             * Reset connections and disable interface
-             * before we unload the driver
-             */
-            mWifiStateTracker.resetConnections(true);
-
-            if (!mWifiStateTracker.unloadDriver()) {
-                Slog.e(TAG, "Failed to unload Wi-Fi driver.");
-                if (!failedToStopSupplicantOrUnloadDriver) {
-                    setWifiEnabledState(WIFI_STATE_UNKNOWN, uid);
-                    failedToStopSupplicantOrUnloadDriver = true;
-                }
-            }
-
-            if (failedToStopSupplicantOrUnloadDriver) {
-                return false;
-            }
-        }
-
-        // Success!
-
-        if (persist) {
-            persistWifiEnabled(enable);
-        }
-        setWifiEnabledState(eventualWifiState, uid);
-        return true;
-    }
-
-    private void setWifiEnabledState(int wifiState, int uid) {
-        final int previousWifiState = mWifiStateTracker.getWifiState();
-
-        long ident = Binder.clearCallingIdentity();
-        try {
-            if (wifiState == WIFI_STATE_ENABLED) {
-                mBatteryStats.noteWifiOn(uid);
-            } else if (wifiState == WIFI_STATE_DISABLED) {
-                mBatteryStats.noteWifiOff(uid);
-            }
-        } catch (RemoteException e) {
-        } finally {
-            Binder.restoreCallingIdentity(ident);
-        }
-
-        // Update state
-        mWifiStateTracker.setWifiState(wifiState);
-
-        // Broadcast
-        final Intent intent = new Intent(WifiManager.WIFI_STATE_CHANGED_ACTION);
-        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
-        intent.putExtra(WifiManager.EXTRA_WIFI_STATE, wifiState);
-        intent.putExtra(WifiManager.EXTRA_PREVIOUS_WIFI_STATE, previousWifiState);
-        mContext.sendStickyBroadcast(intent);
+        return mWifiStateTracker.startScan(forceActive);
     }
 
     private void enforceAccessPermission() {
@@ -563,6 +295,40 @@
     }
 
     /**
+     * see {@link android.net.wifi.WifiManager#setWifiEnabled(boolean)}
+     * @param enable {@code true} to enable, {@code false} to disable.
+     * @return {@code true} if the enable/disable operation was
+     *         started or is already in the queue.
+     */
+    public synchronized boolean setWifiEnabled(boolean enable) {
+        enforceChangePermission();
+
+        if (DBG) {
+            Slog.e(TAG, "Invoking mWifiStateTracker.setWifiEnabled\n");
+        }
+
+        // set a flag if the user is enabling Wifi while in airplane mode
+        if (enable && isAirplaneModeOn() && isAirplaneToggleable()) {
+            mAirplaneModeOverwridden.set(true);
+        }
+
+        mWifiStateTracker.setWifiEnabled(enable);
+        persistWifiEnabled(enable);
+
+        if (enable) {
+            if (!mIsReceiverRegistered) {
+                registerForBroadcasts();
+                mIsReceiverRegistered = true;
+            }
+        } else if (mIsReceiverRegistered){
+            mContext.unregisterReceiver(mReceiver);
+            mIsReceiverRegistered = false;
+        }
+
+        return true;
+    }
+
+    /**
      * see {@link WifiManager#getWifiState()}
      * @return One of {@link WifiManager#WIFI_STATE_DISABLED},
      *         {@link WifiManager#WIFI_STATE_DISABLING},
@@ -576,62 +342,49 @@
     }
 
     /**
-     * see {@link android.net.wifi.WifiManager#disconnect()}
-     * @return {@code true} if the operation succeeds
-     */
-    public boolean disconnect() {
-        enforceChangePermission();
-
-        return mWifiStateTracker.disconnect();
-    }
-
-    /**
-     * see {@link android.net.wifi.WifiManager#reconnect()}
-     * @return {@code true} if the operation succeeds
-     */
-    public boolean reconnect() {
-        enforceChangePermission();
-
-        return mWifiStateTracker.reconnectCommand();
-    }
-
-    /**
-     * see {@link android.net.wifi.WifiManager#reassociate()}
-     * @return {@code true} if the operation succeeds
-     */
-    public boolean reassociate() {
-        enforceChangePermission();
-
-        return mWifiStateTracker.reassociate();
-    }
-
-    /**
      * see {@link android.net.wifi.WifiManager#setWifiApEnabled(WifiConfiguration, boolean)}
      * @param wifiConfig SSID, security and channel details as
      *        part of WifiConfiguration
-     * @param enabled, true to enable and false to disable
+     * @param enabled true to enable and false to disable
      * @return {@code true} if the start operation was
      *         started or is already in the queue.
      */
-    public boolean setWifiApEnabled(WifiConfiguration wifiConfig, boolean enabled) {
+    public synchronized boolean setWifiApEnabled(WifiConfiguration wifiConfig, boolean enabled) {
         enforceChangePermission();
-        if (mWifiHandler == null) return false;
 
-        synchronized (mWifiHandler) {
-
-            long ident = Binder.clearCallingIdentity();
-            sWakeLock.acquire();
-            Binder.restoreCallingIdentity(ident);
-
-            mLastApEnableUid = Binder.getCallingUid();
-            sendAccessPointMessage(enabled, wifiConfig, Binder.getCallingUid());
+        if (enabled) {
+            /* Use default config if there is no existing config */
+            if (wifiConfig == null && ((wifiConfig = getWifiApConfiguration()) == null)) {
+                wifiConfig = new WifiConfiguration();
+                wifiConfig.SSID = mContext.getString(R.string.wifi_tether_configure_ssid_default);
+                wifiConfig.allowedKeyManagement.set(KeyMgmt.NONE);
+            }
+            setWifiApConfiguration(wifiConfig);
         }
 
+        mWifiStateTracker.setWifiApEnabled(wifiConfig, enabled);
+
         return true;
     }
 
-    public WifiConfiguration getWifiApConfiguration() {
+    /**
+     * see {@link WifiManager#getWifiApState()}
+     * @return One of {@link WifiManager#WIFI_AP_STATE_DISABLED},
+     *         {@link WifiManager#WIFI_AP_STATE_DISABLING},
+     *         {@link WifiManager#WIFI_AP_STATE_ENABLED},
+     *         {@link WifiManager#WIFI_AP_STATE_ENABLING},
+     *         {@link WifiManager#WIFI_AP_STATE_FAILED}
+     */
+    public int getWifiApEnabledState() {
         enforceAccessPermission();
+        return mWifiStateTracker.getWifiApState();
+    }
+
+    /**
+     * see {@link WifiManager#getWifiApConfiguration()}
+     * @return soft access point configuration
+     */
+    public synchronized WifiConfiguration getWifiApConfiguration() {
         final ContentResolver cr = mContext.getContentResolver();
         WifiConfiguration wifiConfig = new WifiConfiguration();
         int authType;
@@ -649,7 +402,11 @@
         }
     }
 
-    public void setWifiApConfiguration(WifiConfiguration wifiConfig) {
+    /**
+     * see {@link WifiManager#setWifiApConfiguration(WifiConfiguration)}
+     * @param wifiConfig WifiConfiguration details for soft access point
+     */
+    public synchronized void setWifiApConfiguration(WifiConfiguration wifiConfig) {
         enforceChangePermission();
         final ContentResolver cr = mContext.getContentResolver();
         boolean isWpa;
@@ -665,143 +422,30 @@
     }
 
     /**
-     * Enables/disables Wi-Fi AP synchronously. The driver is loaded
-     * and soft access point configured as a single operation.
-     * @param enable {@code true} to turn Wi-Fi on, {@code false} to turn it off.
-     * @param uid The UID of the process making the request.
-     * @param wifiConfig The WifiConfiguration for AP
-     * @return {@code true} if the operation succeeds (or if the existing state
-     *         is the same as the requested state)
+     * see {@link android.net.wifi.WifiManager#disconnect()}
+     * @return {@code true} if the operation succeeds
      */
-    private boolean setWifiApEnabledBlocking(boolean enable,
-                                int uid, WifiConfiguration wifiConfig) {
-        final int eventualWifiApState = enable ? WIFI_AP_STATE_ENABLED : WIFI_AP_STATE_DISABLED;
-
-        if (mWifiApState == eventualWifiApState) {
-            /* Configuration changed on a running access point */
-            if(enable && (wifiConfig != null)) {
-                try {
-                    nwService.setAccessPoint(wifiConfig, mWifiStateTracker.getInterfaceName(),
-                                             SOFTAP_IFACE);
-                    setWifiApConfiguration(wifiConfig);
-                    return true;
-                } catch(Exception e) {
-                    Slog.e(TAG, "Exception in nwService during AP restart");
-                    try {
-                        nwService.stopAccessPoint();
-                    } catch (Exception ee) {
-                        Slog.e(TAG, "Could not stop AP, :" + ee);
-                    }
-                    setWifiApEnabledState(WIFI_AP_STATE_FAILED, uid, DriverAction.DRIVER_UNLOAD);
-                    return false;
-                }
-            } else {
-                return true;
-            }
-        }
-
-        /**
-         * Fail AP if Wifi is enabled
-         */
-        if ((mWifiStateTracker.getWifiState() == WIFI_STATE_ENABLED) && enable) {
-            setWifiApEnabledState(WIFI_AP_STATE_FAILED, uid, DriverAction.NO_DRIVER_UNLOAD);
-            return false;
-        }
-
-        setWifiApEnabledState(enable ? WIFI_AP_STATE_ENABLING :
-                                       WIFI_AP_STATE_DISABLING, uid, DriverAction.NO_DRIVER_UNLOAD);
-
-        if (enable) {
-
-            /* Use default config if there is no existing config */
-            if (wifiConfig == null && ((wifiConfig = getWifiApConfiguration()) == null)) {
-                wifiConfig = new WifiConfiguration();
-                wifiConfig.SSID = mContext.getString(R.string.wifi_tether_configure_ssid_default);
-                wifiConfig.allowedKeyManagement.set(KeyMgmt.NONE);
-            }
-
-            if (!mWifiStateTracker.loadDriver()) {
-                Slog.e(TAG, "Failed to load Wi-Fi driver for AP mode");
-                setWifiApEnabledState(WIFI_AP_STATE_FAILED, uid, DriverAction.NO_DRIVER_UNLOAD);
-                return false;
-            }
-
-            try {
-                nwService.startAccessPoint(wifiConfig, mWifiStateTracker.getInterfaceName(),
-                                           SOFTAP_IFACE);
-            } catch(Exception e) {
-                Slog.e(TAG, "Exception in startAccessPoint()");
-                setWifiApEnabledState(WIFI_AP_STATE_FAILED, uid, DriverAction.DRIVER_UNLOAD);
-                return false;
-            }
-
-            setWifiApConfiguration(wifiConfig);
-
-        } else {
-
-            try {
-                nwService.stopAccessPoint();
-            } catch(Exception e) {
-                Slog.e(TAG, "Exception in stopAccessPoint()");
-                setWifiApEnabledState(WIFI_AP_STATE_FAILED, uid, DriverAction.DRIVER_UNLOAD);
-                return false;
-            }
-
-            if (!mWifiStateTracker.unloadDriver()) {
-                Slog.e(TAG, "Failed to unload Wi-Fi driver for AP mode");
-                setWifiApEnabledState(WIFI_AP_STATE_FAILED, uid, DriverAction.NO_DRIVER_UNLOAD);
-                return false;
-            }
-        }
-
-        setWifiApEnabledState(eventualWifiApState, uid, DriverAction.NO_DRIVER_UNLOAD);
-        return true;
+    public boolean disconnect() {
+        enforceChangePermission();
+        return mWifiStateTracker.disconnectCommand();
     }
 
     /**
-     * see {@link WifiManager#getWifiApState()}
-     * @return One of {@link WifiManager#WIFI_AP_STATE_DISABLED},
-     *         {@link WifiManager#WIFI_AP_STATE_DISABLING},
-     *         {@link WifiManager#WIFI_AP_STATE_ENABLED},
-     *         {@link WifiManager#WIFI_AP_STATE_ENABLING},
-     *         {@link WifiManager#WIFI_AP_STATE_FAILED}
+     * see {@link android.net.wifi.WifiManager#reconnect()}
+     * @return {@code true} if the operation succeeds
      */
-    public int getWifiApEnabledState() {
-        enforceAccessPermission();
-        return mWifiApState;
+    public boolean reconnect() {
+        enforceChangePermission();
+        return mWifiStateTracker.reconnectCommand();
     }
 
-    private void setWifiApEnabledState(int wifiAPState, int uid, DriverAction flag) {
-        final int previousWifiApState = mWifiApState;
-
-        /**
-         * Unload the driver if going to a failed state
-         */
-        if ((mWifiApState == WIFI_AP_STATE_FAILED) && (flag == DriverAction.DRIVER_UNLOAD)) {
-            mWifiStateTracker.unloadDriver();
-        }
-
-        long ident = Binder.clearCallingIdentity();
-        try {
-            if (wifiAPState == WIFI_AP_STATE_ENABLED) {
-                mBatteryStats.noteWifiOn(uid);
-            } else if (wifiAPState == WIFI_AP_STATE_DISABLED) {
-                mBatteryStats.noteWifiOff(uid);
-            }
-        } catch (RemoteException e) {
-        } finally {
-            Binder.restoreCallingIdentity(ident);
-        }
-
-        // Update state
-        mWifiApState = wifiAPState;
-
-        // Broadcast
-        final Intent intent = new Intent(WifiManager.WIFI_AP_STATE_CHANGED_ACTION);
-        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
-        intent.putExtra(WifiManager.EXTRA_WIFI_AP_STATE, wifiAPState);
-        intent.putExtra(WifiManager.EXTRA_PREVIOUS_WIFI_AP_STATE, previousWifiApState);
-        mContext.sendStickyBroadcast(intent);
+    /**
+     * see {@link android.net.wifi.WifiManager#reassociate()}
+     * @return {@code true} if the operation succeeds
+     */
+    public boolean reassociate() {
+        enforceChangePermission();
+        return mWifiStateTracker.reassociateCommand();
     }
 
     /**
@@ -810,217 +454,7 @@
      */
     public List<WifiConfiguration> getConfiguredNetworks() {
         enforceAccessPermission();
-        String listStr;
-
-        /*
-         * We don't cache the list, because we want to allow
-         * for the possibility that the configuration file
-         * has been modified through some external means,
-         * such as the wpa_cli command line program.
-         */
-        listStr = mWifiStateTracker.listNetworks();
-
-        List<WifiConfiguration> networks =
-            new ArrayList<WifiConfiguration>();
-        if (listStr == null)
-            return networks;
-
-        String[] lines = listStr.split("\n");
-        // Skip the first line, which is a header
-       for (int i = 1; i < lines.length; i++) {
-           String[] result = lines[i].split("\t");
-           // network-id | ssid | bssid | flags
-           WifiConfiguration config = new WifiConfiguration();
-           try {
-               config.networkId = Integer.parseInt(result[0]);
-           } catch(NumberFormatException e) {
-               continue;
-           }
-           if (result.length > 3) {
-               if (result[3].indexOf("[CURRENT]") != -1)
-                   config.status = WifiConfiguration.Status.CURRENT;
-               else if (result[3].indexOf("[DISABLED]") != -1)
-                   config.status = WifiConfiguration.Status.DISABLED;
-               else
-                   config.status = WifiConfiguration.Status.ENABLED;
-           } else {
-               config.status = WifiConfiguration.Status.ENABLED;
-           }
-           readNetworkVariables(config);
-           networks.add(config);
-       }
-
-        return networks;
-    }
-
-    /**
-     * Read the variables from the supplicant daemon that are needed to
-     * fill in the WifiConfiguration object.
-     * <p/>
-     * The caller must hold the synchronization monitor.
-     * @param config the {@link WifiConfiguration} object to be filled in.
-     */
-    private void readNetworkVariables(WifiConfiguration config) {
-
-        int netId = config.networkId;
-        if (netId < 0)
-            return;
-
-        /*
-         * TODO: maybe should have a native method that takes an array of
-         * variable names and returns an array of values. But we'd still
-         * be doing a round trip to the supplicant daemon for each variable.
-         */
-        String value;
-
-        value = mWifiStateTracker.getNetworkVariable(netId, WifiConfiguration.ssidVarName);
-        if (!TextUtils.isEmpty(value)) {
-            config.SSID = value;
-        } else {
-            config.SSID = null;
-        }
-
-        value = mWifiStateTracker.getNetworkVariable(netId, WifiConfiguration.bssidVarName);
-        if (!TextUtils.isEmpty(value)) {
-            config.BSSID = value;
-        } else {
-            config.BSSID = null;
-        }
-
-        value = mWifiStateTracker.getNetworkVariable(netId, WifiConfiguration.priorityVarName);
-        config.priority = -1;
-        if (!TextUtils.isEmpty(value)) {
-            try {
-                config.priority = Integer.parseInt(value);
-            } catch (NumberFormatException ignore) {
-            }
-        }
-
-        value = mWifiStateTracker.getNetworkVariable(netId, WifiConfiguration.hiddenSSIDVarName);
-        config.hiddenSSID = false;
-        if (!TextUtils.isEmpty(value)) {
-            try {
-                config.hiddenSSID = Integer.parseInt(value) != 0;
-            } catch (NumberFormatException ignore) {
-            }
-        }
-
-        value = mWifiStateTracker.getNetworkVariable(netId, WifiConfiguration.wepTxKeyIdxVarName);
-        config.wepTxKeyIndex = -1;
-        if (!TextUtils.isEmpty(value)) {
-            try {
-                config.wepTxKeyIndex = Integer.parseInt(value);
-            } catch (NumberFormatException ignore) {
-            }
-        }
-
-        /*
-         * Get up to 4 WEP keys. Note that the actual keys are not passed back,
-         * just a "*" if the key is set, or the null string otherwise.
-         */
-        for (int i = 0; i < 4; i++) {
-            value = mWifiStateTracker.getNetworkVariable(netId, WifiConfiguration.wepKeyVarNames[i]);
-            if (!TextUtils.isEmpty(value)) {
-                config.wepKeys[i] = value;
-            } else {
-                config.wepKeys[i] = null;
-            }
-        }
-
-        /*
-         * Get the private shared key. Note that the actual keys are not passed back,
-         * just a "*" if the key is set, or the null string otherwise.
-         */
-        value = mWifiStateTracker.getNetworkVariable(netId, WifiConfiguration.pskVarName);
-        if (!TextUtils.isEmpty(value)) {
-            config.preSharedKey = value;
-        } else {
-            config.preSharedKey = null;
-        }
-
-        value = mWifiStateTracker.getNetworkVariable(config.networkId,
-                WifiConfiguration.Protocol.varName);
-        if (!TextUtils.isEmpty(value)) {
-            String vals[] = value.split(" ");
-            for (String val : vals) {
-                int index =
-                    lookupString(val, WifiConfiguration.Protocol.strings);
-                if (0 <= index) {
-                    config.allowedProtocols.set(index);
-                }
-            }
-        }
-
-        value = mWifiStateTracker.getNetworkVariable(config.networkId,
-                WifiConfiguration.KeyMgmt.varName);
-        if (!TextUtils.isEmpty(value)) {
-            String vals[] = value.split(" ");
-            for (String val : vals) {
-                int index =
-                    lookupString(val, WifiConfiguration.KeyMgmt.strings);
-                if (0 <= index) {
-                    config.allowedKeyManagement.set(index);
-                }
-            }
-        }
-
-        value = mWifiStateTracker.getNetworkVariable(config.networkId,
-                WifiConfiguration.AuthAlgorithm.varName);
-        if (!TextUtils.isEmpty(value)) {
-            String vals[] = value.split(" ");
-            for (String val : vals) {
-                int index =
-                    lookupString(val, WifiConfiguration.AuthAlgorithm.strings);
-                if (0 <= index) {
-                    config.allowedAuthAlgorithms.set(index);
-                }
-            }
-        }
-
-        value = mWifiStateTracker.getNetworkVariable(config.networkId,
-                WifiConfiguration.PairwiseCipher.varName);
-        if (!TextUtils.isEmpty(value)) {
-            String vals[] = value.split(" ");
-            for (String val : vals) {
-                int index =
-                    lookupString(val, WifiConfiguration.PairwiseCipher.strings);
-                if (0 <= index) {
-                    config.allowedPairwiseCiphers.set(index);
-                }
-            }
-        }
-
-        value = mWifiStateTracker.getNetworkVariable(config.networkId,
-                WifiConfiguration.GroupCipher.varName);
-        if (!TextUtils.isEmpty(value)) {
-            String vals[] = value.split(" ");
-            for (String val : vals) {
-                int index =
-                    lookupString(val, WifiConfiguration.GroupCipher.strings);
-                if (0 <= index) {
-                    config.allowedGroupCiphers.set(index);
-                }
-            }
-        }
-
-        for (WifiConfiguration.EnterpriseField field :
-                config.enterpriseFields) {
-            value = mWifiStateTracker.getNetworkVariable(netId,
-                    field.varName());
-            if (!TextUtils.isEmpty(value)) {
-                if (field != config.eap) value = removeDoubleQuotes(value);
-                field.setValue(value);
-            }
-        }
-    }
-
-    private static String removeDoubleQuotes(String string) {
-        if (string.length() <= 2) return "";
-        return string.substring(1, string.length() - 1);
-    }
-
-    private static String convertToQuotedString(String string) {
-        return "\"" + string + "\"";
+        return mWifiStateTracker.getConfiguredNetworks();
     }
 
     /**
@@ -1030,280 +464,10 @@
      */
     public int addOrUpdateNetwork(WifiConfiguration config) {
         enforceChangePermission();
-
-        /*
-         * If the supplied networkId is -1, we create a new empty
-         * network configuration. Otherwise, the networkId should
-         * refer to an existing configuration.
-         */
-        int netId = config.networkId;
-        boolean newNetwork = netId == -1;
-        boolean doReconfig = false;
-        // networkId of -1 means we want to create a new network
-        synchronized (mWifiStateTracker) {
-            if (newNetwork) {
-                netId = mWifiStateTracker.addNetwork();
-                if (netId < 0) {
-                    if (DBG) {
-                        Slog.d(TAG, "Failed to add a network!");
-                    }
-                    return -1;
-                }
-                doReconfig = true;
-            }
-            mNeedReconfig = mNeedReconfig || doReconfig;
-        }
-
-        setVariables: {
-            /*
-             * Note that if a networkId for a non-existent network
-             * was supplied, then the first setNetworkVariable()
-             * will fail, so we don't bother to make a separate check
-             * for the validity of the ID up front.
-             */
-            if (config.SSID != null &&
-                    !mWifiStateTracker.setNetworkVariable(
-                        netId,
-                        WifiConfiguration.ssidVarName,
-                        config.SSID)) {
-                if (DBG) {
-                    Slog.d(TAG, "failed to set SSID: "+config.SSID);
-                }
-                break setVariables;
-            }
-
-            if (config.BSSID != null &&
-                    !mWifiStateTracker.setNetworkVariable(
-                        netId,
-                        WifiConfiguration.bssidVarName,
-                        config.BSSID)) {
-                if (DBG) {
-                    Slog.d(TAG, "failed to set BSSID: "+config.BSSID);
-                }
-                break setVariables;
-            }
-
-            String allowedKeyManagementString =
-                makeString(config.allowedKeyManagement, WifiConfiguration.KeyMgmt.strings);
-            if (config.allowedKeyManagement.cardinality() != 0 &&
-                    !mWifiStateTracker.setNetworkVariable(
-                        netId,
-                        WifiConfiguration.KeyMgmt.varName,
-                        allowedKeyManagementString)) {
-                if (DBG) {
-                    Slog.d(TAG, "failed to set key_mgmt: "+
-                            allowedKeyManagementString);
-                }
-                break setVariables;
-            }
-
-            String allowedProtocolsString =
-                makeString(config.allowedProtocols, WifiConfiguration.Protocol.strings);
-            if (config.allowedProtocols.cardinality() != 0 &&
-                    !mWifiStateTracker.setNetworkVariable(
-                        netId,
-                        WifiConfiguration.Protocol.varName,
-                        allowedProtocolsString)) {
-                if (DBG) {
-                    Slog.d(TAG, "failed to set proto: "+
-                            allowedProtocolsString);
-                }
-                break setVariables;
-            }
-
-            String allowedAuthAlgorithmsString =
-                makeString(config.allowedAuthAlgorithms, WifiConfiguration.AuthAlgorithm.strings);
-            if (config.allowedAuthAlgorithms.cardinality() != 0 &&
-                    !mWifiStateTracker.setNetworkVariable(
-                        netId,
-                        WifiConfiguration.AuthAlgorithm.varName,
-                        allowedAuthAlgorithmsString)) {
-                if (DBG) {
-                    Slog.d(TAG, "failed to set auth_alg: "+
-                            allowedAuthAlgorithmsString);
-                }
-                break setVariables;
-            }
-
-            String allowedPairwiseCiphersString =
-                makeString(config.allowedPairwiseCiphers, WifiConfiguration.PairwiseCipher.strings);
-            if (config.allowedPairwiseCiphers.cardinality() != 0 &&
-                    !mWifiStateTracker.setNetworkVariable(
-                        netId,
-                        WifiConfiguration.PairwiseCipher.varName,
-                        allowedPairwiseCiphersString)) {
-                if (DBG) {
-                    Slog.d(TAG, "failed to set pairwise: "+
-                            allowedPairwiseCiphersString);
-                }
-                break setVariables;
-            }
-
-            String allowedGroupCiphersString =
-                makeString(config.allowedGroupCiphers, WifiConfiguration.GroupCipher.strings);
-            if (config.allowedGroupCiphers.cardinality() != 0 &&
-                    !mWifiStateTracker.setNetworkVariable(
-                        netId,
-                        WifiConfiguration.GroupCipher.varName,
-                        allowedGroupCiphersString)) {
-                if (DBG) {
-                    Slog.d(TAG, "failed to set group: "+
-                            allowedGroupCiphersString);
-                }
-                break setVariables;
-            }
-
-            // Prevent client screw-up by passing in a WifiConfiguration we gave it
-            // by preventing "*" as a key.
-            if (config.preSharedKey != null && !config.preSharedKey.equals("*") &&
-                    !mWifiStateTracker.setNetworkVariable(
-                        netId,
-                        WifiConfiguration.pskVarName,
-                        config.preSharedKey)) {
-                if (DBG) {
-                    Slog.d(TAG, "failed to set psk: "+config.preSharedKey);
-                }
-                break setVariables;
-            }
-
-            boolean hasSetKey = false;
-            if (config.wepKeys != null) {
-                for (int i = 0; i < config.wepKeys.length; i++) {
-                    // Prevent client screw-up by passing in a WifiConfiguration we gave it
-                    // by preventing "*" as a key.
-                    if (config.wepKeys[i] != null && !config.wepKeys[i].equals("*")) {
-                        if (!mWifiStateTracker.setNetworkVariable(
-                                    netId,
-                                    WifiConfiguration.wepKeyVarNames[i],
-                                    config.wepKeys[i])) {
-                            if (DBG) {
-                                Slog.d(TAG,
-                                        "failed to set wep_key"+i+": " +
-                                        config.wepKeys[i]);
-                            }
-                            break setVariables;
-                        }
-                        hasSetKey = true;
-                    }
-                }
-            }
-
-            if (hasSetKey) {
-                if (!mWifiStateTracker.setNetworkVariable(
-                            netId,
-                            WifiConfiguration.wepTxKeyIdxVarName,
-                            Integer.toString(config.wepTxKeyIndex))) {
-                    if (DBG) {
-                        Slog.d(TAG,
-                                "failed to set wep_tx_keyidx: "+
-                                config.wepTxKeyIndex);
-                    }
-                    break setVariables;
-                }
-            }
-
-            if (!mWifiStateTracker.setNetworkVariable(
-                        netId,
-                        WifiConfiguration.priorityVarName,
-                        Integer.toString(config.priority))) {
-                if (DBG) {
-                    Slog.d(TAG, config.SSID + ": failed to set priority: "
-                            +config.priority);
-                }
-                break setVariables;
-            }
-
-            if (config.hiddenSSID && !mWifiStateTracker.setNetworkVariable(
-                        netId,
-                        WifiConfiguration.hiddenSSIDVarName,
-                        Integer.toString(config.hiddenSSID ? 1 : 0))) {
-                if (DBG) {
-                    Slog.d(TAG, config.SSID + ": failed to set hiddenSSID: "+
-                            config.hiddenSSID);
-                }
-                break setVariables;
-            }
-
-            for (WifiConfiguration.EnterpriseField field
-                    : config.enterpriseFields) {
-                String varName = field.varName();
-                String value = field.value();
-                if (value != null) {
-                    if (field != config.eap) {
-                        value = (value.length() == 0) ? "NULL" : convertToQuotedString(value);
-                    }
-                    if (!mWifiStateTracker.setNetworkVariable(
-                                netId,
-                                varName,
-                                value)) {
-                        if (DBG) {
-                            Slog.d(TAG, config.SSID + ": failed to set " + varName +
-                                    ": " + value);
-                        }
-                        break setVariables;
-                    }
-                }
-            }
-            return netId;
-        }
-
-        /*
-         * For an update, if one of the setNetworkVariable operations fails,
-         * we might want to roll back all the changes already made. But the
-         * chances are that if anything is going to go wrong, it'll happen
-         * the first time we try to set one of the variables.
-         */
-        if (newNetwork) {
-            removeNetwork(netId);
-            if (DBG) {
-                Slog.d(TAG,
-                        "Failed to set a network variable, removed network: "
-                        + netId);
-            }
-        }
-        return -1;
+        return mWifiStateTracker.addOrUpdateNetwork(config);
     }
 
-    private static String makeString(BitSet set, String[] strings) {
-        StringBuffer buf = new StringBuffer();
-        int nextSetBit = -1;
-
-        /* Make sure all set bits are in [0, strings.length) to avoid
-         * going out of bounds on strings.  (Shouldn't happen, but...) */
-        set = set.get(0, strings.length);
-
-        while ((nextSetBit = set.nextSetBit(nextSetBit + 1)) != -1) {
-            buf.append(strings[nextSetBit].replace('_', '-')).append(' ');
-        }
-
-        // remove trailing space
-        if (set.cardinality() > 0) {
-            buf.setLength(buf.length() - 1);
-        }
-
-        return buf.toString();
-    }
-
-    private static int lookupString(String string, String[] strings) {
-        int size = strings.length;
-
-        string = string.replace('-', '_');
-
-        for (int i = 0; i < size; i++)
-            if (string.equals(strings[i]))
-                return i;
-
-        if (DBG) {
-            // if we ever get here, we should probably add the
-            // value to WifiConfiguration to reflect that it's
-            // supported by the WPA supplicant
-            Slog.w(TAG, "Failed to look-up a string: " + string);
-        }
-
-        return -1;
-    }
-
-    /**
+     /**
      * See {@link android.net.wifi.WifiManager#removeNetwork(int)}
      * @param netId the integer that identifies the network configuration
      * to the supplicant
@@ -1311,7 +475,6 @@
      */
     public boolean removeNetwork(int netId) {
         enforceChangePermission();
-
         return mWifiStateTracker.removeNetwork(netId);
     }
 
@@ -1324,14 +487,7 @@
      */
     public boolean enableNetwork(int netId, boolean disableOthers) {
         enforceChangePermission();
-
-        String ifname = mWifiStateTracker.getInterfaceName();
-        NetworkUtils.enableInterface(ifname);
-        boolean result = mWifiStateTracker.enableNetwork(netId, disableOthers);
-        if (!result) {
-            NetworkUtils.disableInterface(ifname);
-        }
-        return result;
+        return mWifiStateTracker.enableNetwork(netId, disableOthers);
     }
 
     /**
@@ -1342,7 +498,6 @@
      */
     public boolean disableNetwork(int netId) {
         enforceChangePermission();
-
         return mWifiStateTracker.disableNetwork(netId);
     }
 
@@ -1366,180 +521,19 @@
      */
     public List<ScanResult> getScanResults() {
         enforceAccessPermission();
-        String reply;
-
-        reply = mWifiStateTracker.scanResults();
-        if (reply == null) {
-            return null;
-        }
-
-        List<ScanResult> scanList = new ArrayList<ScanResult>();
-
-        int lineCount = 0;
-
-        int replyLen = reply.length();
-        // Parse the result string, keeping in mind that the last line does
-        // not end with a newline.
-        for (int lineBeg = 0, lineEnd = 0; lineEnd <= replyLen; ++lineEnd) {
-            if (lineEnd == replyLen || reply.charAt(lineEnd) == '\n') {
-                ++lineCount;
-                /*
-                 * Skip the first line, which is a header
-                 */
-                if (lineCount == 1) {
-                    lineBeg = lineEnd + 1;
-                    continue;
-                }
-                if (lineEnd > lineBeg) {
-                    String line = reply.substring(lineBeg, lineEnd);
-                    ScanResult scanResult = parseScanResult(line);
-                    if (scanResult != null) {
-                        scanList.add(scanResult);
-                    } else if (DBG) {
-                        Slog.w(TAG, "misformatted scan result for: " + line);
-                    }
-                }
-                lineBeg = lineEnd + 1;
-            }
-        }
-        mWifiStateTracker.setScanResultsList(scanList);
-        return scanList;
-    }
-
-    /**
-     * Parse the scan result line passed to us by wpa_supplicant (helper).
-     * @param line the line to parse
-     * @return the {@link ScanResult} object
-     */
-    private ScanResult parseScanResult(String line) {
-        ScanResult scanResult = null;
-        if (line != null) {
-            /*
-             * Cache implementation (LinkedHashMap) is not synchronized, thus,
-             * must synchronized here!
-             */
-            synchronized (mScanResultCache) {
-                String[] result = scanResultPattern.split(line);
-                if (3 <= result.length && result.length <= 5) {
-                    String bssid = result[0];
-                    // bssid | frequency | level | flags | ssid
-                    int frequency;
-                    int level;
-                    try {
-                        frequency = Integer.parseInt(result[1]);
-                        level = Integer.parseInt(result[2]);
-                        /* some implementations avoid negative values by adding 256
-                         * so we need to adjust for that here.
-                         */
-                        if (level > 0) level -= 256;
-                    } catch (NumberFormatException e) {
-                        frequency = 0;
-                        level = 0;
-                    }
-
-                    /*
-                     * The formatting of the results returned by
-                     * wpa_supplicant is intended to make the fields
-                     * line up nicely when printed,
-                     * not to make them easy to parse. So we have to
-                     * apply some heuristics to figure out which field
-                     * is the SSID and which field is the flags.
-                     */
-                    String ssid;
-                    String flags;
-                    if (result.length == 4) {
-                        if (result[3].charAt(0) == '[') {
-                            flags = result[3];
-                            ssid = "";
-                        } else {
-                            flags = "";
-                            ssid = result[3];
-                        }
-                    } else if (result.length == 5) {
-                        flags = result[3];
-                        ssid = result[4];
-                    } else {
-                        // Here, we must have 3 fields: no flags and ssid
-                        // set
-                        flags = "";
-                        ssid = "";
-                    }
-
-                    // bssid + ssid is the hash key
-                    String key = bssid + ssid;
-                    scanResult = mScanResultCache.get(key);
-                    if (scanResult != null) {
-                        scanResult.level = level;
-                        scanResult.SSID = ssid;
-                        scanResult.capabilities = flags;
-                        scanResult.frequency = frequency;
-                    } else {
-                        // Do not add scan results that have no SSID set
-                        if (0 < ssid.trim().length()) {
-                            scanResult =
-                                new ScanResult(
-                                    ssid, bssid, flags, level, frequency);
-                            mScanResultCache.put(key, scanResult);
-                        }
-                    }
-                } else {
-                    Slog.w(TAG, "Misformatted scan result text with " +
-                          result.length + " fields: " + line);
-                }
-            }
-        }
-
-        return scanResult;
-    }
-
-    /**
-     * Parse the "flags" field passed back in a scan result by wpa_supplicant,
-     * and construct a {@code WifiConfiguration} that describes the encryption,
-     * key management, and authenticaion capabilities of the access point.
-     * @param flags the string returned by wpa_supplicant
-     * @return the {@link WifiConfiguration} object, filled in
-     */
-    WifiConfiguration parseScanFlags(String flags) {
-        WifiConfiguration config = new WifiConfiguration();
-
-        if (flags.length() == 0) {
-            config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
-        }
-        // ... to be implemented
-        return config;
+        return mWifiStateTracker.getScanResultsList();
     }
 
     /**
      * Tell the supplicant to persist the current list of configured networks.
      * @return {@code true} if the operation succeeded
+     *
+     * TODO: deprecate this
      */
     public boolean saveConfiguration() {
-        boolean result;
+        boolean result = true;
         enforceChangePermission();
-
-        synchronized (mWifiStateTracker) {
-            result = mWifiStateTracker.saveConfig();
-            if (result && mNeedReconfig) {
-                mNeedReconfig = false;
-                result = mWifiStateTracker.reloadConfig();
-
-                if (result) {
-                    Intent intent = new Intent(WifiManager.NETWORK_IDS_CHANGED_ACTION);
-                    mContext.sendBroadcast(intent);
-                }
-            }
-        }
-        // Inform the backup manager about a data change
-        IBackupManager ibm = IBackupManager.Stub.asInterface(
-                ServiceManager.getService(Context.BACKUP_SERVICE));
-        if (ibm != null) {
-            try {
-                ibm.dataChanged("com.android.providers.settings");
-            } catch (Exception e) {
-                // Try again later
-            }
-        }
-        return result;
+        return mWifiStateTracker.saveConfig();
     }
 
     /**
@@ -1554,7 +548,7 @@
      * @return {@code true} if the operation succeeds, {@code false} otherwise, e.g.,
      * {@code numChannels} is outside the valid range.
      */
-    public boolean setNumAllowedChannels(int numChannels, boolean persist) {
+    public synchronized boolean setNumAllowedChannels(int numChannels, boolean persist) {
         Slog.i(TAG, "WifiService trying to setNumAllowed to "+numChannels+
                 " with persist set to "+persist);
         enforceChangePermission();
@@ -1576,28 +570,15 @@
             return false;
         }
 
-        if (mWifiHandler == null) return false;
-
-        Message.obtain(mWifiHandler,
-                MESSAGE_SET_CHANNELS, numChannels, (persist ? 1 : 0)).sendToTarget();
-
-        return true;
-    }
-
-    /**
-     * sets the number of allowed radio frequency channels synchronously
-     * @param numChannels the number of allowed channels. Must be greater than 0
-     * and less than or equal to 16.
-     * @param persist {@code true} if the setting should be remembered.
-     * @return {@code true} if the operation succeeds, {@code false} otherwise
-     */
-    private boolean setNumAllowedChannelsBlocking(int numChannels, boolean persist) {
         if (persist) {
             Settings.Secure.putInt(mContext.getContentResolver(),
                     Settings.Secure.WIFI_NUM_ALLOWED_CHANNELS,
                     numChannels);
         }
-        return mWifiStateTracker.setNumAllowedChannels(numChannels);
+
+        mWifiStateTracker.setNumAllowedChannels(numChannels);
+
+        return true;
     }
 
     /**
@@ -1675,8 +656,8 @@
                     WifiInfo info = mWifiStateTracker.requestConnectionInfo();
                     if (info.getSupplicantState() != SupplicantState.COMPLETED) {
                         // we used to go to sleep immediately, but this caused some race conditions
-                        // we don't have time to track down for this release.  Delay instead, but not
-                        // as long as we would if connected (below)
+                        // we don't have time to track down for this release.  Delay instead,
+                        // but not as long as we would if connected (below)
                         // TODO - fix the race conditions and switch back to the immediate turn-off
                         long triggerTime = System.currentTimeMillis() + (2*60*1000); // 2 min
                         Slog.d(TAG, "setting ACTION_DEVICE_IDLE timer for 120,000 ms");
@@ -1775,31 +756,9 @@
         }
     };
 
-    private void sendEnableMessage(boolean enable, boolean persist, int uid) {
-        Message msg = Message.obtain(mWifiHandler,
-                                     (enable ? MESSAGE_ENABLE_WIFI : MESSAGE_DISABLE_WIFI),
-                                     (persist ? 1 : 0), uid);
-        msg.sendToTarget();
-    }
-
-    private void sendStartMessage(boolean scanOnlyMode) {
-        Message.obtain(mWifiHandler, MESSAGE_START_WIFI, scanOnlyMode ? 1 : 0, 0).sendToTarget();
-    }
-
-    private void sendAccessPointMessage(boolean enable, WifiConfiguration wifiConfig, int uid) {
-        Message.obtain(mWifiHandler,
-                (enable ? MESSAGE_START_ACCESS_POINT : MESSAGE_STOP_ACCESS_POINT),
-                uid, 0, wifiConfig).sendToTarget();
-    }
-
     private void updateWifiState() {
-        // send a message so it's all serialized
-        Message.obtain(mWifiHandler, MESSAGE_UPDATE_STATE, 0, 0).sendToTarget();
-    }
-
-    private void doUpdateWifiState() {
         boolean wifiEnabled = getPersistedWifiEnabled();
-        boolean airplaneMode = isAirplaneModeOn() && !mAirplaneModeOverwridden;
+        boolean airplaneMode = isAirplaneModeOn() && !mAirplaneModeOverwridden.get();
         boolean lockHeld = mLocks.hasLocks();
         int strongestLockMode;
         boolean wifiShouldBeEnabled = wifiEnabled && !airplaneMode;
@@ -1810,43 +769,23 @@
             strongestLockMode = WifiManager.WIFI_MODE_FULL;
         }
 
-        synchronized (mWifiHandler) {
-            if ((mWifiStateTracker.getWifiState() == WIFI_STATE_ENABLING) && !airplaneMode) {
-                return;
-            }
+        /* Disable tethering when airplane mode is enabled */
+        if (airplaneMode) {
+            mWifiStateTracker.setWifiApEnabled(null, false);
+        }
 
-            /* Disable tethering when airplane mode is enabled */
-            if (airplaneMode &&
-                (mWifiApState == WIFI_AP_STATE_ENABLING || mWifiApState == WIFI_AP_STATE_ENABLED)) {
-                sWakeLock.acquire();
-                sendAccessPointMessage(false, null, mLastApEnableUid);
-            }
-
-            if (wifiShouldBeEnabled) {
-                if (wifiShouldBeStarted) {
-                    sWakeLock.acquire();
-                    sendEnableMessage(true, false, mLastEnableUid);
-                    sWakeLock.acquire();
-                    sendStartMessage(strongestLockMode == WifiManager.WIFI_MODE_SCAN_ONLY);
-                } else if (!mWifiStateTracker.isDriverStopped()) {
-                    int wakeLockTimeout =
-                            Settings.Secure.getInt(
-                                    mContext.getContentResolver(),
-                                    Settings.Secure.WIFI_MOBILE_DATA_TRANSITION_WAKELOCK_TIMEOUT_MS,
-                                    DEFAULT_WAKELOCK_TIMEOUT);
-                    /*
-                     * We are assuming that ConnectivityService can make
-                     * a transition to cellular data within wakeLockTimeout time.
-                     * The wakelock is released by the delayed message.
-                     */
-                    sDriverStopWakeLock.acquire();
-                    mWifiHandler.sendEmptyMessage(MESSAGE_STOP_WIFI);
-                    mWifiHandler.sendEmptyMessageDelayed(MESSAGE_RELEASE_WAKELOCK, wakeLockTimeout);
-                }
+        if (wifiShouldBeEnabled) {
+            if (wifiShouldBeStarted) {
+                mWifiStateTracker.setWifiEnabled(true);
+                mWifiStateTracker.setScanOnlyMode(
+                        strongestLockMode == WifiManager.WIFI_MODE_SCAN_ONLY);
+                mWifiStateTracker.startWifi(true);
             } else {
-                sWakeLock.acquire();
-                sendEnableMessage(false, false, mLastEnableUid);
+                mWifiStateTracker.requestCmWakeLock();
+                mWifiStateTracker.disconnectAndStop();
             }
+        } else {
+            mWifiStateTracker.setWifiEnabled(false);
         }
     }
 
@@ -1884,75 +823,6 @@
                 Settings.System.AIRPLANE_MODE_ON, 0) == 1;
     }
 
-    /**
-     * Handler that allows posting to the WifiThread.
-     */
-    private class WifiHandler extends Handler {
-        public WifiHandler(Looper looper) {
-            super(looper);
-        }
-
-        @Override
-        public void handleMessage(Message msg) {
-            switch (msg.what) {
-
-                case MESSAGE_ENABLE_WIFI:
-                    setWifiEnabledBlocking(true, msg.arg1 == 1, msg.arg2);
-                    if (mWifiWatchdogService == null) {
-                        mWifiWatchdogService = new WifiWatchdogService(mContext, mWifiStateTracker);
-                    }
-                    sWakeLock.release();
-                    break;
-
-                case MESSAGE_START_WIFI:
-                    mWifiStateTracker.setScanOnlyMode(msg.arg1 != 0);
-                    mWifiStateTracker.restart();
-                    sWakeLock.release();
-                    break;
-
-                case MESSAGE_UPDATE_STATE:
-                    doUpdateWifiState();
-                    break;
-
-                case MESSAGE_DISABLE_WIFI:
-                    // a non-zero msg.arg1 value means the "enabled" setting
-                    // should be persisted
-                    setWifiEnabledBlocking(false, msg.arg1 == 1, msg.arg2);
-                    mWifiWatchdogService = null;
-                    sWakeLock.release();
-                    break;
-
-                case MESSAGE_STOP_WIFI:
-                    mWifiStateTracker.disconnectAndStop();
-                    // don't release wakelock
-                    break;
-
-                case MESSAGE_RELEASE_WAKELOCK:
-                    sDriverStopWakeLock.release();
-                    break;
-
-                case MESSAGE_START_ACCESS_POINT:
-                    setWifiApEnabledBlocking(true,
-                                             msg.arg1,
-                                             (WifiConfiguration) msg.obj);
-                    sWakeLock.release();
-                    break;
-
-                case MESSAGE_STOP_ACCESS_POINT:
-                    setWifiApEnabledBlocking(false,
-                                             msg.arg1,
-                                             (WifiConfiguration) msg.obj);
-                    sWakeLock.release();
-                    break;
-
-                case MESSAGE_SET_CHANNELS:
-                    setNumAllowedChannelsBlocking(msg.arg1, msg.arg2 == 1);
-                    break;
-
-            }
-        }
-    }
-
     @Override
     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
@@ -1962,7 +832,7 @@
                     + ", uid=" + Binder.getCallingUid());
             return;
         }
-        pw.println("Wi-Fi is " + stateName(mWifiStateTracker.getWifiState()));
+        pw.println("Wi-Fi is " + mWifiStateTracker.getWifiStateByName());
         pw.println("Stay-awake conditions: " +
                 Settings.System.getInt(mContext.getContentResolver(),
                                        Settings.System.STAY_ON_WHILE_PLUGGED_IN, 0));
@@ -1994,23 +864,6 @@
         mLocks.dump(pw);
     }
 
-    private static String stateName(int wifiState) {
-        switch (wifiState) {
-            case WIFI_STATE_DISABLING:
-                return "disabling";
-            case WIFI_STATE_DISABLED:
-                return "disabled";
-            case WIFI_STATE_ENABLING:
-                return "enabling";
-            case WIFI_STATE_ENABLED:
-                return "enabled";
-            case WIFI_STATE_UNKNOWN:
-                return "unknown state";
-            default:
-                return "[invalid state]";
-        }
-    }
-
     private class WifiLock extends DeathRecipient {
         WifiLock(int lockMode, String tag, IBinder binder) {
             super(lockMode, tag, binder);
diff --git a/services/java/com/android/server/WifiWatchdogService.java b/services/java/com/android/server/WifiWatchdogService.java
index 445dd03..be14cd3 100644
--- a/services/java/com/android/server/WifiWatchdogService.java
+++ b/services/java/com/android/server/WifiWatchdogService.java
@@ -275,12 +275,13 @@
     /**
      * Unregister broadcasts and quit the watchdog thread
      */
-    private void quit() {
-        unregisterForWifiBroadcasts();
-        mContext.getContentResolver().unregisterContentObserver(mContentObserver);
-        mHandler.removeAllActions();
-        mHandler.getLooper().quit();
-    }
+    //TODO: Change back to running WWS when needed
+//    private void quit() {
+//        unregisterForWifiBroadcasts();
+//        mContext.getContentResolver().unregisterContentObserver(mContentObserver);
+//        mHandler.removeAllActions();
+//        mHandler.getLooper().quit();
+//    }
 
     /**
      * Waits for the main watchdog thread to create the handler.
@@ -751,7 +752,7 @@
         // Black list this "bad" AP, this will cause an attempt to connect to another
         blacklistAp(ap.bssid);
         // Initiate an association to an alternate AP
-        mWifiStateTracker.reassociate();
+        mWifiStateTracker.reassociateCommand();
     }
 
     private void blacklistAp(String bssid) {
@@ -762,10 +763,7 @@
         // Before taking action, make sure we should not cancel our processing
         if (shouldCancel()) return;
         
-        if (!mWifiStateTracker.addToBlacklist(bssid)) {
-            // There's a known bug where this method returns failure on success
-            //Slog.e(TAG, "Blacklisting " + bssid + " failed");
-        }
+        mWifiStateTracker.addToBlacklist(bssid);
 
         if (D) {
             myLogD("Blacklisting " + bssid);
@@ -860,10 +858,7 @@
              * (and blacklisted them). Clear the blacklist so the AP with best
              * signal is chosen.
              */
-            if (!mWifiStateTracker.clearBlacklist()) {
-                // There's a known bug where this method returns failure on success
-                //Slog.e(TAG, "Clearing blacklist failed");
-            }
+            mWifiStateTracker.clearBlacklist();
             
             if (V) {
                 myLogV("handleSleep: Set state to SLEEP and cleared blacklist");
@@ -1151,7 +1146,7 @@
 
         private void handleWifiStateChanged(int wifiState) {
             if (wifiState == WifiManager.WIFI_STATE_DISABLED) {
-                quit();
+                onDisconnected();
             } else if (wifiState == WifiManager.WIFI_STATE_ENABLED) {
                 onEnabled();
             }
diff --git a/services/java/com/android/server/WindowManagerService.java b/services/java/com/android/server/WindowManagerService.java
index cd687da..c4de958 100644
--- a/services/java/com/android/server/WindowManagerService.java
+++ b/services/java/com/android/server/WindowManagerService.java
@@ -156,7 +156,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;
@@ -1791,18 +1790,11 @@
         boolean reportNewConfig = false;
         WindowState attachedWindow = null;
         WindowState win = null;
+        long origId;
 
         synchronized(mWindowMap) {
-            // Instantiating a Display requires talking with the simulator,
-            // so don't do it until we know the system is mostly up and
-            // running.
             if (mDisplay == null) {
-                WindowManager wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
-                mDisplay = wm.getDefaultDisplay();
-                mInitialDisplayWidth = mDisplay.getWidth();
-                mInitialDisplayHeight = mDisplay.getHeight();
-                mInputManager.setDisplaySize(0, mInitialDisplayWidth, mInitialDisplayHeight);
-                reportNewConfig = true;
+                throw new IllegalStateException("Display has not been initialialized");
             }
 
             if (mWindowMap.containsKey(client.asBinder())) {
@@ -1908,7 +1900,7 @@
 
             res = WindowManagerImpl.ADD_OKAY;
 
-            final long origId = Binder.clearCallingIdentity();
+            origId = Binder.clearCallingIdentity();
 
             if (addToken) {
                 mTokenMap.put(attrs.token, token);
@@ -1985,14 +1977,10 @@
             }
         }
 
-        // sendNewConfiguration() checks caller permissions so we must call it with
-        // privilege.  updateOrientationFromAppTokens() clears and resets the caller
-        // identity anyway, so it's safe to just clear & restore around this whole
-        // block.
-        final long origId = Binder.clearCallingIdentity();
         if (reportNewConfig) {
             sendNewConfiguration();
         }
+
         Binder.restoreCallingIdentity(origId);
 
         return res;
@@ -3105,8 +3093,11 @@
                 
             } else if (currentConfig != null) {
                 // No obvious action we need to take, but if our current
-                // state mismatches the activity maanager's, update it
+                // state mismatches the activity manager's, update it,
+                // disregarding font scale, which should remain set to
+                // the value of the previous configuration.
                 mTempConfiguration.setToDefaults();
+                mTempConfiguration.fontScale = currentConfig.fontScale;
                 if (computeNewConfigurationLocked(mTempConfiguration)) {
                     if (currentConfig.diff(mTempConfiguration) != 0) {
                         mWaitingForConfig = true;
@@ -4422,8 +4413,7 @@
             final int N = mWindows.size();
             for (int i=0; i<N; i++) {
                 WindowState w = mWindows.get(i);
-                if (w.isVisibleLw() && !w.mObscured
-                        && (w.mOrientationChanging || !w.isDrawnLw())) {
+                if (w.isVisibleLw() && !w.mObscured && !w.isDrawnLw()) {
                     return;
                 }
             }
@@ -5542,6 +5532,22 @@
     }
 
     public void systemReady() {
+        synchronized(mWindowMap) {
+            if (mDisplay != null) {
+                throw new IllegalStateException("Display already initialized");
+            }
+            WindowManager wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
+            mDisplay = wm.getDefaultDisplay();
+            mInitialDisplayWidth = mDisplay.getWidth();
+            mInitialDisplayHeight = mDisplay.getHeight();
+            mInputManager.setDisplaySize(0, mInitialDisplayWidth, mInitialDisplayHeight);
+        }
+
+        try {
+            mActivityManager.updateConfiguration(null);
+        } catch (RemoteException e) {
+        }
+        
         mPolicy.systemReady();
     }
 
@@ -6819,7 +6825,7 @@
             final AppWindowToken atoken = mAppToken;
             return mSurface != null && !mAttachedHidden
                     && (atoken == null ? mPolicyVisibility : !atoken.hiddenRequested)
-                    && (mOrientationChanging || (!mDrawPending && !mCommitDrawPending))
+                    && !mDrawPending && !mCommitDrawPending
                     && !mExiting && !mDestroying;
         }
 
@@ -6923,14 +6929,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,
@@ -8451,6 +8455,11 @@
 
     private final void performLayoutAndPlaceSurfacesLockedInner(
             boolean recoveringMemory) {
+        if (mDisplay == null) {
+            Slog.i(TAG, "skipping performLayoutAndPlaceSurfacesLockedInner with no mDisplay");
+            return;
+        }
+
         final long currentTime = SystemClock.uptimeMillis();
         final int dw = mDisplay.getWidth();
         final int dh = mDisplay.getHeight();
@@ -9229,12 +9238,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);
@@ -9631,30 +9634,26 @@
         } else if (animating) {
             requestAnimationLocked(currentTime+(1000/60)-SystemClock.uptimeMillis());
         }
-        
+
         mInputMonitor.updateInputWindowsLw();
-        
-        if (DEBUG_FREEZE) Slog.v(TAG, "Layout: mDisplayFrozen=" + mDisplayFrozen
-                + " holdScreen=" + holdScreen);
-        if (!mDisplayFrozen) {
-            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);
-            }
+
+        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) {
@@ -9943,8 +9942,6 @@
             mFreezeGcPending = now;
         }
 
-        if (DEBUG_FREEZE) Slog.v(TAG, "*** FREEZING DISPLAY", new RuntimeException());
-        
         mDisplayFrozen = true;
         
         mInputMonitor.freezeInputDispatchingLw();
@@ -9971,8 +9968,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/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index b2cb941..a32cd4c 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -2359,6 +2359,10 @@
             }
             
             if (proc.thread != null) {
+                if (proc.pid == Process.myPid()) {
+                    Log.w(TAG, "crashApplication: trying to crash self!");
+                    return;
+                }
                 long ident = Binder.clearCallingIdentity();
                 try {
                     proc.thread.scheduleCrash(message);
@@ -6053,7 +6057,7 @@
                     && (mHomeProcess.info.flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
             Iterator it = mHomeProcess.activities.iterator();
             while (it.hasNext()) {
-                HistoryRecord r = (HistoryRecord)it.next();
+                ActivityRecord r = (ActivityRecord)it.next();
                 if (r.isHomeActivity) {
                     Log.i(TAG, "Clearing package preferred activities from " + r.packageName);
                     try {
@@ -11723,7 +11727,69 @@
             }
         }
     }
-    
+
+    public boolean dumpHeap(String process, boolean managed,
+            String path, ParcelFileDescriptor fd) throws RemoteException {
+
+        try {
+            synchronized (this) {
+                // note: hijacking SET_ACTIVITY_WATCHER, but should be changed to
+                // its own permission (same as profileControl).
+                if (checkCallingPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER)
+                        != PackageManager.PERMISSION_GRANTED) {
+                    throw new SecurityException("Requires permission "
+                            + android.Manifest.permission.SET_ACTIVITY_WATCHER);
+                }
+
+                if (fd == null) {
+                    throw new IllegalArgumentException("null fd");
+                }
+
+                ProcessRecord proc = null;
+                try {
+                    int pid = Integer.parseInt(process);
+                    synchronized (mPidsSelfLocked) {
+                        proc = mPidsSelfLocked.get(pid);
+                    }
+                } catch (NumberFormatException e) {
+                }
+
+                if (proc == null) {
+                    HashMap<String, SparseArray<ProcessRecord>> all
+                            = mProcessNames.getMap();
+                    SparseArray<ProcessRecord> procs = all.get(process);
+                    if (procs != null && procs.size() > 0) {
+                        proc = procs.valueAt(0);
+                    }
+                }
+
+                if (proc == null || proc.thread == null) {
+                    throw new IllegalArgumentException("Unknown process: " + process);
+                }
+
+                boolean isSecure = "1".equals(SystemProperties.get(SYSTEM_SECURE, "0"));
+                if (isSecure) {
+                    if ((proc.info.flags&ApplicationInfo.FLAG_DEBUGGABLE) == 0) {
+                        throw new SecurityException("Process not debuggable: " + proc);
+                    }
+                }
+
+                proc.thread.dumpHeap(managed, path, fd);
+                fd = null;
+                return true;
+            }
+        } catch (RemoteException e) {
+            throw new IllegalStateException("Process disappeared");
+        } finally {
+            if (fd != null) {
+                try {
+                    fd.close();
+                } catch (IOException e) {
+                }
+            }
+        }
+    }
+
     /** In this method we try to acquire our lock to make sure that we have not deadlocked */
     public void monitor() {
         synchronized (this) { }
diff --git a/services/java/com/android/server/connectivity/Tethering.java b/services/java/com/android/server/connectivity/Tethering.java
index eb0a8a9..acedd26 100644
--- a/services/java/com/android/server/connectivity/Tethering.java
+++ b/services/java/com/android/server/connectivity/Tethering.java
@@ -65,7 +65,8 @@
 public class Tethering extends INetworkManagementEventObserver.Stub {
 
     private Context mContext;
-    private final String TAG = "Tethering";
+    private final static String TAG = "Tethering";
+    private final static boolean DEBUG = false;
 
     private boolean mBooted = false;
     //used to remember if we got connected before boot finished
@@ -174,7 +175,7 @@
     }
 
     public void interfaceLinkStatusChanged(String iface, boolean link) {
-        Log.d(TAG, "interfaceLinkStatusChanged " + iface + ", " + link);
+        if (DEBUG) Log.d(TAG, "interfaceLinkStatusChanged " + iface + ", " + link);
         boolean found = false;
         boolean usb = false;
         if (isWifi(iface)) {
@@ -229,28 +230,30 @@
             usb = true;
         }
         if (found == false) {
-            Log.d(TAG, iface + " is not a tetherable iface, ignoring");
+            if (DEBUG) Log.d(TAG, iface + " is not a tetherable iface, ignoring");
             return;
         }
 
         synchronized (mIfaces) {
             TetherInterfaceSM sm = mIfaces.get(iface);
             if (sm != null) {
-                Log.e(TAG, "active iface (" + iface + ") reported as added, ignoring");
+                if (DEBUG) Log.d(TAG, "active iface (" + iface + ") reported as added, ignoring");
                 return;
             }
             sm = new TetherInterfaceSM(iface, mLooper, usb);
             mIfaces.put(iface, sm);
             sm.start();
         }
-        Log.d(TAG, "interfaceAdded :" + iface);
+        if (DEBUG) Log.d(TAG, "interfaceAdded :" + iface);
     }
 
     public void interfaceRemoved(String iface) {
         synchronized (mIfaces) {
             TetherInterfaceSM sm = mIfaces.get(iface);
             if (sm == null) {
-                Log.e(TAG, "attempting to remove unknown iface (" + iface + "), ignoring");
+                if (DEBUG) {
+                    Log.e(TAG, "attempting to remove unknown iface (" + iface + "), ignoring");
+                }
                 return;
             }
             sm.sendMessage(TetherInterfaceSM.CMD_INTERFACE_DOWN);
@@ -350,8 +353,10 @@
         broadcast.putStringArrayListExtra(ConnectivityManager.EXTRA_ERRORED_TETHER,
                 erroredList);
         mContext.sendStickyBroadcast(broadcast);
-        Log.d(TAG, "sendTetherStateChangedBroadcast " + availableList.size() + ", " +
-                activeList.size() + ", " + erroredList.size());
+        if (DEBUG) {
+            Log.d(TAG, "sendTetherStateChangedBroadcast " + availableList.size() + ", " +
+                    activeList.size() + ", " + erroredList.size());
+        }
 
         if (usbTethered) {
             if (wifiTethered) {
@@ -435,7 +440,7 @@
                 mUsbMassStorageOff = true;
                 updateUsbStatus();
             } else if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
-                Log.d(TAG, "Tethering got CONNECTIVITY_ACTION");
+                if (DEBUG) Log.d(TAG, "Tethering got CONNECTIVITY_ACTION");
                 mTetherMasterSM.sendMessage(TetherMasterSM.CMD_UPSTREAM_CHANGED);
             } else if (action.equals(Intent.ACTION_BOOT_COMPLETED)) {
                 mBooted = true;
@@ -468,7 +473,7 @@
 
     // toggled when we enter/leave the fully teathered state
     private boolean enableUsbRndis(boolean enabled) {
-        Log.d(TAG, "enableUsbRndis(" + enabled + ")");
+        if (DEBUG) Log.d(TAG, "enableUsbRndis(" + enabled + ")");
         IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
                 INetworkManagementService service = INetworkManagementService.Stub.asInterface(b);
 
@@ -493,7 +498,7 @@
 
     // configured when we start tethering and unconfig'd on error or conclusion
     private boolean configureUsbIface(boolean enabled) {
-        Log.d(TAG, "configureUsbIface(" + enabled + ")");
+        if (DEBUG) Log.d(TAG, "configureUsbIface(" + enabled + ")");
 
         IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
         INetworkManagementService service = INetworkManagementService.Stub.asInterface(b);
@@ -740,7 +745,7 @@
 
             @Override
             public boolean processMessage(Message message) {
-                Log.d(TAG, "InitialState.processMessage what=" + message.what);
+                if (DEBUG) Log.d(TAG, "InitialState.processMessage what=" + message.what);
                 boolean retValue = true;
                 switch (message.what) {
                     case CMD_TETHER_REQUESTED:
@@ -781,7 +786,7 @@
             }
             @Override
             public boolean processMessage(Message message) {
-                Log.d(TAG, "StartingState.processMessage what=" + message.what);
+                if (DEBUG) Log.d(TAG, "StartingState.processMessage what=" + message.what);
                 boolean retValue = true;
                 switch (message.what) {
                     // maybe a parent class?
@@ -833,7 +838,7 @@
                     return;
                 }
                 if (mUsb) Tethering.this.enableUsbRndis(true);
-                Log.d(TAG, "Tethered " + mIfaceName);
+                if (DEBUG) Log.d(TAG, "Tethered " + mIfaceName);
                 setAvailable(false);
                 setTethered(true);
                 sendTetherStateChangedBroadcast();
@@ -844,7 +849,7 @@
             }
             @Override
             public boolean processMessage(Message message) {
-                Log.d(TAG, "TetheredState.processMessage what=" + message.what);
+                if (DEBUG) Log.d(TAG, "TetheredState.processMessage what=" + message.what);
                 boolean retValue = true;
                 boolean error = false;
                 switch (message.what) {
@@ -887,7 +892,7 @@
                         } else if (message.what == CMD_INTERFACE_DOWN) {
                             transitionTo(mUnavailableState);
                         }
-                        Log.d(TAG, "Untethered " + mIfaceName);
+                        if (DEBUG) Log.d(TAG, "Untethered " + mIfaceName);
                         break;
                     case CMD_TETHER_CONNECTION_CHANGED:
                         String newUpstreamIfaceName = (String)(message.obj);
@@ -960,7 +965,7 @@
                                     ConnectivityManager.TETHER_ERROR_MASTER_ERROR);
                             break;
                         }
-                        Log.d(TAG, "Tether lost upstream connection " + mIfaceName);
+                        if (DEBUG) Log.d(TAG, "Tether lost upstream connection " + mIfaceName);
                         sendTetherStateChangedBroadcast();
                         if (mUsb) {
                             if (!Tethering.this.configureUsbIface(false)) {
@@ -1198,8 +1203,10 @@
                 IBinder b = ServiceManager.getService(Context.CONNECTIVITY_SERVICE);
                 IConnectivityManager cm = IConnectivityManager.Stub.asInterface(b);
                 mConnectionRequested = false;
-                Log.d(TAG, "chooseUpstreamType(" + tryCell + "),  dunRequired ="
-                        + mDunRequired + ", iface=" + iface);
+                if (DEBUG) {
+                    Log.d(TAG, "chooseUpstreamType(" + tryCell + "),  dunRequired ="
+                            + mDunRequired + ", iface=" + iface);
+                }
                 if (iface != null) {
                     try {
                         if (mDunRequired) {
@@ -1207,7 +1214,7 @@
                             NetworkInfo info = cm.getNetworkInfo(
                                     ConnectivityManager.TYPE_MOBILE_DUN);
                             if (info.isConnected()) {
-                                Log.d(TAG, "setting dun ifacename =" + iface);
+                                if (DEBUG) Log.d(TAG, "setting dun ifacename =" + iface);
                                 // even if we're already connected - it may be somebody else's
                                 // refcount, so add our own
                                 turnOnMobileConnection();
@@ -1219,11 +1226,11 @@
                                 }
                             }
                         } else {
-                            Log.d(TAG, "checking if hipri brought us this connection");
+                            if (DEBUG) Log.d(TAG, "checking if hipri brought us this connection");
                             NetworkInfo info = cm.getNetworkInfo(
                                     ConnectivityManager.TYPE_MOBILE_HIPRI);
                             if (info.isConnected()) {
-                                Log.d(TAG, "yes - hipri in use");
+                                if (DEBUG) Log.d(TAG, "yes - hipri in use");
                                 // even if we're already connected - it may be sombody else's
                                 // refcount, so add our own
                                 turnOnMobileConnection();
@@ -1245,7 +1252,7 @@
                 notifyTetheredOfNewUpstreamIface(iface);
             }
             protected void notifyTetheredOfNewUpstreamIface(String ifaceName) {
-                Log.d(TAG, "notifying tethered with iface =" + ifaceName);
+                if (DEBUG) Log.d(TAG, "notifying tethered with iface =" + ifaceName);
                 mUpstreamIfaceName = ifaceName;
                 for (Object o : mNotifyList) {
                     TetherInterfaceSM sm = (TetherInterfaceSM)o;
@@ -1262,19 +1269,19 @@
             }
             @Override
             public boolean processMessage(Message message) {
-                Log.d(TAG, "MasterInitialState.processMessage what=" + message.what);
+                if (DEBUG) Log.d(TAG, "MasterInitialState.processMessage what=" + message.what);
                 boolean retValue = true;
                 switch (message.what) {
                     case CMD_TETHER_MODE_REQUESTED:
                         mDunRequired = isDunRequired();
                         TetherInterfaceSM who = (TetherInterfaceSM)message.obj;
-                        Log.d(TAG, "Tether Mode requested by " + who.toString());
+                        if (DEBUG) Log.d(TAG, "Tether Mode requested by " + who.toString());
                         mNotifyList.add(who);
                         transitionTo(mTetherModeAliveState);
                         break;
                     case CMD_TETHER_MODE_UNREQUESTED:
                         who = (TetherInterfaceSM)message.obj;
-                        Log.d(TAG, "Tether Mode unrequested by " + who.toString());
+                        if (DEBUG) Log.d(TAG, "Tether Mode unrequested by " + who.toString());
                         int index = mNotifyList.indexOf(who);
                         if (index != -1) {
                             mNotifyList.remove(who);
@@ -1304,7 +1311,7 @@
             }
             @Override
             public boolean processMessage(Message message) {
-                Log.d(TAG, "TetherModeAliveState.processMessage what=" + message.what);
+                if (DEBUG) Log.d(TAG, "TetherModeAliveState.processMessage what=" + message.what);
                 boolean retValue = true;
                 switch (message.what) {
                     case CMD_TETHER_MODE_REQUESTED:
@@ -1332,8 +1339,10 @@
                         // make sure we're still using a requested connection - may have found
                         // wifi or something since then.
                         if (mConnectionRequested) {
-                            Log.d(TAG, "renewing mobile connection - requeuing for another " +
-                                    CELL_CONNECTION_RENEW_MS + "ms");
+                            if (DEBUG) {
+                                Log.d(TAG, "renewing mobile connection - requeuing for another " +
+                                        CELL_CONNECTION_RENEW_MS + "ms");
+                            }
                             turnOnMobileConnection();
                         }
                         break;
diff --git a/services/java/com/android/server/location/ComprehensiveCountryDetector.java b/services/java/com/android/server/location/ComprehensiveCountryDetector.java
new file mode 100755
index 0000000..e692f8d
--- /dev/null
+++ b/services/java/com/android/server/location/ComprehensiveCountryDetector.java
@@ -0,0 +1,359 @@
+/*
+ * 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.location;
+
+import java.util.Locale;
+import java.util.Timer;
+import java.util.TimerTask;
+
+import android.content.Context;
+import android.location.Country;
+import android.location.CountryListener;
+import android.location.Geocoder;
+import android.provider.Settings;
+import android.telephony.PhoneStateListener;
+import android.telephony.ServiceState;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.util.Slog;
+
+/**
+ * This class is used to detect the country where the user is. The sources of
+ * country are queried in order of reliability, like
+ * <ul>
+ * <li>Mobile network</li>
+ * <li>Location</li>
+ * <li>SIM's country</li>
+ * <li>Phone's locale</li>
+ * </ul>
+ * <p>
+ * Call the {@link #detectCountry()} to get the available country immediately.
+ * <p>
+ * To be notified of the future country change, using the
+ * {@link #setCountryListener(CountryListener)}
+ * <p>
+ * Using the {@link #stop()} to stop listening to the country change.
+ * <p>
+ * The country information will be refreshed every
+ * {@link #LOCATION_REFRESH_INTERVAL} once the location based country is used.
+ *
+ * @hide
+ */
+public class ComprehensiveCountryDetector extends CountryDetectorBase {
+
+    private final static String TAG = "ComprehensiveCountryDetector";
+
+    /**
+     * The refresh interval when the location based country was used
+     */
+    private final static long LOCATION_REFRESH_INTERVAL = 1000 * 60 * 60 * 24; // 1 day
+
+    protected CountryDetectorBase mLocationBasedCountryDetector;
+    protected Timer mLocationRefreshTimer;
+
+    private final int mPhoneType;
+    private Country mCountry;
+    private TelephonyManager mTelephonyManager;
+    private Country mCountryFromLocation;
+    private boolean mStopped = false;
+    private ServiceState mLastState;
+
+    private PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
+        @Override
+        public void onServiceStateChanged(ServiceState serviceState) {
+            // TODO: Find out how often we will be notified, if this method is called too
+            // many times, let's consider querying the network.
+            Slog.d(TAG, "onServiceStateChanged");
+            // We only care the state change
+            if (mLastState == null || mLastState.getState() != serviceState.getState()) {
+                detectCountry(true, true);
+                mLastState = new ServiceState(serviceState);
+            }
+        }
+    };
+
+    /**
+     * The listener for receiving the notification from LocationBasedCountryDetector.
+     */
+    private CountryListener mLocationBasedCountryDetectionListener = new CountryListener() {
+        public void onCountryDetected(Country country) {
+            mCountryFromLocation = country;
+            // Don't start the LocationBasedCountryDetector.
+            detectCountry(true, false);
+            stopLocationBasedDetector();
+        }
+    };
+
+    public ComprehensiveCountryDetector(Context context) {
+        super(context);
+        mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
+        mPhoneType = mTelephonyManager.getPhoneType();
+    }
+
+    @Override
+    public Country detectCountry() {
+        // Don't start the LocationBasedCountryDetector if we have been stopped.
+        return detectCountry(false, !mStopped);
+    }
+
+    @Override
+    public void stop() {
+        Slog.i(TAG, "Stop the detector.");
+        cancelLocationRefresh();
+        removePhoneStateListener();
+        stopLocationBasedDetector();
+        mListener = null;
+        mStopped = true;
+    }
+
+    /**
+     * Get the country from different sources in order of the reliability.
+     */
+    private Country getCountry() {
+        Country result = null;
+        result = getNetworkBasedCountry();
+        if (result == null) {
+            result = getLastKnownLocationBasedCountry();
+        }
+        if (result == null) {
+            result = getSimBasedCountry();
+        }
+        if (result == null) {
+            result = getLocaleCountry();
+        }
+        return result;
+    }
+
+    /**
+     * @return the country from the mobile network.
+     */
+    protected Country getNetworkBasedCountry() {
+        String countryIso = null;
+        // TODO: The document says the result may be unreliable on CDMA networks. Shall we use
+        // it on CDMA phone? We may test the Android primarily used countries.
+        if (mPhoneType == TelephonyManager.PHONE_TYPE_GSM) {
+            countryIso = mTelephonyManager.getNetworkCountryIso();
+            if (!TextUtils.isEmpty(countryIso)) {
+                return new Country(countryIso, Country.COUNTRY_SOURCE_NETWORK);
+            }
+        }
+        return null;
+    }
+
+    /**
+     * @return the cached location based country.
+     */
+    protected Country getLastKnownLocationBasedCountry() {
+        return mCountryFromLocation;
+    }
+
+    /**
+     * @return the country from SIM card
+     */
+    protected Country getSimBasedCountry() {
+        String countryIso = null;
+        countryIso = mTelephonyManager.getSimCountryIso();
+        if (!TextUtils.isEmpty(countryIso)) {
+            return new Country(countryIso, Country.COUNTRY_SOURCE_SIM);
+        }
+        return null;
+    }
+
+    /**
+     * @return the country from the system's locale.
+     */
+    protected Country getLocaleCountry() {
+        Locale defaultLocale = Locale.getDefault();
+        if (defaultLocale != null) {
+            return new Country(defaultLocale.getCountry(), Country.COUNTRY_SOURCE_LOCALE);
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * @param notifyChange indicates whether the listener should be notified the change of the
+     * country
+     * @param startLocationBasedDetection indicates whether the LocationBasedCountryDetector could
+     * be started if the current country source is less reliable than the location.
+     * @return the current available UserCountry
+     */
+    private Country detectCountry(boolean notifyChange, boolean startLocationBasedDetection) {
+        Country country = getCountry();
+        runAfterDetectionAsync(mCountry != null ? new Country(mCountry) : mCountry, country,
+                notifyChange, startLocationBasedDetection);
+        mCountry = country;
+        return mCountry;
+    }
+
+    /**
+     * Run the tasks in the service's thread.
+     */
+    protected void runAfterDetectionAsync(final Country country, final Country detectedCountry,
+            final boolean notifyChange, final boolean startLocationBasedDetection) {
+        mHandler.post(new Runnable() {
+            public void run() {
+                runAfterDetection(
+                        country, detectedCountry, notifyChange, startLocationBasedDetection);
+            }
+        });
+    }
+
+    @Override
+    public void setCountryListener(CountryListener listener) {
+        CountryListener prevListener = mListener;
+        mListener = listener;
+        if (mListener == null) {
+            // Stop listening all services
+            removePhoneStateListener();
+            stopLocationBasedDetector();
+            cancelLocationRefresh();
+        } else if (prevListener == null) {
+            addPhoneStateListener();
+            detectCountry(false, true);
+        }
+    }
+
+    void runAfterDetection(final Country country, final Country detectedCountry,
+            final boolean notifyChange, final boolean startLocationBasedDetection) {
+        if (notifyChange) {
+            notifyIfCountryChanged(country, detectedCountry);
+        }
+        if (startLocationBasedDetection && (detectedCountry == null
+                || detectedCountry.getSource() > Country.COUNTRY_SOURCE_LOCATION)
+                && isAirplaneModeOff() && mListener != null && isGeoCoderImplemented()) {
+            // Start finding location when the source is less reliable than the
+            // location and the airplane mode is off (as geocoder will not
+            // work).
+            // TODO : Shall we give up starting the detector within a
+            // period of time?
+            startLocationBasedDetector(mLocationBasedCountryDetectionListener);
+        }
+        if (detectedCountry == null
+                || detectedCountry.getSource() >= Country.COUNTRY_SOURCE_LOCATION) {
+            // Schedule the location refresh if the country source is
+            // not more reliable than the location or no country is
+            // found.
+            // TODO: Listen to the preference change of GPS, Wifi etc,
+            // and start detecting the country.
+            scheduleLocationRefresh();
+        } else {
+            // Cancel the location refresh once the current source is
+            // more reliable than the location.
+            cancelLocationRefresh();
+            stopLocationBasedDetector();
+        }
+    }
+
+    /**
+     * Find the country from LocationProvider.
+     */
+    private synchronized void startLocationBasedDetector(CountryListener listener) {
+        if (mLocationBasedCountryDetector != null) {
+            return;
+        }
+        mLocationBasedCountryDetector = createLocationBasedCountryDetector();
+        mLocationBasedCountryDetector.setCountryListener(listener);
+        mLocationBasedCountryDetector.detectCountry();
+    }
+
+    private synchronized void stopLocationBasedDetector() {
+        if (mLocationBasedCountryDetector != null) {
+            mLocationBasedCountryDetector.stop();
+            mLocationBasedCountryDetector = null;
+        }
+    }
+
+    protected CountryDetectorBase createLocationBasedCountryDetector() {
+        return new LocationBasedCountryDetector(mContext);
+    }
+
+    protected boolean isAirplaneModeOff() {
+        return Settings.System.getInt(
+                mContext.getContentResolver(), Settings.System.AIRPLANE_MODE_ON, 0) == 0;
+    }
+
+    /**
+     * Notify the country change.
+     */
+    private void notifyIfCountryChanged(final Country country, final Country detectedCountry) {
+        if (detectedCountry != null && mListener != null
+                && (country == null || !country.equals(detectedCountry))) {
+            Slog.d(TAG,
+                    "The country was changed from " + country != null ? country.getCountryIso() :
+                        country + " to " + detectedCountry.getCountryIso());
+            notifyListener(detectedCountry);
+        }
+    }
+
+    /**
+     * Schedule the next location refresh. We will do nothing if the scheduled task exists.
+     */
+    private synchronized void scheduleLocationRefresh() {
+        if (mLocationRefreshTimer != null) return;
+        mLocationRefreshTimer = new Timer();
+        mLocationRefreshTimer.schedule(new TimerTask() {
+            @Override
+            public void run() {
+                mLocationRefreshTimer = null;
+                detectCountry(false, true);
+            }
+        }, LOCATION_REFRESH_INTERVAL);
+    }
+
+    /**
+     * Cancel the scheduled refresh task if it exists
+     */
+    private synchronized void cancelLocationRefresh() {
+        if (mLocationRefreshTimer != null) {
+            mLocationRefreshTimer.cancel();
+            mLocationRefreshTimer = null;
+        }
+    }
+
+    protected synchronized void addPhoneStateListener() {
+        if (mPhoneStateListener == null && mPhoneType == TelephonyManager.PHONE_TYPE_GSM) {
+            mLastState = null;
+            mPhoneStateListener = new PhoneStateListener() {
+                @Override
+                public void onServiceStateChanged(ServiceState serviceState) {
+                    // TODO: Find out how often we will be notified, if this
+                    // method is called too
+                    // many times, let's consider querying the network.
+                    Slog.d(TAG, "onServiceStateChanged");
+                    // We only care the state change
+                    if (mLastState == null || mLastState.getState() != serviceState.getState()) {
+                        detectCountry(true, true);
+                        mLastState = new ServiceState(serviceState);
+                    }
+                }
+            };
+            mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_SERVICE_STATE);
+        }
+    }
+
+    protected synchronized void removePhoneStateListener() {
+        if (mPhoneStateListener != null) {
+            mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
+            mPhoneStateListener = null;
+        }
+    }
+
+    protected boolean isGeoCoderImplemented() {
+        return Geocoder.isImplemented();
+    }
+}
diff --git a/services/java/com/android/server/location/CountryDetectorBase.java b/services/java/com/android/server/location/CountryDetectorBase.java
new file mode 100644
index 0000000..8326ef9
--- /dev/null
+++ b/services/java/com/android/server/location/CountryDetectorBase.java
@@ -0,0 +1,72 @@
+/*
+ * 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.location;
+
+import android.content.Context;
+import android.location.Country;
+import android.location.CountryListener;
+import android.os.Handler;
+
+/**
+ * This class defines the methods need to be implemented by the country
+ * detector.
+ * <p>
+ * Calling {@link #detectCountry} to start detecting the country. The country
+ * could be returned immediately if it is available.
+ *
+ * @hide
+ */
+public abstract class CountryDetectorBase {
+    protected final Handler mHandler;
+    protected final Context mContext;
+    protected CountryListener mListener;
+    protected Country mDetectedCountry;
+
+    public CountryDetectorBase(Context ctx) {
+        mContext = ctx;
+        mHandler = new Handler();
+    }
+
+    /**
+     * Start detecting the country that the user is in.
+     *
+     * @return the country if it is available immediately, otherwise null should
+     *         be returned.
+     */
+    public abstract Country detectCountry();
+
+    /**
+     * Register a listener to receive the notification when the country is detected or changed.
+     * <p>
+     * The previous listener will be replaced if it exists.
+     */
+    public void setCountryListener(CountryListener listener) {
+        mListener = listener;
+    }
+
+    /**
+     * Stop detecting the country. The detector should release all system services and be ready to
+     * be freed
+     */
+    public abstract void stop();
+
+    protected void notifyListener(Country country) {
+        if (mListener != null) {
+            mListener.onCountryDetected(country);
+        }
+    }
+}
diff --git a/services/java/com/android/server/location/LocationBasedCountryDetector.java b/services/java/com/android/server/location/LocationBasedCountryDetector.java
new file mode 100755
index 0000000..139f05d
--- /dev/null
+++ b/services/java/com/android/server/location/LocationBasedCountryDetector.java
@@ -0,0 +1,235 @@
+/*
+ * 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.location;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Timer;
+import java.util.TimerTask;
+
+import android.content.Context;
+import android.location.Address;
+import android.location.Country;
+import android.location.Geocoder;
+import android.location.Location;
+import android.location.LocationListener;
+import android.location.LocationManager;
+import android.os.Bundle;
+import android.util.Slog;
+
+/**
+ * This class detects which country the user currently is in through the enabled
+ * location providers and the GeoCoder
+ * <p>
+ * Use {@link #detectCountry} to start querying. If the location can not be
+ * resolved within the given time, the last known location will be used to get
+ * the user country through the GeoCoder. The IllegalStateException will be
+ * thrown if there is a ongoing query.
+ * <p>
+ * The current query can be stopped by {@link #stop()}
+ *
+ * @hide
+ */
+public class LocationBasedCountryDetector extends CountryDetectorBase {
+    private final static String TAG = "LocationBasedCountryDetector";
+    private final static long QUERY_LOCATION_TIMEOUT = 1000 * 60 * 5; // 5 mins
+
+    /**
+     * Used for canceling location query
+     */
+    protected Timer mTimer;
+
+    /**
+     * The thread to query the country from the GeoCoder.
+     */
+    protected Thread mQueryThread;
+    protected List<LocationListener> mLocationListeners;
+
+    private LocationManager mLocationManager;
+    private List<String> mEnabledProviders;
+
+    public LocationBasedCountryDetector(Context ctx) {
+        super(ctx);
+        mLocationManager = (LocationManager) ctx.getSystemService(Context.LOCATION_SERVICE);
+    }
+
+    /**
+     * @return the ISO 3166-1 two letters country code from the location
+     */
+    protected String getCountryFromLocation(Location location) {
+        String country = null;
+        Geocoder geoCoder = new Geocoder(mContext);
+        try {
+            List<Address> addresses = geoCoder.getFromLocation(
+                    location.getLatitude(), location.getLongitude(), 1);
+            if (addresses != null && addresses.size() > 0) {
+                country = addresses.get(0).getCountryCode();
+            }
+        } catch (IOException e) {
+            Slog.w(TAG, "Exception occurs when getting country from location");
+        }
+        return country;
+    }
+
+    /**
+     * Register the listeners with the location providers
+     */
+    protected void registerEnabledProviders(List<LocationListener> listeners) {
+        int total = listeners.size();
+        for (int i = 0; i< total; i++) {
+            mLocationManager.requestLocationUpdates(
+                    mEnabledProviders.get(i), 0, 0, listeners.get(i));
+        }
+    }
+
+    /**
+     * Unregister the listeners with the location providers
+     */
+    protected void unregisterProviders(List<LocationListener> listeners) {
+        for (LocationListener listener : listeners) {
+            mLocationManager.removeUpdates(listener);
+        }
+    }
+
+    /**
+     * @return the last known location from all providers
+     */
+    protected Location getLastKnownLocation() {
+        List<String> providers = mLocationManager.getAllProviders();
+        Location bestLocation = null;
+        for (String provider : providers) {
+            Location lastKnownLocation = mLocationManager.getLastKnownLocation(provider);
+            if (lastKnownLocation != null) {
+                if (bestLocation == null || bestLocation.getTime() < lastKnownLocation.getTime()) {
+                    bestLocation = lastKnownLocation;
+                }
+            }
+        }
+        return bestLocation;
+    }
+
+    /**
+     * @return the timeout for querying the location.
+     */
+    protected long getQueryLocationTimeout() {
+        return QUERY_LOCATION_TIMEOUT;
+    }
+
+    /**
+     * @return the total number of enabled location providers
+     */
+    protected int getTotalEnabledProviders() {
+        if (mEnabledProviders == null) {
+            mEnabledProviders = mLocationManager.getProviders(true);
+        }
+        return mEnabledProviders.size();
+    }
+
+    /**
+     * Start detecting the country.
+     * <p>
+     * Queries the location from all location providers, then starts a thread to query the
+     * country from GeoCoder.
+     */
+    @Override
+    public synchronized Country detectCountry() {
+        if (mLocationListeners  != null) {
+            throw new IllegalStateException();
+        }
+        // Request the location from all enabled providers.
+        int totalProviders = getTotalEnabledProviders();
+        if (totalProviders > 0) {
+            mLocationListeners = new ArrayList<LocationListener>(totalProviders);
+            for (int i = 0; i < totalProviders; i++) {
+                LocationListener listener = new LocationListener () {
+                    public void onLocationChanged(Location location) {
+                        if (location != null) {
+                            LocationBasedCountryDetector.this.stop();
+                            queryCountryCode(location);
+                        }
+                    }
+                    public void onProviderDisabled(String provider) {
+                    }
+                    public void onProviderEnabled(String provider) {
+                    }
+                    public void onStatusChanged(String provider, int status, Bundle extras) {
+                    }
+                };
+                mLocationListeners.add(listener);
+            }
+            registerEnabledProviders(mLocationListeners);
+            mTimer = new Timer();
+            mTimer.schedule(new TimerTask() {
+                @Override
+                public void run() {
+                    mTimer = null;
+                    LocationBasedCountryDetector.this.stop();
+                    // Looks like no provider could provide the location, let's try the last
+                    // known location.
+                    queryCountryCode(getLastKnownLocation());
+                }
+            }, getQueryLocationTimeout());
+        } else {
+            // There is no provider enabled.
+            queryCountryCode(getLastKnownLocation());
+        }
+        return mDetectedCountry;
+    }
+
+    /**
+     * Stop the current query without notifying the listener.
+     */
+    @Override
+    public synchronized void stop() {
+        if (mLocationListeners != null) {
+            unregisterProviders(mLocationListeners);
+            mLocationListeners = null;
+        }
+        if (mTimer != null) {
+            mTimer.cancel();
+            mTimer = null;
+        }
+    }
+
+    /**
+     * Start a new thread to query the country from Geocoder.
+     */
+    private synchronized void queryCountryCode(final Location location) {
+        if (location == null) {
+            notifyListener(null);
+            return;
+        }
+        if (mQueryThread != null) return;
+        mQueryThread = new Thread(new Runnable() {
+            public void run() {
+                String countryIso = null;
+                if (location != null) {
+                    countryIso = getCountryFromLocation(location);
+                }
+                if (countryIso != null) {
+                    mDetectedCountry = new Country(countryIso, Country.COUNTRY_SOURCE_LOCATION);
+                } else {
+                    mDetectedCountry = null;
+                }
+                notifyListener(mDetectedCountry);
+                mQueryThread = null;
+            }
+        });
+        mQueryThread.start();
+    }
+}
diff --git a/services/jni/Android.mk b/services/jni/Android.mk
index cdc0a6f..459551d 100644
--- a/services/jni/Android.mk
+++ b/services/jni/Android.mk
@@ -8,6 +8,7 @@
     com_android_server_LightsService.cpp \
     com_android_server_PowerManagerService.cpp \
     com_android_server_SystemServer.cpp \
+    com_android_server_UsbObserver.cpp \
     com_android_server_VibratorService.cpp \
 	com_android_server_location_GpsLocationProvider.cpp \
     onload.cpp
@@ -25,6 +26,8 @@
 	libutils \
 	libui
 
+LOCAL_STATIC_LIBRARIES := libusbhost
+
 ifeq ($(TARGET_SIMULATOR),true)
 ifeq ($(TARGET_OS),linux)
 ifeq ($(TARGET_ARCH),x86)
diff --git a/services/jni/com_android_server_UsbObserver.cpp b/services/jni/com_android_server_UsbObserver.cpp
new file mode 100644
index 0000000..7c478d5
--- /dev/null
+++ b/services/jni/com_android_server_UsbObserver.cpp
@@ -0,0 +1,174 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "UsbObserver"
+#include "utils/Log.h"
+
+#include "jni.h"
+#include "JNIHelp.h"
+#include "android_runtime/AndroidRuntime.h"
+#include "utils/Vector.h"
+
+#include <usbhost/usbhost.h>
+#include <linux/version.h>
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 20)
+#include <linux/usb/ch9.h>
+#else
+#include <linux/usb_ch9.h>
+#endif
+
+#include <stdio.h>
+
+namespace android
+{
+
+static jmethodID method_usbCameraAdded;
+static jmethodID method_usbCameraRemoved;
+
+Vector<int> mDeviceList;
+
+static void checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName) {
+    if (env->ExceptionCheck()) {
+        LOGE("An exception was thrown by callback '%s'.", methodName);
+        LOGE_EX(env);
+        env->ExceptionClear();
+    }
+}
+
+static int usb_device_added(const char *devname, void* client_data) {
+    // check to see if it is a camera
+    struct usb_descriptor_header* desc;
+    struct usb_descriptor_iter iter;
+
+    struct usb_device *device = usb_device_open(devname);
+    if (!device) {
+        LOGE("usb_device_open failed\n");
+        return 0;
+    }
+
+    usb_descriptor_iter_init(device, &iter);
+
+    while ((desc = usb_descriptor_iter_next(&iter)) != NULL) {
+        if (desc->bDescriptorType == USB_DT_INTERFACE) {
+            struct usb_interface_descriptor *interface = (struct usb_interface_descriptor *)desc;
+
+            if (interface->bInterfaceClass == USB_CLASS_STILL_IMAGE &&
+                interface->bInterfaceSubClass == 1 && // Still Image Capture
+                interface->bInterfaceProtocol == 1)     // Picture Transfer Protocol (PIMA 15470)
+            {
+                LOGD("Found camera: \"%s\" \"%s\"\n", usb_device_get_manufacturer_name(device),
+                        usb_device_get_product_name(device));
+
+                // interface should be followed by three endpoints
+                struct usb_endpoint_descriptor *ep;
+                struct usb_endpoint_descriptor *ep_in_desc = NULL;
+                struct usb_endpoint_descriptor *ep_out_desc = NULL;
+                struct usb_endpoint_descriptor *ep_intr_desc = NULL;
+                for (int i = 0; i < 3; i++) {
+                    ep = (struct usb_endpoint_descriptor *)usb_descriptor_iter_next(&iter);
+                    if (!ep || ep->bDescriptorType != USB_DT_ENDPOINT) {
+                        LOGE("endpoints not found\n");
+                        goto done;
+                    }
+                    if (ep->bmAttributes == USB_ENDPOINT_XFER_BULK) {
+                        if (ep->bEndpointAddress & USB_ENDPOINT_DIR_MASK)
+                            ep_in_desc = ep;
+                        else
+                            ep_out_desc = ep;
+                    } else if (ep->bmAttributes == USB_ENDPOINT_XFER_INT &&
+                        ep->bEndpointAddress & USB_ENDPOINT_DIR_MASK) {
+                        ep_intr_desc = ep;
+                    }
+                }
+                if (!ep_in_desc || !ep_out_desc || !ep_intr_desc) {
+                    LOGE("endpoints not found\n");
+                    goto done;
+                }
+
+                // if we got here, we found a camera
+                JNIEnv* env = AndroidRuntime::getJNIEnv();
+                jobject thiz = (jobject)client_data;
+
+                int id = usb_device_get_unique_id_from_name(devname);
+                mDeviceList.add(id);
+
+                env->CallVoidMethod(thiz, method_usbCameraAdded, id);
+                checkAndClearExceptionFromCallback(env, __FUNCTION__);
+            }
+        }
+    }
+done:
+    usb_device_close(device);
+    return 0;
+}
+
+static int usb_device_removed(const char *devname, void* client_data) {
+    int id = usb_device_get_unique_id_from_name(devname);
+
+    // see if it is a device we know about
+    for (int i = 0; i < mDeviceList.size(); i++) {
+        if (id  == mDeviceList[i]) {
+            mDeviceList.removeAt(i);
+
+            JNIEnv* env = AndroidRuntime::getJNIEnv();
+            jobject thiz = (jobject)client_data;
+
+            env->CallVoidMethod(thiz, method_usbCameraRemoved, id);
+            checkAndClearExceptionFromCallback(env, __FUNCTION__);
+            break;
+        }
+    }
+    return 0;
+}
+
+static void android_server_UsbObserver_monitorUsbHostBus(JNIEnv *env, jobject thiz)
+{
+    struct usb_host_context* context = usb_host_init();
+    if (!context) {
+        LOGE("usb_host_init failed");
+        return;
+    }
+    // this will never return so it is safe to pass thiz directly
+    usb_host_run(context, usb_device_added, usb_device_removed, NULL, (void *)thiz);
+}
+
+static JNINativeMethod method_table[] = {
+    { "monitorUsbHostBus", "()V", (void*)android_server_UsbObserver_monitorUsbHostBus }
+};
+
+int register_android_server_UsbObserver(JNIEnv *env)
+{
+    jclass clazz = env->FindClass("com/android/server/UsbObserver");
+    if (clazz == NULL) {
+        LOGE("Can't find com/android/server/UsbObserver");
+        return -1;
+    }
+    method_usbCameraAdded = env->GetMethodID(clazz, "usbCameraAdded", "(I)V");
+    if (method_usbCameraAdded == NULL) {
+        LOGE("Can't find usbCameraAdded");
+        return -1;
+    }
+    method_usbCameraRemoved = env->GetMethodID(clazz, "usbCameraRemoved", "(I)V");
+    if (method_usbCameraRemoved == NULL) {
+        LOGE("Can't find usbCameraRemoved");
+        return -1;
+    }
+
+    return jniRegisterNativeMethods(env, "com/android/server/UsbObserver",
+            method_table, NELEM(method_table));
+}
+
+};
diff --git a/services/jni/com_android_server_location_GpsLocationProvider.cpp b/services/jni/com_android_server_location_GpsLocationProvider.cpp
index 59d7cde..93068e6 100755
--- a/services/jni/com_android_server_location_GpsLocationProvider.cpp
+++ b/services/jni/com_android_server_location_GpsLocationProvider.cpp
@@ -245,9 +245,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);
@@ -413,12 +413,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/jni/onload.cpp b/services/jni/onload.cpp
index cd4f0a4..3502aca 100644
--- a/services/jni/onload.cpp
+++ b/services/jni/onload.cpp
@@ -9,6 +9,7 @@
 int register_android_server_InputManager(JNIEnv* env);
 int register_android_server_LightsService(JNIEnv* env);
 int register_android_server_PowerManagerService(JNIEnv* env);
+int register_android_server_UsbObserver(JNIEnv* env);
 int register_android_server_VibratorService(JNIEnv* env);
 int register_android_server_SystemServer(JNIEnv* env);
 int register_android_server_location_GpsLocationProvider(JNIEnv* env);
@@ -32,6 +33,7 @@
     register_android_server_LightsService(env);
     register_android_server_AlarmManagerService(env);
     register_android_server_BatteryService(env);
+    register_android_server_UsbObserver(env);
     register_android_server_VibratorService(env);
     register_android_server_SystemServer(env);
     register_android_server_location_GpsLocationProvider(env);
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..f6e3a82
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/AccessibilityManagerServiceTest.java
@@ -0,0 +1,729 @@
+/*
+ * 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 in which we are waiting for the system to start the mock
+     * accessibility services.
+     */
+    private static final long TIMEOUT_START_MOCK_ACCESSIBILITY_SERVICES = 300;
+
+    /**
+     * 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, true, false);
+
+        // 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, true, false);
+
+        // 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, true, false);
+
+        // 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, true, false);
+
+        // 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, true, true);
+
+        // 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, true, true);
+
+        // 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, true, true);
+
+        // 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, true, true);
+
+        // 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, true, true);
+
+        // 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 or the
+     * {@link #TIMEOUT_START_MOCK_ACCESSIBILITY_SERVICES} is exceeded.
+     *
+     * @param context A context handle to access the settings.
+     * @param firstMockServiceEnabled If the first mock accessibility service is enabled.
+     * @param secondMockServiceEnabled If the second mock accessibility service is enabled.
+     * @throws IllegalStateException If some of the requested for enabling mock services
+     *         is not properly started.
+     * @throws Exception Exception If any error occurs.
+     */
+    private void ensureOnlyMockServicesEnabled(Context context, boolean firstMockServiceEnabled,
+            boolean secondMockServiceEnabled) throws Exception {
+        String enabledServices = Settings.Secure.getString(context.getContentResolver(),
+                Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
+
+        StringBuilder servicesToEnable = new StringBuilder();
+        if (firstMockServiceEnabled) {
+            servicesToEnable.append(MyFirstMockAccessibilityService.sComponentName).append(":");
+        }
+        if (secondMockServiceEnabled) {
+            servicesToEnable.append(MySecondMockAccessibilityService.sComponentName).append(":");
+        }
+
+        if (servicesToEnable.equals(enabledServices)) {
+            return;
+        }
+
+        Settings.Secure.putString(context.getContentResolver(),
+                Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, servicesToEnable.toString());
+
+        // we have enabled the services of interest and need to wait until they
+        // are instantiated and started (if needed) and the system binds to them
+        boolean firstMockServiceOK = false;
+        boolean secondMockServiceOK = false;
+        long start = SystemClock.uptimeMillis();
+        long pollingInterval = TIMEOUT_START_MOCK_ACCESSIBILITY_SERVICES / 6;
+
+        while (SystemClock.uptimeMillis() - start < TIMEOUT_START_MOCK_ACCESSIBILITY_SERVICES)  {
+            firstMockServiceOK = !firstMockServiceEnabled
+                    || (MyFirstMockAccessibilityService.sInstance != null
+                    && MyFirstMockAccessibilityService.sInstance.isSystemBoundAsClient());
+
+            secondMockServiceOK = !secondMockServiceEnabled
+                    || (MySecondMockAccessibilityService.sInstance != null
+                    && MySecondMockAccessibilityService.sInstance.isSystemBoundAsClient());
+
+            if (firstMockServiceOK && secondMockServiceOK) {
+                return;
+            }
+
+            Thread.sleep(pollingInterval);
+        }
+
+        StringBuilder message = new StringBuilder();
+        message.append("Mock accessibility services not started or system not bound as a client: ");
+        if (!firstMockServiceOK) {
+            message.append(MyFirstMockAccessibilityService.sComponentName);
+            message.append(" ");
+        }
+        if (!secondMockServiceOK) {
+            message.append(MySecondMockAccessibilityService.sComponentName);
+        }
+        throw new IllegalStateException(message.toString());
+    }
+
+    /**
+     * 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/CountryDetectorServiceTest.java b/services/tests/servicestests/src/com/android/server/CountryDetectorServiceTest.java
new file mode 100644
index 0000000..17a1585
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/CountryDetectorServiceTest.java
@@ -0,0 +1,109 @@
+/*
+ * 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.content.Context;
+import android.location.Country;
+import android.location.CountryListener;
+import android.location.ICountryListener;
+import android.os.RemoteException;
+import android.test.AndroidTestCase;
+
+public class CountryDetectorServiceTest extends AndroidTestCase {
+    private class CountryListenerTester extends ICountryListener.Stub {
+        private Country mCountry;
+
+        @Override
+        public void onCountryDetected(Country country) throws RemoteException {
+            mCountry = country;
+        }
+
+        public Country getCountry() {
+            return mCountry;
+        }
+
+        public boolean isNotified() {
+            return mCountry != null;
+        }
+    }
+
+    private class CountryDetectorServiceTester extends CountryDetectorService {
+
+        private CountryListener mListener;
+
+        public CountryDetectorServiceTester(Context context) {
+            super(context);
+        }
+
+        @Override
+        public void notifyReceivers(Country country) {
+            super.notifyReceivers(country);
+        }
+
+        @Override
+        protected void setCountryListener(final CountryListener listener) {
+            mListener = listener;
+        }
+
+        public boolean isListenerSet() {
+            return mListener != null;
+        }
+    }
+
+    public void testAddRemoveListener() throws RemoteException {
+        CountryDetectorServiceTester serviceTester = new CountryDetectorServiceTester(getContext());
+        serviceTester.systemReady();
+        waitForSystemReady(serviceTester);
+        CountryListenerTester listenerTester = new CountryListenerTester();
+        serviceTester.addCountryListener(listenerTester);
+        assertTrue(serviceTester.isListenerSet());
+        serviceTester.removeCountryListener(listenerTester);
+        assertFalse(serviceTester.isListenerSet());
+    }
+
+    public void testNotifyListeners() throws RemoteException {
+        CountryDetectorServiceTester serviceTester = new CountryDetectorServiceTester(getContext());
+        CountryListenerTester listenerTesterA = new CountryListenerTester();
+        CountryListenerTester listenerTesterB = new CountryListenerTester();
+        Country country = new Country("US", Country.COUNTRY_SOURCE_NETWORK);
+        serviceTester.systemReady();
+        waitForSystemReady(serviceTester);
+        serviceTester.addCountryListener(listenerTesterA);
+        serviceTester.addCountryListener(listenerTesterB);
+        serviceTester.notifyReceivers(country);
+        assertTrue(serviceTester.isListenerSet());
+        assertTrue(listenerTesterA.isNotified());
+        assertTrue(listenerTesterB.isNotified());
+        serviceTester.removeCountryListener(listenerTesterA);
+        serviceTester.removeCountryListener(listenerTesterB);
+        assertFalse(serviceTester.isListenerSet());
+    }
+
+    private void waitForSystemReady(CountryDetectorService service) {
+        int count = 5;
+        while (count-- > 0) {
+            try {
+                Thread.sleep(500);
+            } catch (Exception e) {
+            }
+            if (service.isSystemReady()) {
+                return;
+            }
+        }
+        throw new RuntimeException("Wait System Ready timeout");
+    }
+}
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..1bc9b86
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/MockAccessibilityService.java
@@ -0,0 +1,256 @@
+/*
+ * 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.Intent;
+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;
+
+    /**
+     * Flag if the system is bound as a client to this service.
+     */
+    private boolean mIsSystemBoundAsClient;
+
+    /**
+     * 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;
+    }
+
+    @Override
+    protected void onServiceConnected() {
+        mIsSystemBoundAsClient = true;
+    }
+
+    @Override
+    public boolean onUnbind(Intent intent) {
+        mIsSystemBoundAsClient = false;
+        return false;
+    }
+
+    /**
+     * Returns if the system is bound as client to this service.
+     *
+     * @return True if the system is bound, false otherwise.
+     */
+    public boolean isSystemBoundAsClient() {
+        return mIsSystemBoundAsClient;
+    }
+
+    /**
+     * 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/services/tests/servicestests/src/com/android/server/location/ComprehensiveCountryDetectorTest.java b/services/tests/servicestests/src/com/android/server/location/ComprehensiveCountryDetectorTest.java
new file mode 100644
index 0000000..98966c0
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/location/ComprehensiveCountryDetectorTest.java
@@ -0,0 +1,299 @@
+/*
+ * 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.location;
+
+import android.location.Country;
+import android.location.CountryListener;
+import android.test.AndroidTestCase;
+
+public class ComprehensiveCountryDetectorTest extends AndroidTestCase {
+    private class TestCountryDetector extends ComprehensiveCountryDetector {
+        public final static String COUNTRY_ISO = "us";
+        private boolean mLocationBasedDetectorStarted;
+        private boolean mLocationBasedDetectorStopped;
+        protected boolean mNotified;
+        private boolean listenerAdded = false;
+
+        private Country mNotifiedCountry;
+        public TestCountryDetector() {
+            super(getContext());
+        }
+
+        public void notifyLocationBasedListener(Country country) {
+            mNotified = true;
+            mNotifiedCountry = country;
+            mLocationBasedCountryDetector.notifyListener(country);
+        }
+
+        public boolean locationBasedDetectorStarted() {
+            return mLocationBasedCountryDetector != null && mLocationBasedDetectorStarted;
+        }
+
+        public boolean locationBasedDetectorStopped() {
+            return mLocationBasedCountryDetector == null && mLocationBasedDetectorStopped;
+        }
+
+        public boolean locationRefreshStarted() {
+            return mLocationRefreshTimer != null;
+        }
+
+        public boolean locationRefreshCancelled() {
+            return mLocationRefreshTimer == null;
+        }
+
+        @Override
+        protected CountryDetectorBase createLocationBasedCountryDetector() {
+            return new CountryDetectorBase(mContext) {
+                @Override
+                public Country detectCountry() {
+                    mLocationBasedDetectorStarted = true;
+                    return null;
+                }
+
+                @Override
+                public void stop() {
+                    mLocationBasedDetectorStopped = true;
+                }
+            };
+        }
+
+        @Override
+        protected Country getNetworkBasedCountry() {
+            return null;
+        }
+
+        @Override
+        protected Country getLastKnownLocationBasedCountry() {
+            return mNotifiedCountry;
+        }
+
+        @Override
+        protected Country getSimBasedCountry() {
+            return null;
+        }
+
+        @Override
+        protected Country getLocaleCountry() {
+            return null;
+        }
+
+        @Override
+        protected void runAfterDetectionAsync(final Country country, final Country detectedCountry,
+                final boolean notifyChange, final boolean startLocationBasedDetection) {
+            runAfterDetection(country, detectedCountry, notifyChange, startLocationBasedDetection);
+        };
+
+        @Override
+        protected boolean isAirplaneModeOff() {
+            return true;
+        }
+
+        @Override
+        protected synchronized void addPhoneStateListener() {
+            listenerAdded = true;
+        }
+
+        @Override
+        protected synchronized void removePhoneStateListener() {
+            listenerAdded = false;
+        }
+
+        @Override
+        protected boolean isGeoCoderImplemented() {
+            return true;
+        }
+
+        public boolean isPhoneStateListenerAdded() {
+            return listenerAdded;
+        }
+    }
+
+    private class CountryListenerImpl implements CountryListener {
+        private boolean mNotified;
+        private Country mCountry;
+
+        public void onCountryDetected(Country country) {
+            mNotified = true;
+            mCountry = country;
+        }
+
+        public boolean notified() {
+            return mNotified;
+        }
+
+        public Country getCountry() {
+            return mCountry;
+        }
+    }
+
+    public void testDetectNetworkBasedCountry() {
+        final Country resultCountry = new Country(
+                TestCountryDetector.COUNTRY_ISO, Country.COUNTRY_SOURCE_NETWORK);
+        TestCountryDetector countryDetector = new TestCountryDetector() {
+            @Override
+            protected Country getNetworkBasedCountry() {
+                return resultCountry;
+            }
+        };
+        CountryListenerImpl listener = new CountryListenerImpl();
+        countryDetector.setCountryListener(listener);
+        Country country = countryDetector.detectCountry();
+        assertTrue(sameCountry(country, resultCountry));
+        assertFalse(listener.notified());
+        assertFalse(countryDetector.locationBasedDetectorStarted());
+        assertFalse(countryDetector.locationRefreshStarted());
+        countryDetector.stop();
+    }
+
+    public void testDetectLocationBasedCountry() {
+        final Country resultCountry = new Country(
+                TestCountryDetector.COUNTRY_ISO, Country.COUNTRY_SOURCE_SIM);
+        final Country locationBasedCountry = new Country(
+                TestCountryDetector.COUNTRY_ISO, Country.COUNTRY_SOURCE_LOCATION);
+        TestCountryDetector countryDetector = new TestCountryDetector() {
+            @Override
+            protected Country getSimBasedCountry() {
+                return resultCountry;
+            }
+        };
+        CountryListenerImpl listener = new CountryListenerImpl();
+        countryDetector.setCountryListener(listener);
+        Country country = countryDetector.detectCountry();
+        assertTrue(sameCountry(country, resultCountry));
+        assertTrue(countryDetector.locationBasedDetectorStarted());
+        countryDetector.notifyLocationBasedListener(locationBasedCountry);
+        assertTrue(listener.notified());
+        assertTrue(sameCountry(listener.getCountry(), locationBasedCountry));
+        assertTrue(countryDetector.locationBasedDetectorStopped());
+        assertTrue(countryDetector.locationRefreshStarted());
+        countryDetector.stop();
+        assertTrue(countryDetector.locationRefreshCancelled());
+    }
+
+    public void testLocaleBasedCountry() {
+        final Country resultCountry = new Country(
+                TestCountryDetector.COUNTRY_ISO, Country.COUNTRY_SOURCE_LOCALE);
+        TestCountryDetector countryDetector = new TestCountryDetector() {
+            @Override
+            protected Country getLocaleCountry() {
+                return resultCountry;
+            }
+        };
+        CountryListenerImpl listener = new CountryListenerImpl();
+        countryDetector.setCountryListener(listener);
+        Country country = countryDetector.detectCountry();
+        assertTrue(sameCountry(country, resultCountry));
+        assertFalse(listener.notified());
+        assertTrue(countryDetector.locationBasedDetectorStarted());
+        assertTrue(countryDetector.locationRefreshStarted());
+        countryDetector.stop();
+        assertTrue(countryDetector.locationRefreshCancelled());
+    }
+
+    public void testStoppingDetector() {
+        // Test stopping detector when LocationBasedCountryDetector was started
+        final Country resultCountry = new Country(
+                TestCountryDetector.COUNTRY_ISO, Country.COUNTRY_SOURCE_SIM);
+        TestCountryDetector countryDetector = new TestCountryDetector() {
+            @Override
+            protected Country getSimBasedCountry() {
+                return resultCountry;
+            }
+        };
+        CountryListenerImpl listener = new CountryListenerImpl();
+        countryDetector.setCountryListener(listener);
+        Country country = countryDetector.detectCountry();
+        assertTrue(sameCountry(country, resultCountry));
+        assertTrue(countryDetector.locationBasedDetectorStarted());
+        countryDetector.stop();
+        // The LocationBasedDetector should be stopped.
+        assertTrue(countryDetector.locationBasedDetectorStopped());
+        // The location refresh should not running.
+        assertTrue(countryDetector.locationRefreshCancelled());
+    }
+
+    public void testLocationBasedCountryNotFound() {
+        final Country resultCountry = new Country(
+                TestCountryDetector.COUNTRY_ISO, Country.COUNTRY_SOURCE_SIM);
+        TestCountryDetector countryDetector = new TestCountryDetector() {
+            @Override
+            protected Country getSimBasedCountry() {
+                return resultCountry;
+            }
+        };
+        CountryListenerImpl listener = new CountryListenerImpl();
+        countryDetector.setCountryListener(listener);
+        Country country = countryDetector.detectCountry();
+        assertTrue(sameCountry(country, resultCountry));
+        assertTrue(countryDetector.locationBasedDetectorStarted());
+        countryDetector.notifyLocationBasedListener(null);
+        assertFalse(listener.notified());
+        assertTrue(sameCountry(listener.getCountry(), null));
+        assertTrue(countryDetector.locationBasedDetectorStopped());
+        assertTrue(countryDetector.locationRefreshStarted());
+        countryDetector.stop();
+        assertTrue(countryDetector.locationRefreshCancelled());
+    }
+
+    public void testNoCountryFound() {
+        TestCountryDetector countryDetector = new TestCountryDetector();
+        CountryListenerImpl listener = new CountryListenerImpl();
+        countryDetector.setCountryListener(listener);
+        Country country = countryDetector.detectCountry();
+        assertTrue(sameCountry(country, null));
+        assertTrue(countryDetector.locationBasedDetectorStarted());
+        countryDetector.notifyLocationBasedListener(null);
+        assertFalse(listener.notified());
+        assertTrue(sameCountry(listener.getCountry(), null));
+        assertTrue(countryDetector.locationBasedDetectorStopped());
+        assertTrue(countryDetector.locationRefreshStarted());
+        countryDetector.stop();
+        assertTrue(countryDetector.locationRefreshCancelled());
+    }
+
+    public void testAddRemoveListener() {
+        TestCountryDetector countryDetector = new TestCountryDetector();
+        CountryListenerImpl listener = new CountryListenerImpl();
+        countryDetector.setCountryListener(listener);
+        assertTrue(countryDetector.isPhoneStateListenerAdded());
+        assertTrue(countryDetector.locationBasedDetectorStarted());
+        countryDetector.setCountryListener(null);
+        assertFalse(countryDetector.isPhoneStateListenerAdded());
+        assertTrue(countryDetector.locationBasedDetectorStopped());
+    }
+
+    public void testGeocoderNotImplemented() {
+        TestCountryDetector countryDetector = new TestCountryDetector() {
+            @Override
+            protected boolean isGeoCoderImplemented() {
+                return false;
+            }
+        };
+        CountryListenerImpl listener = new CountryListenerImpl();
+        countryDetector.setCountryListener(listener);
+        assertTrue(countryDetector.isPhoneStateListenerAdded());
+        assertFalse(countryDetector.locationBasedDetectorStarted());
+        countryDetector.setCountryListener(null);
+        assertFalse(countryDetector.isPhoneStateListenerAdded());
+    }
+
+    private boolean sameCountry(Country country1, Country country2) {
+        return country1 == null && country2 == null || country1 != null && country2 != null &&
+        country1.getCountryIso().equalsIgnoreCase(country2.getCountryIso()) &&
+        country1.getSource() == country2.getSource();
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/location/LocationBasedCountryDetectorTest.java b/services/tests/servicestests/src/com/android/server/location/LocationBasedCountryDetectorTest.java
new file mode 100755
index 0000000..71e8e2a
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/location/LocationBasedCountryDetectorTest.java
@@ -0,0 +1,324 @@
+/*
+ * 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.location;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Timer;
+
+import android.location.Country;
+import android.location.CountryListener;
+import android.location.Location;
+import android.location.LocationListener;
+import android.test.AndroidTestCase;
+
+public class LocationBasedCountryDetectorTest extends AndroidTestCase {
+    private class TestCountryDetector extends LocationBasedCountryDetector {
+        public static final int TOTAL_PROVIDERS = 2;
+        protected Object countryFoundLocker = new Object();
+        protected boolean notifyCountry = false;
+        private final Location mLocation;
+        private final String mCountry;
+        private final long mQueryLocationTimeout;
+        private List<LocationListener> mListeners;
+
+        public TestCountryDetector(String country, String provider) {
+            this(country, provider, 1000 * 60 * 5);
+        }
+
+        public TestCountryDetector(String country, String provider, long queryLocationTimeout) {
+            super(getContext());
+            mCountry = country;
+            mLocation = new Location(provider);
+            mQueryLocationTimeout = queryLocationTimeout;
+            mListeners = new ArrayList<LocationListener>();
+        }
+
+        @Override
+        protected String getCountryFromLocation(Location location) {
+            synchronized (countryFoundLocker) {
+                if (!notifyCountry) {
+                    try {
+                        countryFoundLocker.wait();
+                    } catch (InterruptedException e) {
+                    }
+                }
+            }
+            if (mLocation.getProvider().endsWith(location.getProvider())) {
+                return mCountry;
+            } else {
+                return null;
+            }
+        }
+
+        @Override
+        protected Location getLastKnownLocation() {
+            return mLocation;
+        }
+
+        @Override
+        protected void registerEnabledProviders(List<LocationListener> listeners) {
+            mListeners.addAll(listeners);
+        }
+
+        @Override
+        protected void unregisterProviders(List<LocationListener> listeners) {
+            for (LocationListener listener : mLocationListeners) {
+                assertTrue(mListeners.remove(listener));
+            }
+        }
+
+        @Override
+        protected long getQueryLocationTimeout() {
+            return mQueryLocationTimeout;
+        }
+
+        @Override
+        protected int getTotalEnabledProviders() {
+            return TOTAL_PROVIDERS;
+        }
+
+        public void notifyLocationFound() {
+            // Listener could be removed in the notification.
+            LocationListener[] listeners = new LocationListener[mListeners.size()];
+            mLocationListeners.toArray(listeners);
+            for (LocationListener listener :listeners) {
+                listener.onLocationChanged(mLocation);
+            }
+        }
+
+        public int getListenersCount() {
+            return mListeners.size();
+        }
+
+        public void notifyCountryFound() {
+            synchronized (countryFoundLocker) {
+                notifyCountry = true;
+                countryFoundLocker.notify();
+            }
+        }
+
+        public Timer getTimer() {
+            return mTimer;
+        }
+
+        public Thread getQueryThread() {
+            return mQueryThread;
+        }
+    }
+
+    private class CountryListenerImpl implements CountryListener {
+        private boolean mNotified;
+        private String mCountryCode;
+        public void onCountryDetected(Country country) {
+            mNotified = true;
+            if (country != null) {
+                mCountryCode = country.getCountryIso();
+            }
+        }
+
+        public boolean notified() {
+            return mNotified;
+        }
+
+        public String getCountry() {
+            return mCountryCode;
+        }
+    }
+
+    public void testFindingCountry() {
+        final String country = "us";
+        final String provider = "Good";
+        CountryListenerImpl countryListener = new CountryListenerImpl();
+        TestCountryDetector detector = new TestCountryDetector(country, provider);
+        detector.setCountryListener(countryListener);
+        detector.detectCountry();
+        assertEquals(detector.getListenersCount(), TestCountryDetector.TOTAL_PROVIDERS);
+        detector.notifyLocationFound();
+        // All listeners should be unregistered
+        assertEquals(detector.getListenersCount(), 0);
+        assertNull(detector.getTimer());
+        Thread queryThread = waitForQueryThreadLaunched(detector);
+        detector.notifyCountryFound();
+        // Wait for query thread ending
+        waitForThreadEnding(queryThread);
+        // QueryThread should be set to NULL
+        assertNull(detector.getQueryThread());
+        assertTrue(countryListener.notified());
+        assertEquals(countryListener.getCountry(), country);
+    }
+
+    public void testFindingCountryCancelled() {
+        final String country = "us";
+        final String provider = "Good";
+        CountryListenerImpl countryListener = new CountryListenerImpl();
+        TestCountryDetector detector = new TestCountryDetector(country, provider);
+        detector.setCountryListener(countryListener);
+        detector.detectCountry();
+        assertEquals(detector.getListenersCount(), TestCountryDetector.TOTAL_PROVIDERS);
+        detector.notifyLocationFound();
+        // All listeners should be unregistered
+        assertEquals(detector.getListenersCount(), 0);
+        // The time should be stopped
+        assertNull(detector.getTimer());
+        Thread queryThread = waitForQueryThreadLaunched(detector);
+        detector.stop();
+        // There is no way to stop the thread, let's test it could be stopped, after get country
+        detector.notifyCountryFound();
+        // Wait for query thread ending
+        waitForThreadEnding(queryThread);
+        // QueryThread should be set to NULL
+        assertNull(detector.getQueryThread());
+        assertTrue(countryListener.notified());
+        assertEquals(countryListener.getCountry(), country);
+    }
+
+    public void testFindingLocationCancelled() {
+        final String country = "us";
+        final String provider = "Good";
+        CountryListenerImpl countryListener = new CountryListenerImpl();
+        TestCountryDetector detector = new TestCountryDetector(country, provider);
+        detector.setCountryListener(countryListener);
+        detector.detectCountry();
+        assertEquals(detector.getListenersCount(), TestCountryDetector.TOTAL_PROVIDERS);
+        detector.stop();
+        // All listeners should be unregistered
+        assertEquals(detector.getListenersCount(), 0);
+        // The time should be stopped
+        assertNull(detector.getTimer());
+        // QueryThread should still be NULL
+        assertNull(detector.getQueryThread());
+        assertFalse(countryListener.notified());
+    }
+
+    public void testFindingLocationFailed() {
+        final String country = "us";
+        final String provider = "Good";
+        long timeout = 1000;
+        TestCountryDetector detector = new TestCountryDetector(country, provider, timeout) {
+            @Override
+            protected Location getLastKnownLocation() {
+                return null;
+            }
+        };
+        CountryListenerImpl countryListener = new CountryListenerImpl();
+        detector.setCountryListener(countryListener);
+        detector.detectCountry();
+        assertEquals(detector.getListenersCount(), TestCountryDetector.TOTAL_PROVIDERS);
+        waitForTimerReset(detector);
+        // All listeners should be unregistered
+        assertEquals(detector.getListenersCount(), 0);
+        // QueryThread should still be NULL
+        assertNull(detector.getQueryThread());
+        assertTrue(countryListener.notified());
+        assertNull(countryListener.getCountry());
+    }
+
+    public void testFindingCountryFailed() {
+        final String country = "us";
+        final String provider = "Good";
+        TestCountryDetector detector = new TestCountryDetector(country, provider) {
+            @Override
+            protected String getCountryFromLocation(Location location) {
+                synchronized (countryFoundLocker) {
+                    if (! notifyCountry) {
+                        try {
+                            countryFoundLocker.wait();
+                        } catch (InterruptedException e) {
+                        }
+                    }
+                }
+                // We didn't find country.
+                return null;
+            }
+        };
+        CountryListenerImpl countryListener = new CountryListenerImpl();
+        detector.setCountryListener(countryListener);
+        detector.detectCountry();
+        assertEquals(detector.getListenersCount(), TestCountryDetector.TOTAL_PROVIDERS);
+        detector.notifyLocationFound();
+        // All listeners should be unregistered
+        assertEquals(detector.getListenersCount(), 0);
+        assertNull(detector.getTimer());
+        Thread queryThread = waitForQueryThreadLaunched(detector);
+        detector.notifyCountryFound();
+        // Wait for query thread ending
+        waitForThreadEnding(queryThread);
+        // QueryThread should be set to NULL
+        assertNull(detector.getQueryThread());
+        // CountryListener should be notified
+        assertTrue(countryListener.notified());
+        assertNull(countryListener.getCountry());
+    }
+
+    public void testFindingCountryWithLastKnownLocation() {
+        final String country = "us";
+        final String provider = "Good";
+        long timeout = 1000;
+        TestCountryDetector detector = new TestCountryDetector(country, provider, timeout);
+        CountryListenerImpl countryListener = new CountryListenerImpl();
+        detector.setCountryListener(countryListener);
+        detector.detectCountry();
+        assertEquals(detector.getListenersCount(), TestCountryDetector.TOTAL_PROVIDERS);
+        waitForTimerReset(detector);
+        // All listeners should be unregistered
+        assertEquals(detector.getListenersCount(), 0);
+        Thread queryThread = waitForQueryThreadLaunched(detector);
+        detector.notifyCountryFound();
+        // Wait for query thread ending
+        waitForThreadEnding(queryThread);
+        // QueryThread should be set to NULL
+        assertNull(detector.getQueryThread());
+        // CountryListener should be notified
+        assertTrue(countryListener.notified());
+        assertEquals(countryListener.getCountry(), country);
+    }
+
+    private void waitForTimerReset(TestCountryDetector detector) {
+        int count = 5;
+        long interval = 1000;
+        try {
+            while (count-- > 0 && detector.getTimer() != null) {
+                Thread.sleep(interval);
+            }
+        } catch (InterruptedException e) {
+        }
+        Timer timer = detector.getTimer();
+        assertTrue(timer == null);
+    }
+
+    private void waitForThreadEnding(Thread thread) {
+        try {
+            thread.join(5000);
+        } catch (InterruptedException e) {
+            e.printStackTrace();
+        }
+    }
+
+    private Thread waitForQueryThreadLaunched(TestCountryDetector detector) {
+        int count = 5;
+        long interval = 1000;
+        try {
+            while (count-- > 0 && detector.getQueryThread() == null) {
+                Thread.sleep(interval);
+            }
+        } catch (InterruptedException e) {
+        }
+        Thread thread = detector.getQueryThread();
+        assertTrue(thread != null);
+        return thread;
+    }
+}
diff --git a/telephony/java/android/telephony/PhoneNumberFormattingTextWatcher.java b/telephony/java/android/telephony/PhoneNumberFormattingTextWatcher.java
index 8a47339..ffabb7b 100644
--- a/telephony/java/android/telephony/PhoneNumberFormattingTextWatcher.java
+++ b/telephony/java/android/telephony/PhoneNumberFormattingTextWatcher.java
@@ -16,83 +16,200 @@
 
 package android.telephony;
 
+import com.google.i18n.phonenumbers.AsYouTypeFormatter;
+import com.google.i18n.phonenumbers.PhoneNumberUtil;
+
+import android.telephony.PhoneNumberUtils;
 import android.text.Editable;
 import android.text.Selection;
 import android.text.TextWatcher;
-import android.widget.TextView;
 
 import java.util.Locale;
 
 /**
- * Watches a {@link TextView} and if a phone number is entered will format it using
- * {@link PhoneNumberUtils#formatNumber(Editable, int)}. The formatting is based on
- * the current system locale when this object is created and future locale changes
- * may not take effect on this instance.
+ * Watches a {@link android.widget.TextView} and if a phone number is entered
+ * will format it.
+ * <p>
+ * Stop formatting when the user
+ * <ul>
+ * <li>Inputs non-dialable characters</li>
+ * <li>Removes the separator in the middle of string.</li>
+ * </ul>
+ * <p>
+ * The formatting will be restarted once the text is cleared.
  */
 public class PhoneNumberFormattingTextWatcher implements TextWatcher {
+    /**
+     * One or more characters were removed from the end.
+     */
+    private final static int STATE_REMOVE_LAST = 0;
 
-    static private int sFormatType;
-    static private Locale sCachedLocale;
-    private boolean mFormatting;
-    private boolean mDeletingHyphen;
-    private int mHyphenStart;
-    private boolean mDeletingBackward;
+    /**
+     * One or more characters were appended.
+     */
+    private final static int STATE_APPEND = 1;
 
+    /**
+     * One or more digits were changed in the beginning or the middle of text.
+     */
+    private final static int STATE_MODIFY_DIGITS = 2;
+
+    /**
+     * The changes other than the above.
+     */
+    private final static int STATE_OTHER = 3;
+
+    /**
+     * The state of this change could be one value of the above
+     */
+    private int mState;
+
+    /**
+     * Indicates the change was caused by ourselves.
+     */
+    private boolean mSelfChange = false;
+
+    /**
+     * Indicates the formatting has been stopped.
+     */
+    private boolean mStopFormatting;
+
+    private AsYouTypeFormatter mFormatter;
+
+    /**
+     * The formatting is based on the current system locale and future locale changes
+     * may not take effect on this instance.
+     */
     public PhoneNumberFormattingTextWatcher() {
-        if (sCachedLocale == null || sCachedLocale != Locale.getDefault()) {
-            sCachedLocale = Locale.getDefault();
-            sFormatType = PhoneNumberUtils.getFormatTypeForLocale(sCachedLocale);
-        }
+        this(Locale.getDefault().getCountry());
     }
 
-    public synchronized void afterTextChanged(Editable text) {
-        // Make sure to ignore calls to afterTextChanged caused by the work done below
-        if (!mFormatting) {
-            mFormatting = true;
-
-            // If deleting the hyphen, also delete the char before or after that
-            if (mDeletingHyphen && mHyphenStart > 0) {
-                if (mDeletingBackward) {
-                    if (mHyphenStart - 1 < text.length()) {
-                        text.delete(mHyphenStart - 1, mHyphenStart);
-                    }
-                } else if (mHyphenStart < text.length()) {
-                    text.delete(mHyphenStart, mHyphenStart + 1);
-                }
-            }
-
-            PhoneNumberUtils.formatNumber(text, sFormatType);
-
-            mFormatting = false;
-        }
+    /**
+     * The formatting is based on the given <code>countryCode</code>.
+     *
+     * @param countryCode the ISO 3166-1 two-letter country code that indicates the country/region
+     * where the phone number is being entered.
+     *
+     * @hide
+     */
+    public PhoneNumberFormattingTextWatcher(String countryCode) {
+        if (countryCode == null) throw new IllegalArgumentException();
+        mFormatter = PhoneNumberUtil.getInstance().getAsYouTypeFormatter(countryCode);
     }
 
-    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
-        // Check if the user is deleting a hyphen
-        if (!mFormatting) {
-            // Make sure user is deleting one char, without a selection
-            final int selStart = Selection.getSelectionStart(s);
-            final int selEnd = Selection.getSelectionEnd(s);
-            if (s.length() > 1 // Can delete another character
-                    && count == 1 // Deleting only one character
-                    && after == 0 // Deleting
-                    && s.charAt(start) == '-' // a hyphen
-                    && selStart == selEnd) { // no selection
-                mDeletingHyphen = true;
-                mHyphenStart = start;
-                // Check if the user is deleting forward or backward
-                if (selStart == start + 1) {
-                    mDeletingBackward = true;
-                } else {
-                    mDeletingBackward = false;
-                }
-            } else {
-                mDeletingHyphen = false;
-            }
+    public void beforeTextChanged(CharSequence s, int start, int count,
+            int after) {
+        if (mSelfChange || mStopFormatting) {
+            return;
+        }
+        if (count == 0 && s.length() == start) {
+            // Append one or more new chars
+            mState = STATE_APPEND;
+        } else if (after == 0 && start + count == s.length() && count > 0) {
+            // Remove one or more chars from the end of string.
+            mState = STATE_REMOVE_LAST;
+        } else if (count > 0 && !hasSeparator(s, start, count)) {
+            // Remove the dialable chars in the begin or middle of text.
+            mState = STATE_MODIFY_DIGITS;
+        } else {
+            mState = STATE_OTHER;
         }
     }
 
     public void onTextChanged(CharSequence s, int start, int before, int count) {
-        // Does nothing
+        if (mSelfChange || mStopFormatting) {
+            return;
+        }
+        if (mState == STATE_OTHER) {
+            if (count > 0 && !hasSeparator(s, start, count)) {
+                // User inserted the dialable characters in the middle of text.
+                mState = STATE_MODIFY_DIGITS;
+            }
+        }
+        // Check whether we should stop formatting.
+        if (mState == STATE_APPEND && count > 0 && hasSeparator(s, start, count)) {
+            // User appended the non-dialable character, stop formatting.
+            stopFormatting();
+        } else if (mState == STATE_OTHER) {
+            // User must insert or remove the non-dialable characters in the begin or middle of
+            // number, stop formatting.
+            stopFormatting();
+        }
+    }
+
+    public synchronized void afterTextChanged(Editable s) {
+        if (mStopFormatting) {
+            // Restart the formatting when all texts were clear.
+            mStopFormatting = !(s.length() == 0);
+            return;
+        }
+        if (mSelfChange) {
+            // Ignore the change caused by s.replace().
+            return;
+        }
+        String formatted = reformat(s, Selection.getSelectionEnd(s));
+        if (formatted != null) {
+            int rememberedPos = mFormatter.getRememberedPosition();
+            mSelfChange = true;
+            s.replace(0, s.length(), formatted, 0, formatted.length());
+            // The text could be changed by other TextWatcher after we changed it. If we found the
+            // text is not the one we were expecting, just give up calling setSelection().
+            if (formatted.equals(s.toString())) {
+                Selection.setSelection(s, rememberedPos);
+            }
+            mSelfChange = false;
+        }
+    }
+
+    /**
+     * Generate the formatted number by ignoring all non-dialable chars and stick the cursor to the
+     * nearest dialable char to the left. For instance, if the number is  (650) 123-45678 and '4' is
+     * removed then the cursor should be behind '3' instead of '-'.
+     */
+    private String reformat(CharSequence s, int cursor) {
+        // The index of char to the leftward of the cursor.
+        int curIndex = cursor - 1;
+        String formatted = null;
+        mFormatter.clear();
+        char lastNonSeparator = 0;
+        boolean hasCursor = false;
+        int len = s.length();
+        for (int i = 0; i < len; i++) {
+            char c = s.charAt(i);
+            if (PhoneNumberUtils.isNonSeparator(c)) {
+                if (lastNonSeparator != 0) {
+                    formatted = getFormattedNumber(lastNonSeparator, hasCursor);
+                    hasCursor = false;
+                }
+                lastNonSeparator = c;
+            }
+            if (i == curIndex) {
+                hasCursor = true;
+            }
+        }
+        if (lastNonSeparator != 0) {
+            formatted = getFormattedNumber(lastNonSeparator, hasCursor);
+        }
+        return formatted;
+    }
+
+    private String getFormattedNumber(char lastNonSeparator, boolean hasCursor) {
+        return hasCursor ? mFormatter.inputDigitAndRememberPosition(lastNonSeparator)
+                : mFormatter.inputDigit(lastNonSeparator);
+    }
+
+    private void stopFormatting() {
+        mStopFormatting = true;
+        mFormatter.clear();
+    }
+
+    private boolean hasSeparator(final CharSequence s, final int start, final int count) {
+        for (int i = start; i < start + count; i++) {
+            char c = s.charAt(i);
+            if (!PhoneNumberUtils.isNonSeparator(c)) {
+                return true;
+            }
+        }
+        return false;
     }
 }
diff --git a/telephony/java/android/telephony/PhoneNumberUtils.java b/telephony/java/android/telephony/PhoneNumberUtils.java
index 3658961..11315ce 100644
--- a/telephony/java/android/telephony/PhoneNumberUtils.java
+++ b/telephony/java/android/telephony/PhoneNumberUtils.java
@@ -16,6 +16,11 @@
 
 package android.telephony;
 
+import com.google.i18n.phonenumbers.NumberParseException;
+import com.google.i18n.phonenumbers.PhoneNumberUtil;
+import com.google.i18n.phonenumbers.PhoneNumberUtil.PhoneNumberFormat;
+import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber;
+
 import android.content.Context;
 import android.content.Intent;
 import android.database.Cursor;
@@ -617,7 +622,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"
@@ -779,10 +784,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:
@@ -870,10 +875,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);
 
@@ -1004,7 +1009,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[]
@@ -1108,10 +1113,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);
@@ -1138,7 +1143,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;
@@ -1319,6 +1324,88 @@
         }
     }
 
+    /**
+     * Format the given phoneNumber to the E.164 representation.
+     * <p>
+     * The given phone number must have an area code and could have a country
+     * code.
+     * <p>
+     * The defaultCountryIso is used to validate the given number and generate
+     * the E.164 phone number if the given number doesn't have a country code.
+     *
+     * @param phoneNumber
+     *            the phone number to format
+     * @param defaultCountryIso
+     *            the ISO 3166-1 two letters country code
+     * @return the E.164 representation, or null if the given phone number is
+     *         not valid.
+     *
+     * @hide
+     */
+    public static String formatNumberToE164(String phoneNumber, String defaultCountryIso) {
+        PhoneNumberUtil util = PhoneNumberUtil.getInstance();
+        String result = null;
+        try {
+            PhoneNumber pn = util.parse(phoneNumber, defaultCountryIso);
+            if (util.isValidNumber(pn)) {
+                result = util.format(pn, PhoneNumberFormat.E164);
+            }
+        } catch (NumberParseException e) {
+        }
+        return result;
+    }
+
+    /**
+     * Format a phone number.
+     * <p>
+     * If the given number doesn't have the country code, the phone will be
+     * formatted to the default country's convention.
+     *
+     * @param phoneNumber
+     *            the number to be formatted.
+     * @param defaultCountryIso
+     *            the ISO 3166-1 two letters country code whose convention will
+     *            be used if the given number doesn't have the country code.
+     * @return the formatted number, or null if the given number is not valid.
+     *
+     * @hide
+     */
+    public static String formatNumber(String phoneNumber, String defaultCountryIso) {
+        PhoneNumberUtil util = PhoneNumberUtil.getInstance();
+        String result = null;
+        try {
+            PhoneNumber pn = util.parse(phoneNumber, defaultCountryIso);
+            result = util.formatInOriginalFormat(pn, defaultCountryIso);
+        } catch (NumberParseException e) {
+        }
+        return result;
+    }
+
+    /**
+     * Normalize a phone number by removing the characters other than digits. If
+     * the given number has keypad letters, the letters will be converted to
+     * digits first.
+     * 
+     * @param phoneNumber
+     *            the number to be normalized.
+     * @return the normalized number.
+     *
+     * @hide
+     */
+    public static String normalizeNumber(String phoneNumber) {
+        StringBuilder sb = new StringBuilder();
+        int len = phoneNumber.length();
+        for (int i = 0; i < len; i++) {
+            char c = phoneNumber.charAt(i);
+            if (PhoneNumberUtils.isISODigit(c)) {
+                sb.append(c);
+            } else if (c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z') {
+                return normalizeNumber(PhoneNumberUtils.convertKeypadLettersToDigits(phoneNumber));
+            }
+        }
+        return sb.toString();
+    }
+
     // Three and four digit phone numbers for either special services,
     // or 3-6 digit addresses from the network (eg carrier-originated SMS messages) should
     // not match.
@@ -1546,7 +1633,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
@@ -1554,7 +1641,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;
@@ -1732,7 +1819,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) {
@@ -1752,7 +1839,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
@@ -1874,12 +1961,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,
@@ -1891,18 +1978,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
      */
@@ -2037,7 +2124,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/PhoneStateListener.java b/telephony/java/android/telephony/PhoneStateListener.java
index 830af47..38f44d8 100644
--- a/telephony/java/android/telephony/PhoneStateListener.java
+++ b/telephony/java/android/telephony/PhoneStateListener.java
@@ -25,6 +25,7 @@
 import android.util.Log;
 
 import com.android.internal.telephony.IPhoneStateListener;
+import com.android.internal.telephony.Phone;
 
 /**
  * A listener class for monitoring changes in specific telephony states
@@ -284,7 +285,7 @@
         }
 
         public void onDataConnectionStateChanged(int state, int networkType) {
-            Message.obtain(mHandler, LISTEN_DATA_CONNECTION_STATE, state, networkType, null).
+            Message.obtain(mHandler, LISTEN_DATA_CONNECTION_STATE, state, networkType).
                     sendToTarget();
         }
 
diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java
index f5e9751..953696b 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/CallManager.java b/telephony/java/com/android/internal/telephony/CallManager.java
index d77f778..1381a2d 100644
--- a/telephony/java/com/android/internal/telephony/CallManager.java
+++ b/telephony/java/com/android/internal/telephony/CallManager.java
@@ -320,7 +320,7 @@
      * java.lang.Object) registerForPreciseCallStateChanged()}.
      *
      * @exception CallStateException if active call is ringing, waiting, or
-     * dialing/alerting, or heldCall can�t be active.
+     * dialing/alerting, or heldCall can't be active.
      * In these cases, this operation may not be performed.
      */
     public void switchHoldingAndActive(Call heldCall) throws CallStateException {
diff --git a/telephony/java/com/android/internal/telephony/CommandsInterface.java b/telephony/java/com/android/internal/telephony/CommandsInterface.java
index 5de0426..27a4dca 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);
@@ -952,19 +952,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);
@@ -974,7 +974,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
      */
@@ -1079,7 +1079,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
@@ -1152,7 +1152,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
@@ -1183,7 +1183,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/DataConnection.java b/telephony/java/com/android/internal/telephony/DataConnection.java
index 6634017..13c76e5 100644
--- a/telephony/java/com/android/internal/telephony/DataConnection.java
+++ b/telephony/java/com/android/internal/telephony/DataConnection.java
@@ -419,17 +419,14 @@
             if (response.length >= 2) {
                 cid = Integer.parseInt(response[0]);
                 interfaceName = response[1];
+
+                String prefix = "net." + interfaceName + ".";
+                gatewayAddress = SystemProperties.get(prefix + "gw");
+                dnsServers[0] = SystemProperties.get(prefix + "dns1");
+                dnsServers[1] = SystemProperties.get(prefix + "dns2");
+
                 if (response.length > 2) {
                     ipAddress = response[2];
-                    String prefix = "net." + interfaceName + ".";
-                    gatewayAddress = SystemProperties.get(prefix + "gw");
-                    dnsServers[0] = SystemProperties.get(prefix + "dns1");
-                    dnsServers[1] = SystemProperties.get(prefix + "dns2");
-                    if (DBG) {
-                        log("interface=" + interfaceName + " ipAddress=" + ipAddress
-                            + " gateway=" + gatewayAddress + " DNS1=" + dnsServers[0]
-                            + " DNS2=" + dnsServers[1]);
-                    }
 
                     if (isDnsOk(dnsServers)) {
                         result = SetupResult.SUCCESS;
@@ -444,7 +441,14 @@
             }
         }
 
-        if (DBG) log("DataConnection setup result='" + result + "' on cid=" + cid);
+        if (DBG) {
+            log("DataConnection setup result='" + result + "' on cid=" + cid);
+            if (result == SetupResult.SUCCESS) {
+                log("interface=" + interfaceName + " ipAddress=" + ipAddress
+                        + " gateway=" + gatewayAddress + " DNS1=" + dnsServers[0]
+                        + " DNS2=" + dnsServers[1]);
+            }
+        }
         return result;
     }
 
diff --git a/telephony/java/com/android/internal/telephony/DataConnectionTracker.java b/telephony/java/com/android/internal/telephony/DataConnectionTracker.java
index 3b9e6cc..06807c6 100644
--- a/telephony/java/com/android/internal/telephony/DataConnectionTracker.java
+++ b/telephony/java/com/android/internal/telephony/DataConnectionTracker.java
@@ -17,6 +17,7 @@
 package com.android.internal.telephony;
 
 import android.app.PendingIntent;
+import android.net.NetworkProperties;
 import android.os.AsyncResult;
 import android.os.Handler;
 import android.os.Message;
@@ -26,6 +27,10 @@
 import android.text.TextUtils;
 import android.util.Log;
 
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.net.UnknownHostException;
 import java.util.ArrayList;
 
 /**
@@ -188,7 +193,13 @@
     /** CID of active data connection */
     protected int cidActive;
 
-   /**
+    /** indication of our availability (preconditions to trysetupData are met) **/
+    protected boolean mAvailability = false;
+
+    /** all our network properties (dns, gateway, ip, etc) */
+    protected NetworkProperties mNetworkProperties;
+
+    /**
      * Default constructor
      */
     protected DataConnectionTracker(PhoneBase phone) {
@@ -421,7 +432,93 @@
 
     protected abstract void setState(State s);
 
-    protected synchronized boolean isEnabled(int id) {
+    protected NetworkProperties getNetworkProperties(String apnType) {
+        int id = apnTypeToId(apnType);
+        if (isApnIdEnabled(id)) {
+            return mNetworkProperties;
+        } else {
+            return null;
+        }
+    }
+
+    // tell all active apns of the current condition
+    protected void notifyDataConnection(String reason) {
+        for (int id = 0; id < APN_NUM_TYPES; id++) {
+            if (dataEnabled[id]) {
+                phone.notifyDataConnection(reason, apnIdToType(id));
+            }
+        }
+        notifyDataAvailability(reason);
+    }
+
+    // a new APN has gone active and needs to send events to catch up with the current condition
+    private void notifyApnIdUpToCurrent(String reason, int apnId) {
+        switch (state) {
+            case IDLE:
+            case INITING:
+                break;
+            case CONNECTING:
+            case SCANNING:
+                phone.notifyDataConnection(reason, apnIdToType(apnId), Phone.DataState.CONNECTING);
+                break;
+            case CONNECTED:
+            case DISCONNECTING:
+                phone.notifyDataConnection(reason, apnIdToType(apnId), Phone.DataState.CONNECTING);
+                phone.notifyDataConnection(reason, apnIdToType(apnId), Phone.DataState.CONNECTED);
+                break;
+        }
+    }
+
+    // since we normally don't send info to a disconnected APN, we need to do this specially
+    private void notifyApnIdDisconnected(String reason, int apnId) {
+        phone.notifyDataConnection(reason, apnIdToType(apnId), Phone.DataState.DISCONNECTED);
+    }
+
+    // disabled apn's still need avail/unavail notificiations - send them out
+    protected void notifyOffApnsOfAvailability(String reason, boolean availability) {
+        if (mAvailability == availability) return;
+        mAvailability = availability;
+        for (int id = 0; id < APN_NUM_TYPES; id++) {
+            if (!isApnIdEnabled(id)) {
+                notifyApnIdDisconnected(reason, id);
+            }
+        }
+    }
+
+    // we had an availability change - tell the listeners
+    protected void notifyDataAvailability(String reason) {
+        // note that we either just turned all off because we lost availability
+        // or all were off and could now go on, so only have off apns to worry about
+        notifyOffApnsOfAvailability(reason, isDataPossible());
+    }
+
+    /**
+     * The only circumstances under which we report that data connectivity is not
+     * possible are
+     * <ul>
+     * <li>Data is disallowed (roaming, power state, voice call, etc).</li>
+     * <li>The current data state is {@code DISCONNECTED} for a reason other than
+     * having explicitly disabled connectivity. In other words, data is not available
+     * because the phone is out of coverage or some like reason.</li>
+     * </ul>
+     * @return {@code true} if data connectivity is possible, {@code false} otherwise.
+     */
+    protected boolean isDataPossible() {
+        boolean possible = (isDataAllowed() &&
+            !(getDataEnabled() && (state == State.FAILED || state == State.IDLE)));
+        if (!possible && DBG && isDataAllowed()) {
+            log("Data not possible.  No coverage: dataState = " + state);
+        }
+        return possible;
+    }
+
+    protected abstract boolean isDataAllowed();
+
+    public boolean isApnTypeEnabled(String apnType) {
+        return isApnIdEnabled(apnTypeToId(apnType));
+    }
+
+    protected synchronized boolean isApnIdEnabled(int id) {
         if (id != APN_INVALID_ID) {
             return dataEnabled[id];
         }
@@ -444,22 +541,21 @@
             return Phone.APN_REQUEST_FAILED;
         }
 
-        if (DBG) Log.d(LOG_TAG, "enableApnType("+type+"), isApnTypeActive = "
-                + isApnTypeActive(type) + " and state = " + state);
+        if (DBG) {
+            Log.d(LOG_TAG, "enableApnType(" + type + "), isApnTypeActive = "
+                    + isApnTypeActive(type) + ", isApnIdEnabled =" + isApnIdEnabled(id) +
+                    " and state = " + state);
+        }
 
         if (!isApnTypeAvailable(type)) {
             if (DBG) Log.d(LOG_TAG, "type not available");
             return Phone.APN_TYPE_NOT_AVAILABLE;
         }
 
-        // just because it's active doesn't mean we had it explicitly requested before
-        // (a broad default may handle many types).  make sure we mark it enabled
-        // so if the default is disabled we keep the connection for others
-        setEnabled(id, true);
-
-        if (isApnTypeActive(type)) {
-            if (state == State.INITING) return Phone.APN_REQUEST_STARTED;
-            else if (state == State.CONNECTED) return Phone.APN_ALREADY_ACTIVE;
+        if (isApnIdEnabled(id)) {
+            return Phone.APN_ALREADY_ACTIVE;
+        } else {
+            setEnabled(id, true);
         }
         return Phone.APN_REQUEST_STARTED;
     }
@@ -478,7 +574,7 @@
         if (id == APN_INVALID_ID) {
             return Phone.APN_REQUEST_FAILED;
         }
-        if (isEnabled(id)) {
+        if (isApnIdEnabled(id)) {
             setEnabled(id, false);
             if (isApnTypeActive(Phone.APN_TYPE_DEFAULT)) {
                 if (dataEnabled[APN_DEFAULT_ID]) {
@@ -495,9 +591,10 @@
     }
 
     private void setEnabled(int id, boolean enable) {
-        if (DBG) Log.d(LOG_TAG, "setEnabled(" + id + ", " + enable + ") with old state = " +
-                dataEnabled[id] + " and enabledCount = " + enabledCount);
-
+        if (DBG) {
+            Log.d(LOG_TAG, "setEnabled(" + id + ", " + enable + ") with old state = "
+                    + dataEnabled[id] + " and enabledCount = " + enabledCount);
+        }
         Message msg = obtainMessage(EVENT_ENABLE_NEW_APN);
         msg.arg1 = id;
         msg.arg2 = (enable ? ENABLED : DISABLED);
@@ -507,9 +604,8 @@
     protected synchronized void onEnableApn(int apnId, int enabled) {
         if (DBG) {
             Log.d(LOG_TAG, "EVENT_APN_ENABLE_REQUEST " + apnId + ", " + enabled);
-            Log.d(LOG_TAG, " dataEnabled = " + dataEnabled[apnId] +
-                    ", enabledCount = " + enabledCount +
-                    ", isApnTypeActive = " + isApnTypeActive(apnIdToType(apnId)));
+            Log.d(LOG_TAG, " dataEnabled = " + dataEnabled[apnId] + ", enabledCount = "
+                    + enabledCount + ", isApnTypeActive = " + isApnTypeActive(apnIdToType(apnId)));
         }
         if (enabled == ENABLED) {
             if (!dataEnabled[apnId]) {
@@ -520,6 +616,8 @@
             if (!isApnTypeActive(type)) {
                 mRequestedApnType = type;
                 onEnableNewApn();
+            } else {
+                notifyApnIdUpToCurrent(Phone.REASON_APN_SWITCHED, apnId);
             }
         } else {
             // disable
@@ -528,8 +626,17 @@
                 enabledCount--;
                 if (enabledCount == 0) {
                     onCleanUpConnection(true, Phone.REASON_DATA_DISABLED);
-                } else if (dataEnabled[APN_DEFAULT_ID] == true &&
-                        !isApnTypeActive(Phone.APN_TYPE_DEFAULT)) {
+                }
+
+                // send the disconnect msg manually, since the normal route wont send
+                // it (it's not enabled)
+                notifyApnIdDisconnected(Phone.REASON_DATA_DISABLED, apnId);
+                if (dataEnabled[APN_DEFAULT_ID] == true
+                        && !isApnTypeActive(Phone.APN_TYPE_DEFAULT)) {
+                    // TODO - this is an ugly way to restore the default conn - should be done
+                    // by a real contention manager and policy that disconnects the lower pri
+                    // stuff as enable requests come in and pops them back on as we disable back
+                    // down to the lower pri stuff
                     mRequestedApnType = Phone.APN_TYPE_DEFAULT;
                     onEnableNewApn();
                 }
@@ -574,9 +681,47 @@
                 onTrySetupData(Phone.REASON_DATA_ENABLED);
             } else {
                 onCleanUpConnection(true, Phone.REASON_DATA_DISABLED);
-           }
+            }
         }
     }
 
+    protected NetworkProperties makeNetworkProperties(DataConnection connection) {
+        NetworkProperties properties = new NetworkProperties();
+        try {
+            properties.setInterface(NetworkInterface.getByName(connection.getInterface()));
+        } catch (SocketException e) {
+            Log.e(LOG_TAG, "SocketException creating NetworkInterface: " + e);
+        } catch (NullPointerException e) {
+            Log.e(LOG_TAG, "NPE trying to makeNetworkProperties: " + e);
+        }
 
+        try {
+            properties.addAddress(InetAddress.getByName(connection.getIpAddress()));
+        } catch (UnknownHostException e) {
+            Log.e(LOG_TAG, "UnknownHostException setting IpAddress: " + e);
+        } catch (SecurityException e) {
+            Log.e(LOG_TAG, "SecurityException setting IpAddress: " + e);
+        }
+
+        try {
+            properties.setGateway(InetAddress.getByName(connection.getGatewayAddress()));
+        } catch (UnknownHostException e) {
+            Log.e(LOG_TAG, "UnknownHostException setting GatewayAddress: " + e);
+        } catch (SecurityException e) {
+            Log.e(LOG_TAG, "SecurityException setting GatewayAddress: " + e);
+        }
+
+        try {
+            String[] dnsStrings = connection.getDnsServers();
+            for (int i = 0; i<dnsStrings.length; i++) {
+                properties.addDns(InetAddress.getByName(dnsStrings[i]));
+            }
+        } catch (UnknownHostException e) {
+            Log.e(LOG_TAG, "UnknownHostException setting DnsAddress: " + e);
+        } catch (SecurityException e) {
+            Log.e(LOG_TAG, "SecurityException setting DnsAddress: " + e);
+        }
+        // TODO - set Proxy info
+        return properties;
+    }
 }
diff --git a/telephony/java/com/android/internal/telephony/DefaultPhoneNotifier.java b/telephony/java/com/android/internal/telephony/DefaultPhoneNotifier.java
index 4da4b6a..382c19f 100644
--- a/telephony/java/com/android/internal/telephony/DefaultPhoneNotifier.java
+++ b/telephony/java/com/android/internal/telephony/DefaultPhoneNotifier.java
@@ -16,6 +16,7 @@
 
 package com.android.internal.telephony;
 
+import android.net.NetworkProperties;
 import android.os.Bundle;
 import android.os.RemoteException;
 import android.os.ServiceManager;
@@ -92,15 +93,32 @@
         }
     }
 
-    public void notifyDataConnection(Phone sender, String reason) {
+    public void notifyDataConnection(Phone sender, String reason, String apnType) {
+        doNotifyDataConnection(sender, reason, apnType, sender.getDataConnectionState(apnType));
+    }
+
+    public void notifyDataConnection(Phone sender, String reason, String apnType,
+            Phone.DataState state) {
+        doNotifyDataConnection(sender, reason, apnType, state);
+    }
+
+    private void doNotifyDataConnection(Phone sender, String reason, String apnType,
+            Phone.DataState state) {
+        // TODO
+        // use apnType as the key to which connection we're talking about.
+        // pass apnType back up to fetch particular for this one.
         TelephonyManager telephony = TelephonyManager.getDefault();
+        NetworkProperties networkProperties = null;
+        if (state == Phone.DataState.CONNECTED) {
+            networkProperties = sender.getNetworkProperties(apnType);
+        }
         try {
             mRegistry.notifyDataConnection(
-                    convertDataState(sender.getDataConnectionState()),
+                    convertDataState(state),
                     sender.isDataConnectivityPossible(), reason,
                     sender.getActiveApn(),
-                    sender.getActiveApnTypes(),
-                    sender.getInterfaceName(null),
+                    apnType,
+                    networkProperties,
                     ((telephony!=null) ? telephony.getNetworkType() :
                     TelephonyManager.NETWORK_TYPE_UNKNOWN));
         } catch (RemoteException ex) {
@@ -108,9 +126,9 @@
         }
     }
 
-    public void notifyDataConnectionFailed(Phone sender, String reason) {
+    public void notifyDataConnectionFailed(Phone sender, String reason, String apnType) {
         try {
-            mRegistry.notifyDataConnectionFailed(reason);
+            mRegistry.notifyDataConnectionFailed(reason, apnType);
         } catch (RemoteException ex) {
             // system process is dead
         }
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/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 2328717..7a1587b 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -239,9 +239,11 @@
     String getCdmaEriText();
 
     /**
-     * Returns true if CDMA provisioning needs to run.
+     * Returns true if OTA service provisioning needs to run.
+     * Only relevant on some technologies, others will always
+     * return false.
      */
-    boolean getCdmaNeedsProvisioning();
+    boolean needsOtaServiceProvisioning();
 
     /**
       * Returns the unread count of voicemails
diff --git a/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl b/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl
index 5bf8e58..f7b70ee 100644
--- a/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl
@@ -17,6 +17,7 @@
 package com.android.internal.telephony;
 
 import android.content.Intent;
+import android.net.NetworkProperties;
 import android.os.Bundle;
 import android.telephony.ServiceState;
 import android.telephony.SignalStrength;
@@ -32,7 +33,8 @@
     void notifyCallForwardingChanged(boolean cfi);
     void notifyDataActivity(int state);
     void notifyDataConnection(int state, boolean isDataConnectivityPossible,
-            String reason, String apn, in String[] apnTypes, String interfaceName, int networkType);
-    void notifyDataConnectionFailed(String reason);
+            String reason, String apn, String apnType, in NetworkProperties networkProperties,
+            int networkType);
+    void notifyDataConnectionFailed(String reason, String apnType);
     void notifyCellLocation(in Bundle cellLocation);
 }
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 957eddd..005ae37 100644
--- a/telephony/java/com/android/internal/telephony/IccUtils.java
+++ b/telephony/java/com/android/internal/telephony/IccUtils.java
@@ -267,9 +267,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/MccTable.java b/telephony/java/com/android/internal/telephony/MccTable.java
index b73c2f7..5dd29af 100644
--- a/telephony/java/com/android/internal/telephony/MccTable.java
+++ b/telephony/java/com/android/internal/telephony/MccTable.java
@@ -23,346 +23,13 @@
 import android.net.wifi.WifiManager;
 import android.os.RemoteException;
 import android.os.SystemProperties;
-import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.Log;
 
-import java.util.Arrays;
-
-/**
- * The table below is built from two resources:
- *
- * 1) ITU "Mobile Network Code (MNC) for the international
- *   identification plan for mobile terminals and mobile users"
- *   which is available as an annex to the ITU operational bulletin
- *   available here: http://www.itu.int/itu-t/bulletin/annex.html
- *
- * 2) The ISO 3166 country codes list, available here:
- *    http://www.iso.org/iso/en/prods-services/iso3166ma/02iso-3166-code-lists/index.html
- *
- * This table was verified (28 Aug 2009) against
- * http://en.wikipedia.org/wiki/List_of_mobile_country_codes with the
- * only unresolved discrepancy being that this list has an extra entry
- * (461) for China.
- *
- * TODO: Complete the mappings for timezones and language/locale codes.
- *
- * The actual table data used in the Java code is generated from the
- * below Python code for efficiency.  The information is expected to
- * be static, but if changes are required, the table in the python
- * code can be modified and the trailing code run to re-generate the
- * tables that are to be used by Java.
-
-mcc_table = [
-  (202, 'gr', 2, 'Greece'),
-  (204, 'nl', 2, 'Europe/Amsterdam', 'nl', 13, 'Netherlands (Kingdom of the)'),
-  (206, 'be', 2, 'Belgium'),
-  (208, 'fr', 2, 'Europe/Paris', 'fr', 'France'),
-  (212, 'mc', 2, 'Monaco (Principality of)'),
-  (213, 'ad', 2, 'Andorra (Principality of)'),
-  (214, 'es', 2, 'Europe/Madrid', 'es', 'Spain'),
-  (216, 'hu', 2, 'Hungary (Republic of)'),
-  (218, 'ba', 2, 'Bosnia and Herzegovina'),
-  (219, 'hr', 2, 'Croatia (Republic of)'),
-  (220, 'rs', 2, 'Serbia and Montenegro'),
-  (222, 'it', 2, 'Europe/Rome', 'it', 'Italy'),
-  (225, 'va', 2, 'Europe/Rome', 'it', 'Vatican City State'),
-  (226, 'ro', 2, 'Romania'),
-  (228, 'ch', 2, 'Europe/Zurich', 'de', 'Switzerland (Confederation of)'),
-  (230, 'cz', 2, 'Europe/Prague', 'cs', 13, 'Czech Republic'),
-  (231, 'sk', 2, 'Slovak Republic'),
-  (232, 'at', 2, 'Europe/Vienna', 'de', 13, 'Austria'),
-  (234, 'gb', 2, 'Europe/London', 'en', 13, 'United Kingdom of Great Britain and Northern Ireland'),
-  (235, 'gb', 2, 'Europe/London', 'en', 13, 'United Kingdom of Great Britain and Northern Ireland'),
-  (238, 'dk', 2, 'Denmark'),
-  (240, 'se', 2, 'Sweden'),
-  (242, 'no', 2, 'Norway'),
-  (244, 'fi', 2, 'Finland'),
-  (246, 'lt', 2, 'Lithuania (Republic of)'),
-  (247, 'lv', 2, 'Latvia (Republic of)'),
-  (248, 'ee', 2, 'Estonia (Republic of)'),
-  (250, 'ru', 2, 'Russian Federation'),
-  (255, 'ua', 2, 'Ukraine'),
-  (257, 'by', 2, 'Belarus (Republic of)'),
-  (259, 'md', 2, 'Moldova (Republic of)'),
-  (260, 'pl', 2, 'Europe/Warsaw', 'Poland (Republic of)'),
-  (262, 'de', 2, 'Europe/Berlin', 'de', 13, 'Germany (Federal Republic of)'),
-  (266, 'gi', 2, 'Gibraltar'),
-  (268, 'pt', 2, 'Portugal'),
-  (270, 'lu', 2, 'Luxembourg'),
-  (272, 'ie', 2, 'Europe/Dublin', 'en', 'Ireland'),
-  (274, 'is', 2, 'Iceland'),
-  (276, 'al', 2, 'Albania (Republic of)'),
-  (278, 'mt', 2, 'Malta'),
-  (280, 'cy', 2, 'Cyprus (Republic of)'),
-  (282, 'ge', 2, 'Georgia'),
-  (283, 'am', 2, 'Armenia (Republic of)'),
-  (284, 'bg', 2, 'Bulgaria (Republic of)'),
-  (286, 'tr', 2, 'Turkey'),
-  (288, 'fo', 2, 'Faroe Islands'),
-  (289, 'ge', 2, 'Abkhazia (Georgia)'),
-  (290, 'gl', 2, 'Greenland (Denmark)'),
-  (292, 'sm', 2, 'San Marino (Republic of)'),
-  (293, 'sl', 2, 'Slovenia (Republic of)'),
-  (294, 'mk', 2, 'The Former Yugoslav Republic of Macedonia'),
-  (295, 'li', 2, 'Liechtenstein (Principality of)'),
-  (297, 'me', 2, 'Montenegro (Republic of)'),
-  (302, 'ca', 3, '', '', 11, 'Canada'),
-  (308, 'pm', 2, 'Saint Pierre and Miquelon (Collectivit territoriale de la Rpublique franaise)'),
-  (310, 'us', 3, '', 'en', 11, 'United States of America'),
-  (311, 'us', 3, '', 'en', 11, 'United States of America'),
-  (312, 'us', 3, '', 'en', 11, 'United States of America'),
-  (313, 'us', 3, '', 'en', 11, 'United States of America'),
-  (314, 'us', 3, '', 'en', 11, 'United States of America'),
-  (315, 'us', 3, '', 'en', 11, 'United States of America'),
-  (316, 'us', 3, '', 'en', 11, 'United States of America'),
-  (330, 'pr', 2, 'Puerto Rico'),
-  (332, 'vi', 2, 'United States Virgin Islands'),
-  (334, 'mx', 3, 'Mexico'),
-  (338, 'jm', 3, 'Jamaica'),
-  (340, 'gp', 2, 'Guadeloupe (French Department of)'),
-  (342, 'bb', 3, 'Barbados'),
-  (344, 'ag', 3, 'Antigua and Barbuda'),
-  (346, 'ky', 3, 'Cayman Islands'),
-  (348, 'vg', 3, 'British Virgin Islands'),
-  (350, 'bm', 2, 'Bermuda'),
-  (352, 'gd', 2, 'Grenada'),
-  (354, 'ms', 2, 'Montserrat'),
-  (356, 'kn', 2, 'Saint Kitts and Nevis'),
-  (358, 'lc', 2, 'Saint Lucia'),
-  (360, 'vc', 2, 'Saint Vincent and the Grenadines'),
-  (362, 'nl', 2, 'Netherlands Antilles'),
-  (363, 'aw', 2, 'Aruba'),
-  (364, 'bs', 2, 'Bahamas (Commonwealth of the)'),
-  (365, 'ai', 3, 'Anguilla'),
-  (366, 'dm', 2, 'Dominica (Commonwealth of)'),
-  (368, 'cu', 2, 'Cuba'),
-  (370, 'do', 2, 'Dominican Republic'),
-  (372, 'ht', 2, 'Haiti (Republic of)'),
-  (374, 'tt', 2, 'Trinidad and Tobago'),
-  (376, 'tc', 2, 'Turks and Caicos Islands'),
-  (400, 'az', 2, 'Azerbaijani Republic'),
-  (401, 'kz', 2, 'Kazakhstan (Republic of)'),
-  (402, 'bt', 2, 'Bhutan (Kingdom of)'),
-  (404, 'in', 2, 'India (Republic of)'),
-  (405, 'in', 2, 'India (Republic of)'),
-  (410, 'pk', 2, 'Pakistan (Islamic Republic of)'),
-  (412, 'af', 2, 'Afghanistan'),
-  (413, 'lk', 2, 'Sri Lanka (Democratic Socialist Republic of)'),
-  (414, 'mm', 2, 'Myanmar (Union of)'),
-  (415, 'lb', 2, 'Lebanon'),
-  (416, 'jo', 2, 'Jordan (Hashemite Kingdom of)'),
-  (417, 'sy', 2, 'Syrian Arab Republic'),
-  (418, 'iq', 2, 'Iraq (Republic of)'),
-  (419, 'kw', 2, 'Kuwait (State of)'),
-  (420, 'sa', 2, 'Saudi Arabia (Kingdom of)'),
-  (421, 'ye', 2, 'Yemen (Republic of)'),
-  (422, 'om', 2, 'Oman (Sultanate of)'),
-  (423, 'ps', 2, 'Palestine'),
-  (424, 'ae', 2, 'United Arab Emirates'),
-  (425, 'il', 2, 'Israel (State of)'),
-  (426, 'bh', 2, 'Bahrain (Kingdom of)'),
-  (427, 'qa', 2, 'Qatar (State of)'),
-  (428, 'mn', 2, 'Mongolia'),
-  (429, 'np', 2, 'Nepal'),
-  (430, 'ae', 2, 'United Arab Emirates'),
-  (431, 'ae', 2, 'United Arab Emirates'),
-  (432, 'ir', 2, 'Iran (Islamic Republic of)'),
-  (434, 'uz', 2, 'Uzbekistan (Republic of)'),
-  (436, 'tj', 2, 'Tajikistan (Republic of)'),
-  (437, 'kg', 2, 'Kyrgyz Republic'),
-  (438, 'tm', 2, 'Turkmenistan'),
-  (440, 'jp', 2, 'Asia/Tokyo', 'ja', 14, 'Japan'),
-  (441, 'jp', 2, 'Asia/Tokyo', 'ja', 14, 'Japan'),
-  (450, 'kr', 2, 'Korea (Republic of)'),
-  (452, 'vn', 2, 'Viet Nam (Socialist Republic of)'),
-  (454, 'hk', 2, '"Hong Kong, China"'),
-  (455, 'mo', 2, '"Macao, China"'),
-  (456, 'kh', 2, 'Cambodia (Kingdom of)'),
-  (457, 'la', 2, "Lao People's Democratic Republic"),
-  (460, 'cn', 2, "Asia/Beijing", 'zh', 13, "China (People's Republic of)"),
-  (461, 'cn', 2, "Asia/Beijing", 'zh', 13, "China (People's Republic of)"),
-  (466, 'tw', 2, "Taiwan (Republic of China)"),
-  (467, 'kp', 2, "Democratic People's Republic of Korea"),
-  (470, 'bd', 2, "Bangladesh (People's Republic of)"),
-  (472, 'mv', 2, 'Maldives (Republic of)'),
-  (502, 'my', 2, 'Malaysia'),
-  (505, 'au', 2, 'Australia/Sydney', 'en', 11, 'Australia'),
-  (510, 'id', 2, 'Indonesia (Republic of)'),
-  (514, 'tl', 2, 'Democratic Republic of Timor-Leste'),
-  (515, 'ph', 2, 'Philippines (Republic of the)'),
-  (520, 'th', 2, 'Thailand'),
-  (525, 'sg', 2, 'Asia/Singapore', 'en', 11, 'Singapore (Republic of)'),
-  (528, 'bn', 2, 'Brunei Darussalam'),
-  (530, 'nz', 2, 'Pacific/Auckland', 'en', 'New Zealand'),
-  (534, 'mp', 2, 'Northern Mariana Islands (Commonwealth of the)'),
-  (535, 'gu', 2, 'Guam'),
-  (536, 'nr', 2, 'Nauru (Republic of)'),
-  (537, 'pg', 2, 'Papua New Guinea'),
-  (539, 'to', 2, 'Tonga (Kingdom of)'),
-  (540, 'sb', 2, 'Solomon Islands'),
-  (541, 'vu', 2, 'Vanuatu (Republic of)'),
-  (542, 'fj', 2, 'Fiji (Republic of)'),
-  (543, 'wf', 2, "Wallis and Futuna (Territoire franais d'outre-mer)"),
-  (544, 'as', 2, 'American Samoa'),
-  (545, 'ki', 2, 'Kiribati (Republic of)'),
-  (546, 'nc', 2, "New Caledonia (Territoire franais d'outre-mer)"),
-  (547, 'pf', 2, "French Polynesia (Territoire franais d'outre-mer)"),
-  (548, 'ck', 2, 'Cook Islands'),
-  (549, 'ws', 2, 'Samoa (Independent State of)'),
-  (550, 'fm', 2, 'Micronesia (Federated States of)'),
-  (551, 'mh', 2, 'Marshall Islands (Republic of the)'),
-  (552, 'pw', 2, 'Palau (Republic of)'),
-  (602, 'eg', 2, 'Egypt (Arab Republic of)'),
-  (603, 'dz', 2, "Algeria (People's Democratic Republic of)"),
-  (604, 'ma', 2, 'Morocco (Kingdom of)'),
-  (605, 'tn', 2, 'Tunisia'),
-  (606, 'ly', 2, "Libya (Socialist People's Libyan Arab Jamahiriya)"),
-  (607, 'gm', 2, 'Gambia (Republic of the)'),
-  (608, 'sn', 2, 'Senegal (Republic of)'),
-  (609, 'mr', 2, 'Mauritania (Islamic Republic of)'),
-  (610, 'ml', 2, 'Mali (Republic of)'),
-  (611, 'gn', 2, 'Guinea (Republic of)'),
-  (612, 'ci', 2, "Cte d'Ivoire (Republic of)"),
-  (613, 'bf', 2, 'Burkina Faso'),
-  (614, 'ne', 2, 'Niger (Republic of the)'),
-  (615, 'tg', 2, 'Togolese Republic'),
-  (616, 'bj', 2, 'Benin (Republic of)'),
-  (617, 'mu', 2, 'Mauritius (Republic of)'),
-  (618, 'lr', 2, 'Liberia (Republic of)'),
-  (619, 'sl', 2, 'Sierra Leone'),
-  (620, 'gh', 2, 'Ghana'),
-  (621, 'ng', 2, 'Nigeria (Federal Republic of)'),
-  (622, 'td', 2, 'Chad (Republic of)'),
-  (623, 'cf', 2, 'Central African Republic'),
-  (624, 'cm', 2, 'Cameroon (Republic of)'),
-  (625, 'cv', 2, 'Cape Verde (Republic of)'),
-  (626, 'st', 2, 'Sao Tome and Principe (Democratic Republic of)'),
-  (627, 'gq', 2, 'Equatorial Guinea (Republic of)'),
-  (628, 'ga', 2, 'Gabonese Republic'),
-  (629, 'cg', 2, 'Congo (Republic of the)'),
-  (630, 'cg', 2, 'Democratic Republic of the Congo'),
-  (631, 'ao', 2, 'Angola (Republic of)'),
-  (632, 'gw', 2, 'Guinea-Bissau (Republic of)'),
-  (633, 'sc', 2, 'Seychelles (Republic of)'),
-  (634, 'sd', 2, 'Sudan (Republic of the)'),
-  (635, 'rw', 2, 'Rwanda (Republic of)'),
-  (636, 'et', 2, 'Ethiopia (Federal Democratic Republic of)'),
-  (637, 'so', 2, 'Somali Democratic Republic'),
-  (638, 'dj', 2, 'Djibouti (Republic of)'),
-  (639, 'ke', 2, 'Kenya (Republic of)'),
-  (640, 'tz', 2, 'Tanzania (United Republic of)'),
-  (641, 'ug', 2, 'Uganda (Republic of)'),
-  (642, 'bi', 2, 'Burundi (Republic of)'),
-  (643, 'mz', 2, 'Mozambique (Republic of)'),
-  (645, 'zm', 2, 'Zambia (Republic of)'),
-  (646, 'mg', 2, 'Madagascar (Republic of)'),
-  (647, 're', 2, 'Reunion (French Department of)'),
-  (648, 'zw', 2, 'Zimbabwe (Republic of)'),
-  (649, 'na', 2, 'Namibia (Republic of)'),
-  (650, 'mw', 2, 'Malawi'),
-  (651, 'ls', 2, 'Lesotho (Kingdom of)'),
-  (652, 'bw', 2, 'Botswana (Republic of)'),
-  (653, 'sz', 2, 'Swaziland (Kingdom of)'),
-  (654, 'km', 2, 'Comoros (Union of the)'),
-  (655, 'za', 2, 'Africa/Johannesburg', 'en', 'South Africa (Republic of)'),
-  (657, 'er', 2, 'Eritrea'),
-  (702, 'bz', 2, 'Belize'),
-  (704, 'gt', 2, 'Guatemala (Republic of)'),
-  (706, 'sv', 2, 'El Salvador (Republic of)'),
-  (708, 'hn', 3, 'Honduras (Republic of)'),
-  (710, 'ni', 2, 'Nicaragua'),
-  (712, 'cr', 2, 'Costa Rica'),
-  (714, 'pa', 2, 'Panama (Republic of)'),
-  (716, 'pe', 2, 'Peru'),
-  (722, 'ar', 3, 'Argentine Republic'),
-  (724, 'br', 2, 'Brazil (Federative Republic of)'),
-  (730, 'cl', 2, 'Chile'),
-  (732, 'co', 3, 'Colombia (Republic of)'),
-  (734, 've', 2, 'Venezuela (Bolivarian Republic of)'),
-  (736, 'bo', 2, 'Bolivia (Republic of)'),
-  (738, 'gy', 2, 'Guyana'),
-  (740, 'ec', 2, 'Ecuador'),
-  (742, 'gf', 2, 'French Guiana (French Department of)'),
-  (744, 'py', 2, 'Paraguay (Republic of)'),
-  (746, 'sr', 2, 'Suriname (Republic of)'),
-  (748, 'uy', 2, 'Uruguay (Eastern Republic of)'),
-  (750, 'fk', 2, 'Falkland Islands (Malvinas)')]
-
-get_mcc = lambda elt: elt[0]
-get_iso = lambda elt: elt[1]
-get_sd = lambda elt: elt[2]
-get_tz = lambda elt: len(elt) > 4 and elt[3] or ''
-get_lang = lambda elt: len(elt) > 5 and elt[4] or ''
-get_wifi = lambda elt: len(elt) > 6 and elt[5] or 0
-
-mcc_codes = ['0x%04x' % get_mcc(elt) for elt in mcc_table]
-tz_set = sorted(x for x in set(get_tz(elt) for elt in mcc_table))
-lang_set = sorted(x for x in set(get_lang(elt) for elt in mcc_table))
-
-def mk_ind_code(elt):
-  iso = get_iso(elt)
-  iso_code = ((ord(iso[0]) << 8) | ord(iso[1])) & 0xFFFF # 16 bits
-  wifi = get_wifi(elt) & 0x000F                          #  4 bits
-  sd = get_sd(elt) & 0x0003                              #  2 bits
-  tz_ind = tz_set.index(get_tz(elt)) & 0x001F            #  5 bits
-  lang_ind = lang_set.index(get_lang(elt)) & 0x000F      #  4 bits
-  return (iso_code << 16) | (wifi << 11) | (sd << 9) | (tz_ind << 4) | lang_ind
-
-ind_codes = ['0x%08x' % mk_ind_code(elt) for elt in mcc_table]
-
-def fmt_list(title, l, batch_sz):
-  sl = []
-  for i in range(len(l) / batch_sz + (len(l) % batch_sz and 1 or 0)):
-    j = i * batch_sz
-    sl.append((' ' * 8) + ', '.join(l[j:j + batch_sz]))
-  return '    private static final %s = {\n' % title + ',\n'.join(sl) + '\n    };\n'
-
-def do_autogen_comment(extra_desc=[]):
-  print '    /' + '**\n    * AUTO GENERATED (by the Python code above)'
-  for line in extra_desc:
-    print '    * %s' % line
-  print '    *' + '/'
-
-do_autogen_comment()
-print fmt_list('String[] TZ_STRINGS', ['"%s"' % x for x in tz_set], 1)
-do_autogen_comment()
-print fmt_list('String[] LANG_STRINGS', ['"%s"' % x for x in lang_set], 10)
-do_autogen_comment(['This table is a list of MCC codes.  The index in this table',
-                    'of a given MCC code is the index of extra information about',
-                    'that MCC in the IND_CODES table.'])
-print fmt_list('short[] MCC_CODES', mcc_codes, 10)
-do_autogen_comment(['The values in this table are broken down as follows (msb to lsb):',
-                    '    iso country code 16 bits',
-                    '    (unused)          1 bit',
-                    '    wifi channel      4 bits',
-                    '    smalled digit     2 bits',
-                    '    default timezone  5 bits',
-                    '    default language  4 bits'])
-print fmt_list('int[] IND_CODES', ind_codes, 6)
-
-def parse_ind_code(ind):
-  mcc = eval(mcc_codes[ind])
-  code = eval(ind_codes[ind])
-  iso_lsb = int((code >> 16) & 0x00FF)
-  iso_msb = int((code >> 24) & 0x00FF)
-  iso = '%s%s' % (chr(iso_msb), chr(iso_lsb))
-  wifi = int((code >> 11) & 0x000F)
-  sd = int((code >> 9) & 0x0003)
-  tz_ind = (code >> 4) & 0x001F
-  lang_ind = (code >> 0) & 0x000F
-  return (mcc, iso, sd, tz_set[tz_ind], lang_set[lang_ind], wifi)
-
-fmt_str = 'mcc = %s, iso = %s, sd = %s, tz = %s, lang = %s, wifi = %s'
-orig_table = [fmt_str % (get_mcc(elt), get_iso(elt), get_sd(elt),
-                         get_tz(elt), get_lang(elt), get_wifi(elt))
-              for elt in mcc_table]
-derived_table = [fmt_str % parse_ind_code(i) for i in range(len(ind_codes))]
-for i in range(len(orig_table)):
-  if orig_table[i] == derived_table[i]: continue
-  print 'MISMATCH ERROR : ', orig_table[i], " != ", derived_table[i]
-
-*/
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Locale;
+import libcore.icu.TimeZones;
 
 /**
  * Mobile Country Code
@@ -371,200 +38,153 @@
  */
 public final class MccTable
 {
-    /**
-     * AUTO GENERATED (by the Python code above)
-     */
-    private static final String[] TZ_STRINGS = {
-        "",
-        "Africa/Johannesburg",
-        "Asia/Beijing",
-        "Asia/Singapore",
-        "Asia/Tokyo",
-        "Australia/Sydney",
-        "Europe/Amsterdam",
-        "Europe/Berlin",
-        "Europe/Dublin",
-        "Europe/London",
-        "Europe/Madrid",
-        "Europe/Paris",
-        "Europe/Prague",
-        "Europe/Rome",
-        "Europe/Vienna",
-        "Europe/Warsaw",
-        "Europe/Zurich",
-        "Pacific/Auckland"
-    };
-
-    /**
-     * AUTO GENERATED (by the Python code above)
-     */
-    private static final String[] LANG_STRINGS = {
-        "", "cs", "de", "en", "es", "fr", "it", "ja", "nl", "zh"
-    };
-
-    /**
-     * AUTO GENERATED (by the Python code above)
-     * This table is a list of MCC codes.  The index in this table
-     * of a given MCC code is the index of extra information about
-     * that MCC in the IND_CODES table.
-     */
-    private static final short[] MCC_CODES = {
-        0x00ca, 0x00cc, 0x00ce, 0x00d0, 0x00d4, 0x00d5, 0x00d6, 0x00d8, 0x00da, 0x00db,
-        0x00dc, 0x00de, 0x00e1, 0x00e2, 0x00e4, 0x00e6, 0x00e7, 0x00e8, 0x00ea, 0x00eb,
-        0x00ee, 0x00f0, 0x00f2, 0x00f4, 0x00f6, 0x00f7, 0x00f8, 0x00fa, 0x00ff, 0x0101,
-        0x0103, 0x0104, 0x0106, 0x010a, 0x010c, 0x010e, 0x0110, 0x0112, 0x0114, 0x0116,
-        0x0118, 0x011a, 0x011b, 0x011c, 0x011e, 0x0120, 0x0121, 0x0122, 0x0124, 0x0125,
-        0x0126, 0x0127, 0x0129, 0x012e, 0x0134, 0x0136, 0x0137, 0x0138, 0x0139, 0x013a,
-        0x013b, 0x013c, 0x014a, 0x014c, 0x014e, 0x0152, 0x0154, 0x0156, 0x0158, 0x015a,
-        0x015c, 0x015e, 0x0160, 0x0162, 0x0164, 0x0166, 0x0168, 0x016a, 0x016b, 0x016c,
-        0x016d, 0x016e, 0x0170, 0x0172, 0x0174, 0x0176, 0x0178, 0x0190, 0x0191, 0x0192,
-        0x0194, 0x0195, 0x019a, 0x019c, 0x019d, 0x019e, 0x019f, 0x01a0, 0x01a1, 0x01a2,
-        0x01a3, 0x01a4, 0x01a5, 0x01a6, 0x01a7, 0x01a8, 0x01a9, 0x01aa, 0x01ab, 0x01ac,
-        0x01ad, 0x01ae, 0x01af, 0x01b0, 0x01b2, 0x01b4, 0x01b5, 0x01b6, 0x01b8, 0x01b9,
-        0x01c2, 0x01c4, 0x01c6, 0x01c7, 0x01c8, 0x01c9, 0x01cc, 0x01cd, 0x01d2, 0x01d3,
-        0x01d6, 0x01d8, 0x01f6, 0x01f9, 0x01fe, 0x0202, 0x0203, 0x0208, 0x020d, 0x0210,
-        0x0212, 0x0216, 0x0217, 0x0218, 0x0219, 0x021b, 0x021c, 0x021d, 0x021e, 0x021f,
-        0x0220, 0x0221, 0x0222, 0x0223, 0x0224, 0x0225, 0x0226, 0x0227, 0x0228, 0x025a,
-        0x025b, 0x025c, 0x025d, 0x025e, 0x025f, 0x0260, 0x0261, 0x0262, 0x0263, 0x0264,
-        0x0265, 0x0266, 0x0267, 0x0268, 0x0269, 0x026a, 0x026b, 0x026c, 0x026d, 0x026e,
-        0x026f, 0x0270, 0x0271, 0x0272, 0x0273, 0x0274, 0x0275, 0x0276, 0x0277, 0x0278,
-        0x0279, 0x027a, 0x027b, 0x027c, 0x027d, 0x027e, 0x027f, 0x0280, 0x0281, 0x0282,
-        0x0283, 0x0285, 0x0286, 0x0287, 0x0288, 0x0289, 0x028a, 0x028b, 0x028c, 0x028d,
-        0x028e, 0x028f, 0x0291, 0x02be, 0x02c0, 0x02c2, 0x02c4, 0x02c6, 0x02c8, 0x02ca,
-        0x02cc, 0x02d2, 0x02d4, 0x02da, 0x02dc, 0x02de, 0x02e0, 0x02e2, 0x02e4, 0x02e6,
-        0x02e8, 0x02ea, 0x02ec, 0x02ee
-    };
-
-    /**
-     * AUTO GENERATED (by the Python code above)
-     * The values in this table are broken down as follows (msb to lsb):
-     *     iso country code 16 bits
-     *     (unused)          1 bit
-     *     wifi channel      4 bits
-     *     smalled digit     2 bits
-     *     default timezone  5 bits
-     *     default language  4 bits
-     */
-    private static final int[] IND_CODES = {
-        0x67720400, 0x6e6c6c68, 0x62650400, 0x667204b5, 0x6d630400, 0x61640400,
-        0x657304a4, 0x68750400, 0x62610400, 0x68720400, 0x72730400, 0x697404d6,
-        0x766104d6, 0x726f0400, 0x63680502, 0x637a6cc1, 0x736b0400, 0x61746ce2,
-        0x67626c93, 0x67626c93, 0x646b0400, 0x73650400, 0x6e6f0400, 0x66690400,
-        0x6c740400, 0x6c760400, 0x65650400, 0x72750400, 0x75610400, 0x62790400,
-        0x6d640400, 0x706c04f0, 0x64656c72, 0x67690400, 0x70740400, 0x6c750400,
-        0x69650483, 0x69730400, 0x616c0400, 0x6d740400, 0x63790400, 0x67650400,
-        0x616d0400, 0x62670400, 0x74720400, 0x666f0400, 0x67650400, 0x676c0400,
-        0x736d0400, 0x736c0400, 0x6d6b0400, 0x6c690400, 0x6d650400, 0x63615e00,
-        0x706d0400, 0x75735e03, 0x75735e03, 0x75735e03, 0x75735e03, 0x75735e03,
-        0x75735e03, 0x75735e03, 0x70720400, 0x76690400, 0x6d780600, 0x6a6d0600,
-        0x67700400, 0x62620600, 0x61670600, 0x6b790600, 0x76670600, 0x626d0400,
-        0x67640400, 0x6d730400, 0x6b6e0400, 0x6c630400, 0x76630400, 0x6e6c0400,
-        0x61770400, 0x62730400, 0x61690600, 0x646d0400, 0x63750400, 0x646f0400,
-        0x68740400, 0x74740400, 0x74630400, 0x617a0400, 0x6b7a0400, 0x62740400,
-        0x696e0400, 0x696e0400, 0x706b0400, 0x61660400, 0x6c6b0400, 0x6d6d0400,
-        0x6c620400, 0x6a6f0400, 0x73790400, 0x69710400, 0x6b770400, 0x73610400,
-        0x79650400, 0x6f6d0400, 0x70730400, 0x61650400, 0x696c0400, 0x62680400,
-        0x71610400, 0x6d6e0400, 0x6e700400, 0x61650400, 0x61650400, 0x69720400,
-        0x757a0400, 0x746a0400, 0x6b670400, 0x746d0400, 0x6a707447, 0x6a707447,
-        0x6b720400, 0x766e0400, 0x686b0400, 0x6d6f0400, 0x6b680400, 0x6c610400,
-        0x636e6c29, 0x636e6c29, 0x74770400, 0x6b700400, 0x62640400, 0x6d760400,
-        0x6d790400, 0x61755c53, 0x69640400, 0x746c0400, 0x70680400, 0x74680400,
-        0x73675c33, 0x626e0400, 0x6e7a0513, 0x6d700400, 0x67750400, 0x6e720400,
-        0x70670400, 0x746f0400, 0x73620400, 0x76750400, 0x666a0400, 0x77660400,
-        0x61730400, 0x6b690400, 0x6e630400, 0x70660400, 0x636b0400, 0x77730400,
-        0x666d0400, 0x6d680400, 0x70770400, 0x65670400, 0x647a0400, 0x6d610400,
-        0x746e0400, 0x6c790400, 0x676d0400, 0x736e0400, 0x6d720400, 0x6d6c0400,
-        0x676e0400, 0x63690400, 0x62660400, 0x6e650400, 0x74670400, 0x626a0400,
-        0x6d750400, 0x6c720400, 0x736c0400, 0x67680400, 0x6e670400, 0x74640400,
-        0x63660400, 0x636d0400, 0x63760400, 0x73740400, 0x67710400, 0x67610400,
-        0x63670400, 0x63670400, 0x616f0400, 0x67770400, 0x73630400, 0x73640400,
-        0x72770400, 0x65740400, 0x736f0400, 0x646a0400, 0x6b650400, 0x747a0400,
-        0x75670400, 0x62690400, 0x6d7a0400, 0x7a6d0400, 0x6d670400, 0x72650400,
-        0x7a770400, 0x6e610400, 0x6d770400, 0x6c730400, 0x62770400, 0x737a0400,
-        0x6b6d0400, 0x7a610413, 0x65720400, 0x627a0400, 0x67740400, 0x73760400,
-        0x686e0600, 0x6e690400, 0x63720400, 0x70610400, 0x70650400, 0x61720600,
-        0x62720400, 0x636c0400, 0x636f0600, 0x76650400, 0x626f0400, 0x67790400,
-        0x65630400, 0x67660400, 0x70790400, 0x73720400, 0x75790400, 0x666b0400
-    };
-
     static final String LOG_TAG = "MccTable";
 
+    static ArrayList<MccEntry> table;
+
+    static class MccEntry implements Comparable<MccEntry>
+    {
+        int mcc;
+        String iso;
+        int smallestDigitsMnc;
+        String language;
+        int wifiChannels;
+
+        MccEntry(int mnc, String iso, int smallestDigitsMCC) {
+            this(mnc, iso, smallestDigitsMCC, null);
+        }
+
+        MccEntry(int mnc, String iso, int smallestDigitsMCC, String language) {
+            this(mnc, iso, smallestDigitsMCC, language, 0);
+        }
+
+        MccEntry(int mnc, String iso, int smallestDigitsMCC, String language, int wifiChannels) {
+            this.mcc = mnc;
+            this.iso = iso;
+            this.smallestDigitsMnc = smallestDigitsMCC;
+            this.language = language;
+            this.wifiChannels = wifiChannels;
+        }
+
+
+        public int compareTo(MccEntry o)
+        {
+            return mcc - o.mcc;
+        }
+    }
+
+    private static MccEntry
+    entryForMcc(int mcc)
+    {
+        int index;
+
+        MccEntry m;
+
+        m = new MccEntry(mcc, null, 0);
+
+        index = Collections.binarySearch(table, m);
+
+        if (index < 0) {
+            return null;
+        } else {
+            return table.get(index);
+        }
+    }
+
     /**
-     * Given a Mobile Country Code, returns a default time zone ID
-     * if available.  Returns null if unavailable.
+     * Returns a default time zone ID for the given MCC.
+     * @param mcc Mobile Country Code
+     * @return default TimeZone ID, or null if not specified
      */
     public static String defaultTimeZoneForMcc(int mcc) {
-        int index = Arrays.binarySearch(MCC_CODES, (short)mcc);
-        if (index < 0) {
+        MccEntry entry;
+
+        entry = entryForMcc(mcc);
+        if (entry == null || entry.iso == null) {
             return null;
+        } else {
+            Locale locale;
+            if (entry.language == null) {
+                locale = new Locale(entry.iso);
+            } else {
+                locale = new Locale(entry.language, entry.iso);
+            }
+            String[] tz = TimeZones.forLocale(locale);
+            if (tz.length == 0) return null;
+            return tz[0];
         }
-        int indCode = IND_CODES[index];
-        int tzInd = (indCode >>> 4) & 0x001F;
-        String tz = TZ_STRINGS[tzInd];
-        if (tz == "") {
-            return null;
-        }
-        return tz;
     }
 
     /**
-     * Given a Mobile Country Code, returns an ISO two-character
-     * country code if available.  Returns "" if unavailable.
+     * Given a GSM Mobile Country Code, returns
+     * an ISO two-character country code if available.
+     * Returns "" if unavailable.
      */
-    public static String countryCodeForMcc(int mcc) {
-        int index = Arrays.binarySearch(MCC_CODES, (short)mcc);
-        if (index < 0) {
+    public static String
+    countryCodeForMcc(int mcc)
+    {
+        MccEntry entry;
+
+        entry = entryForMcc(mcc);
+
+        if (entry == null) {
             return "";
+        } else {
+            return entry.iso;
         }
-        int indCode = IND_CODES[index];
-        byte[] iso = {(byte)((indCode >>> 24) & 0x00FF), (byte)((indCode >>> 16) & 0x00FF)};
-        return new String(iso);
     }
 
     /**
-     * Given a GSM Mobile Country Code, returns an ISO 2-3 character
-     * language code if available.  Returns null if unavailable.
+     * Given a GSM Mobile Country Code, returns
+     * an ISO 2-3 character language code if available.
+     * Returns null if unavailable.
      */
     public static String defaultLanguageForMcc(int mcc) {
-        int index = Arrays.binarySearch(MCC_CODES, (short)mcc);
-        if (index < 0) {
+        MccEntry entry;
+
+        entry = entryForMcc(mcc);
+
+        if (entry == null) {
             return null;
+        } else {
+            return entry.language;
         }
-        int indCode = IND_CODES[index];
-        int langInd = indCode & 0x000F;
-        String lang = LANG_STRINGS[langInd];
-        if (lang == "") {
-            return null;
-        }
-        return lang;
     }
 
     /**
-     * Given a GSM Mobile Country Code, returns the corresponding
-     * smallest number of digits field.  Returns 2 if unavailable.
+     * Given a GSM Mobile Country Code, returns
+     * the smallest number of digits that M if available.
+     * Returns 2 if unavailable.
      */
-    public static int smallestDigitsMccForMnc(int mcc) {
-        int index = Arrays.binarySearch(MCC_CODES, (short)mcc);
-        if (index < 0) {
+    public static int
+    smallestDigitsMccForMnc(int mcc)
+    {
+        MccEntry entry;
+
+        entry = entryForMcc(mcc);
+
+        if (entry == null) {
             return 2;
+        } else {
+            return entry.smallestDigitsMnc;
         }
-        int indCode = IND_CODES[index];
-        int smDig = (indCode >>> 9) & 0x0003;
-        return smDig;
     }
 
     /**
      * Given a GSM Mobile Country Code, returns the number of wifi
      * channels allowed in that country.  Returns 0 if unavailable.
      */
-    public static int wifiChannelsForMcc(int mcc) {
-        int index = Arrays.binarySearch(MCC_CODES, (short)mcc);
-        if (index < 0) {
+    public static int
+    wifiChannelsForMcc(int mcc) {
+        MccEntry entry;
+
+        entry = entryForMcc(mcc);
+
+        if (entry == null) {
             return 0;
+        } else {
+            return entry.wifiChannels;
         }
-        int indCode = IND_CODES[index];
-        int wifi = (indCode >>> 11) & 0x000F;
-        return wifi;
     }
 
     /**
@@ -656,4 +276,262 @@
             wM.setNumAllowedChannels(wifiChannels, true);
         }
     }
+
+    static {
+        table = new ArrayList<MccEntry>(240);
+
+
+        /*
+         * The table below is built from two resources:
+         *
+         * 1) ITU "Mobile Network Code (MNC) for the international
+         *   identification plan for mobile terminals and mobile users"
+         *   which is available as an annex to the ITU operational bulletin
+         *   available here: http://www.itu.int/itu-t/bulletin/annex.html
+         *
+         * 2) The ISO 3166 country codes list, available here:
+         *    http://www.iso.org/iso/en/prods-services/iso3166ma/02iso-3166-code-lists/index.html
+         *
+         * This table has not been verified.
+         *
+         */
+
+		table.add(new MccEntry(202,"gr",2));	//Greece
+		table.add(new MccEntry(204,"nl",2,"nl",13));	//Netherlands (Kingdom of the)
+		table.add(new MccEntry(206,"be",2));	//Belgium
+		table.add(new MccEntry(208,"fr",2,"fr"));	//France
+		table.add(new MccEntry(212,"mc",2));	//Monaco (Principality of)
+		table.add(new MccEntry(213,"ad",2));	//Andorra (Principality of)
+		table.add(new MccEntry(214,"es",2,"es"));	//Spain
+		table.add(new MccEntry(216,"hu",2));	//Hungary (Republic of)
+		table.add(new MccEntry(218,"ba",2));	//Bosnia and Herzegovina
+		table.add(new MccEntry(219,"hr",2));	//Croatia (Republic of)
+		table.add(new MccEntry(220,"rs",2));	//Serbia and Montenegro
+		table.add(new MccEntry(222,"it",2,"it"));	//Italy
+		table.add(new MccEntry(225,"va",2,"it"));	//Vatican City State
+		table.add(new MccEntry(226,"ro",2));	//Romania
+		table.add(new MccEntry(228,"ch",2,"de"));	//Switzerland (Confederation of)
+		table.add(new MccEntry(230,"cz",2,"cs",13));	//Czech Republic
+		table.add(new MccEntry(231,"sk",2));	//Slovak Republic
+		table.add(new MccEntry(232,"at",2,"de",13));	//Austria
+		table.add(new MccEntry(234,"gb",2,"en",13));	//United Kingdom of Great Britain and Northern Ireland
+		table.add(new MccEntry(235,"gb",2,"en",13));	//United Kingdom of Great Britain and Northern Ireland
+		table.add(new MccEntry(238,"dk",2));	//Denmark
+		table.add(new MccEntry(240,"se",2));	//Sweden
+		table.add(new MccEntry(242,"no",2));	//Norway
+		table.add(new MccEntry(244,"fi",2));	//Finland
+		table.add(new MccEntry(246,"lt",2));	//Lithuania (Republic of)
+		table.add(new MccEntry(247,"lv",2));	//Latvia (Republic of)
+		table.add(new MccEntry(248,"ee",2));	//Estonia (Republic of)
+		table.add(new MccEntry(250,"ru",2));	//Russian Federation
+		table.add(new MccEntry(255,"ua",2));	//Ukraine
+		table.add(new MccEntry(257,"by",2));	//Belarus (Republic of)
+		table.add(new MccEntry(259,"md",2));	//Moldova (Republic of)
+		table.add(new MccEntry(260,"pl",2));	//Poland (Republic of)
+		table.add(new MccEntry(262,"de",2,"de",13));	//Germany (Federal Republic of)
+		table.add(new MccEntry(266,"gi",2));	//Gibraltar
+		table.add(new MccEntry(268,"pt",2));	//Portugal
+		table.add(new MccEntry(270,"lu",2));	//Luxembourg
+		table.add(new MccEntry(272,"ie",2,"en"));	//Ireland
+		table.add(new MccEntry(274,"is",2));	//Iceland
+		table.add(new MccEntry(276,"al",2));	//Albania (Republic of)
+		table.add(new MccEntry(278,"mt",2));	//Malta
+		table.add(new MccEntry(280,"cy",2));	//Cyprus (Republic of)
+		table.add(new MccEntry(282,"ge",2));	//Georgia
+		table.add(new MccEntry(283,"am",2));	//Armenia (Republic of)
+		table.add(new MccEntry(284,"bg",2));	//Bulgaria (Republic of)
+		table.add(new MccEntry(286,"tr",2));	//Turkey
+		table.add(new MccEntry(288,"fo",2));	//Faroe Islands
+                table.add(new MccEntry(289,"ge",2));    //Abkhazia (Georgia)
+		table.add(new MccEntry(290,"gl",2));	//Greenland (Denmark)
+		table.add(new MccEntry(292,"sm",2));	//San Marino (Republic of)
+		table.add(new MccEntry(293,"sl",2));	//Slovenia (Republic of)
+                table.add(new MccEntry(294,"mk",2));   //The Former Yugoslav Republic of Macedonia
+		table.add(new MccEntry(295,"li",2));	//Liechtenstein (Principality of)
+                table.add(new MccEntry(297,"me",2));    //Montenegro (Republic of)
+		table.add(new MccEntry(302,"ca",3,"",11));	//Canada
+		table.add(new MccEntry(308,"pm",2));	//Saint Pierre and Miquelon (Collectivit territoriale de la Rpublique franaise)
+		table.add(new MccEntry(310,"us",3,"en",11));	//United States of America
+		table.add(new MccEntry(311,"us",3,"en",11));	//United States of America
+		table.add(new MccEntry(312,"us",3,"en",11));	//United States of America
+		table.add(new MccEntry(313,"us",3,"en",11));	//United States of America
+		table.add(new MccEntry(314,"us",3,"en",11));	//United States of America
+		table.add(new MccEntry(315,"us",3,"en",11));	//United States of America
+		table.add(new MccEntry(316,"us",3,"en",11));	//United States of America
+		table.add(new MccEntry(330,"pr",2));	//Puerto Rico
+		table.add(new MccEntry(332,"vi",2));	//United States Virgin Islands
+		table.add(new MccEntry(334,"mx",3));	//Mexico
+		table.add(new MccEntry(338,"jm",3));	//Jamaica
+		table.add(new MccEntry(340,"gp",2));	//Guadeloupe (French Department of)
+		table.add(new MccEntry(342,"bb",3));	//Barbados
+		table.add(new MccEntry(344,"ag",3));	//Antigua and Barbuda
+		table.add(new MccEntry(346,"ky",3));	//Cayman Islands
+		table.add(new MccEntry(348,"vg",3));	//British Virgin Islands
+		table.add(new MccEntry(350,"bm",2));	//Bermuda
+		table.add(new MccEntry(352,"gd",2));	//Grenada
+		table.add(new MccEntry(354,"ms",2));	//Montserrat
+		table.add(new MccEntry(356,"kn",2));	//Saint Kitts and Nevis
+		table.add(new MccEntry(358,"lc",2));	//Saint Lucia
+		table.add(new MccEntry(360,"vc",2));	//Saint Vincent and the Grenadines
+		table.add(new MccEntry(362,"nl",2));	//Netherlands Antilles
+		table.add(new MccEntry(363,"aw",2));	//Aruba
+		table.add(new MccEntry(364,"bs",2));	//Bahamas (Commonwealth of the)
+		table.add(new MccEntry(365,"ai",3));	//Anguilla
+		table.add(new MccEntry(366,"dm",2));	//Dominica (Commonwealth of)
+		table.add(new MccEntry(368,"cu",2));	//Cuba
+		table.add(new MccEntry(370,"do",2));	//Dominican Republic
+		table.add(new MccEntry(372,"ht",2));	//Haiti (Republic of)
+		table.add(new MccEntry(374,"tt",2));	//Trinidad and Tobago
+		table.add(new MccEntry(376,"tc",2));	//Turks and Caicos Islands
+		table.add(new MccEntry(400,"az",2));	//Azerbaijani Republic
+		table.add(new MccEntry(401,"kz",2));	//Kazakhstan (Republic of)
+		table.add(new MccEntry(402,"bt",2));	//Bhutan (Kingdom of)
+		table.add(new MccEntry(404,"in",2));	//India (Republic of)
+		table.add(new MccEntry(405,"in",2));	//India (Republic of)
+		table.add(new MccEntry(410,"pk",2));	//Pakistan (Islamic Republic of)
+		table.add(new MccEntry(412,"af",2));	//Afghanistan
+		table.add(new MccEntry(413,"lk",2));	//Sri Lanka (Democratic Socialist Republic of)
+		table.add(new MccEntry(414,"mm",2));	//Myanmar (Union of)
+		table.add(new MccEntry(415,"lb",2));	//Lebanon
+		table.add(new MccEntry(416,"jo",2));	//Jordan (Hashemite Kingdom of)
+		table.add(new MccEntry(417,"sy",2));	//Syrian Arab Republic
+		table.add(new MccEntry(418,"iq",2));	//Iraq (Republic of)
+		table.add(new MccEntry(419,"kw",2));	//Kuwait (State of)
+		table.add(new MccEntry(420,"sa",2));	//Saudi Arabia (Kingdom of)
+		table.add(new MccEntry(421,"ye",2));	//Yemen (Republic of)
+		table.add(new MccEntry(422,"om",2));	//Oman (Sultanate of)
+                table.add(new MccEntry(423,"ps",2));    //Palestine
+		table.add(new MccEntry(424,"ae",2));	//United Arab Emirates
+		table.add(new MccEntry(425,"il",2));	//Israel (State of)
+		table.add(new MccEntry(426,"bh",2));	//Bahrain (Kingdom of)
+		table.add(new MccEntry(427,"qa",2));	//Qatar (State of)
+		table.add(new MccEntry(428,"mn",2));	//Mongolia
+		table.add(new MccEntry(429,"np",2));	//Nepal
+		table.add(new MccEntry(430,"ae",2));	//United Arab Emirates
+		table.add(new MccEntry(431,"ae",2));	//United Arab Emirates
+		table.add(new MccEntry(432,"ir",2));	//Iran (Islamic Republic of)
+		table.add(new MccEntry(434,"uz",2));	//Uzbekistan (Republic of)
+		table.add(new MccEntry(436,"tj",2));	//Tajikistan (Republic of)
+		table.add(new MccEntry(437,"kg",2));	//Kyrgyz Republic
+		table.add(new MccEntry(438,"tm",2));	//Turkmenistan
+		table.add(new MccEntry(440,"jp",2,"ja",14));	//Japan
+		table.add(new MccEntry(441,"jp",2,"ja",14));	//Japan
+		table.add(new MccEntry(450,"kr",2));	//Korea (Republic of)
+		table.add(new MccEntry(452,"vn",2));	//Viet Nam (Socialist Republic of)
+		table.add(new MccEntry(454,"hk",2));	//"Hong Kong, China"
+		table.add(new MccEntry(455,"mo",2));	//"Macao, China"
+		table.add(new MccEntry(456,"kh",2));	//Cambodia (Kingdom of)
+		table.add(new MccEntry(457,"la",2));	//Lao People's Democratic Republic
+		table.add(new MccEntry(460,"cn",2,"zh",13));	//China (People's Republic of)
+		table.add(new MccEntry(461,"cn",2,"zh",13));	//China (People's Republic of)
+		table.add(new MccEntry(466,"tw",2));	//"Taiwan, China"
+		table.add(new MccEntry(467,"kp",2));	//Democratic People's Republic of Korea
+		table.add(new MccEntry(470,"bd",2));	//Bangladesh (People's Republic of)
+		table.add(new MccEntry(472,"mv",2));	//Maldives (Republic of)
+		table.add(new MccEntry(502,"my",2));	//Malaysia
+		table.add(new MccEntry(505,"au",2,"en",11));	//Australia
+		table.add(new MccEntry(510,"id",2));	//Indonesia (Republic of)
+		table.add(new MccEntry(514,"tl",2));	//Democratic Republic of Timor-Leste
+		table.add(new MccEntry(515,"ph",2));	//Philippines (Republic of the)
+		table.add(new MccEntry(520,"th",2));	//Thailand
+		table.add(new MccEntry(525,"sg",2,"en",11));	//Singapore (Republic of)
+		table.add(new MccEntry(528,"bn",2));	//Brunei Darussalam
+		table.add(new MccEntry(530,"nz",2, "en"));	//New Zealand
+		table.add(new MccEntry(534,"mp",2));	//Northern Mariana Islands (Commonwealth of the)
+		table.add(new MccEntry(535,"gu",2));	//Guam
+		table.add(new MccEntry(536,"nr",2));	//Nauru (Republic of)
+		table.add(new MccEntry(537,"pg",2));	//Papua New Guinea
+		table.add(new MccEntry(539,"to",2));	//Tonga (Kingdom of)
+		table.add(new MccEntry(540,"sb",2));	//Solomon Islands
+		table.add(new MccEntry(541,"vu",2));	//Vanuatu (Republic of)
+		table.add(new MccEntry(542,"fj",2));	//Fiji (Republic of)
+		table.add(new MccEntry(543,"wf",2));	//Wallis and Futuna (Territoire franais d'outre-mer)
+		table.add(new MccEntry(544,"as",2));	//American Samoa
+		table.add(new MccEntry(545,"ki",2));	//Kiribati (Republic of)
+		table.add(new MccEntry(546,"nc",2));	//New Caledonia (Territoire franais d'outre-mer)
+		table.add(new MccEntry(547,"pf",2));	//French Polynesia (Territoire franais d'outre-mer)
+		table.add(new MccEntry(548,"ck",2));	//Cook Islands
+		table.add(new MccEntry(549,"ws",2));	//Samoa (Independent State of)
+		table.add(new MccEntry(550,"fm",2));	//Micronesia (Federated States of)
+		table.add(new MccEntry(551,"mh",2));	//Marshall Islands (Republic of the)
+		table.add(new MccEntry(552,"pw",2));	//Palau (Republic of)
+		table.add(new MccEntry(602,"eg",2));	//Egypt (Arab Republic of)
+		table.add(new MccEntry(603,"dz",2));	//Algeria (People's Democratic Republic of)
+		table.add(new MccEntry(604,"ma",2));	//Morocco (Kingdom of)
+		table.add(new MccEntry(605,"tn",2));	//Tunisia
+		table.add(new MccEntry(606,"ly",2));	//Libya (Socialist People's Libyan Arab Jamahiriya)
+		table.add(new MccEntry(607,"gm",2));	//Gambia (Republic of the)
+		table.add(new MccEntry(608,"sn",2));	//Senegal (Republic of)
+		table.add(new MccEntry(609,"mr",2));	//Mauritania (Islamic Republic of)
+		table.add(new MccEntry(610,"ml",2));	//Mali (Republic of)
+		table.add(new MccEntry(611,"gn",2));	//Guinea (Republic of)
+		table.add(new MccEntry(612,"ci",2));	//Cte d'Ivoire (Republic of)
+		table.add(new MccEntry(613,"bf",2));	//Burkina Faso
+		table.add(new MccEntry(614,"ne",2));	//Niger (Republic of the)
+		table.add(new MccEntry(615,"tg",2));	//Togolese Republic
+		table.add(new MccEntry(616,"bj",2));	//Benin (Republic of)
+		table.add(new MccEntry(617,"mu",2));	//Mauritius (Republic of)
+		table.add(new MccEntry(618,"lr",2));	//Liberia (Republic of)
+		table.add(new MccEntry(619,"sl",2));	//Sierra Leone
+		table.add(new MccEntry(620,"gh",2));	//Ghana
+		table.add(new MccEntry(621,"ng",2));	//Nigeria (Federal Republic of)
+		table.add(new MccEntry(622,"td",2));	//Chad (Republic of)
+		table.add(new MccEntry(623,"cf",2));	//Central African Republic
+		table.add(new MccEntry(624,"cm",2));	//Cameroon (Republic of)
+		table.add(new MccEntry(625,"cv",2));	//Cape Verde (Republic of)
+		table.add(new MccEntry(626,"st",2));	//Sao Tome and Principe (Democratic Republic of)
+		table.add(new MccEntry(627,"gq",2));	//Equatorial Guinea (Republic of)
+		table.add(new MccEntry(628,"ga",2));	//Gabonese Republic
+		table.add(new MccEntry(629,"cg",2));	//Congo (Republic of the)
+		table.add(new MccEntry(630,"cg",2));	//Democratic Republic of the Congo
+		table.add(new MccEntry(631,"ao",2));	//Angola (Republic of)
+		table.add(new MccEntry(632,"gw",2));	//Guinea-Bissau (Republic of)
+		table.add(new MccEntry(633,"sc",2));	//Seychelles (Republic of)
+		table.add(new MccEntry(634,"sd",2));	//Sudan (Republic of the)
+		table.add(new MccEntry(635,"rw",2));	//Rwanda (Republic of)
+		table.add(new MccEntry(636,"et",2));	//Ethiopia (Federal Democratic Republic of)
+		table.add(new MccEntry(637,"so",2));	//Somali Democratic Republic
+		table.add(new MccEntry(638,"dj",2));	//Djibouti (Republic of)
+		table.add(new MccEntry(639,"ke",2));	//Kenya (Republic of)
+		table.add(new MccEntry(640,"tz",2));	//Tanzania (United Republic of)
+		table.add(new MccEntry(641,"ug",2));	//Uganda (Republic of)
+		table.add(new MccEntry(642,"bi",2));	//Burundi (Republic of)
+		table.add(new MccEntry(643,"mz",2));	//Mozambique (Republic of)
+		table.add(new MccEntry(645,"zm",2));	//Zambia (Republic of)
+		table.add(new MccEntry(646,"mg",2));	//Madagascar (Republic of)
+		table.add(new MccEntry(647,"re",2));	//Reunion (French Department of)
+		table.add(new MccEntry(648,"zw",2));	//Zimbabwe (Republic of)
+		table.add(new MccEntry(649,"na",2));	//Namibia (Republic of)
+		table.add(new MccEntry(650,"mw",2));	//Malawi
+		table.add(new MccEntry(651,"ls",2));	//Lesotho (Kingdom of)
+		table.add(new MccEntry(652,"bw",2));	//Botswana (Republic of)
+		table.add(new MccEntry(653,"sz",2));	//Swaziland (Kingdom of)
+		table.add(new MccEntry(654,"km",2));	//Comoros (Union of the)
+		table.add(new MccEntry(655,"za",2,"en"));	//South Africa (Republic of)
+		table.add(new MccEntry(657,"er",2));	//Eritrea
+		table.add(new MccEntry(702,"bz",2));	//Belize
+		table.add(new MccEntry(704,"gt",2));	//Guatemala (Republic of)
+		table.add(new MccEntry(706,"sv",2));	//El Salvador (Republic of)
+		table.add(new MccEntry(708,"hn",3));	//Honduras (Republic of)
+		table.add(new MccEntry(710,"ni",2));	//Nicaragua
+		table.add(new MccEntry(712,"cr",2));	//Costa Rica
+		table.add(new MccEntry(714,"pa",2));	//Panama (Republic of)
+		table.add(new MccEntry(716,"pe",2));	//Peru
+		table.add(new MccEntry(722,"ar",3));	//Argentine Republic
+		table.add(new MccEntry(724,"br",2));	//Brazil (Federative Republic of)
+		table.add(new MccEntry(730,"cl",2));	//Chile
+		table.add(new MccEntry(732,"co",3));	//Colombia (Republic of)
+		table.add(new MccEntry(734,"ve",2));	//Venezuela (Bolivarian Republic of)
+		table.add(new MccEntry(736,"bo",2));	//Bolivia (Republic of)
+		table.add(new MccEntry(738,"gy",2));	//Guyana
+		table.add(new MccEntry(740,"ec",2));	//Ecuador
+		table.add(new MccEntry(742,"gf",2));	//French Guiana (French Department of)
+		table.add(new MccEntry(744,"py",2));	//Paraguay (Republic of)
+		table.add(new MccEntry(746,"sr",2));	//Suriname (Republic of)
+		table.add(new MccEntry(748,"uy",2));	//Uruguay (Eastern Republic of)
+		table.add(new MccEntry(750,"fk",2));	//Falkland Islands (Malvinas)
+        //table.add(new MccEntry(901,"",2));	//"International Mobile, shared code"
+
+        Collections.sort(table);
+    }
 }
diff --git a/telephony/java/com/android/internal/telephony/Phone.java b/telephony/java/com/android/internal/telephony/Phone.java
index 23325f6..769b2fc8 100644
--- a/telephony/java/com/android/internal/telephony/Phone.java
+++ b/telephony/java/com/android/internal/telephony/Phone.java
@@ -18,6 +18,7 @@
 
 import android.content.Context;
 import android.content.SharedPreferences;
+import android.net.NetworkProperties;
 import android.os.Handler;
 import android.os.Message;
 import android.preference.PreferenceManager;
@@ -99,8 +100,9 @@
     static final String PHONE_NAME_KEY = "phoneName";
     static final String FAILURE_REASON_KEY = "reason";
     static final String STATE_CHANGE_REASON_KEY = "reason";
-    static final String DATA_APN_TYPES_KEY = "apnType";
+    static final String DATA_APN_TYPE_KEY = "apnType";
     static final String DATA_APN_KEY = "apn";
+    static final String DATA_NETWORK_PROPERTIES_KEY = "dataProperties";
 
     static final String DATA_IFACE_NAME_KEY = "iface";
     static final String NETWORK_UNAVAILABLE_KEY = "networkUnvailable";
@@ -241,13 +243,21 @@
     CellLocation getCellLocation();
 
     /**
-     * Get the current DataState. No change notification exists at this
-     * interface -- use
+     * Get the current for the default apn DataState. No change notification
+     * exists at this interface -- use
      * {@link android.telephony.PhoneStateListener} instead.
      */
     DataState getDataConnectionState();
 
     /**
+     * Get the current DataState. No change notification exists at this
+     * interface -- use
+     * {@link android.telephony.PhoneStateListener} instead.
+     * @param apnType specify for which apn to get connection state info.
+     */
+    DataState getDataConnectionState(String apnType);
+
+    /**
      * Get the current DataActivityState. No change notification exists at this
      * interface -- use
      * {@link android.telephony.TelephonyManager} instead.
@@ -311,6 +321,11 @@
     String getActiveApn();
 
     /**
+     * Return the NetworkProperties for the named apn or null if not available
+     */
+    NetworkProperties getNetworkProperties(String apnType);
+
+    /**
      * Get current signal strength. No change notification available on this
      * interface. Use <code>PhoneStateNotifier</code> or an equivalent.
      * An ASU is 0-31 or -1 if unknown (for GSM, dBm = -113 - 2 * asu).
@@ -1533,6 +1548,11 @@
     boolean isOtaSpNumber(String dialStr);
 
     /**
+     * Returns true if OTA Service Provisioning needs to be performed.
+     */
+    boolean needsOtaServiceProvisioning();
+
+    /**
      * Register for notifications when CDMA call waiting comes
      *
      * @param h Handler that receives the notification message.
diff --git a/telephony/java/com/android/internal/telephony/PhoneBase.java b/telephony/java/com/android/internal/telephony/PhoneBase.java
index 74601e6..e5968a7 100644
--- a/telephony/java/com/android/internal/telephony/PhoneBase.java
+++ b/telephony/java/com/android/internal/telephony/PhoneBase.java
@@ -21,6 +21,7 @@
 import android.content.Context;
 import android.content.res.Configuration;
 import android.content.SharedPreferences;
+import android.net.NetworkProperties;
 import android.net.wifi.WifiManager;
 import android.os.AsyncResult;
 import android.os.Handler;
@@ -737,8 +738,13 @@
         mNotifier.notifyMessageWaitingChanged(this);
     }
 
-    public void notifyDataConnection(String reason) {
-        mNotifier.notifyDataConnection(this, reason);
+    public void notifyDataConnection(String reason, String apnType,
+            Phone.DataState state) {
+        mNotifier.notifyDataConnection(this, reason, apnType, state);
+    }
+
+    public void notifyDataConnection(String reason, String apnType) {
+        mNotifier.notifyDataConnection(this, reason, apnType);
     }
 
     public abstract String getPhoneName();
@@ -824,9 +830,19 @@
         logUnexpectedCdmaMethodCall("unregisterForSubscriptionInfoReady");
     }
 
+    /**
+     * Returns true if OTA Service Provisioning needs to be performed.
+     * If not overridden return false.
+     */
+    public boolean needsOtaServiceProvisioning() {
+        return false;
+    }
+
+    /**
+     * Return true if number is an OTASP number.
+     * If not overridden return false.
+     */
     public  boolean isOtaSpNumber(String dialStr) {
-        // This function should be overridden by the class CDMAPhone. Not implemented in GSMPhone.
-        logUnexpectedCdmaMethodCall("isOtaSpNumber");
         return false;
     }
 
@@ -940,6 +956,10 @@
         return mDataConnection.getActiveApnTypes();
     }
 
+    public NetworkProperties getNetworkProperties(String apnType) {
+        return mDataConnection.getNetworkProperties(apnType);
+    }
+
     public String getActiveApn() {
         return mDataConnection.getActiveApnString();
     }
@@ -952,6 +972,10 @@
         return mDataConnection.disableApnType(type);
     }
 
+    public boolean isDataConnectivityPossible() {
+        return ((mDataConnection != null) && (mDataConnection.isDataPossible()));
+    }
+
     /**
      * simulateDataConnection
      *
@@ -980,7 +1004,7 @@
         }
 
         mDataConnection.setState(dcState);
-        notifyDataConnection(null);
+        notifyDataConnection(null, Phone.APN_TYPE_DEFAULT);
     }
 
     /**
@@ -1026,4 +1050,8 @@
         Log.e(LOG_TAG, "Error! " + name + "() in PhoneBase should not be " +
                 "called, CDMAPhone inactive.");
     }
+
+    public DataState getDataConnectionState() {
+        return getDataConnectionState(APN_TYPE_DEFAULT);
+    }
 }
diff --git a/telephony/java/com/android/internal/telephony/PhoneNotifier.java b/telephony/java/com/android/internal/telephony/PhoneNotifier.java
index e96eeae..691271f 100644
--- a/telephony/java/com/android/internal/telephony/PhoneNotifier.java
+++ b/telephony/java/com/android/internal/telephony/PhoneNotifier.java
@@ -33,9 +33,12 @@
 
     public void notifyCallForwardingChanged(Phone sender);
 
-    public void notifyDataConnection(Phone sender, String reason);
+    public void notifyDataConnection(Phone sender, String reason, String apnType);
 
-    public void notifyDataConnectionFailed(Phone sender, String reason);
+    public void notifyDataConnection(Phone sender, String reason, String apnType,
+            Phone.DataState state);
+
+    public void notifyDataConnectionFailed(Phone sender, String reason, String apnType);
 
     public void notifyDataActivity(Phone sender);
 
diff --git a/telephony/java/com/android/internal/telephony/PhoneProxy.java b/telephony/java/com/android/internal/telephony/PhoneProxy.java
index e1511e6..d84859c 100644
--- a/telephony/java/com/android/internal/telephony/PhoneProxy.java
+++ b/telephony/java/com/android/internal/telephony/PhoneProxy.java
@@ -21,6 +21,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.SharedPreferences;
+import android.net.NetworkProperties;
 import android.os.Handler;
 import android.os.Message;
 import android.os.SystemProperties;
@@ -172,7 +173,11 @@
     }
 
     public DataState getDataConnectionState() {
-        return mActivePhone.getDataConnectionState();
+        return mActivePhone.getDataConnectionState(Phone.APN_TYPE_DEFAULT);
+    }
+
+    public DataState getDataConnectionState(String apnType) {
+        return mActivePhone.getDataConnectionState(apnType);
     }
 
     public DataActivityState getDataActivityState() {
@@ -207,6 +212,10 @@
         return mActivePhone.getActiveApnTypes();
     }
 
+    public NetworkProperties getNetworkProperties(String apnType) {
+        return mActivePhone.getNetworkProperties(apnType);
+    }
+
     public String getActiveApn() {
         return mActivePhone.getActiveApn();
     }
@@ -764,6 +773,10 @@
         mActivePhone.exitEmergencyCallbackMode();
     }
 
+    public boolean needsOtaServiceProvisioning(){
+        return mActivePhone.needsOtaServiceProvisioning();
+    }
+
     public boolean isOtaSpNumber(String dialStr){
         return mActivePhone.isOtaSpNumber(dialStr);
     }
diff --git a/telephony/java/com/android/internal/telephony/RIL.java b/telephony/java/com/android/internal/telephony/RIL.java
index 569ac5c..1a0dd89 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 ca526a5..9e17eb1 100644
--- a/telephony/java/com/android/internal/telephony/SMSDispatcher.java
+++ b/telephony/java/com/android/internal/telephony/SMSDispatcher.java
@@ -143,7 +143,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;
@@ -265,6 +265,7 @@
         mCm.unregisterForOn(this);
     }
 
+    @Override
     protected void finalize() {
         Log.d(TAG, "SMSDispatcher finalized");
     }
@@ -345,7 +346,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");
@@ -358,7 +359,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 {
@@ -372,7 +373,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");
@@ -679,7 +680,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>
@@ -689,7 +690,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
@@ -706,7 +707,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>
@@ -742,7 +743,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
@@ -758,17 +759,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
@@ -937,14 +938,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;
@@ -953,7 +954,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/CDMAPhone.java b/telephony/java/com/android/internal/telephony/cdma/CDMAPhone.java
index 27eae22..0c3c534 100755
--- a/telephony/java/com/android/internal/telephony/cdma/CDMAPhone.java
+++ b/telephony/java/com/android/internal/telephony/cdma/CDMAPhone.java
@@ -509,14 +509,6 @@
         return false;
     }
 
-    public boolean isDataConnectivityPossible() {
-        boolean noData = mDataConnection.getDataEnabled() &&
-                getDataConnectionState() == DataState.DISCONNECTED;
-        return !noData && getIccCard().getState() == IccCard.State.READY &&
-                getServiceState().getState() == ServiceState.STATE_IN_SERVICE &&
-                (mDataConnection.getDataOnRoamingEnabled() || !getServiceState().getRoaming());
-    }
-
     /**
      * Removes the given MMI from the pending list and notifies registrants that
      * it is complete.
@@ -607,7 +599,7 @@
         }
     }
 
-    public DataState getDataConnectionState() {
+    public DataState getDataConnectionState(String apnType) {
         DataState ret = DataState.DISCONNECTED;
 
         if (mSST == null) {
@@ -619,6 +611,8 @@
             // If we're out of service, open TCP sockets may still work
             // but no data will flow
             ret = DataState.DISCONNECTED;
+        } else if (mDataConnection.isApnTypeEnabled(apnType) == false) {
+            ret = DataState.DISCONNECTED;
         } else {
             switch (mDataConnection.getState()) {
                 case FAILED:
@@ -872,26 +866,6 @@
         mRuimRecords.setVoiceMessageWaiting(1, mwi);
     }
 
-    /**
-     * Returns true if CDMA OTA Service Provisioning needs to be performed.
-     */
-    /* package */ boolean
-    needsOtaServiceProvisioning() {
-        String cdmaMin = getCdmaMin();
-        boolean needsProvisioning;
-        if (cdmaMin == null || (cdmaMin.length() < 6)) {
-            if (DBG) Log.d(LOG_TAG, "needsOtaServiceProvisioning: illegal cdmaMin='"
-                                    + cdmaMin + "' assume provisioning needed.");
-            needsProvisioning = true;
-        } else {
-            needsProvisioning = (cdmaMin.equals(UNACTIVATED_MIN_VALUE)
-                    || cdmaMin.substring(0,6).equals(UNACTIVATED_MIN2_VALUE))
-                    || SystemProperties.getBoolean("test_cdma_setup", false);
-        }
-        if (DBG) Log.d(LOG_TAG, "needsOtaServiceProvisioning: ret=" + needsProvisioning);
-        return needsProvisioning;
-    }
-
     @Override
     public void exitEmergencyCallbackMode() {
         if (mWakeLock.isHeld()) {
@@ -1185,6 +1159,26 @@
         mSMS.setCellBroadcastConfig(configValuesArray, response);
     }
 
+    /**
+     * Returns true if OTA Service Provisioning needs to be performed.
+     */
+    @Override
+    public boolean needsOtaServiceProvisioning() {
+        String cdmaMin = getCdmaMin();
+        boolean needsProvisioning;
+        if (cdmaMin == null || (cdmaMin.length() < 6)) {
+            if (DBG) Log.d(LOG_TAG, "needsOtaServiceProvisioning: illegal cdmaMin='"
+                                    + cdmaMin + "' assume provisioning needed.");
+            needsProvisioning = true;
+        } else {
+            needsProvisioning = (cdmaMin.equals(UNACTIVATED_MIN_VALUE)
+                    || cdmaMin.substring(0,6).equals(UNACTIVATED_MIN2_VALUE))
+                    || SystemProperties.getBoolean("test_cdma_setup", false);
+        }
+        if (DBG) Log.d(LOG_TAG, "needsOtaServiceProvisioning: ret=" + needsProvisioning);
+        return needsProvisioning;
+    }
+
     private static final String IS683A_FEATURE_CODE = "*228";
     private static final int IS683A_FEATURE_CODE_NUM_DIGITS = 4;
     private static final int IS683A_SYS_SEL_CODE_NUM_DIGITS = 2;
diff --git a/telephony/java/com/android/internal/telephony/cdma/CdmaDataConnectionTracker.java b/telephony/java/com/android/internal/telephony/cdma/CdmaDataConnectionTracker.java
index 9f2a44b..8a3af3b 100644
--- a/telephony/java/com/android/internal/telephony/cdma/CdmaDataConnectionTracker.java
+++ b/telephony/java/com/android/internal/telephony/cdma/CdmaDataConnectionTracker.java
@@ -53,6 +53,7 @@
 import com.android.internal.telephony.RetryManager;
 import com.android.internal.telephony.ServiceStateTracker;
 
+import java.net.NetworkInterface;
 import java.util.ArrayList;
 
 /**
@@ -305,9 +306,40 @@
         return true;
     }
 
-    private boolean isDataAllowed() {
-        boolean roaming = phone.getServiceState().getRoaming();
-        return getAnyDataEnabled() && (!roaming || getDataOnRoamingEnabled()) && mMasterDataEnabled;
+    protected boolean isDataAllowed() {
+        int psState = mCdmaPhone.mSST.getCurrentCdmaDataConnectionState();
+        boolean roaming = (phone.getServiceState().getRoaming() && !getDataOnRoamingEnabled());
+        boolean desiredPowerState = mCdmaPhone.mSST.getDesiredPowerState();
+
+        boolean allowed = (psState == ServiceState.STATE_IN_SERVICE &&
+                (phone.mCM.getRadioState() == CommandsInterface.RadioState.NV_READY ||
+                 mCdmaPhone.mRuimRecords.getRecordsLoaded()) &&
+                (mCdmaPhone.mSST.isConcurrentVoiceAndData() ||
+                 phone.getState() == Phone.State.IDLE) &&
+                !roaming &&
+                mMasterDataEnabled &&
+                desiredPowerState &&
+                !mPendingRestartRadio &&
+                !mCdmaPhone.needsOtaServiceProvisioning());
+        if (!allowed && DBG) {
+            String reason = "";
+            if (psState != ServiceState.STATE_IN_SERVICE) reason += " - psState= " + psState;
+            if (phone.mCM.getRadioState() != CommandsInterface.RadioState.NV_READY &&
+                    !mCdmaPhone.mRuimRecords.getRecordsLoaded()) {
+                reason += " - radioState= " + phone.mCM.getRadioState() + " - RUIM not loaded";
+            }
+            if (phone.getState() != Phone.State.IDLE &&
+                    mCdmaPhone.mSST.isConcurrentVoiceAndData()) {
+                reason += " - concurrentVoiceAndData not allowed and state= " + phone.getState();
+            }
+            if (roaming) reason += " - Roaming";
+            if (!mMasterDataEnabled) reason += " - mMasterDataEnabled= false";
+            if (!desiredPowerState) reason += " - desiredPowerState= false";
+            if (mPendingRestartRadio) reason += " - mPendingRestartRadio= true";
+            if (mCdmaPhone.needsOtaServiceProvisioning()) reason += " - needs Provisioning";
+            log("Data not allowed due to" + reason);
+        }
+        return allowed;
     }
 
     private boolean trySetupData(String reason) {
@@ -317,7 +349,8 @@
             // Assume data is connected on the simulator
             // FIXME  this can be improved
             setState(State.CONNECTED);
-            phone.notifyDataConnection(reason);
+            notifyDataConnection(reason);
+            notifyOffApnsOfAvailability(reason, true);
 
             Log.i(LOG_TAG, "(fix?) We're on the simulator; assuming data is connected");
             return true;
@@ -327,36 +360,13 @@
         boolean roaming = phone.getServiceState().getRoaming();
         boolean desiredPowerState = mCdmaPhone.mSST.getDesiredPowerState();
 
-        if ((state == State.IDLE || state == State.SCANNING)
-                && (psState == ServiceState.STATE_IN_SERVICE)
-                && ((phone.mCM.getRadioState() == CommandsInterface.RadioState.NV_READY) ||
-                        mCdmaPhone.mRuimRecords.getRecordsLoaded())
-                && (mCdmaPhone.mSST.isConcurrentVoiceAndData() ||
-                        phone.getState() == Phone.State.IDLE )
-                && isDataAllowed()
-                && desiredPowerState
-                && !mPendingRestartRadio
-                && !mCdmaPhone.needsOtaServiceProvisioning()) {
-
-            return setupData(reason);
-
+        if ((state == State.IDLE || state == State.SCANNING) &&
+                isDataAllowed() && getAnyDataEnabled()) {
+            boolean retValue = setupData(reason);
+            notifyOffApnsOfAvailability(reason, retValue);
+            return retValue;
         } else {
-            if (DBG) {
-                    log("trySetupData: Not ready for data: " +
-                    " dataState=" + state +
-                    " PS state=" + psState +
-                    " radio state=" + phone.mCM.getRadioState() +
-                    " ruim=" + mCdmaPhone.mRuimRecords.getRecordsLoaded() +
-                    " concurrentVoice&Data=" + mCdmaPhone.mSST.isConcurrentVoiceAndData() +
-                    " phoneState=" + phone.getState() +
-                    " dataEnabled=" + getAnyDataEnabled() +
-                    " roaming=" + roaming +
-                    " dataOnRoamingEnable=" + getDataOnRoamingEnabled() +
-                    " desiredPowerState=" + desiredPowerState +
-                    " PendingRestartRadio=" + mPendingRestartRadio +
-                    " MasterDataEnabled=" + mMasterDataEnabled +
-                    " needsOtaServiceProvisioning=" + mCdmaPhone.needsOtaServiceProvisioning());
-            }
+            notifyOffApnsOfAvailability(reason, false);
             return false;
         }
     }
@@ -382,6 +392,7 @@
         }
 
         setState(State.DISCONNECTING);
+        notifyDataAvailability(reason);
 
         boolean notificationDeferred = false;
         for (DataConnection conn : dataConnectionList) {
@@ -440,13 +451,13 @@
         conn.connect(msg, mActiveApn);
 
         setState(State.INITING);
-        phone.notifyDataConnection(reason);
+        notifyDataConnection(reason);
         return true;
     }
 
     private void notifyDefaultData(String reason) {
         setState(State.CONNECTED);
-        phone.notifyDataConnection(reason);
+        notifyDataConnection(reason);
         startNetStatPoll();
         mRetryMgr.resetRetryCount();
     }
@@ -622,12 +633,13 @@
 
     private void notifyNoData(FailCause lastFailCauseCode) {
         setState(State.FAILED);
+        notifyDataAvailability(null);
     }
 
     private void gotoIdleAndNotifyDataConnection(String reason) {
         if (DBG) log("gotoIdleAndNotifyDataConnection: reason=" + reason);
         setState(State.IDLE);
-        phone.notifyDataConnection(reason);
+        notifyDataConnection(reason);
         mActiveApn = null;
     }
 
@@ -687,11 +699,13 @@
             // Assume data is connected on the simulator
             // FIXME  this can be improved
             setState(State.CONNECTED);
-            phone.notifyDataConnection(null);
+            notifyDataConnection(null);
 
             Log.i(LOG_TAG, "We're on the simulator; assuming data is connected");
         }
 
+        notifyDataAvailability(null);
+
         if (state != State.IDLE) {
             cleanUpConnection(true, null);
         }
@@ -723,6 +737,8 @@
         }
 
         if (ar.exception == null) {
+            mNetworkProperties = makeNetworkProperties(mActiveDataConnection);
+
             // everything is setup
             notifyDefaultData(reason);
         } else {
@@ -762,7 +778,7 @@
             onRestartRadio();
         }
 
-        phone.notifyDataConnection(reason);
+        notifyDataConnection(reason);
         mActiveApn = null;
         if (retryAfterDisconnected(reason)) {
           trySetupData(reason);
@@ -789,7 +805,8 @@
     protected void onVoiceCallStarted() {
         if (state == State.CONNECTED && !mCdmaPhone.mSST.isConcurrentVoiceAndData()) {
             stopNetStatPoll();
-            phone.notifyDataConnection(Phone.REASON_VOICE_CALL_STARTED);
+            notifyDataConnection(Phone.REASON_VOICE_CALL_STARTED);
+            notifyDataAvailability(Phone.REASON_VOICE_CALL_STARTED);
         }
     }
 
@@ -800,11 +817,12 @@
         if (state == State.CONNECTED) {
             if (!mCdmaPhone.mSST.isConcurrentVoiceAndData()) {
                 startNetStatPoll();
-                phone.notifyDataConnection(Phone.REASON_VOICE_CALL_ENDED);
+                notifyDataConnection(Phone.REASON_VOICE_CALL_ENDED);
             } else {
                 // clean slate after call end.
                 resetPollStats();
             }
+            notifyDataAvailability(Phone.REASON_VOICE_CALL_ENDED);
         } else {
             mRetryMgr.resetRetryCount();
             // in case data setup was attempted when we were on a voice call
@@ -838,7 +856,7 @@
     private void onCdmaDataDetached() {
         if (state == State.CONNECTED) {
             startNetStatPoll();
-            phone.notifyDataConnection(Phone.REASON_CDMA_DATA_DETACHED);
+            notifyDataConnection(Phone.REASON_CDMA_DATA_DETACHED);
         } else {
             if (state == State.FAILED) {
                 cleanUpConnection(false, Phone.REASON_CDMA_DATA_DETACHED);
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/CdmaServiceStateTracker.java b/telephony/java/com/android/internal/telephony/cdma/CdmaServiceStateTracker.java
index 2cad6cc..5d4dc58 100644
--- a/telephony/java/com/android/internal/telephony/cdma/CdmaServiceStateTracker.java
+++ b/telephony/java/com/android/internal/telephony/cdma/CdmaServiceStateTracker.java
@@ -1119,7 +1119,7 @@
         }
 
         if (hasCdmaDataConnectionChanged || hasNetworkTypeChanged) {
-            phone.notifyDataConnection(null);
+            phone.notifyDataConnection(null, null);
         }
 
         if (hasRoamingOn) {
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 b50502c..65d87f5 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/GSMPhone.java b/telephony/java/com/android/internal/telephony/gsm/GSMPhone.java
index 5f093d4..876c223 100644
--- a/telephony/java/com/android/internal/telephony/gsm/GSMPhone.java
+++ b/telephony/java/com/android/internal/telephony/gsm/GSMPhone.java
@@ -291,7 +291,7 @@
         return mPendingMMIs;
     }
 
-    public DataState getDataConnectionState() {
+    public DataState getDataConnectionState(String apnType) {
         DataState ret = DataState.DISCONNECTED;
 
         if (mSST == null) {
@@ -304,6 +304,8 @@
             // If we're out of service, open TCP sockets may still work
             // but no data will flow
             ret = DataState.DISCONNECTED;
+        } else if (mDataConnection.isApnTypeEnabled(apnType) == false) {
+            ret = DataState.DISCONNECTED;
         } else { /* mSST.gprsState == ServiceState.STATE_IN_SERVICE */
             switch (mDataConnection.getState()) {
                 case FAILED:
@@ -405,8 +407,8 @@
     }
 
     /*package*/ void
-    notifyDataConnectionFailed(String reason) {
-        mNotifier.notifyDataConnectionFailed(this, reason);
+    notifyDataConnectionFailed(String reason, String apnType) {
+        mNotifier.notifyDataConnectionFailed(this, reason, apnType);
     }
 
     /*package*/ void
@@ -1093,27 +1095,6 @@
     }
 
     /**
-     * The only circumstances under which we report that data connectivity is not
-     * possible are
-     * <ul>
-     * <li>Data roaming is disallowed and we are roaming.</li>
-     * <li>The current data state is {@code DISCONNECTED} for a reason other than
-     * having explicitly disabled connectivity. In other words, data is not available
-     * because the phone is out of coverage or some like reason.</li>
-     * </ul>
-     * @return {@code true} if data connectivity is possible, {@code false} otherwise.
-     */
-    public boolean isDataConnectivityPossible() {
-        // TODO: Currently checks if any GPRS connection is active. Should it only
-        // check for "default"?
-        boolean noData = mDataConnection.getDataEnabled() &&
-            getDataConnectionState() == DataState.DISCONNECTED;
-        return !noData && getIccCard().getState() == SimCard.State.READY &&
-                getServiceState().getState() == ServiceState.STATE_IN_SERVICE &&
-            (mDataConnection.getDataOnRoamingEnabled() || !getServiceState().getRoaming());
-    }
-
-    /**
      * Removes the given MMI from the pending list and notifies
      * registrants that it is complete.
      * @param mmi MMI that is done
diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java b/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java
index f6d4491..c76da80 100644
--- a/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java
+++ b/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java
@@ -30,6 +30,8 @@
 import android.net.ConnectivityManager;
 import android.net.IConnectivityManager;
 import android.net.NetworkInfo;
+import android.net.NetworkProperties;
+import android.net.ProxyProperties;
 import android.net.TrafficStats;
 import android.net.Uri;
 import android.net.wifi.WifiManager;
@@ -58,6 +60,9 @@
 import com.android.internal.telephony.DataConnection.FailCause;
 
 import java.io.IOException;
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.net.UnknownHostException;
 import java.util.ArrayList;
 
 /**
@@ -388,12 +393,6 @@
         return pdps;
     }
 
-    private boolean isDataAllowed() {
-        boolean roaming = phone.getServiceState().getRoaming();
-        return getAnyDataEnabled() && (!roaming || getDataOnRoamingEnabled()) &&
-                mMasterDataEnabled;
-    }
-
     //****** Called from ServiceStateTracker
     /**
      * Invoked when ServiceStateTracker observes a transition from GPRS
@@ -405,13 +404,13 @@
          * when GPRS detaches, but we should stop the network polling.
          */
         stopNetStatPoll();
-        phone.notifyDataConnection(Phone.REASON_GPRS_DETACHED);
+        notifyDataConnection(Phone.REASON_GPRS_DETACHED);
     }
 
     private void onGprsAttached() {
         if (state == State.CONNECTED) {
             startNetStatPoll();
-            phone.notifyDataConnection(Phone.REASON_GPRS_ATTACHED);
+            notifyDataConnection(Phone.REASON_GPRS_ATTACHED);
         } else {
             if (state == State.FAILED) {
                 cleanUpConnection(false, Phone.REASON_GPRS_ATTACHED);
@@ -421,6 +420,35 @@
         }
     }
 
+    protected boolean isDataAllowed() {
+        int gprsState = mGsmPhone.mSST.getCurrentGprsState();
+        boolean desiredPowerState = mGsmPhone.mSST.getDesiredPowerState();
+
+        boolean allowed = ((gprsState == ServiceState.STATE_IN_SERVICE || noAutoAttach) &&
+                mGsmPhone.mSIMRecords.getRecordsLoaded() &&
+                phone.getState() == Phone.State.IDLE &&
+                mMasterDataEnabled &&
+                (!phone.getServiceState().getRoaming() || getDataOnRoamingEnabled()) &&
+                !mIsPsRestricted &&
+                desiredPowerState);
+        if (!allowed && DBG) {
+            String reason = "";
+            if (gprsState != ServiceState.STATE_IN_SERVICE) reason += " - gprs= " + gprsState;
+            if (!mGsmPhone.mSIMRecords.getRecordsLoaded()) reason += " - SIM not loaded";
+            if (phone.getState() != Phone.State.IDLE) {
+                reason += " - PhoneState= " + phone.getState();
+            }
+            if (!mMasterDataEnabled) reason += " - mMasterDataEnabled= false";
+            if (phone.getServiceState().getRoaming() && getDataOnRoamingEnabled()) {
+                reason += " - Roaming";
+            }
+            if (mIsPsRestricted) reason += " - mIsPsRestricted= true";
+            if (!desiredPowerState) reason += " - desiredPowerState= false";
+            log("Data not allowed due to" + reason);
+        }
+        return allowed;
+    }
+
     private boolean trySetupData(String reason) {
         if (DBG) log("***trySetupData due to " + (reason == null ? "(unspecified)" : reason));
 
@@ -430,7 +458,7 @@
             // Assume data is connected on the simulator
             // FIXME  this can be improved
             setState(State.CONNECTED);
-            phone.notifyDataConnection(reason);
+            notifyDataConnection(reason);
 
             Log.i(LOG_TAG, "(fix?) We're on the simulator; assuming data is connected");
             return true;
@@ -439,19 +467,15 @@
         int gprsState = mGsmPhone.mSST.getCurrentGprsState();
         boolean desiredPowerState = mGsmPhone.mSST.getDesiredPowerState();
 
-        if ((state == State.IDLE || state == State.SCANNING)
-                && (gprsState == ServiceState.STATE_IN_SERVICE || noAutoAttach)
-                && mGsmPhone.mSIMRecords.getRecordsLoaded()
-                && phone.getState() == Phone.State.IDLE
-                && isDataAllowed()
-                && !mIsPsRestricted
-                && desiredPowerState ) {
+        if (((state == State.IDLE) || (state == State.SCANNING)) &&
+                isDataAllowed() && getAnyDataEnabled()) {
 
             if (state == State.IDLE) {
                 waitingApns = buildWaitingApns();
                 if (waitingApns.isEmpty()) {
                     if (DBG) log("No APN found");
                     notifyNoData(GsmDataConnection.FailCause.MISSING_UNKNOWN_APN);
+                    notifyOffApnsOfAvailability(reason, false);
                     return false;
                 } else {
                     log ("Create from allApns : " + apnListToString(allApns));
@@ -461,22 +485,11 @@
             if (DBG) {
                 log ("Setup waitngApns : " + apnListToString(waitingApns));
             }
-            return setupData(reason);
+            boolean retValue = setupData(reason);
+            notifyOffApnsOfAvailability(reason, retValue);
+            return retValue;
         } else {
-            if (DBG)
-                log("trySetupData: Not ready for data: " +
-                    " dataState=" + state +
-                    " gprsState=" + gprsState +
-                    " sim=" + mGsmPhone.mSIMRecords.getRecordsLoaded() +
-                    " UMTS=" + mGsmPhone.mSST.isConcurrentVoiceAndData() +
-                    " phoneState=" + phone.getState() +
-                    " isDataAllowed=" + isDataAllowed() +
-                    " dataEnabled=" + getAnyDataEnabled() +
-                    " roaming=" + phone.getServiceState().getRoaming() +
-                    " dataOnRoamingEnable=" + getDataOnRoamingEnabled() +
-                    " ps restricted=" + mIsPsRestricted +
-                    " desiredPowerState=" + desiredPowerState +
-                    " MasterDataEnabled=" + mMasterDataEnabled);
+            notifyOffApnsOfAvailability(reason, false);
             return false;
         }
     }
@@ -595,7 +608,7 @@
         pdp.connect(msg, apn);
 
         setState(State.INITING);
-        phone.notifyDataConnection(reason);
+        notifyDataConnection(reason);
         return true;
     }
 
@@ -746,7 +759,7 @@
 
     private void notifyDefaultData(String reason) {
         setState(State.CONNECTED);
-        phone.notifyDataConnection(reason);
+        notifyDataConnection(reason);
         startNetStatPoll();
         // reset reconnect timer
         mRetryMgr.resetRetryCount();
@@ -756,7 +769,7 @@
     private void gotoIdleAndNotifyDataConnection(String reason) {
         if (DBG) log("gotoIdleAndNotifyDataConnection: reason=" + reason);
         setState(State.IDLE);
-        phone.notifyDataConnection(reason);
+        notifyDataConnection(reason);
         mActiveApn = null;
     }
 
@@ -998,7 +1011,7 @@
             if (!mRetryMgr.isRetryNeeded()) {
                 if (!mRequestedApnType.equals(Phone.APN_TYPE_DEFAULT)) {
                     // if no more retries on a secondary APN attempt, tell the world and revert.
-                    phone.notifyDataConnection(Phone.REASON_APN_FAILED);
+                    notifyDataConnection(Phone.REASON_APN_FAILED);
                     onEnableApn(apnTypeToId(mRequestedApnType), DISABLED);
                     return;
                 }
@@ -1091,7 +1104,7 @@
             // Assume data is connected on the simulator
             // FIXME  this can be improved
             setState(State.CONNECTED);
-            phone.notifyDataConnection(null);
+            notifyDataConnection(null);
 
             Log.i(LOG_TAG, "We're on the simulator; assuming data is connected");
         }
@@ -1125,6 +1138,25 @@
         }
 
         if (ar.exception == null) {
+            mNetworkProperties = makeNetworkProperties(mActivePdp);
+
+            ApnSetting apn = mActivePdp.getApn();
+            if (apn.proxy != null && apn.proxy.length() != 0) {
+                try {
+                    ProxyProperties proxy = new ProxyProperties();
+                    proxy.setAddress(InetAddress.getByName(apn.proxy));
+                    proxy.setPort(Integer.parseInt(apn.port));
+                    mNetworkProperties.setHttpProxy(proxy);
+                } catch (UnknownHostException e) {
+                    Log.e(LOG_TAG, "UnknownHostException making ProxyProperties: " + e);
+                } catch (SecurityException e) {
+                    Log.e(LOG_TAG, "SecurityException making ProxyProperties: " + e);
+                } catch (NumberFormatException e) {
+                    Log.e(LOG_TAG, "NumberFormatException making ProxyProperties (" + apn.port +
+                            "): " + e);
+                }
+            }
+
             // everything is setup
             if (isApnTypeActive(Phone.APN_TYPE_DEFAULT)) {
                 SystemProperties.set("gsm.defaultpdpcontext.active", "true");
@@ -1158,7 +1190,7 @@
             if (cause.isPermanentFail()) {
                 notifyNoData(cause);
                 if (!mRequestedApnType.equals(Phone.APN_TYPE_DEFAULT)) {
-                    phone.notifyDataConnection(Phone.REASON_APN_FAILED);
+                    notifyDataConnection(Phone.REASON_APN_FAILED);
                     onEnableApn(apnTypeToId(mRequestedApnType), DISABLED);
                 }
                 return;
@@ -1188,7 +1220,7 @@
            reason = (String) ar.userObj;
         }
         setState(State.IDLE);
-        phone.notifyDataConnection(reason);
+        notifyDataConnection(reason);
         mActiveApn = null;
         if (retryAfterDisconnected(reason)) {
             trySetupData(reason);
@@ -1219,7 +1251,7 @@
     protected void onVoiceCallStarted() {
         if (state == State.CONNECTED && ! mGsmPhone.mSST.isConcurrentVoiceAndData()) {
             stopNetStatPoll();
-            phone.notifyDataConnection(Phone.REASON_VOICE_CALL_STARTED);
+            notifyDataConnection(Phone.REASON_VOICE_CALL_STARTED);
         }
     }
 
@@ -1227,7 +1259,7 @@
         if (state == State.CONNECTED) {
             if (!mGsmPhone.mSST.isConcurrentVoiceAndData()) {
                 startNetStatPoll();
-                phone.notifyDataConnection(Phone.REASON_VOICE_CALL_ENDED);
+                notifyDataConnection(Phone.REASON_VOICE_CALL_ENDED);
             } else {
                 // clean slate after call end.
                 resetPollStats();
diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java b/telephony/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java
index d720516..f09ff06 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.
@@ -152,6 +154,7 @@
     }
 
     /** {@inheritDoc} */
+    @Override
     protected void sendData(String destAddr, String scAddr, int destPort,
             byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent) {
         SmsMessage.SubmitPdu pdu = SmsMessage.getSubmitPdu(
@@ -160,6 +163,7 @@
     }
 
     /** {@inheritDoc} */
+    @Override
     protected void sendText(String destAddr, String scAddr, String text,
             PendingIntent sentIntent, PendingIntent deliveryIntent) {
         SmsMessage.SubmitPdu pdu = SmsMessage.getSubmitPdu(
@@ -168,6 +172,7 @@
     }
 
     /** {@inheritDoc} */
+    @Override
     protected void sendMultipartText(String destinationAddress, String scAddress,
             ArrayList<String> parts, ArrayList<PendingIntent> sentIntents,
             ArrayList<PendingIntent> deliveryIntents) {
@@ -307,8 +312,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");
@@ -323,12 +329,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");
@@ -343,6 +350,7 @@
     }
 
     /** {@inheritDoc} */
+    @Override
     protected void acknowledgeLastIncomingSms(boolean success, int result, Message response){
         // FIXME unit test leaves cm == null. this should change
         if (mCm != null) {
@@ -351,6 +359,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.");
@@ -358,6 +367,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.");
@@ -365,6 +375,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/GsmServiceStateTracker.java b/telephony/java/com/android/internal/telephony/gsm/GsmServiceStateTracker.java
index 90ecbd7..b639eea 100644
--- a/telephony/java/com/android/internal/telephony/gsm/GsmServiceStateTracker.java
+++ b/telephony/java/com/android/internal/telephony/gsm/GsmServiceStateTracker.java
@@ -1018,7 +1018,8 @@
         }
 
         if (hasNetworkTypeChanged) {
-            phone.notifyDataConnection(null);
+            // TODO - do we really want this?
+            phone.notifyDataConnection(null, null);
         }
 
         if (hasRoamingOn) {
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 4fd62fb..278e1ba 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;
@@ -311,7 +310,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;
@@ -377,7 +376,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.
@@ -482,7 +481,7 @@
         return bo;
     }
 
-    static class PduParser {
+    private static class PduParser {
         byte pdu[];
         int cur;
         SmsHeader userDataHeader;
@@ -490,10 +489,6 @@
         int mUserDataSeptetPadding;
         int mUserDataSize;
 
-        PduParser(String s) {
-            this(IccUtils.hexStringToBytes(s));
-        }
-
         PduParser(byte[] pdu) {
             this.pdu = pdu;
             cur = 0;
@@ -545,7 +540,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;
@@ -573,7 +568,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)
 
@@ -632,7 +627,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 {
@@ -671,10 +666,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() {
@@ -694,7 +689,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++];
@@ -825,11 +820,13 @@
     }
 
     /** {@inheritDoc} */
+    @Override
     public int getProtocolIdentifier() {
         return protocolIdentifier;
     }
 
     /** {@inheritDoc} */
+    @Override
     public boolean isReplace() {
         return (protocolIdentifier & 0xc0) == 0x40
                 && (protocolIdentifier & 0x3f) > 0
@@ -837,12 +834,14 @@
     }
 
     /** {@inheritDoc} */
+    @Override
     public boolean isCphsMwiMessage() {
         return ((GsmSmsAddress) originatingAddress).isCphsVoiceMessageClear()
                 || ((GsmSmsAddress) originatingAddress).isCphsVoiceMessageSet();
     }
 
     /** {@inheritDoc} */
+    @Override
     public boolean isMWIClearMessage() {
         if (isMwi && (mwiSense == false)) {
             return true;
@@ -853,6 +852,7 @@
     }
 
     /** {@inheritDoc} */
+    @Override
     public boolean isMWISetMessage() {
         if (isMwi && (mwiSense == true)) {
             return true;
@@ -863,6 +863,7 @@
     }
 
     /** {@inheritDoc} */
+    @Override
     public boolean isMwiDontStore() {
         if (isMwi && mwiDontStore) {
             return true;
@@ -882,31 +883,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);
@@ -1160,6 +1164,7 @@
     /**
      * {@inheritDoc}
      */
+    @Override
     public MessageClass getMessageClass() {
         return messageClass;
     }
diff --git a/telephony/java/com/android/internal/telephony/gsm/UsimPhoneBookManager.java b/telephony/java/com/android/internal/telephony/gsm/UsimPhoneBookManager.java
index b642541..41f3b23 100755
--- a/telephony/java/com/android/internal/telephony/gsm/UsimPhoneBookManager.java
+++ b/telephony/java/com/android/internal/telephony/gsm/UsimPhoneBookManager.java
@@ -27,8 +27,6 @@
 import com.android.internal.telephony.IccUtils;
 import com.android.internal.telephony.PhoneBase;
 
-import org.apache.harmony.luni.lang.reflect.ListOfTypes;
-
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Map;
diff --git a/telephony/java/com/android/internal/telephony/test/SimulatedCommands.java b/telephony/java/com/android/internal/telephony/test/SimulatedCommands.java
index a120f52..fdcf78d 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.UUSInfo;
 import com.android.internal.telephony.gsm.CallFailCause;
@@ -335,7 +333,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
@@ -441,7 +439,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
      */
@@ -468,7 +466,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) {
@@ -479,7 +477,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"
@@ -496,7 +494,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"
@@ -513,7 +511,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) {
@@ -524,7 +522,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) {
@@ -535,7 +533,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) {
@@ -547,7 +545,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
@@ -572,7 +570,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) {
@@ -593,7 +591,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) {
@@ -614,7 +612,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) {
@@ -634,7 +632,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) {
@@ -654,7 +652,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) {
@@ -690,7 +688,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) {
@@ -708,7 +706,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) {
@@ -785,7 +783,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];
@@ -844,7 +842,6 @@
         ret[11] = null;
         ret[12] = null;
         ret[13] = null;
-        ret[14] = null;
 
         resultSuccess(result, ret);
     }
@@ -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 sendDtmf(char c, 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 startDtmf(char c, Message result) {
@@ -913,7 +910,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) {
@@ -922,7 +919,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) {
@@ -957,6 +954,7 @@
         unimplemented(response);
     }
 
+    @Deprecated
     public void setupDefaultPDP(String apn, String user, String password, Message result) {
         unimplemented(result);
     }
@@ -968,9 +966,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) {
@@ -1047,7 +1043,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/mockril/Android.mk b/telephony/mockril/Android.mk
new file mode 100644
index 0000000..95ae84c
--- /dev/null
+++ b/telephony/mockril/Android.mk
@@ -0,0 +1,30 @@
+#
+# Copyright (C) 2010 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+#  You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+#
+
+LOCAL_PATH:=$(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_JAVA_LIBRARIES := core framework
+
+LOCAL_STATIC_JAVA_LIBRARIES := librilproto-java
+
+LOCAL_MODULE := mockrilcontroller
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/telephony/mockril/src/com/android/internal/telephony/mockril/MockRilController.java b/telephony/mockril/src/com/android/internal/telephony/mockril/MockRilController.java
new file mode 100644
index 0000000..0bf321e
--- /dev/null
+++ b/telephony/mockril/src/com/android/internal/telephony/mockril/MockRilController.java
@@ -0,0 +1,107 @@
+/*
+ * 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.internal.telephony.mockril;
+
+import android.os.Bundle;
+import android.util.Log;
+
+import com.android.internal.communication.MsgHeader;
+import com.android.internal.communication.Msg;
+import com.android.internal.telephony.RilChannel;
+import com.android.internal.telephony.ril_proto.RilCtrlCmds;
+import com.android.internal.telephony.ril_proto.RilCmds;
+import com.google.protobuf.micro.MessageMicro;
+
+import java.io.IOException;
+
+/**
+ * Contain a list of commands to control Mock RIL. Before using these commands the devices
+ * needs to be set with Mock RIL. Refer to hardware/ril/mockril/README.txt for details.
+ *
+ */
+public class MockRilController {
+    private static final String TAG = "MockRILController";
+    private RilChannel mRilChannel = null;
+    private Msg mMessage = null;
+
+    public MockRilController() throws IOException {
+        mRilChannel = RilChannel.makeRilChannel();
+    }
+
+    /**
+     * Close the channel after the communication is done.
+     * This method has to be called after the test is finished.
+     */
+    public void closeChannel() {
+        mRilChannel.close();
+    }
+
+    /**
+     * Send commands and return true on success
+     * @param cmd for MsgHeader
+     * @param token for MsgHeader
+     * @param status for MsgHeader
+     * @param pbData for Msg data
+     * @return true if command is sent successfully, false if it fails
+     */
+    private boolean sendCtrlCommand(int cmd, long token, int status, MessageMicro pbData) {
+        try {
+            Msg.send(mRilChannel, cmd, token, status, pbData);
+        } catch (IOException e) {
+            Log.v(TAG, "send command : %d failed: " + e.getStackTrace());
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Get control response
+     * @return Msg if response is received, else return null.
+     */
+    private Msg getCtrlResponse() {
+        Msg response = null;
+        try {
+            response = Msg.recv(mRilChannel);
+        } catch (IOException e) {
+            Log.v(TAG, "receive response for getRadioState() error: " + e.getStackTrace());
+            return null;
+        }
+        return response;
+    }
+
+    /**
+     * @return the radio state if it is valid, otherwise return -1
+     */
+    public int getRadioState() {
+        if (!sendCtrlCommand(RilCtrlCmds.CTRL_CMD_GET_RADIO_STATE, 0, 0, null)) {
+            return -1;
+        }
+        Msg response = getCtrlResponse();
+        if (response == null) {
+            Log.v(TAG, "failed to get response");
+            return -1;
+        }
+        response.printHeader(TAG);
+        RilCtrlCmds.CtrlRspRadioState resp =
+            response.getDataAs(RilCtrlCmds.CtrlRspRadioState.class);
+        int state = resp.getState();
+        if ((state >= RilCmds.RADIOSTATE_OFF) && (state <= RilCmds.RADIOSTATE_NV_READY))
+            return state;
+        else
+            return -1;
+    }
+}
diff --git a/telephony/tests/telephonymockriltests/Android.mk b/telephony/tests/telephonymockriltests/Android.mk
new file mode 100644
index 0000000..9731d0d
--- /dev/null
+++ b/telephony/tests/telephonymockriltests/Android.mk
@@ -0,0 +1,14 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_STATIC_JAVA_LIBRARIES := mockrilcontroller
+
+LOCAL_JAVA_LIBRARIES := android.test.runner
+
+LOCAL_PACKAGE_NAME := TelephonyMockRilTests
+
+include $(BUILD_PACKAGE)
diff --git a/telephony/tests/telephonymockriltests/AndroidManifest.xml b/telephony/tests/telephonymockriltests/AndroidManifest.xml
new file mode 100644
index 0000000..63f44a2
--- /dev/null
+++ b/telephony/tests/telephonymockriltests/AndroidManifest.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.telephonymockriltests">
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+        <activity android:label="TelephonyMockRilTest"
+                android:name="TelephonyMockRilTest">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+    </application>
+
+    <instrumentation android:name=".TelephonyMockTestRunner"
+        android:targetPackage="com.android.telephonymockriltests"
+        android:label="Test runner for Telephony Tests Using Mock RIL"
+    />
+
+    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
+    <uses-permission android:name="android.permission.INTERNET" />
+
+</manifest>
diff --git a/telephony/tests/telephonymockriltests/src/com/android/telephonymockriltests/TelephonyMockTestRunner.java b/telephony/tests/telephonymockriltests/src/com/android/telephonymockriltests/TelephonyMockTestRunner.java
new file mode 100644
index 0000000..78ee738
--- /dev/null
+++ b/telephony/tests/telephonymockriltests/src/com/android/telephonymockriltests/TelephonyMockTestRunner.java
@@ -0,0 +1,64 @@
+/*
+ * 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.telephonymockriltests;
+
+import android.os.Bundle;
+import android.test.InstrumentationTestRunner;
+import android.test.InstrumentationTestSuite;
+import com.android.internal.telephony.mockril.MockRilController;
+import android.util.Log;
+
+import com.android.telephonymockriltests.functional.SimpleTestUsingMockRil;
+
+import java.io.IOException;
+import junit.framework.TestSuite;
+import junit.framework.TestCase;
+
+/**
+ * Test runner for telephony tests that using Mock RIL
+ *
+ */
+public class TelephonyMockTestRunner extends InstrumentationTestRunner {
+    private static final String TAG="TelephonyMockTestRunner";
+    public MockRilController mController;
+
+    @Override
+    public TestSuite getAllTests() {
+        TestSuite suite = new InstrumentationTestSuite(this);
+        suite.addTestSuite(SimpleTestUsingMockRil.class);
+        return suite;
+    }
+
+    @Override
+    public void onCreate(Bundle icicle) {
+        try {
+            mController = new MockRilController();
+        } catch (IOException e) {
+            e.printStackTrace();
+            TestCase.assertTrue("Create Mock RIl Controller failed", false);
+        }
+        TestCase.assertNotNull(mController);
+        super.onCreate(icicle);
+    }
+
+    @Override
+    public void finish(int resultCode, Bundle results) {
+        if (mController != null)
+            mController.closeChannel();
+        super.finish(resultCode, results);
+    }
+}
diff --git a/telephony/tests/telephonymockriltests/src/com/android/telephonymockriltests/functional/SimpleTestUsingMockRil.java b/telephony/tests/telephonymockriltests/src/com/android/telephonymockriltests/functional/SimpleTestUsingMockRil.java
new file mode 100644
index 0000000..87001c8
--- /dev/null
+++ b/telephony/tests/telephonymockriltests/src/com/android/telephonymockriltests/functional/SimpleTestUsingMockRil.java
@@ -0,0 +1,49 @@
+/*
+ * 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.telephonymockriltests.functional;
+
+import com.android.internal.telephony.mockril.MockRilController;
+import android.test.InstrumentationTestCase;
+import android.util.Log;
+
+import com.android.telephonymockriltests.TelephonyMockTestRunner;
+
+/**
+ * A simple test that using Mock RIL Controller
+ */
+public class SimpleTestUsingMockRil extends InstrumentationTestCase {
+    private static final String TAG = "SimpleTestUsingMockRil";
+    private MockRilController mMockRilCtrl = null;
+    private TelephonyMockTestRunner mRunner;
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        mRunner = (TelephonyMockTestRunner)getInstrumentation();
+        mMockRilCtrl = mRunner.mController;
+        assertNotNull(mMockRilCtrl);
+    }
+
+    /**
+     * Get the current radio state of RIL
+     */
+    public void testGetRadioState() {
+        int state = mMockRilCtrl.getRadioState();
+        Log.v(TAG, "testGetRadioState: " + state);
+        assertTrue(state >= 0 && state <= 9);
+    }
+}
diff --git a/telephony/tests/telephonytests/Android.mk b/telephony/tests/telephonytests/Android.mk
index 45e265a..98e4403 100644
--- a/telephony/tests/telephonytests/Android.mk
+++ b/telephony/tests/telephonytests/Android.mk
@@ -5,6 +5,8 @@
 
 LOCAL_SRC_FILES := $(call all-subdir-java-files)
 
+LOCAL_STATIC_JAVA_LIBRARIES := librilproto-java
+
 LOCAL_JAVA_LIBRARIES := android.test.runner
 
 LOCAL_PACKAGE_NAME := FrameworksTelephonyTests
diff --git a/telephony/tests/telephonytests/AndroidManifest.xml b/telephony/tests/telephonytests/AndroidManifest.xml
index 6a97423..ba1d957 100644
--- a/telephony/tests/telephonytests/AndroidManifest.xml
+++ b/telephony/tests/telephonytests/AndroidManifest.xml
@@ -32,6 +32,13 @@
         android:targetPackage="com.android.frameworks.telephonytests"
         android:label="Frameworks Telephony Tests">
     </instrumentation>
+
+    <instrumentation android:name=".TelephonyMockRilTestRunner"
+        android:targetPackage="com.android.frameworks.telephonytests"
+        android:label="Test Runner for Mock Ril Tests"
+    />
+
     <uses-permission android:name="android.permission.READ_PHONE_STATE" />
+    <uses-permission android:name="android.permission.INTERNET" />
 
 </manifest>
diff --git a/telephony/tests/telephonytests/src/android/telephony/PhoneNumberUtilsTest.java b/telephony/tests/telephonytests/src/android/telephony/PhoneNumberUtilsTest.java
deleted file mode 100644
index de59b81..0000000
--- a/telephony/tests/telephonytests/src/android/telephony/PhoneNumberUtilsTest.java
+++ /dev/null
@@ -1,506 +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 com.android.internal.telephony;
-
-import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
-import android.text.SpannableStringBuilder;
-import android.telephony.PhoneNumberUtils;
-import android.telephony.TelephonyManager;
-import android.content.Context;
-
-import junit.framework.TestCase;
-
-public class PhoneNumberUtilsTest extends AndroidTestCase {
-
-    @SmallTest
-    public void testExtractNetworkPortion() throws Exception {
-        assertEquals(
-                "+17005554141",
-                PhoneNumberUtils.extractNetworkPortion("+17005554141")
-        );
-
-        assertEquals(
-                "+17005554141",
-                PhoneNumberUtils.extractNetworkPortion("+1 (700).555-4141")
-        );
-
-        assertEquals(
-                "17005554141",
-                PhoneNumberUtils.extractNetworkPortion("1 (700).555-4141")
-        );
-
-        // This may seem wrong, but it's probably ok
-        assertEquals(
-                "17005554141*#",
-                PhoneNumberUtils.extractNetworkPortion("1 (700).555-4141*#")
-        );
-
-        assertEquals(
-                "170055541NN",
-                PhoneNumberUtils.extractNetworkPortion("1 (700).555-41NN")
-        );
-
-        assertEquals(
-                "170055541NN",
-                PhoneNumberUtils.extractNetworkPortion("1 (700).555-41NN,1234")
-        );
-
-        assertEquals(
-                "170055541NN",
-                PhoneNumberUtils.extractNetworkPortion("1 (700).555-41NN;1234")
-        );
-
-        // An MMI string is unperterbed, even though it contains a
-        // (valid in this case) embedded +
-        assertEquals(
-                "**21**17005554141#",
-                PhoneNumberUtils.extractNetworkPortion("**21**+17005554141#")
-                //TODO this is the correct result, although the above
-                //result has been returned since change 31776
-                //"**21**+17005554141#"
-        );
-
-        assertEquals("", PhoneNumberUtils.extractNetworkPortion(""));
-
-        assertEquals("", PhoneNumberUtils.extractNetworkPortion(",1234"));
-
-        byte [] b = new byte[20];
-        b[0] = (byte) 0x81; b[1] = (byte) 0x71; b[2] = (byte) 0x00; b[3] = (byte) 0x55;
-        b[4] = (byte) 0x05; b[5] = (byte) 0x20; b[6] = (byte) 0xF0;
-        assertEquals("17005550020",
-            PhoneNumberUtils.calledPartyBCDToString(b, 0, 7));
-
-        b[0] = (byte) 0x80; b[1] = (byte) 0x71; b[2] = (byte) 0x00; b[3] = (byte) 0x55;
-        b[4] = (byte) 0x05; b[5] = (byte) 0x20; b[6] = (byte) 0xF0;
-        assertEquals("17005550020",
-            PhoneNumberUtils.calledPartyBCDToString(b, 0, 7));
-
-        b[0] = (byte) 0x90; b[1] = (byte) 0x71; b[2] = (byte) 0x00; b[3] = (byte) 0x55;
-        b[4] = (byte) 0x05; b[5] = (byte) 0x20; b[6] = (byte) 0xF0;
-        assertEquals("+17005550020",
-            PhoneNumberUtils.calledPartyBCDToString(b, 0, 7));
-
-        b[0] = (byte) 0x91; b[1] = (byte) 0x71; b[2] = (byte) 0x00; b[3] = (byte) 0x55;
-        b[4] = (byte) 0x05; b[5] = (byte) 0x20; b[6] = (byte) 0xF0;
-        assertEquals("+17005550020",
-                PhoneNumberUtils.calledPartyBCDToString(b, 0, 7));
-
-        byte[] bRet = PhoneNumberUtils.networkPortionToCalledPartyBCD("+17005550020");
-        assertEquals(7, bRet.length);
-        for (int i = 0; i < 7; i++) {
-            assertEquals(b[i], bRet[i]);
-        }
-
-        bRet = PhoneNumberUtils.networkPortionToCalledPartyBCDWithLength("+17005550020");
-        assertEquals(8, bRet.length);
-        assertEquals(bRet[0], 7);
-        for (int i = 1; i < 8; i++) {
-            assertEquals(b[i - 1], bRet[i]);
-        }
-
-        bRet = PhoneNumberUtils.networkPortionToCalledPartyBCD("7005550020");
-        assertEquals("7005550020",
-            PhoneNumberUtils.calledPartyBCDToString(bRet, 0, bRet.length));
-
-        b[0] = (byte) 0x81; b[1] = (byte) 0x71; b[2] = (byte) 0x00; b[3] = (byte) 0x55;
-        b[4] = (byte) 0x05; b[5] = (byte) 0x20; b[6] = (byte) 0xB0;
-        assertEquals("17005550020#",
-            PhoneNumberUtils.calledPartyBCDToString(b, 0, 7));
-
-        b[0] = (byte) 0x91; b[1] = (byte) 0x71; b[2] = (byte) 0x00; b[3] = (byte) 0x55;
-        b[4] = (byte) 0x05; b[5] = (byte) 0x20; b[6] = (byte) 0xB0;
-        assertEquals("+17005550020#",
-            PhoneNumberUtils.calledPartyBCDToString(b, 0, 7));
-
-        b[0] = (byte) 0x81; b[1] = (byte) 0x2A; b[2] = (byte) 0xB1;
-        assertEquals("*21#",
-            PhoneNumberUtils.calledPartyBCDToString(b, 0, 3));
-
-        b[0] = (byte) 0x81; b[1] = (byte) 0x2B; b[2] = (byte) 0xB1;
-        assertEquals("#21#",
-            PhoneNumberUtils.calledPartyBCDToString(b, 0, 3));
-
-        b[0] = (byte) 0x91; b[1] = (byte) 0x2A; b[2] = (byte) 0xB1;
-        assertEquals("*21#+",
-            PhoneNumberUtils.calledPartyBCDToString(b, 0, 3));
-
-        b[0] = (byte) 0x81; b[1] = (byte) 0xAA; b[2] = (byte) 0x12; b[3] = (byte) 0xFB;
-        assertEquals("**21#",
-            PhoneNumberUtils.calledPartyBCDToString(b, 0, 4));
-
-        b[0] = (byte) 0x91; b[1] = (byte) 0xAA; b[2] = (byte) 0x12; b[3] = (byte) 0xFB;
-        assertEquals("**21#+",
-            PhoneNumberUtils.calledPartyBCDToString(b, 0, 4));
-
-        b[0] = (byte) 0x81; b[1] = (byte) 0x9A; b[2] = (byte) 0xA9; b[3] = (byte) 0x71;
-        b[4] = (byte) 0x00; b[5] = (byte) 0x55; b[6] = (byte) 0x05; b[7] = (byte) 0x20;
-        b[8] = (byte) 0xB0;
-        assertEquals("*99*17005550020#",
-            PhoneNumberUtils.calledPartyBCDToString(b, 0, 9));
-
-        b[0] = (byte) 0x91; b[1] = (byte) 0x9A; b[2] = (byte) 0xA9; b[3] = (byte) 0x71;
-        b[4] = (byte) 0x00; b[5] = (byte) 0x55; b[6] = (byte) 0x05; b[7] = (byte) 0x20;
-        b[8] = (byte) 0xB0;
-        assertEquals("*99*+17005550020#",
-            PhoneNumberUtils.calledPartyBCDToString(b, 0, 9));
-
-        b[0] = (byte) 0x81; b[1] = (byte) 0xAA; b[2] = (byte) 0x12; b[3] = (byte) 0x1A;
-        b[4] = (byte) 0x07; b[5] = (byte) 0x50; b[6] = (byte) 0x55; b[7] = (byte) 0x00;
-        b[8] = (byte) 0x02; b[9] = (byte) 0xFB;
-        assertEquals("**21*17005550020#",
-            PhoneNumberUtils.calledPartyBCDToString(b, 0, 10));
-
-        b[0] = (byte) 0x91; b[1] = (byte) 0xAA; b[2] = (byte) 0x12; b[3] = (byte) 0x1A;
-        b[4] = (byte) 0x07; b[5] = (byte) 0x50; b[6] = (byte) 0x55; b[7] = (byte) 0x00;
-        b[8] = (byte) 0x02; b[9] = (byte) 0xFB;
-        assertEquals("**21*+17005550020#",
-            PhoneNumberUtils.calledPartyBCDToString(b, 0, 10));
-
-        b[0] = (byte) 0x81; b[1] = (byte) 0x2A; b[2] = (byte) 0xA1; b[3] = (byte) 0x71;
-        b[4] = (byte) 0x00; b[5] = (byte) 0x55; b[6] = (byte) 0x05; b[7] = (byte) 0x20;
-        b[8] = (byte) 0xF0;
-        assertEquals("*21*17005550020",
-            PhoneNumberUtils.calledPartyBCDToString(b, 0, 9));
-
-        b[0] = (byte) 0x91; b[1] = (byte) 0x2A; b[2] = (byte) 0xB1; b[3] = (byte) 0x71;
-        b[4] = (byte) 0x00; b[5] = (byte) 0x55; b[6] = (byte) 0x05; b[7] = (byte) 0x20;
-        b[8] = (byte) 0xF0;
-        assertEquals("*21#+17005550020",
-            PhoneNumberUtils.calledPartyBCDToString(b, 0, 9));
-
-        assertNull(PhoneNumberUtils.extractNetworkPortion(null));
-        assertNull(PhoneNumberUtils.extractPostDialPortion(null));
-        assertTrue(PhoneNumberUtils.compare(null, null));
-        assertFalse(PhoneNumberUtils.compare(null, "123"));
-        assertFalse(PhoneNumberUtils.compare("123", null));
-        assertNull(PhoneNumberUtils.toCallerIDMinMatch(null));
-        assertNull(PhoneNumberUtils.getStrippedReversed(null));
-        assertNull(PhoneNumberUtils.stringFromStringAndTOA(null, 1));
-    }
-
-    @SmallTest
-    public void testExtractNetworkPortionAlt() throws Exception {
-        assertEquals(
-                "+17005554141",
-                PhoneNumberUtils.extractNetworkPortionAlt("+17005554141")
-        );
-
-        assertEquals(
-                "+17005554141",
-                PhoneNumberUtils.extractNetworkPortionAlt("+1 (700).555-4141")
-        );
-
-        assertEquals(
-                "17005554141",
-                PhoneNumberUtils.extractNetworkPortionAlt("1 (700).555-4141")
-        );
-
-        // This may seem wrong, but it's probably ok
-        assertEquals(
-                "17005554141*#",
-                PhoneNumberUtils.extractNetworkPortionAlt("1 (700).555-4141*#")
-        );
-
-        assertEquals(
-                "170055541NN",
-                PhoneNumberUtils.extractNetworkPortionAlt("1 (700).555-41NN")
-        );
-
-        assertEquals(
-                "170055541NN",
-                PhoneNumberUtils.extractNetworkPortionAlt("1 (700).555-41NN,1234")
-        );
-
-        assertEquals(
-                "170055541NN",
-                PhoneNumberUtils.extractNetworkPortionAlt("1 (700).555-41NN;1234")
-        );
-
-        // An MMI string is unperterbed, even though it contains a
-        // (valid in this case) embedded +
-        assertEquals(
-                "**21**+17005554141#",
-                PhoneNumberUtils.extractNetworkPortionAlt("**21**+17005554141#")
-        );
-
-        assertEquals(
-                "*31#+447966164208",
-                PhoneNumberUtils.extractNetworkPortionAlt("*31#+447966164208")
-        );
-
-        assertEquals(
-                "*31#+447966164208",
-                PhoneNumberUtils.extractNetworkPortionAlt("*31# (+44) 79 6616 4208")
-        );
-
-        assertEquals("", PhoneNumberUtils.extractNetworkPortionAlt(""));
-
-        assertEquals("", PhoneNumberUtils.extractNetworkPortionAlt(",1234"));
-
-        assertNull(PhoneNumberUtils.extractNetworkPortionAlt(null));
-    }
-
-    @SmallTest
-    public void testB() throws Exception {
-        assertEquals("", PhoneNumberUtils.extractPostDialPortion("+17005554141"));
-        assertEquals("", PhoneNumberUtils.extractPostDialPortion("+1 (700).555-4141"));
-        assertEquals("", PhoneNumberUtils.extractPostDialPortion("+1 (700).555-41NN"));
-        assertEquals(",1234", PhoneNumberUtils.extractPostDialPortion("+1 (700).555-41NN,1234"));
-        assertEquals(";1234", PhoneNumberUtils.extractPostDialPortion("+1 (700).555-41NN;1234"));
-        assertEquals(";1234,;N",
-                PhoneNumberUtils.extractPostDialPortion("+1 (700).555-41NN;1-2.34 ,;N"));
-    }
-
-    @SmallTest
-    public void testCompare() throws Exception {
-        // this is odd
-        assertFalse(PhoneNumberUtils.compare("", ""));
-
-        assertTrue(PhoneNumberUtils.compare("911", "911"));
-        assertFalse(PhoneNumberUtils.compare("911", "18005550911"));
-        assertTrue(PhoneNumberUtils.compare("5555", "5555"));
-        assertFalse(PhoneNumberUtils.compare("5555", "180055555555"));
-
-        assertTrue(PhoneNumberUtils.compare("+17005554141", "+17005554141"));
-        assertTrue(PhoneNumberUtils.compare("+17005554141", "+1 (700).555-4141"));
-        assertTrue(PhoneNumberUtils.compare("+17005554141", "+1 (700).555-4141,1234"));
-        assertTrue(PhoneNumberUtils.compare("+17005554141", "17005554141"));
-        assertTrue(PhoneNumberUtils.compare("+17005554141", "7005554141"));
-        assertTrue(PhoneNumberUtils.compare("+17005554141", "5554141"));
-        assertTrue(PhoneNumberUtils.compare("17005554141", "5554141"));
-        assertTrue(PhoneNumberUtils.compare("+17005554141", "01117005554141"));
-        assertTrue(PhoneNumberUtils.compare("+17005554141", "0017005554141"));
-        assertTrue(PhoneNumberUtils.compare("17005554141", "0017005554141"));
-
-
-        assertTrue(PhoneNumberUtils.compare("+17005554141", "**31#+17005554141"));
-
-        assertFalse(PhoneNumberUtils.compare("+1 999 7005554141", "+1 7005554141"));
-        assertTrue(PhoneNumberUtils.compare("011 1 7005554141", "7005554141"));
-
-        assertFalse(PhoneNumberUtils.compare("011 11 7005554141", "+17005554141"));
-
-        assertFalse(PhoneNumberUtils.compare("+17005554141", "7085882300"));
-
-        assertTrue(PhoneNumberUtils.compare("+44 207 792 3490", "0 207 792 3490"));
-
-        assertFalse(PhoneNumberUtils.compare("+44 207 792 3490", "00 207 792 3490"));
-        assertFalse(PhoneNumberUtils.compare("+44 207 792 3490", "011 207 792 3490"));
-
-        /***** FIXME's ******/
-        //
-        // MMI header should be ignored
-        assertFalse(PhoneNumberUtils.compare("+17005554141", "**31#17005554141"));
-
-        // It's too bad this is false
-        // +44 (0) 207 792 3490 is not a dialable number
-        // but it is commonly how European phone numbers are written
-        assertFalse(PhoneNumberUtils.compare("+44 207 792 3490", "+44 (0) 207 792 3490"));
-
-        // The japanese international prefix, for example, messes us up
-        // But who uses a GSM phone in Japan?
-        assertFalse(PhoneNumberUtils.compare("+44 207 792 3490", "010 44 207 792 3490"));
-
-        // The Australian one messes us up too
-        assertFalse(PhoneNumberUtils.compare("+44 207 792 3490", "0011 44 207 792 3490"));
-
-        // The Russian trunk prefix messes us up, as does current
-        // Russian area codes (which bein with 0)
-
-        assertFalse(PhoneNumberUtils.compare("+7(095)9100766", "8(095)9100766"));
-
-        // 444 is not a valid country code, but
-        // matchIntlPrefixAndCC doesnt know this
-        assertTrue(PhoneNumberUtils.compare("+444 207 792 3490", "0 207 792 3490"));
-
-        // compare SMS short code
-        assertTrue(PhoneNumberUtils.compare("404-04", "40404"));
-    }
-
-
-    @SmallTest
-    public void testToCallerIDIndexable() throws Exception {
-        assertEquals("1414555", PhoneNumberUtils.toCallerIDMinMatch("17005554141"));
-        assertEquals("1414555", PhoneNumberUtils.toCallerIDMinMatch("1-700-555-4141"));
-        assertEquals("1414555", PhoneNumberUtils.toCallerIDMinMatch("1-700-555-4141,1234"));
-        assertEquals("1414555", PhoneNumberUtils.toCallerIDMinMatch("1-700-555-4141;1234"));
-
-        //this seems wrong, or at least useless
-        assertEquals("NN14555", PhoneNumberUtils.toCallerIDMinMatch("1-700-555-41NN"));
-
-        //<shrug> -- these are all not useful, but not terribly wrong
-        assertEquals("", PhoneNumberUtils.toCallerIDMinMatch(""));
-        assertEquals("0032", PhoneNumberUtils.toCallerIDMinMatch("2300"));
-        assertEquals("0032+", PhoneNumberUtils.toCallerIDMinMatch("+2300"));
-        assertEquals("#130#*", PhoneNumberUtils.toCallerIDMinMatch("*#031#"));
-    }
-
-    @SmallTest
-    public void testGetIndexable() throws Exception {
-        assertEquals("14145550071", PhoneNumberUtils.getStrippedReversed("1-700-555-4141"));
-        assertEquals("14145550071", PhoneNumberUtils.getStrippedReversed("1-700-555-4141,1234"));
-        assertEquals("14145550071", PhoneNumberUtils.getStrippedReversed("1-700-555-4141;1234"));
-
-        //this seems wrong, or at least useless
-        assertEquals("NN145550071", PhoneNumberUtils.getStrippedReversed("1-700-555-41NN"));
-
-        //<shrug> -- these are all not useful, but not terribly wrong
-        assertEquals("", PhoneNumberUtils.getStrippedReversed(""));
-        assertEquals("0032", PhoneNumberUtils.getStrippedReversed("2300"));
-        assertEquals("0032+", PhoneNumberUtils.getStrippedReversed("+2300"));
-        assertEquals("#130#*", PhoneNumberUtils.getStrippedReversed("*#031#"));
-    }
-
-    @SmallTest
-    public void testNanpFormatting() {
-        SpannableStringBuilder number = new SpannableStringBuilder();
-        number.append("8005551212");
-        PhoneNumberUtils.formatNanpNumber(number);
-        assertEquals("800-555-1212", number.toString());
-
-        number.clear();
-        number.append("800555121");
-        PhoneNumberUtils.formatNanpNumber(number);
-        assertEquals("800-555-121", number.toString());
-
-        number.clear();
-        number.append("555-1212");
-        PhoneNumberUtils.formatNanpNumber(number);
-        assertEquals("555-1212", number.toString());
-
-        number.clear();
-        number.append("800-55512");
-        PhoneNumberUtils.formatNanpNumber(number);
-        assertEquals("800-555-12", number.toString());
-
-        number.clear();
-        number.append("46645");
-        PhoneNumberUtils.formatNanpNumber(number);
-        assertEquals("46645", number.toString());
-    }
-
-    @SmallTest
-    public void testConvertKeypadLettersToDigits() {
-        assertEquals("1-800-4664-411",
-                     PhoneNumberUtils.convertKeypadLettersToDigits("1-800-GOOG-411"));
-        assertEquals("18004664411",
-                     PhoneNumberUtils.convertKeypadLettersToDigits("1800GOOG411"));
-        assertEquals("1-800-466-4411",
-                     PhoneNumberUtils.convertKeypadLettersToDigits("1-800-466-4411"));
-        assertEquals("18004664411",
-                     PhoneNumberUtils.convertKeypadLettersToDigits("18004664411"));
-        assertEquals("222-333-444-555-666-7777-888-9999",
-                     PhoneNumberUtils.convertKeypadLettersToDigits(
-                             "ABC-DEF-GHI-JKL-MNO-PQRS-TUV-WXYZ"));
-        assertEquals("222-333-444-555-666-7777-888-9999",
-                     PhoneNumberUtils.convertKeypadLettersToDigits(
-                             "abc-def-ghi-jkl-mno-pqrs-tuv-wxyz"));
-        assertEquals("(800) 222-3334",
-                     PhoneNumberUtils.convertKeypadLettersToDigits("(800) ABC-DEFG"));
-    }
-
-    // To run this test, the device has to be registered with network
-    public void testCheckAndProcessPlusCode() {
-        assertEquals("0118475797000",
-                PhoneNumberUtils.cdmaCheckAndProcessPlusCode("+8475797000"));
-        assertEquals("18475797000",
-                PhoneNumberUtils.cdmaCheckAndProcessPlusCode("+18475797000"));
-        assertEquals("0111234567",
-                PhoneNumberUtils.cdmaCheckAndProcessPlusCode("+1234567"));
-        assertEquals("01123456700000",
-                PhoneNumberUtils.cdmaCheckAndProcessPlusCode("+23456700000"));
-        assertEquals("01111875767800",
-                PhoneNumberUtils.cdmaCheckAndProcessPlusCode("+11875767800"));
-        assertEquals("8475797000,18475231753",
-                PhoneNumberUtils.cdmaCheckAndProcessPlusCode("8475797000,+18475231753"));
-        assertEquals("0118475797000,18475231753",
-                PhoneNumberUtils.cdmaCheckAndProcessPlusCode("+8475797000,+18475231753"));
-        assertEquals("8475797000;0118469312345",
-                PhoneNumberUtils.cdmaCheckAndProcessPlusCode("8475797000;+8469312345"));
-        assertEquals("8475797000,0111234567",
-                PhoneNumberUtils.cdmaCheckAndProcessPlusCode("8475797000,+1234567"));
-        assertEquals("847597000;01111875767000",
-                PhoneNumberUtils.cdmaCheckAndProcessPlusCode("847597000;+11875767000"));
-        assertEquals("8475797000,,0118469312345",
-                PhoneNumberUtils.cdmaCheckAndProcessPlusCode("8475797000,,+8469312345"));
-        assertEquals("8475797000;,0118469312345",
-                PhoneNumberUtils.cdmaCheckAndProcessPlusCode("8475797000;,+8469312345"));
-        assertEquals("8475797000,;18475231753",
-                PhoneNumberUtils.cdmaCheckAndProcessPlusCode("8475797000,;+18475231753"));
-        assertEquals("8475797000;,01111875767000",
-                PhoneNumberUtils.cdmaCheckAndProcessPlusCode("8475797000;,+11875767000"));
-        assertEquals("8475797000,;01111875767000",
-                PhoneNumberUtils.cdmaCheckAndProcessPlusCode("8475797000,;+11875767000"));
-        assertEquals("8475797000,,,01111875767000",
-                PhoneNumberUtils.cdmaCheckAndProcessPlusCode("8475797000,,,+11875767000"));
-        assertEquals("8475797000;,,01111875767000",
-                PhoneNumberUtils.cdmaCheckAndProcessPlusCode("8475797000;,,+11875767000"));
-        assertEquals("+;,8475797000",
-                PhoneNumberUtils.cdmaCheckAndProcessPlusCode("+;,8475797000"));
-        assertEquals("8475797000,",
-                PhoneNumberUtils.cdmaCheckAndProcessPlusCode("8475797000,"));
-        assertEquals("847+579-7000",
-                PhoneNumberUtils.cdmaCheckAndProcessPlusCode("847+579-7000"));
-        assertEquals(",8475797000",
-                PhoneNumberUtils.cdmaCheckAndProcessPlusCode(",8475797000"));
-        assertEquals(";;8475797000,,",
-                PhoneNumberUtils.cdmaCheckAndProcessPlusCode(";;8475797000,,"));
-        assertEquals("+this+is$weird;,+",
-                PhoneNumberUtils.cdmaCheckAndProcessPlusCode("+this+is$weird;,+"));
-        assertEquals("",
-                PhoneNumberUtils.cdmaCheckAndProcessPlusCode(""));
-        assertNull(PhoneNumberUtils.cdmaCheckAndProcessPlusCode(null));
-    }
-
-    @SmallTest
-    public void testCheckAndProcessPlusCodeByNumberFormat() {
-        assertEquals("18475797000",
-                PhoneNumberUtils.cdmaCheckAndProcessPlusCodeByNumberFormat("+18475797000",
-                PhoneNumberUtils.FORMAT_NANP,PhoneNumberUtils.FORMAT_NANP));
-        assertEquals("+18475797000",
-                PhoneNumberUtils.cdmaCheckAndProcessPlusCodeByNumberFormat("+18475797000",
-                PhoneNumberUtils.FORMAT_NANP,PhoneNumberUtils.FORMAT_JAPAN));
-        assertEquals("+18475797000",
-                PhoneNumberUtils.cdmaCheckAndProcessPlusCodeByNumberFormat("+18475797000",
-                PhoneNumberUtils.FORMAT_NANP,PhoneNumberUtils.FORMAT_UNKNOWN));
-        assertEquals("+18475797000",
-                PhoneNumberUtils.cdmaCheckAndProcessPlusCodeByNumberFormat("+18475797000",
-                PhoneNumberUtils.FORMAT_JAPAN,PhoneNumberUtils.FORMAT_JAPAN));
-        assertEquals("+18475797000",
-                PhoneNumberUtils.cdmaCheckAndProcessPlusCodeByNumberFormat("+18475797000",
-                PhoneNumberUtils.FORMAT_UNKNOWN,PhoneNumberUtils.FORMAT_UNKNOWN));
-    }
-
-    /**
-     * Basic checks for the VoiceMail number.
-     */
-    @SmallTest
-    public void testWithNumberNotEqualToVoiceMail() throws Exception {
-        assertFalse(PhoneNumberUtils.isVoiceMailNumber("911"));
-        assertFalse(PhoneNumberUtils.isVoiceMailNumber("tel:911"));
-        assertFalse(PhoneNumberUtils.isVoiceMailNumber("+18001234567"));
-        assertFalse(PhoneNumberUtils.isVoiceMailNumber(""));
-        assertFalse(PhoneNumberUtils.isVoiceMailNumber(null));
-        // This test fails on a device without a sim card
-        /*TelephonyManager mTelephonyManager =
-            (TelephonyManager)getContext().getSystemService(Context.TELEPHONY_SERVICE);
-        String mVoiceMailNumber = mTelephonyManager.getDefault().getVoiceMailNumber();
-        assertTrue(PhoneNumberUtils.isVoiceMailNumber(mVoiceMailNumber));
-        */
-    }
-}
diff --git a/telephony/tests/telephonytests/src/android/telephony/PhoneNumberWatcherTest.java b/telephony/tests/telephonytests/src/android/telephony/PhoneNumberWatcherTest.java
deleted file mode 100644
index 88eaecd..0000000
--- a/telephony/tests/telephonytests/src/android/telephony/PhoneNumberWatcherTest.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * 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.
- */
-
-package com.android.internal.telephony;
-
-import android.telephony.PhoneNumberFormattingTextWatcher;
-import android.test.suitebuilder.annotation.SmallTest;
-import android.text.Selection;
-import android.text.SpannableStringBuilder;
-import android.text.TextWatcher;
-
-import junit.framework.TestCase;
-
-public class PhoneNumberWatcherTest extends TestCase {
-    @SmallTest
-    public void testHyphenation() throws Exception {
-        SpannableStringBuilder number = new SpannableStringBuilder();
-        TextWatcher tw = new PhoneNumberFormattingTextWatcher();
-        number.append("555-1212");
-        // Move the cursor to the left edge
-        Selection.setSelection(number, 0);
-        tw.beforeTextChanged(number, 0, 0, 1);
-        // Insert an 8 at the beginning
-        number.insert(0, "8");
-        tw.afterTextChanged(number);
-        assertEquals("855-512-12", number.toString());
-    }
-    
-    @SmallTest
-    public void testHyphenDeletion() throws Exception {
-        SpannableStringBuilder number = new SpannableStringBuilder();
-        TextWatcher tw = new PhoneNumberFormattingTextWatcher();
-        number.append("555-1212");
-        // Move the cursor to after the hyphen
-        Selection.setSelection(number, 4);
-        // Delete the hyphen
-        tw.beforeTextChanged(number, 3, 1, 0);
-        number.delete(3, 4);
-        tw.afterTextChanged(number);
-        // Make sure that it deleted the character before the hyphen 
-        assertEquals("551-212", number.toString());
-        
-        // Make sure it deals with left edge boundary case
-        number.insert(0, "-");
-        Selection.setSelection(number, 1);
-        tw.beforeTextChanged(number, 0, 1, 0);
-        number.delete(0, 1);
-        tw.afterTextChanged(number);
-        // Make sure that it deleted the character before the hyphen 
-        assertEquals("551-212", number.toString());
-    }
-}
diff --git a/telephony/tests/telephonytests/src/com/android/frameworks/telephonytests/TelephonyMockRilTestRunner.java b/telephony/tests/telephonytests/src/com/android/frameworks/telephonytests/TelephonyMockRilTestRunner.java
new file mode 100644
index 0000000..9192f57
--- /dev/null
+++ b/telephony/tests/telephonytests/src/com/android/frameworks/telephonytests/TelephonyMockRilTestRunner.java
@@ -0,0 +1,93 @@
+/*
+ * 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.frameworks.telephonytests;
+
+import android.os.Bundle;
+
+import android.test.InstrumentationTestRunner;
+import android.test.InstrumentationTestSuite;
+import android.util.Log;
+
+import java.io.IOException;
+
+import com.android.internal.telephony.RilChannel;
+import com.android.internal.telephony.mockril.MockRilTest;
+
+import junit.framework.TestSuite;
+
+public class TelephonyMockRilTestRunner extends InstrumentationTestRunner {
+
+    public RilChannel mMockRilChannel;
+
+    @Override
+    public TestSuite getAllTests() {
+        log("getAllTests E");
+        TestSuite suite = new InstrumentationTestSuite(this);
+        suite.addTestSuite(MockRilTest.class);
+        log("getAllTests X");
+        return suite;
+    }
+
+    @Override
+    public ClassLoader getLoader() {
+        log("getLoader EX");
+        return TelephonyMockRilTestRunner.class.getClassLoader();
+    }
+
+    @Override
+    public void onCreate(Bundle icicle) {
+        log("onCreate E");
+        try {
+            mMockRilChannel = RilChannel.makeRilChannel();
+        } catch (IOException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        }
+        log("onCreate X");
+
+        super.onCreate(icicle);
+    }
+
+    @Override
+    public void onDestroy() {
+        // I've not seen this called
+        log("onDestroy EX");
+        super.onDestroy();
+    }
+
+    @Override
+    public void onStart() {
+        // Called when the instrumentation thread is started.
+        // At the moment we don't need the thread so return
+        // which will shut down this unused thread.
+        log("onStart EX");
+        super.onStart();
+    }
+
+    @Override
+    public void finish(int resultCode, Bundle results) {
+        // Called when complete so I ask the mMockRilChannel to quit.
+        log("finish E");
+        mMockRilChannel.close();
+        log("finish X");
+        super.finish(resultCode, results);
+    }
+
+    private void log(String s) {
+        Log.e("TelephonyMockRilTestRunner", s);
+    }
+}
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/com/android/internal/telephony/MccTableTest.java b/telephony/tests/telephonytests/src/com/android/internal/telephony/MccTableTest.java
index 2d6977c..7eb3df8 100644
--- a/telephony/tests/telephonytests/src/com/android/internal/telephony/MccTableTest.java
+++ b/telephony/tests/telephonytests/src/com/android/internal/telephony/MccTableTest.java
@@ -28,7 +28,7 @@
 
     @SmallTest
     public void testTimeZone() throws Exception {
-        assertEquals(MccTable.defaultTimeZoneForMcc(208), "Europe/Paris");
+        assertEquals(MccTable.defaultTimeZoneForMcc(208), "ECT");
         assertEquals(MccTable.defaultTimeZoneForMcc(232), "Europe/Vienna");
         assertEquals(MccTable.defaultTimeZoneForMcc(655), "Africa/Johannesburg");
         assertEquals(MccTable.defaultTimeZoneForMcc(440), "Asia/Tokyo");
diff --git a/telephony/tests/telephonytests/src/com/android/internal/telephony/PhoneNumberUtilsTest.java b/telephony/tests/telephonytests/src/com/android/internal/telephony/PhoneNumberUtilsTest.java
new file mode 100644
index 0000000..5ef1c69
--- /dev/null
+++ b/telephony/tests/telephonytests/src/com/android/internal/telephony/PhoneNumberUtilsTest.java
@@ -0,0 +1,529 @@
+/*
+ * 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 com.android.internal.telephony;
+
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.text.SpannableStringBuilder;
+import android.telephony.PhoneNumberUtils;
+import android.telephony.TelephonyManager;
+import android.content.Context;
+
+import junit.framework.TestCase;
+
+public class PhoneNumberUtilsTest extends AndroidTestCase {
+
+    @SmallTest
+    public void testExtractNetworkPortion() throws Exception {
+        assertEquals(
+                "+17005554141",
+                PhoneNumberUtils.extractNetworkPortion("+17005554141")
+        );
+
+        assertEquals(
+                "+17005554141",
+                PhoneNumberUtils.extractNetworkPortion("+1 (700).555-4141")
+        );
+
+        assertEquals(
+                "17005554141",
+                PhoneNumberUtils.extractNetworkPortion("1 (700).555-4141")
+        );
+
+        // This may seem wrong, but it's probably ok
+        assertEquals(
+                "17005554141*#",
+                PhoneNumberUtils.extractNetworkPortion("1 (700).555-4141*#")
+        );
+
+        assertEquals(
+                "170055541NN",
+                PhoneNumberUtils.extractNetworkPortion("1 (700).555-41NN")
+        );
+
+        assertEquals(
+                "170055541NN",
+                PhoneNumberUtils.extractNetworkPortion("1 (700).555-41NN,1234")
+        );
+
+        assertEquals(
+                "170055541NN",
+                PhoneNumberUtils.extractNetworkPortion("1 (700).555-41NN;1234")
+        );
+
+        // An MMI string is unperterbed, even though it contains a
+        // (valid in this case) embedded +
+        assertEquals(
+                "**21**17005554141#",
+                PhoneNumberUtils.extractNetworkPortion("**21**+17005554141#")
+                //TODO this is the correct result, although the above
+                //result has been returned since change 31776
+                //"**21**+17005554141#"
+        );
+
+        assertEquals("", PhoneNumberUtils.extractNetworkPortion(""));
+
+        assertEquals("", PhoneNumberUtils.extractNetworkPortion(",1234"));
+
+        byte [] b = new byte[20];
+        b[0] = (byte) 0x81; b[1] = (byte) 0x71; b[2] = (byte) 0x00; b[3] = (byte) 0x55;
+        b[4] = (byte) 0x05; b[5] = (byte) 0x20; b[6] = (byte) 0xF0;
+        assertEquals("17005550020",
+            PhoneNumberUtils.calledPartyBCDToString(b, 0, 7));
+
+        b[0] = (byte) 0x80; b[1] = (byte) 0x71; b[2] = (byte) 0x00; b[3] = (byte) 0x55;
+        b[4] = (byte) 0x05; b[5] = (byte) 0x20; b[6] = (byte) 0xF0;
+        assertEquals("17005550020",
+            PhoneNumberUtils.calledPartyBCDToString(b, 0, 7));
+
+        b[0] = (byte) 0x90; b[1] = (byte) 0x71; b[2] = (byte) 0x00; b[3] = (byte) 0x55;
+        b[4] = (byte) 0x05; b[5] = (byte) 0x20; b[6] = (byte) 0xF0;
+        assertEquals("+17005550020",
+            PhoneNumberUtils.calledPartyBCDToString(b, 0, 7));
+
+        b[0] = (byte) 0x91; b[1] = (byte) 0x71; b[2] = (byte) 0x00; b[3] = (byte) 0x55;
+        b[4] = (byte) 0x05; b[5] = (byte) 0x20; b[6] = (byte) 0xF0;
+        assertEquals("+17005550020",
+                PhoneNumberUtils.calledPartyBCDToString(b, 0, 7));
+
+        byte[] bRet = PhoneNumberUtils.networkPortionToCalledPartyBCD("+17005550020");
+        assertEquals(7, bRet.length);
+        for (int i = 0; i < 7; i++) {
+            assertEquals(b[i], bRet[i]);
+        }
+
+        bRet = PhoneNumberUtils.networkPortionToCalledPartyBCDWithLength("+17005550020");
+        assertEquals(8, bRet.length);
+        assertEquals(bRet[0], 7);
+        for (int i = 1; i < 8; i++) {
+            assertEquals(b[i - 1], bRet[i]);
+        }
+
+        bRet = PhoneNumberUtils.networkPortionToCalledPartyBCD("7005550020");
+        assertEquals("7005550020",
+            PhoneNumberUtils.calledPartyBCDToString(bRet, 0, bRet.length));
+
+        b[0] = (byte) 0x81; b[1] = (byte) 0x71; b[2] = (byte) 0x00; b[3] = (byte) 0x55;
+        b[4] = (byte) 0x05; b[5] = (byte) 0x20; b[6] = (byte) 0xB0;
+        assertEquals("17005550020#",
+            PhoneNumberUtils.calledPartyBCDToString(b, 0, 7));
+
+        b[0] = (byte) 0x91; b[1] = (byte) 0x71; b[2] = (byte) 0x00; b[3] = (byte) 0x55;
+        b[4] = (byte) 0x05; b[5] = (byte) 0x20; b[6] = (byte) 0xB0;
+        assertEquals("+17005550020#",
+            PhoneNumberUtils.calledPartyBCDToString(b, 0, 7));
+
+        b[0] = (byte) 0x81; b[1] = (byte) 0x2A; b[2] = (byte) 0xB1;
+        assertEquals("*21#",
+            PhoneNumberUtils.calledPartyBCDToString(b, 0, 3));
+
+        b[0] = (byte) 0x81; b[1] = (byte) 0x2B; b[2] = (byte) 0xB1;
+        assertEquals("#21#",
+            PhoneNumberUtils.calledPartyBCDToString(b, 0, 3));
+
+        b[0] = (byte) 0x91; b[1] = (byte) 0x2A; b[2] = (byte) 0xB1;
+        assertEquals("*21#+",
+            PhoneNumberUtils.calledPartyBCDToString(b, 0, 3));
+
+        b[0] = (byte) 0x81; b[1] = (byte) 0xAA; b[2] = (byte) 0x12; b[3] = (byte) 0xFB;
+        assertEquals("**21#",
+            PhoneNumberUtils.calledPartyBCDToString(b, 0, 4));
+
+        b[0] = (byte) 0x91; b[1] = (byte) 0xAA; b[2] = (byte) 0x12; b[3] = (byte) 0xFB;
+        assertEquals("**21#+",
+            PhoneNumberUtils.calledPartyBCDToString(b, 0, 4));
+
+        b[0] = (byte) 0x81; b[1] = (byte) 0x9A; b[2] = (byte) 0xA9; b[3] = (byte) 0x71;
+        b[4] = (byte) 0x00; b[5] = (byte) 0x55; b[6] = (byte) 0x05; b[7] = (byte) 0x20;
+        b[8] = (byte) 0xB0;
+        assertEquals("*99*17005550020#",
+            PhoneNumberUtils.calledPartyBCDToString(b, 0, 9));
+
+        b[0] = (byte) 0x91; b[1] = (byte) 0x9A; b[2] = (byte) 0xA9; b[3] = (byte) 0x71;
+        b[4] = (byte) 0x00; b[5] = (byte) 0x55; b[6] = (byte) 0x05; b[7] = (byte) 0x20;
+        b[8] = (byte) 0xB0;
+        assertEquals("*99*+17005550020#",
+            PhoneNumberUtils.calledPartyBCDToString(b, 0, 9));
+
+        b[0] = (byte) 0x81; b[1] = (byte) 0xAA; b[2] = (byte) 0x12; b[3] = (byte) 0x1A;
+        b[4] = (byte) 0x07; b[5] = (byte) 0x50; b[6] = (byte) 0x55; b[7] = (byte) 0x00;
+        b[8] = (byte) 0x02; b[9] = (byte) 0xFB;
+        assertEquals("**21*17005550020#",
+            PhoneNumberUtils.calledPartyBCDToString(b, 0, 10));
+
+        b[0] = (byte) 0x91; b[1] = (byte) 0xAA; b[2] = (byte) 0x12; b[3] = (byte) 0x1A;
+        b[4] = (byte) 0x07; b[5] = (byte) 0x50; b[6] = (byte) 0x55; b[7] = (byte) 0x00;
+        b[8] = (byte) 0x02; b[9] = (byte) 0xFB;
+        assertEquals("**21*+17005550020#",
+            PhoneNumberUtils.calledPartyBCDToString(b, 0, 10));
+
+        b[0] = (byte) 0x81; b[1] = (byte) 0x2A; b[2] = (byte) 0xA1; b[3] = (byte) 0x71;
+        b[4] = (byte) 0x00; b[5] = (byte) 0x55; b[6] = (byte) 0x05; b[7] = (byte) 0x20;
+        b[8] = (byte) 0xF0;
+        assertEquals("*21*17005550020",
+            PhoneNumberUtils.calledPartyBCDToString(b, 0, 9));
+
+        b[0] = (byte) 0x91; b[1] = (byte) 0x2A; b[2] = (byte) 0xB1; b[3] = (byte) 0x71;
+        b[4] = (byte) 0x00; b[5] = (byte) 0x55; b[6] = (byte) 0x05; b[7] = (byte) 0x20;
+        b[8] = (byte) 0xF0;
+        assertEquals("*21#+17005550020",
+            PhoneNumberUtils.calledPartyBCDToString(b, 0, 9));
+
+        assertNull(PhoneNumberUtils.extractNetworkPortion(null));
+        assertNull(PhoneNumberUtils.extractPostDialPortion(null));
+        assertTrue(PhoneNumberUtils.compare(null, null));
+        assertFalse(PhoneNumberUtils.compare(null, "123"));
+        assertFalse(PhoneNumberUtils.compare("123", null));
+        assertNull(PhoneNumberUtils.toCallerIDMinMatch(null));
+        assertNull(PhoneNumberUtils.getStrippedReversed(null));
+        assertNull(PhoneNumberUtils.stringFromStringAndTOA(null, 1));
+    }
+
+    @SmallTest
+    public void testExtractNetworkPortionAlt() throws Exception {
+        assertEquals(
+                "+17005554141",
+                PhoneNumberUtils.extractNetworkPortionAlt("+17005554141")
+        );
+
+        assertEquals(
+                "+17005554141",
+                PhoneNumberUtils.extractNetworkPortionAlt("+1 (700).555-4141")
+        );
+
+        assertEquals(
+                "17005554141",
+                PhoneNumberUtils.extractNetworkPortionAlt("1 (700).555-4141")
+        );
+
+        // This may seem wrong, but it's probably ok
+        assertEquals(
+                "17005554141*#",
+                PhoneNumberUtils.extractNetworkPortionAlt("1 (700).555-4141*#")
+        );
+
+        assertEquals(
+                "170055541NN",
+                PhoneNumberUtils.extractNetworkPortionAlt("1 (700).555-41NN")
+        );
+
+        assertEquals(
+                "170055541NN",
+                PhoneNumberUtils.extractNetworkPortionAlt("1 (700).555-41NN,1234")
+        );
+
+        assertEquals(
+                "170055541NN",
+                PhoneNumberUtils.extractNetworkPortionAlt("1 (700).555-41NN;1234")
+        );
+
+        // An MMI string is unperterbed, even though it contains a
+        // (valid in this case) embedded +
+        assertEquals(
+                "**21**+17005554141#",
+                PhoneNumberUtils.extractNetworkPortionAlt("**21**+17005554141#")
+        );
+
+        assertEquals(
+                "*31#+447966164208",
+                PhoneNumberUtils.extractNetworkPortionAlt("*31#+447966164208")
+        );
+
+        assertEquals(
+                "*31#+447966164208",
+                PhoneNumberUtils.extractNetworkPortionAlt("*31# (+44) 79 6616 4208")
+        );
+
+        assertEquals("", PhoneNumberUtils.extractNetworkPortionAlt(""));
+
+        assertEquals("", PhoneNumberUtils.extractNetworkPortionAlt(",1234"));
+
+        assertNull(PhoneNumberUtils.extractNetworkPortionAlt(null));
+    }
+
+    @SmallTest
+    public void testB() throws Exception {
+        assertEquals("", PhoneNumberUtils.extractPostDialPortion("+17005554141"));
+        assertEquals("", PhoneNumberUtils.extractPostDialPortion("+1 (700).555-4141"));
+        assertEquals("", PhoneNumberUtils.extractPostDialPortion("+1 (700).555-41NN"));
+        assertEquals(",1234", PhoneNumberUtils.extractPostDialPortion("+1 (700).555-41NN,1234"));
+        assertEquals(";1234", PhoneNumberUtils.extractPostDialPortion("+1 (700).555-41NN;1234"));
+        assertEquals(";1234,;N",
+                PhoneNumberUtils.extractPostDialPortion("+1 (700).555-41NN;1-2.34 ,;N"));
+    }
+
+    @SmallTest
+    public void testCompare() throws Exception {
+        // this is odd
+        assertFalse(PhoneNumberUtils.compare("", ""));
+
+        assertTrue(PhoneNumberUtils.compare("911", "911"));
+        assertFalse(PhoneNumberUtils.compare("911", "18005550911"));
+        assertTrue(PhoneNumberUtils.compare("5555", "5555"));
+        assertFalse(PhoneNumberUtils.compare("5555", "180055555555"));
+
+        assertTrue(PhoneNumberUtils.compare("+17005554141", "+17005554141"));
+        assertTrue(PhoneNumberUtils.compare("+17005554141", "+1 (700).555-4141"));
+        assertTrue(PhoneNumberUtils.compare("+17005554141", "+1 (700).555-4141,1234"));
+        assertTrue(PhoneNumberUtils.compare("+17005554141", "17005554141"));
+        assertTrue(PhoneNumberUtils.compare("+17005554141", "7005554141"));
+        assertTrue(PhoneNumberUtils.compare("+17005554141", "5554141"));
+        assertTrue(PhoneNumberUtils.compare("17005554141", "5554141"));
+        assertTrue(PhoneNumberUtils.compare("+17005554141", "01117005554141"));
+        assertTrue(PhoneNumberUtils.compare("+17005554141", "0017005554141"));
+        assertTrue(PhoneNumberUtils.compare("17005554141", "0017005554141"));
+
+
+        assertTrue(PhoneNumberUtils.compare("+17005554141", "**31#+17005554141"));
+
+        assertFalse(PhoneNumberUtils.compare("+1 999 7005554141", "+1 7005554141"));
+        assertTrue(PhoneNumberUtils.compare("011 1 7005554141", "7005554141"));
+
+        assertFalse(PhoneNumberUtils.compare("011 11 7005554141", "+17005554141"));
+
+        assertFalse(PhoneNumberUtils.compare("+17005554141", "7085882300"));
+
+        assertTrue(PhoneNumberUtils.compare("+44 207 792 3490", "0 207 792 3490"));
+
+        assertFalse(PhoneNumberUtils.compare("+44 207 792 3490", "00 207 792 3490"));
+        assertFalse(PhoneNumberUtils.compare("+44 207 792 3490", "011 207 792 3490"));
+
+        /***** FIXME's ******/
+        //
+        // MMI header should be ignored
+        assertFalse(PhoneNumberUtils.compare("+17005554141", "**31#17005554141"));
+
+        // It's too bad this is false
+        // +44 (0) 207 792 3490 is not a dialable number
+        // but it is commonly how European phone numbers are written
+        assertFalse(PhoneNumberUtils.compare("+44 207 792 3490", "+44 (0) 207 792 3490"));
+
+        // The japanese international prefix, for example, messes us up
+        // But who uses a GSM phone in Japan?
+        assertFalse(PhoneNumberUtils.compare("+44 207 792 3490", "010 44 207 792 3490"));
+
+        // The Australian one messes us up too
+        assertFalse(PhoneNumberUtils.compare("+44 207 792 3490", "0011 44 207 792 3490"));
+
+        // The Russian trunk prefix messes us up, as does current
+        // Russian area codes (which bein with 0)
+
+        assertFalse(PhoneNumberUtils.compare("+7(095)9100766", "8(095)9100766"));
+
+        // 444 is not a valid country code, but
+        // matchIntlPrefixAndCC doesnt know this
+        assertTrue(PhoneNumberUtils.compare("+444 207 792 3490", "0 207 792 3490"));
+
+        // compare SMS short code
+        assertTrue(PhoneNumberUtils.compare("404-04", "40404"));
+    }
+
+
+    @SmallTest
+    public void testToCallerIDIndexable() throws Exception {
+        assertEquals("1414555", PhoneNumberUtils.toCallerIDMinMatch("17005554141"));
+        assertEquals("1414555", PhoneNumberUtils.toCallerIDMinMatch("1-700-555-4141"));
+        assertEquals("1414555", PhoneNumberUtils.toCallerIDMinMatch("1-700-555-4141,1234"));
+        assertEquals("1414555", PhoneNumberUtils.toCallerIDMinMatch("1-700-555-4141;1234"));
+
+        //this seems wrong, or at least useless
+        assertEquals("NN14555", PhoneNumberUtils.toCallerIDMinMatch("1-700-555-41NN"));
+
+        //<shrug> -- these are all not useful, but not terribly wrong
+        assertEquals("", PhoneNumberUtils.toCallerIDMinMatch(""));
+        assertEquals("0032", PhoneNumberUtils.toCallerIDMinMatch("2300"));
+        assertEquals("0032+", PhoneNumberUtils.toCallerIDMinMatch("+2300"));
+        assertEquals("#130#*", PhoneNumberUtils.toCallerIDMinMatch("*#031#"));
+    }
+
+    @SmallTest
+    public void testGetIndexable() throws Exception {
+        assertEquals("14145550071", PhoneNumberUtils.getStrippedReversed("1-700-555-4141"));
+        assertEquals("14145550071", PhoneNumberUtils.getStrippedReversed("1-700-555-4141,1234"));
+        assertEquals("14145550071", PhoneNumberUtils.getStrippedReversed("1-700-555-4141;1234"));
+
+        //this seems wrong, or at least useless
+        assertEquals("NN145550071", PhoneNumberUtils.getStrippedReversed("1-700-555-41NN"));
+
+        //<shrug> -- these are all not useful, but not terribly wrong
+        assertEquals("", PhoneNumberUtils.getStrippedReversed(""));
+        assertEquals("0032", PhoneNumberUtils.getStrippedReversed("2300"));
+        assertEquals("0032+", PhoneNumberUtils.getStrippedReversed("+2300"));
+        assertEquals("#130#*", PhoneNumberUtils.getStrippedReversed("*#031#"));
+    }
+
+    @SmallTest
+    public void testNanpFormatting() {
+        SpannableStringBuilder number = new SpannableStringBuilder();
+        number.append("8005551212");
+        PhoneNumberUtils.formatNanpNumber(number);
+        assertEquals("800-555-1212", number.toString());
+
+        number.clear();
+        number.append("800555121");
+        PhoneNumberUtils.formatNanpNumber(number);
+        assertEquals("800-555-121", number.toString());
+
+        number.clear();
+        number.append("555-1212");
+        PhoneNumberUtils.formatNanpNumber(number);
+        assertEquals("555-1212", number.toString());
+
+        number.clear();
+        number.append("800-55512");
+        PhoneNumberUtils.formatNanpNumber(number);
+        assertEquals("800-555-12", number.toString());
+
+        number.clear();
+        number.append("46645");
+        PhoneNumberUtils.formatNanpNumber(number);
+        assertEquals("46645", number.toString());
+    }
+
+    @SmallTest
+    public void testConvertKeypadLettersToDigits() {
+        assertEquals("1-800-4664-411",
+                     PhoneNumberUtils.convertKeypadLettersToDigits("1-800-GOOG-411"));
+        assertEquals("18004664411",
+                     PhoneNumberUtils.convertKeypadLettersToDigits("1800GOOG411"));
+        assertEquals("1-800-466-4411",
+                     PhoneNumberUtils.convertKeypadLettersToDigits("1-800-466-4411"));
+        assertEquals("18004664411",
+                     PhoneNumberUtils.convertKeypadLettersToDigits("18004664411"));
+        assertEquals("222-333-444-555-666-7777-888-9999",
+                     PhoneNumberUtils.convertKeypadLettersToDigits(
+                             "ABC-DEF-GHI-JKL-MNO-PQRS-TUV-WXYZ"));
+        assertEquals("222-333-444-555-666-7777-888-9999",
+                     PhoneNumberUtils.convertKeypadLettersToDigits(
+                             "abc-def-ghi-jkl-mno-pqrs-tuv-wxyz"));
+        assertEquals("(800) 222-3334",
+                     PhoneNumberUtils.convertKeypadLettersToDigits("(800) ABC-DEFG"));
+    }
+
+    // To run this test, the device has to be registered with network
+    public void testCheckAndProcessPlusCode() {
+        assertEquals("0118475797000",
+                PhoneNumberUtils.cdmaCheckAndProcessPlusCode("+8475797000"));
+        assertEquals("18475797000",
+                PhoneNumberUtils.cdmaCheckAndProcessPlusCode("+18475797000"));
+        assertEquals("0111234567",
+                PhoneNumberUtils.cdmaCheckAndProcessPlusCode("+1234567"));
+        assertEquals("01123456700000",
+                PhoneNumberUtils.cdmaCheckAndProcessPlusCode("+23456700000"));
+        assertEquals("01111875767800",
+                PhoneNumberUtils.cdmaCheckAndProcessPlusCode("+11875767800"));
+        assertEquals("8475797000,18475231753",
+                PhoneNumberUtils.cdmaCheckAndProcessPlusCode("8475797000,+18475231753"));
+        assertEquals("0118475797000,18475231753",
+                PhoneNumberUtils.cdmaCheckAndProcessPlusCode("+8475797000,+18475231753"));
+        assertEquals("8475797000;0118469312345",
+                PhoneNumberUtils.cdmaCheckAndProcessPlusCode("8475797000;+8469312345"));
+        assertEquals("8475797000,0111234567",
+                PhoneNumberUtils.cdmaCheckAndProcessPlusCode("8475797000,+1234567"));
+        assertEquals("847597000;01111875767000",
+                PhoneNumberUtils.cdmaCheckAndProcessPlusCode("847597000;+11875767000"));
+        assertEquals("8475797000,,0118469312345",
+                PhoneNumberUtils.cdmaCheckAndProcessPlusCode("8475797000,,+8469312345"));
+        assertEquals("8475797000;,0118469312345",
+                PhoneNumberUtils.cdmaCheckAndProcessPlusCode("8475797000;,+8469312345"));
+        assertEquals("8475797000,;18475231753",
+                PhoneNumberUtils.cdmaCheckAndProcessPlusCode("8475797000,;+18475231753"));
+        assertEquals("8475797000;,01111875767000",
+                PhoneNumberUtils.cdmaCheckAndProcessPlusCode("8475797000;,+11875767000"));
+        assertEquals("8475797000,;01111875767000",
+                PhoneNumberUtils.cdmaCheckAndProcessPlusCode("8475797000,;+11875767000"));
+        assertEquals("8475797000,,,01111875767000",
+                PhoneNumberUtils.cdmaCheckAndProcessPlusCode("8475797000,,,+11875767000"));
+        assertEquals("8475797000;,,01111875767000",
+                PhoneNumberUtils.cdmaCheckAndProcessPlusCode("8475797000;,,+11875767000"));
+        assertEquals("+;,8475797000",
+                PhoneNumberUtils.cdmaCheckAndProcessPlusCode("+;,8475797000"));
+        assertEquals("8475797000,",
+                PhoneNumberUtils.cdmaCheckAndProcessPlusCode("8475797000,"));
+        assertEquals("847+579-7000",
+                PhoneNumberUtils.cdmaCheckAndProcessPlusCode("847+579-7000"));
+        assertEquals(",8475797000",
+                PhoneNumberUtils.cdmaCheckAndProcessPlusCode(",8475797000"));
+        assertEquals(";;8475797000,,",
+                PhoneNumberUtils.cdmaCheckAndProcessPlusCode(";;8475797000,,"));
+        assertEquals("+this+is$weird;,+",
+                PhoneNumberUtils.cdmaCheckAndProcessPlusCode("+this+is$weird;,+"));
+        assertEquals("",
+                PhoneNumberUtils.cdmaCheckAndProcessPlusCode(""));
+        assertNull(PhoneNumberUtils.cdmaCheckAndProcessPlusCode(null));
+    }
+
+    @SmallTest
+    public void testCheckAndProcessPlusCodeByNumberFormat() {
+        assertEquals("18475797000",
+                PhoneNumberUtils.cdmaCheckAndProcessPlusCodeByNumberFormat("+18475797000",
+                PhoneNumberUtils.FORMAT_NANP,PhoneNumberUtils.FORMAT_NANP));
+        assertEquals("+18475797000",
+                PhoneNumberUtils.cdmaCheckAndProcessPlusCodeByNumberFormat("+18475797000",
+                PhoneNumberUtils.FORMAT_NANP,PhoneNumberUtils.FORMAT_JAPAN));
+        assertEquals("+18475797000",
+                PhoneNumberUtils.cdmaCheckAndProcessPlusCodeByNumberFormat("+18475797000",
+                PhoneNumberUtils.FORMAT_NANP,PhoneNumberUtils.FORMAT_UNKNOWN));
+        assertEquals("+18475797000",
+                PhoneNumberUtils.cdmaCheckAndProcessPlusCodeByNumberFormat("+18475797000",
+                PhoneNumberUtils.FORMAT_JAPAN,PhoneNumberUtils.FORMAT_JAPAN));
+        assertEquals("+18475797000",
+                PhoneNumberUtils.cdmaCheckAndProcessPlusCodeByNumberFormat("+18475797000",
+                PhoneNumberUtils.FORMAT_UNKNOWN,PhoneNumberUtils.FORMAT_UNKNOWN));
+    }
+
+    /**
+     * Basic checks for the VoiceMail number.
+     */
+    @SmallTest
+    public void testWithNumberNotEqualToVoiceMail() throws Exception {
+        assertFalse(PhoneNumberUtils.isVoiceMailNumber("911"));
+        assertFalse(PhoneNumberUtils.isVoiceMailNumber("tel:911"));
+        assertFalse(PhoneNumberUtils.isVoiceMailNumber("+18001234567"));
+        assertFalse(PhoneNumberUtils.isVoiceMailNumber(""));
+        assertFalse(PhoneNumberUtils.isVoiceMailNumber(null));
+        // This test fails on a device without a sim card
+        /*TelephonyManager mTelephonyManager =
+            (TelephonyManager)getContext().getSystemService(Context.TELEPHONY_SERVICE);
+        String mVoiceMailNumber = mTelephonyManager.getDefault().getVoiceMailNumber();
+        assertTrue(PhoneNumberUtils.isVoiceMailNumber(mVoiceMailNumber));
+        */
+    }
+
+    @SmallTest
+    public void testFormatNumberToE164() {
+        assertEquals("+16502910000", PhoneNumberUtils.formatNumberToE164("650 2910000", "us"));
+        assertNull(PhoneNumberUtils.formatNumberToE164("1234567", "us"));
+        assertEquals("+18004664114", PhoneNumberUtils.formatNumberToE164("800-GOOG-114", "us"));
+    }
+
+    @SmallTest
+    public void testFormatNumber() {
+        assertEquals("(650) 291-0000", PhoneNumberUtils.formatNumber("650 2910000", "us"));
+        assertEquals("123-4567", PhoneNumberUtils.formatNumber("1234567", "us"));
+        assertEquals("(800) 466-4114", PhoneNumberUtils.formatNumber("800-GOOG-114", "us"));
+
+    }
+
+    @SmallTest
+    public void testNormalizeNumber() {
+        assertEquals("6502910000", PhoneNumberUtils.normalizeNumber("650 2910000"));
+        assertEquals("1234567", PhoneNumberUtils.normalizeNumber("12,3#4*567"));
+        assertEquals("8004664114", PhoneNumberUtils.normalizeNumber("800-GOOG-114"));
+    }
+
+}
diff --git a/telephony/tests/telephonytests/src/com/android/internal/telephony/PhoneNumberWatcherTest.java b/telephony/tests/telephonytests/src/com/android/internal/telephony/PhoneNumberWatcherTest.java
new file mode 100644
index 0000000..d2e573c
--- /dev/null
+++ b/telephony/tests/telephonytests/src/com/android/internal/telephony/PhoneNumberWatcherTest.java
@@ -0,0 +1,214 @@
+/*
+ * 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.
+ */
+package com.android.internal.telephony;
+
+import android.telephony.PhoneNumberFormattingTextWatcher;
+import android.test.AndroidTestCase;
+import android.text.Editable;
+import android.text.Selection;
+import android.text.SpannableStringBuilder;
+import android.text.TextWatcher;
+
+public class PhoneNumberWatcherTest extends AndroidTestCase {
+    public void testAppendChars() {
+        final String multiChars = "65012345";
+        final String formatted1 = "(650) 123-45";
+        TextWatcher textWatcher = getTextWatcher();
+        SpannableStringBuilder number = new SpannableStringBuilder();
+        // Append more than one chars
+        textWatcher.beforeTextChanged(number, 0, 0, multiChars.length());
+        number.append(multiChars);
+        Selection.setSelection(number, number.length());
+        textWatcher.onTextChanged(number, 0, 0, number.length());
+        textWatcher.afterTextChanged(number);
+        assertEquals(formatted1, number.toString());
+        assertEquals(formatted1.length(), Selection.getSelectionEnd(number));
+        // Append one chars
+        final char appendChar = '6';
+        final String formatted2 = "(650) 123-456";
+        int len = number.length();
+        textWatcher.beforeTextChanged(number, number.length(), 0, 1);
+        number.append(appendChar);
+        Selection.setSelection(number, number.length());
+        textWatcher.onTextChanged(number, len, 0, 1);
+        textWatcher.afterTextChanged(number);
+        assertEquals(formatted2, number.toString());
+        assertEquals(formatted2.length(), Selection.getSelectionEnd(number));
+    }
+
+    public void testRemoveLastChars() {
+        final String init = "65012345678";
+        final String result1 = "(650) 123-4567";
+        TextWatcher textWatcher = getTextWatcher();
+        // Remove the last char.
+        SpannableStringBuilder number = new SpannableStringBuilder(init);
+        int len = number.length();
+        textWatcher.beforeTextChanged(number, len - 1, 1, 0);
+        number.delete(len - 1, len);
+        Selection.setSelection(number, number.length());
+        textWatcher.onTextChanged(number, number.length() - 1, 1, 0);
+        textWatcher.afterTextChanged(number);
+        assertEquals(result1, number.toString());
+        assertEquals(result1.length(), Selection.getSelectionEnd(number));
+        // Remove last 5 chars
+        final String result2 = "(650) 123";
+        textWatcher.beforeTextChanged(number, number.length() - 4, 4, 0);
+        number.delete(number.length() - 5, number.length());
+        Selection.setSelection(number, number.length());
+        textWatcher.onTextChanged(number, number.length(), 4, 0);
+        textWatcher.afterTextChanged(number);
+        assertEquals(result2, number.toString());
+        assertEquals(result2.length(), Selection.getSelectionEnd(number));
+    }
+
+    public void testInsertChars() {
+        final String init = "(650) 23";
+        final String expected1 = "(650) 123";
+        TextWatcher textWatcher = getTextWatcher();
+
+        // Insert one char
+        SpannableStringBuilder number = new SpannableStringBuilder(init);
+        textWatcher.beforeTextChanged(number, 4, 0, 1);
+        number.insert(4, "1"); // (6501) 23
+        Selection.setSelection(number, 5); // make the cursor at right of 1
+        textWatcher.onTextChanged(number, 4, 0, 1);
+        textWatcher.afterTextChanged(number);
+        assertEquals(expected1, number.toString());
+        // the cursor should still at the right of '1'
+        assertEquals(7, Selection.getSelectionEnd(number));
+
+        // Insert multiple chars
+        final String expected2 = "(650) 145-6723";
+        textWatcher.beforeTextChanged(number, 7, 0, 4);
+        number.insert(7, "4567"); // change to (650) 1456723
+        Selection.setSelection(number, 11); // the cursor is at the right of '7'.
+        textWatcher.onTextChanged(number, 7, 0, 4);
+        textWatcher.afterTextChanged(number);
+        assertEquals(expected2, number.toString());
+        // the cursor should be still at the right of '7'
+        assertEquals(12, Selection.getSelectionEnd(number));
+    }
+
+    public void testStopFormatting() {
+        final String init = "(650) 123";
+        final String expected1 = "(650) 123 4";
+        TextWatcher textWatcher = getTextWatcher();
+
+        // Append space
+        SpannableStringBuilder number = new SpannableStringBuilder(init);
+        textWatcher.beforeTextChanged(number, 9, 0, 2);
+        number.insert(9, " 4"); // (6501) 23 4
+        Selection.setSelection(number, number.length()); // make the cursor at right of 4
+        textWatcher.onTextChanged(number, 9, 0, 2);
+        textWatcher.afterTextChanged(number);
+        assertEquals(expected1, number.toString());
+        // the cursor should still at the right of '1'
+        assertEquals(expected1.length(), Selection.getSelectionEnd(number));
+
+        // Delete a ')'
+        final String expected2 ="(650 123";
+        textWatcher = getTextWatcher();
+        number = new SpannableStringBuilder(init);
+        textWatcher.beforeTextChanged(number, 4, 1, 0);
+        number.delete(4, 5); // (6501 23 4
+        Selection.setSelection(number, 5); // make the cursor at right of 1
+        textWatcher.onTextChanged(number, 4, 1, 0);
+        textWatcher.afterTextChanged(number);
+        assertEquals(expected2, number.toString());
+        // the cursor should still at the right of '1'
+        assertEquals(5, Selection.getSelectionEnd(number));
+
+        // Insert a hyphen
+        final String expected3 ="(650) 12-3";
+        textWatcher = getTextWatcher();
+        number = new SpannableStringBuilder(init);
+        textWatcher.beforeTextChanged(number, 8, 0, 1);
+        number.insert(8, "-"); // (650) 12-3
+        Selection.setSelection(number, 9); // make the cursor at right of -
+        textWatcher.onTextChanged(number, 8, 0, 1);
+        textWatcher.afterTextChanged(number);
+        assertEquals(expected3, number.toString());
+        // the cursor should still at the right of '-'
+        assertEquals(9, Selection.getSelectionEnd(number));
+    }
+
+    public void testRestartFormatting() {
+        final String init = "(650) 123";
+        final String expected1 = "(650) 123 4";
+        TextWatcher textWatcher = getTextWatcher();
+
+        // Append space
+        SpannableStringBuilder number = new SpannableStringBuilder(init);
+        textWatcher.beforeTextChanged(number, 9, 0, 2);
+        number.insert(9, " 4"); // (650) 123 4
+        Selection.setSelection(number, number.length()); // make the cursor at right of 4
+        textWatcher.onTextChanged(number, 9, 0, 2);
+        textWatcher.afterTextChanged(number);
+        assertEquals(expected1, number.toString());
+        // the cursor should still at the right of '4'
+        assertEquals(expected1.length(), Selection.getSelectionEnd(number));
+
+        // Clear the current string, and start formatting again.
+        int len = number.length();
+        textWatcher.beforeTextChanged(number, 0, len, 0);
+        number.delete(0, len);
+        textWatcher.onTextChanged(number, 0, len, 0);
+        textWatcher.afterTextChanged(number);
+
+        final String expected2 = "(650) 123-4";
+        number = new SpannableStringBuilder(init);
+        textWatcher.beforeTextChanged(number, 9, 0, 1);
+        number.insert(9, "4"); // (650) 1234
+        Selection.setSelection(number, number.length()); // make the cursor at right of 4
+        textWatcher.onTextChanged(number, 9, 0, 1);
+        textWatcher.afterTextChanged(number);
+        assertEquals(expected2, number.toString());
+        // the cursor should still at the right of '4'
+        assertEquals(expected2.length(), Selection.getSelectionEnd(number));
+    }
+
+    public void testTextChangedByOtherTextWatcher() {
+        final TextWatcher cleanupTextWatcher = new TextWatcher() {
+            public void afterTextChanged(Editable s) {
+                s.clear();
+            }
+
+            public void beforeTextChanged(CharSequence s, int start, int count,
+                    int after) {
+            }
+
+            public void onTextChanged(CharSequence s, int start, int before,
+                    int count) {
+            }
+        };
+        final String init = "(650) 123";
+        final String expected1 = "";
+        TextWatcher textWatcher = getTextWatcher();
+
+        SpannableStringBuilder number = new SpannableStringBuilder(init);
+        textWatcher.beforeTextChanged(number, 5, 0, 1);
+        number.insert(5, "4"); // (6504) 123
+        Selection.setSelection(number, 5); // make the cursor at right of 4
+        textWatcher.onTextChanged(number, 5, 0, 1);
+        number.setSpan(cleanupTextWatcher, 0, number.length(), 0);
+        textWatcher.afterTextChanged(number);
+        assertEquals(expected1, number.toString());
+    }
+
+    private TextWatcher getTextWatcher() {
+        return new PhoneNumberFormattingTextWatcher("US");
+    }
+}
diff --git a/telephony/tests/telephonytests/src/com/android/internal/telephony/TestPhoneNotifier.java b/telephony/tests/telephonytests/src/com/android/internal/telephony/TestPhoneNotifier.java
index 427795b..8cb05cc 100644
--- a/telephony/tests/telephonytests/src/com/android/internal/telephony/TestPhoneNotifier.java
+++ b/telephony/tests/telephonytests/src/com/android/internal/telephony/TestPhoneNotifier.java
@@ -16,6 +16,8 @@
 
 package com.android.internal.telephony;
 
+import com.android.internal.telephony.Phone;
+
 /**
  * Stub class used for unit tests
  */
@@ -32,7 +34,7 @@
 
     public void notifyCellLocation(Phone sender) {
     }
-    
+
     public void notifySignalStrength(Phone sender) {
     }
 
@@ -42,10 +44,14 @@
     public void notifyCallForwardingChanged(Phone sender) {
     }
 
-    public void notifyDataConnection(Phone sender, String reason) {
+    public void notifyDataConnection(Phone sender, String reason, String apnType) {
     }
 
-    public void notifyDataConnectionFailed(Phone sender, String reason) {
+    public void notifyDataConnection(Phone sender, String reason, String apnType,
+            Phone.DataState state) {
+    }
+
+    public void notifyDataConnectionFailed(Phone sender, String reason, String apnType) {
     }
 
     public void notifyDataActivity(Phone sender) {
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/telephony/tests/telephonytests/src/com/android/internal/telephony/mockril/MockRilTest.java b/telephony/tests/telephonytests/src/com/android/internal/telephony/mockril/MockRilTest.java
new file mode 100644
index 0000000..ee03b7c
--- /dev/null
+++ b/telephony/tests/telephonytests/src/com/android/internal/telephony/mockril/MockRilTest.java
@@ -0,0 +1,176 @@
+/*
+ * 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.internal.telephony.mockril;
+
+import android.util.Log;
+import android.test.InstrumentationTestCase;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+import com.android.internal.communication.MsgHeader;
+import com.android.internal.communication.Msg;
+import com.android.internal.telephony.RilChannel;
+import com.android.internal.telephony.ril_proto.RilCtrlCmds;
+
+import com.android.frameworks.telephonytests.TelephonyMockRilTestRunner;
+import com.google.protobuf.micro.InvalidProtocolBufferMicroException;
+
+// Test suite for test ril
+public class MockRilTest extends InstrumentationTestCase {
+    private static final String TAG = "MockRilTest";
+
+    RilChannel mMockRilChannel;
+    TelephonyMockRilTestRunner mRunner;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mRunner = (TelephonyMockRilTestRunner)getInstrumentation();
+        mMockRilChannel = mRunner.mMockRilChannel;
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    static void log(String s) {
+        Log.v(TAG, s);
+    }
+
+    /**
+     * Test protobuf serialization and deserialization
+     * @throws InvalidProtocolBufferMicroException
+     */
+    public void testProtobufSerDes() throws InvalidProtocolBufferMicroException {
+        log("testProtobufSerdes E");
+
+        RilCtrlCmds.CtrlRspRadioState rs = new RilCtrlCmds.CtrlRspRadioState();
+        assertTrue(String.format("expected rs.state == 0 was %d", rs.getState()),
+                rs.getState() == 0);
+        rs.setState(1);
+        assertTrue(String.format("expected rs.state == 1 was %d", rs.getState()),
+                rs.getState() == 1);
+
+        byte[] rs_ser = rs.toByteArray();
+        RilCtrlCmds.CtrlRspRadioState rsNew = RilCtrlCmds.CtrlRspRadioState.parseFrom(rs_ser);
+        assertTrue(String.format("expected rsNew.state == 1 was %d", rs.getState()),
+                rs.getState() == 1);
+
+        log("testProtobufSerdes X");
+    }
+
+    /**
+     * Test echo command works using writeMsg & readMsg
+     */
+    public void testEchoMsg() throws IOException {
+        log("testEchoMsg E");
+
+        MsgHeader mh = new MsgHeader();
+        mh.setCmd(0);
+        mh.setToken(1);
+        mh.setStatus(2);
+        ByteBuffer data = ByteBuffer.allocate(3);
+        data.put((byte)3);
+        data.put((byte)4);
+        data.put((byte)5);
+        Msg.send(mMockRilChannel, mh, data);
+
+        Msg respMsg = Msg.recv(mMockRilChannel);
+        assertTrue(String.format("expected mhd.header.cmd == 0 was %d",respMsg.getCmd()),
+                respMsg.getCmd() == 0);
+        assertTrue(String.format("expected mhd.header.token == 1 was %d",respMsg.getToken()),
+                respMsg.getToken() == 1);
+        assertTrue(String.format("expected mhd.header.status == 2 was %d", respMsg.getStatus()),
+                respMsg.getStatus() == 2);
+        assertTrue(String.format("expected mhd.data[0] == 3 was %d", respMsg.getData(0)),
+                respMsg.getData(0) == 3);
+        assertTrue(String.format("expected mhd.data[1] == 4 was %d", respMsg.getData(1)),
+                respMsg.getData(1) == 4);
+        assertTrue(String.format("expected mhd.data[2] == 5 was %d", respMsg.getData(2)),
+                respMsg.getData(2) == 5);
+
+        log("testEchoMsg X");
+    }
+
+    /**
+     * Test get as
+     */
+    public void testGetAs() {
+        log("testGetAs E");
+
+        // Use a message header as the protobuf data content
+        MsgHeader mh = new MsgHeader();
+        mh.setCmd(12345);
+        mh.setToken(9876);
+        mh.setStatus(7654);
+        mh.setLengthData(4321);
+        byte[] data = mh.toByteArray();
+        MsgHeader mhResult = Msg.getAs(MsgHeader.class, data);
+
+        assertTrue(String.format("expected cmd == 12345 was %d", mhResult.getCmd()),
+                mhResult.getCmd() == 12345);
+        assertTrue(String.format("expected token == 9876 was %d", mhResult.getToken()),
+                mhResult.getToken() == 9876);
+        assertTrue(String.format("expected status == 7654 was %d", mhResult.getStatus()),
+                mhResult.getStatus() == 7654);
+        assertTrue(String.format("expected lengthData == 4321 was %d", mhResult.getLengthData()),
+                mhResult.getLengthData() == 4321);
+
+        Msg msg = Msg.obtain();
+        msg.setData(ByteBuffer.wrap(data));
+
+        mhResult = msg.getDataAs(MsgHeader.class);
+
+        assertTrue(String.format("expected cmd == 12345 was %d", mhResult.getCmd()),
+                mhResult.getCmd() == 12345);
+        assertTrue(String.format("expected token == 9876 was %d", mhResult.getToken()),
+                mhResult.getToken() == 9876);
+        assertTrue(String.format("expected status == 7654 was %d", mhResult.getStatus()),
+                mhResult.getStatus() == 7654);
+        assertTrue(String.format("expected lengthData == 4321 was %d", mhResult.getLengthData()),
+                mhResult.getLengthData() == 4321);
+
+        log("testGetAs X");
+    }
+
+    public void testGetRadioState() throws IOException {
+        log("testGetRadioState E");
+
+        Msg.send(mMockRilChannel, 1, 9876, 0, null);
+
+        Msg resp = Msg.recv(mMockRilChannel);
+        //resp.printHeader("testGetRadioState");
+
+        assertTrue(String.format("expected cmd == 1 was %d", resp.getCmd()),
+                resp.getCmd() == 1);
+        assertTrue(String.format("expected token == 9876 was %d", resp.getToken()),
+                resp.getToken() == 9876);
+        assertTrue(String.format("expected status == 0 was %d", resp.getStatus()),
+                resp.getStatus() == 0);
+
+        RilCtrlCmds.CtrlRspRadioState rsp = resp.getDataAs(RilCtrlCmds.CtrlRspRadioState.class);
+
+        int state = rsp.getState();
+        log("testGetRadioState state=" + state);
+        assertTrue(String.format("expected RadioState >= 0 && RadioState <= 9 was %d", state),
+                ((state >= 0) && (state <= 9)));
+
+        log("testGetRadioState X");
+    }
+}
diff --git a/test-runner/src/android/test/LoaderTestCase.java b/test-runner/src/android/test/LoaderTestCase.java
new file mode 100644
index 0000000..8be6590
--- /dev/null
+++ b/test-runner/src/android/test/LoaderTestCase.java
@@ -0,0 +1,101 @@
+/*
+ * 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;
+
+import android.content.Loader;
+import android.content.Loader.OnLoadCompleteListener;
+import android.os.AsyncTask;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+
+import java.util.concurrent.ArrayBlockingQueue;
+
+/**
+ * A convenience class for testing {@link Loader}s. This test case
+ * provides a simple way to synchronously get the result from a Loader making
+ * it easy to assert that the Loader returns the expected result.
+ */
+public class LoaderTestCase extends AndroidTestCase {
+    static {
+        // Force class loading of AsyncTask on the main thread so that it's handlers are tied to
+        // the main thread and responses from the worker thread get delivered on the main thread.
+        // The tests are run on another thread, allowing them to block waiting on a response from
+        // the code running on the main thread. The main thread can't block since the AysncTask
+        // results come in via the event loop.
+        new AsyncTask<Void, Void, Void>() {
+            @Override
+            protected Void doInBackground(Void... args) {return null;}
+            @Override
+            protected void onPostExecute(Void result) {}
+        };
+    }
+
+    /**
+     * Runs a Loader synchronously and returns the result of the load. The loader will
+     * be started, stopped, and destroyed by this method so it cannot be reused.
+     *
+     * @param loader The loader to run synchronously
+     * @return The result from the loader
+     */
+    public <T> T getLoaderResultSynchronously(final Loader<T> loader) {
+        // The test thread blocks on this queue until the loader puts it's result in
+        final ArrayBlockingQueue<T> queue = new ArrayBlockingQueue<T>(1);
+
+        // This callback runs on the "main" thread and unblocks the test thread
+        // when it puts the result into the blocking queue
+        final OnLoadCompleteListener<T> listener = new OnLoadCompleteListener<T>() {
+            @Override
+            public void onLoadComplete(Loader<T> completedLoader, T data) {
+                // Shut the loader down
+                completedLoader.unregisterListener(this);
+                completedLoader.stopLoading();
+                completedLoader.destroy();
+
+                // Store the result, unblocking the test thread
+                queue.add(data);
+            }
+        };
+
+        // This handler runs on the "main" thread of the process since AsyncTask
+        // is documented as needing to run on the main thread and many Loaders use
+        // AsyncTask
+        final Handler mainThreadHandler = new Handler(Looper.getMainLooper()) {
+            @Override
+            public void handleMessage(Message msg) {
+                loader.registerListener(0, listener);
+                loader.startLoading();
+            }
+        };
+
+        // Ask the main thread to start the loading process
+        mainThreadHandler.sendEmptyMessage(0);
+
+        // Block on the queue waiting for the result of the load to be inserted
+        T result;
+        while (true) {
+            try {
+                result = queue.take();
+                break;
+            } catch (InterruptedException e) {
+                throw new RuntimeException("waiting thread interrupted", e);
+            }
+        }
+
+        return result;
+    }
+}
diff --git a/test-runner/src/android/test/ProviderTestCase.java b/test-runner/src/android/test/ProviderTestCase.java
index e1172cf..74cebee 100644
--- a/test-runner/src/android/test/ProviderTestCase.java
+++ b/test-runner/src/android/test/ProviderTestCase.java
@@ -73,6 +73,18 @@
         mResolver.addProvider(mProviderAuthority, getProvider());
     }
 
+    /**
+     * Tears down the environment for the test fixture.
+     * <p>
+     * Calls {@link android.content.ContentProvider#shutdown()} on the
+     * {@link android.content.ContentProvider} represented by mProvider.
+     */
+    @Override
+    protected void tearDown() throws Exception {
+        mProvider.shutdown();
+        super.tearDown();
+    }
+
     public MockContentResolver getMockContentResolver() {
         return mResolver;
     }
diff --git a/test-runner/src/android/test/ProviderTestCase2.java b/test-runner/src/android/test/ProviderTestCase2.java
index 1fb5538..64d11c5 100644
--- a/test-runner/src/android/test/ProviderTestCase2.java
+++ b/test-runner/src/android/test/ProviderTestCase2.java
@@ -141,6 +141,18 @@
     }
 
     /**
+     * Tears down the environment for the test fixture.
+     * <p>
+     * Calls {@link android.content.ContentProvider#shutdown()} on the
+     * {@link android.content.ContentProvider} represented by mProvider.
+     */
+    @Override
+    protected void tearDown() throws Exception {
+        mProvider.shutdown();
+        super.tearDown();
+    }
+
+    /**
      * Gets the {@link MockContentResolver} created by this class during initialization. You
      * must use the methods of this resolver to access the provider under test.
      *
diff --git a/test-runner/src/android/test/RenamingDelegatingContext.java b/test-runner/src/android/test/RenamingDelegatingContext.java
index 973b9f2..eee3ad7 100644
--- a/test-runner/src/android/test/RenamingDelegatingContext.java
+++ b/test-runner/src/android/test/RenamingDelegatingContext.java
@@ -21,6 +21,7 @@
 import android.content.Context;
 import android.content.ContextWrapper;
 import android.content.ContentProvider;
+import android.database.DatabaseErrorHandler;
 import android.database.sqlite.SQLiteDatabase;
 import android.os.FileUtils;
 import android.util.Log;
@@ -148,6 +149,17 @@
     }
 
     @Override
+    public SQLiteDatabase openOrCreateDatabase(String name,
+            int mode, SQLiteDatabase.CursorFactory factory, DatabaseErrorHandler errorHandler) {
+        final String internalName = renamedFileName(name);
+        if (!mDatabaseNames.contains(name)) {
+            mDatabaseNames.add(name);
+            mFileContext.deleteDatabase(internalName);
+        }
+        return mFileContext.openOrCreateDatabase(internalName, mode, factory, errorHandler);
+    }
+
+    @Override
     public boolean deleteDatabase(String name) {
         if (mDatabaseNames.contains(name)) {
             mDatabaseNames.remove(name);
diff --git a/test-runner/src/android/test/mock/MockContext.java b/test-runner/src/android/test/mock/MockContext.java
index ffd757c..c31c9cc 100644
--- a/test-runner/src/android/test/mock/MockContext.java
+++ b/test-runner/src/android/test/mock/MockContext.java
@@ -29,6 +29,7 @@
 import android.content.pm.PackageManager;
 import android.content.res.AssetManager;
 import android.content.res.Resources;
+import android.database.DatabaseErrorHandler;
 import android.database.sqlite.SQLiteDatabase;
 import android.graphics.Bitmap;
 import android.graphics.drawable.Drawable;
@@ -184,6 +185,12 @@
     }
 
     @Override
+    public SQLiteDatabase openOrCreateDatabase(String file, int mode,
+            SQLiteDatabase.CursorFactory factory, DatabaseErrorHandler errorHandler) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
     public File getDatabasePath(String name) {
         throw new UnsupportedOperationException();
     }
diff --git a/test-runner/src/android/test/mock/MockCursor.java b/test-runner/src/android/test/mock/MockCursor.java
index 9b1c0ef..baa150a 100644
--- a/test-runner/src/android/test/mock/MockCursor.java
+++ b/test-runner/src/android/test/mock/MockCursor.java
@@ -178,36 +178,11 @@
     }
 
     @SuppressWarnings("deprecation")
-    public boolean commitUpdates() {
-        throw new UnsupportedOperationException("unimplemented mock method");
-    }
-
-    @SuppressWarnings("deprecation")
-    public boolean commitUpdates(Map<? extends Long, ? extends Map<String, Object>> values) {
-        throw new UnsupportedOperationException("unimplemented mock method");
-    }
-
-    @SuppressWarnings("deprecation")
-    public boolean hasUpdates() {
-        throw new UnsupportedOperationException("unimplemented mock method");
-    }
-
-    @SuppressWarnings("deprecation")
     public void setNotificationUri(ContentResolver cr, Uri uri) {
         throw new UnsupportedOperationException("unimplemented mock method");
     }
 
     @SuppressWarnings("deprecation")
-    public boolean supportsUpdates() {
-        throw new UnsupportedOperationException("unimplemented mock method");
-    }
-
-    @SuppressWarnings("deprecation")
-    public boolean deleteRow() {
-        throw new UnsupportedOperationException("unimplemented mock method");
-    }
-
-    @SuppressWarnings("deprecation")
     public void unregisterContentObserver(ContentObserver observer) {
         throw new UnsupportedOperationException("unimplemented mock method");
     }
@@ -217,48 +192,7 @@
         throw new UnsupportedOperationException("unimplemented mock method");
     }
 
-    @SuppressWarnings("deprecation")
-    public boolean updateBlob(int columnIndex, byte[] value) {
-        throw new UnsupportedOperationException("unimplemented mock method");
-    }
-
-    @SuppressWarnings("deprecation")
-    public boolean updateDouble(int columnIndex, double value) {
-        throw new UnsupportedOperationException("unimplemented mock method");
-    }
-
-    @SuppressWarnings("deprecation")
-    public boolean updateFloat(int columnIndex, float value) {
-        throw new UnsupportedOperationException("unimplemented mock method");
-    }
-
-    @SuppressWarnings("deprecation")
-    public boolean updateInt(int columnIndex, int value) {
-        throw new UnsupportedOperationException("unimplemented mock method");
-    }
-
-    @SuppressWarnings("deprecation")
-    public boolean updateLong(int columnIndex, long value) {
-        throw new UnsupportedOperationException("unimplemented mock method");
-    }
-
-    @SuppressWarnings("deprecation")
-    public boolean updateShort(int columnIndex, short value) {
-        throw new UnsupportedOperationException("unimplemented mock method");
-    }
-
-    @SuppressWarnings("deprecation")
-    public boolean updateString(int columnIndex, String value) {
-        throw new UnsupportedOperationException("unimplemented mock method");
-    }
-
-    @SuppressWarnings("deprecation")
-    public boolean updateToNull(int columnIndex) {
-        throw new UnsupportedOperationException("unimplemented mock method");
-    }
-
-    @SuppressWarnings("deprecation")
-    public void abortUpdates() {
+    public int getType(int columnIndex) {
         throw new UnsupportedOperationException("unimplemented mock method");
     }
 }
\ No newline at end of file
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/test-runner/tests/src/android/test/suitebuilder/examples/constructor/PublicConstructorTest.java b/test-runner/tests/src/android/test/suitebuilder/examples/constructor/PublicConstructorTest.java
new file mode 100644
index 0000000..a11e25d
--- /dev/null
+++ b/test-runner/tests/src/android/test/suitebuilder/examples/constructor/PublicConstructorTest.java
@@ -0,0 +1,28 @@
+/*
+ * 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 public constructor test case that should be loaded.
+ */
+public class PublicConstructorTest extends TestCase {
+
+    public void testPublicConstructor() {
+    }
+}
diff --git a/tests/BrowserTestPlugin/jni/PluginObject.h b/tests/BrowserTestPlugin/jni/PluginObject.h
index a058d4a..037367e 100644
--- a/tests/BrowserTestPlugin/jni/PluginObject.h
+++ b/tests/BrowserTestPlugin/jni/PluginObject.h
@@ -65,7 +65,7 @@
 public:
     SubPlugin(NPP inst) : m_inst(inst) {}
     virtual ~SubPlugin() {}
-    virtual int16 handleEvent(const ANPEvent* evt) = 0;
+    virtual int16_t handleEvent(const ANPEvent* evt) = 0;
 
     NPP inst() const { return m_inst; }
 
diff --git a/tests/BrowserTestPlugin/jni/event/EventPlugin.cpp b/tests/BrowserTestPlugin/jni/event/EventPlugin.cpp
index 2eff394..91f1b3d 100644
--- a/tests/BrowserTestPlugin/jni/event/EventPlugin.cpp
+++ b/tests/BrowserTestPlugin/jni/event/EventPlugin.cpp
@@ -138,7 +138,7 @@
     browser->memfree(beginMem);
 }
 
-int16 EventPlugin::handleEvent(const ANPEvent* evt) {
+int16_t EventPlugin::handleEvent(const ANPEvent* evt) {
     switch (evt->eventType) {
 
         case kDraw_ANPEventType: {
diff --git a/tests/BrowserTestPlugin/jni/event/EventPlugin.h b/tests/BrowserTestPlugin/jni/event/EventPlugin.h
index 88b7c9d..043be85 100644
--- a/tests/BrowserTestPlugin/jni/event/EventPlugin.h
+++ b/tests/BrowserTestPlugin/jni/event/EventPlugin.h
@@ -32,7 +32,7 @@
 public:
     EventPlugin(NPP inst);
     virtual ~EventPlugin();
-    virtual int16 handleEvent(const ANPEvent* evt);
+    virtual int16_t handleEvent(const ANPEvent* evt);
 
 private:
     void drawPlugin(const ANPBitmap& bitmap, const ANPRectI& clip);
diff --git a/tests/BrowserTestPlugin/jni/main.cpp b/tests/BrowserTestPlugin/jni/main.cpp
index 402a7e2..511180c 100644
--- a/tests/BrowserTestPlugin/jni/main.cpp
+++ b/tests/BrowserTestPlugin/jni/main.cpp
@@ -34,19 +34,19 @@
 NPNetscapeFuncs* browser;
 #define EXPORT __attribute__((visibility("default")))
 
-NPError NPP_New(NPMIMEType pluginType, NPP instance, uint16 mode, int16 argc,
+NPError NPP_New(NPMIMEType pluginType, NPP instance, uint16_t mode, int16_t argc,
         char* argn[], char* argv[], NPSavedData* saved);
 NPError NPP_Destroy(NPP instance, NPSavedData** save);
 NPError NPP_SetWindow(NPP instance, NPWindow* window);
 NPError NPP_NewStream(NPP instance, NPMIMEType type, NPStream* stream,
-        NPBool seekable, uint16* stype);
+        NPBool seekable, uint16_t* stype);
 NPError NPP_DestroyStream(NPP instance, NPStream* stream, NPReason reason);
-int32   NPP_WriteReady(NPP instance, NPStream* stream);
-int32   NPP_Write(NPP instance, NPStream* stream, int32 offset, int32 len,
+int32_t   NPP_WriteReady(NPP instance, NPStream* stream);
+int32_t   NPP_Write(NPP instance, NPStream* stream, int32_t offset, int32_t len,
         void* buffer);
 void    NPP_StreamAsFile(NPP instance, NPStream* stream, const char* fname);
 void    NPP_Print(NPP instance, NPPrint* platformPrint);
-int16   NPP_HandleEvent(NPP instance, void* event);
+int16_t   NPP_HandleEvent(NPP instance, void* event);
 void    NPP_URLNotify(NPP instance, const char* URL, NPReason reason,
         void* notifyData);
 NPError NPP_GetValue(NPP instance, NPPVariable variable, void *value);
@@ -129,7 +129,7 @@
     return "application/x-browsertestplugin:btp:Android Browser Test Plugin";
 }
 
-NPError NPP_New(NPMIMEType pluginType, NPP instance, uint16 mode, int16 argc,
+NPError NPP_New(NPMIMEType pluginType, NPP instance, uint16_t mode, int16_t argc,
                 char* argn[], char* argv[], NPSavedData* saved)
 {
 
@@ -188,7 +188,7 @@
     return NPERR_NO_ERROR;
 }
 
-NPError NPP_NewStream(NPP instance, NPMIMEType type, NPStream* stream, NPBool seekable, uint16* stype)
+NPError NPP_NewStream(NPP instance, NPMIMEType type, NPStream* stream, NPBool seekable, uint16_t* stype)
 {
     *stype = NP_ASFILEONLY;
     return NPERR_NO_ERROR;
@@ -199,12 +199,12 @@
     return NPERR_NO_ERROR;
 }
 
-int32 NPP_WriteReady(NPP instance, NPStream* stream)
+int32_t NPP_WriteReady(NPP instance, NPStream* stream)
 {
     return 0;
 }
 
-int32 NPP_Write(NPP instance, NPStream* stream, int32 offset, int32 len, void* buffer)
+int32_t NPP_Write(NPP instance, NPStream* stream, int32_t offset, int32_t len, void* buffer)
 {
     return 0;
 }
@@ -217,7 +217,7 @@
 {
 }
 
-int16 NPP_HandleEvent(NPP instance, void* event)
+int16_t NPP_HandleEvent(NPP instance, void* event)
 {
     PluginObject *obj = reinterpret_cast<PluginObject*>(instance->pdata);
     const ANPEvent* evt = reinterpret_cast<const ANPEvent*>(event);
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/CallbackProxy.java b/tests/DumpRenderTree/src/com/android/dumprendertree/CallbackProxy.java
index 5780c43..b62db51 100644
--- a/tests/DumpRenderTree/src/com/android/dumprendertree/CallbackProxy.java
+++ b/tests/DumpRenderTree/src/com/android/dumprendertree/CallbackProxy.java
@@ -74,6 +74,8 @@
     private static final int LAYOUT_SET_CAN_OPEN_WINDOWS = 42;
     private static final int SET_GEOLOCATION_PERMISSION = 43;
     private static final int OVERRIDE_PREFERENCE = 44;
+    private static final int LAYOUT_DUMP_CHILD_FRAMES_TEXT = 45;
+    private static final int SET_XSS_AUDITOR_ENABLED = 46;
     
     CallbackProxy(EventSender eventSender, 
             LayoutTestController layoutTestController) {
@@ -175,7 +177,11 @@
             break;
 
         case LAYOUT_DUMP_TEXT:
-            mLayoutTestController.dumpAsText();
+            mLayoutTestController.dumpAsText(msg.arg1 == 1);
+            break;
+
+        case LAYOUT_DUMP_CHILD_FRAMES_TEXT:
+            mLayoutTestController.dumpChildFramesAsText();
             break;
 
         case LAYOUT_DUMP_HISTORY:
@@ -273,6 +279,10 @@
             boolean value = msg.getData().getBoolean("value");
             mLayoutTestController.overridePreference(key, value);
             break;
+
+        case SET_XSS_AUDITOR_ENABLED:
+            mLayoutTestController.setXSSAuditorEnabled(msg.arg1 == 1);
+            break;
         }
     }
 
@@ -377,7 +387,15 @@
     }
 
     public void dumpAsText() {
-        obtainMessage(LAYOUT_DUMP_TEXT).sendToTarget();
+        obtainMessage(LAYOUT_DUMP_TEXT, 0).sendToTarget();
+    }
+
+    public void dumpAsText(boolean enablePixelTests) {
+        obtainMessage(LAYOUT_DUMP_TEXT, enablePixelTests ? 1 : 0).sendToTarget();
+    }
+
+    public void dumpChildFramesAsText() {
+        obtainMessage(LAYOUT_DUMP_CHILD_FRAMES_TEXT).sendToTarget();
     }
 
     public void dumpBackForwardList() {
@@ -498,4 +516,8 @@
         message.getData().putBoolean("value", value);
         message.sendToTarget();
     }
+
+    public void setXSSAuditorEnabled(boolean flag) {
+        obtainMessage(SET_XSS_AUDITOR_ENABLED, flag ? 1 : 0, 0).sendToTarget();
+    }
 }
diff --git a/tests/DumpRenderTree/src/com/android/dumprendertree/FileFilter.java b/tests/DumpRenderTree/src/com/android/dumprendertree/FileFilter.java
index 77fd3ed..9f580a3 100644
--- a/tests/DumpRenderTree/src/com/android/dumprendertree/FileFilter.java
+++ b/tests/DumpRenderTree/src/com/android/dumprendertree/FileFilter.java
@@ -73,43 +73,73 @@
 
     static final String[] ignoreTestList = {
         "editing/selection/move-left-right.html", // Causes DumpRenderTree to hang
+        "fast/js/excessive-comma-usage.html", // Tests huge initializer list, causes OOM.
         "fast/js/regexp-charclass-crash.html", // RegExp is too large, causing OOM
         "fast/regex/test1.html", // Causes DumpRenderTree to hang with V8
-        "fast/regex/slow.html" // Causes DumpRenderTree to hang with V8
+        "fast/regex/slow.html", // Causes DumpRenderTree to hang with V8
+        "ietestcenter/Javascript/15.4.4.15-3-14.html", // hangs the layout tests
+        // http://b/issue?id=2889595
+        "ietestcenter/Javascript/15.4.4.15-3-29.html", // hangs the layout tests
+        // http://b/issue?id=2889596
+        "ietestcenter/Javascript/15.4.4.15-3-8.html" // hangs the layout tests
+        // http://b/issue?id=2889598
     };
 
     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/touch-target.html"); // Requires multi-touch
         ignoreResultList.add("http/tests/appcache/empty-manifest.html"); // flaky
+        ignoreResultList.add("http/tests/appcache/fallback.html"); // http://b/issue?id=2713004
         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
+        ignoreResultList.add("storage/open-database-creation-callback-isolated-world.html"); // Requires layoutTestController.evaluateScriptInIsolatedWorld()
+        ignoreResultList.add("storage/statement-error-callback-isolated-world.html"); // Requires layoutTestController.evaluateScriptInIsolatedWorld()
+        ignoreResultList.add("storage/statement-success-callback-isolated-world.html"); // Requires layoutTestController.evaluateScriptInIsolatedWorld()
+        ignoreResultList.add("storage/transaction-callback-isolated-world.html"); // Requires layoutTestController.evaluateScriptInIsolatedWorld()
+        ignoreResultList.add("storage/transaction-error-callback-isolated-world.html"); // Requires layoutTestController.evaluateScriptInIsolatedWorld()
+        ignoreResultList.add("storage/transaction-success-callback-isolated-world.html"); // Requires layoutTestController.evaluateScriptInIsolatedWorld()
 
-        // 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
         ignoreResultList.add("storage/domstorage/localstorage/private-browsing-affects-storage.html"); // private browsing not supported
         ignoreResultList.add("storage/domstorage/sessionstorage/private-browsing-affects-storage.html"); // private browsing not supported
+        ignoreResultList.add("storage/indexeddb"); // indexeddb not supported
         ignoreResultList.add("storage/private-browsing-readonly.html"); // private browsing not supported
         ignoreResultList.add("websocket/tests/workers"); // workers not supported
 
+        // Expected failures due to missing expected results
+        ignoreResultList.add("dom/xhtml/level3/core/canonicalform08.xhtml");
+        ignoreResultList.add("dom/xhtml/level3/core/canonicalform09.xhtml");
+        ignoreResultList.add("dom/xhtml/level3/core/documentgetinputencoding03.xhtml");
+        ignoreResultList.add("dom/xhtml/level3/core/entitygetinputencoding02.xhtml");
+        ignoreResultList.add("dom/xhtml/level3/core/entitygetxmlversion02.xhtml");
+        ignoreResultList.add("dom/xhtml/level3/core/nodegetbaseuri05.xhtml");
+        ignoreResultList.add("dom/xhtml/level3/core/nodegetbaseuri07.xhtml");
+        ignoreResultList.add("dom/xhtml/level3/core/nodegetbaseuri09.xhtml");
+        ignoreResultList.add("dom/xhtml/level3/core/nodegetbaseuri10.xhtml");
+        ignoreResultList.add("dom/xhtml/level3/core/nodegetbaseuri11.xhtml");
+        ignoreResultList.add("dom/xhtml/level3/core/nodegetbaseuri15.xhtml");
+        ignoreResultList.add("dom/xhtml/level3/core/nodegetbaseuri17.xhtml");
+        ignoreResultList.add("dom/xhtml/level3/core/nodegetbaseuri18.xhtml");
+        ignoreResultList.add("dom/xhtml/level3/core/nodelookupnamespaceuri01.xhtml");
+        ignoreResultList.add("dom/xhtml/level3/core/nodelookupprefix19.xhtml");
+
         // TODO: These need to be triaged
         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 +202,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/FsUtils.java b/tests/DumpRenderTree/src/com/android/dumprendertree/FsUtils.java
index 6cfce41..5d34e25 100644
--- a/tests/DumpRenderTree/src/com/android/dumprendertree/FsUtils.java
+++ b/tests/DumpRenderTree/src/com/android/dumprendertree/FsUtils.java
@@ -49,8 +49,13 @@
         //no creation of instances
     }
 
-    public static void findLayoutTestsRecursively(BufferedOutputStream bos,
+    /**
+     * @return the number of tests in the list.
+     */
+    public static int writeLayoutTestListRecursively(BufferedOutputStream bos,
             String dir, boolean ignoreResultsInDir) throws IOException {
+
+        int testCount = 0;
         Log.v(LOGTAG, "Searching tests under " + dir);
 
         File d = new File(dir);
@@ -68,7 +73,7 @@
                 // If this is not a test directory, we don't recurse into it.
                 if (!FileFilter.isNonTestDir(s)) {
                     Log.v(LOGTAG, "Recursing on " + s);
-                    findLayoutTestsRecursively(bos, s, ignoreResultsInDir);
+                    testCount += writeLayoutTestListRecursively(bos, s, ignoreResultsInDir);
                 }
                 continue;
             }
@@ -79,7 +84,9 @@
                 continue;
             }
 
-            if ((s.toLowerCase().endsWith(".html") || s.toLowerCase().endsWith(".xml"))
+            if ((s.toLowerCase().endsWith(".html")
+                    || s.toLowerCase().endsWith(".xml")
+                    || s.toLowerCase().endsWith(".xhtml"))
                     && !s.endsWith("TEMPLATE.html")) {
                 Log.v(LOGTAG, "Recording " + s);
                 bos.write(s.getBytes());
@@ -88,8 +95,10 @@
                     bos.write((" IGNORE_RESULT").getBytes());
                 }
                 bos.write('\n');
+                testCount++;
             }
         }
+        return testCount;
     }
 
     public static void updateTestStatus(String statusFile, String s) {
diff --git a/tests/DumpRenderTree/src/com/android/dumprendertree/LayoutTestController.java b/tests/DumpRenderTree/src/com/android/dumprendertree/LayoutTestController.java
index 9236345..15288b5 100644
--- a/tests/DumpRenderTree/src/com/android/dumprendertree/LayoutTestController.java
+++ b/tests/DumpRenderTree/src/com/android/dumprendertree/LayoutTestController.java
@@ -18,29 +18,30 @@
 
 public interface LayoutTestController {
 
-	public void dumpAsText();
-	public void waitUntilDone();
-	public void notifyDone();
-	
-	// Force a redraw of the page
-	public void display();
-	// Used with pixel dumps of content
-	public void testRepaint();
-	
-	// If the page title changes, add the information to the output.
-	public void dumpTitleChanges();
-	public void dumpBackForwardList();
-	public void dumpChildFrameScrollPositions();
-	public void dumpEditingCallbacks();
-	
-	// Show/Hide window for window.onBlur() testing
-	public void setWindowIsKey(boolean b);
-	// Mac function, used to disable events going to the window
-	public void setMainFrameIsFirstResponder(boolean b);
-	
-	public void dumpSelectionRect();
-	
-	// invalidate and draw one line at a time of the web view.
+    public void dumpAsText(boolean enablePixelTests);
+    public void dumpChildFramesAsText();
+    public void waitUntilDone();
+    public void notifyDone();
+
+    // Force a redraw of the page
+    public void display();
+    // Used with pixel dumps of content
+    public void testRepaint();
+
+    // If the page title changes, add the information to the output.
+    public void dumpTitleChanges();
+    public void dumpBackForwardList();
+    public void dumpChildFrameScrollPositions();
+    public void dumpEditingCallbacks();
+
+    // Show/Hide window for window.onBlur() testing
+    public void setWindowIsKey(boolean b);
+    // Mac function, used to disable events going to the window
+    public void setMainFrameIsFirstResponder(boolean b);
+
+    public void dumpSelectionRect();
+
+    // invalidate and draw one line at a time of the web view.
     public void repaintSweepHorizontally();
     
     // History testing functions
@@ -67,4 +68,7 @@
     public void setGeolocationPermission(boolean allow);
 
     public void overridePreference(String key, boolean value);
+
+    // For XSSAuditor tests
+    public void setXSSAuditorEnabled(boolean flag);
 }
diff --git a/tests/DumpRenderTree/src/com/android/dumprendertree/LayoutTestsAutoTest.java b/tests/DumpRenderTree/src/com/android/dumprendertree/LayoutTestsAutoTest.java
index 9ccf549..132f17a 100644
--- a/tests/DumpRenderTree/src/com/android/dumprendertree/LayoutTestsAutoTest.java
+++ b/tests/DumpRenderTree/src/com/android/dumprendertree/LayoutTestsAutoTest.java
@@ -20,9 +20,7 @@
 import com.android.dumprendertree.forwarder.AdbUtils;
 import com.android.dumprendertree.forwarder.ForwardService;
 
-import android.app.Instrumentation;
 import android.content.Intent;
-import android.os.Bundle;
 import android.os.Environment;
 import android.test.ActivityInstrumentationTestCase2;
 import android.util.Log;
@@ -158,18 +156,11 @@
     private String mJsEngine;
     private String mTestPathPrefix;
     private boolean mFinished;
+    private int mTestCount;
+    private int mResumeIndex;
 
     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() {
@@ -190,6 +181,7 @@
         } catch (Exception e) {
             Log.e(LOGTAG, "Error while reading test list : " + e.getMessage());
         }
+        mTestCount = mTestList.size();
     }
 
     private void resumeTestList() {
@@ -200,6 +192,7 @@
                 if (mTestList.elementAt(i).equals(line)) {
                     mTestList = new Vector<String>(mTestList.subList(i+1, mTestList.size()));
                     mTestListIgnoreResult = new Vector<Boolean>(mTestListIgnoreResult.subList(i+1, mTestListIgnoreResult.size()));
+                    mResumeIndex = i + 1;
                     break;
                 }
             }
@@ -232,14 +225,22 @@
         // The generic result is at <path>/<name>-expected.txt
         // First try the Android-specific result at
         // platform/android-<js-engine>/<path>/<name>-expected.txt
+        // then
+        // platform/android/<path>/<name>-expected.txt
         int pos = test.lastIndexOf('.');
         if (pos == -1)
             return null;
         String genericExpectedResult = test.substring(0, pos) + "-expected.txt";
         String androidExpectedResultsDir = "platform/android-" + mJsEngine + "/";
-        String androidExpectedResult =
-            genericExpectedResult.replaceFirst(LAYOUT_TESTS_ROOT, LAYOUT_TESTS_ROOT + androidExpectedResultsDir);
+        String androidExpectedResult = genericExpectedResult.replaceFirst(LAYOUT_TESTS_ROOT,
+                LAYOUT_TESTS_ROOT + androidExpectedResultsDir);
         File f = new File(androidExpectedResult);
+        if (f.exists())
+            return androidExpectedResult;
+        androidExpectedResultsDir = "platform/android/";
+        androidExpectedResult = genericExpectedResult.replaceFirst(LAYOUT_TESTS_ROOT,
+                LAYOUT_TESTS_ROOT + androidExpectedResultsDir);
+        f = new File(androidExpectedResult);
         return f.exists() ? androidExpectedResult : genericExpectedResult;
     }
 
@@ -300,7 +301,7 @@
         }
     }
 
-    private void runTestAndWaitUntilDone(TestShellActivity activity, String test, int timeout, boolean ignoreResult) {
+    private void runTestAndWaitUntilDone(TestShellActivity activity, String test, int timeout, boolean ignoreResult, int testNumber) {
         activity.setCallback(new TestShellCallback() {
             public void finished() {
                 synchronized (LayoutTestsAutoTest.this) {
@@ -336,6 +337,9 @@
         intent.putExtra(TestShellActivity.TEST_URL, FsUtils.getTestUrl(test));
         intent.putExtra(TestShellActivity.RESULT_FILE, resultFile);
         intent.putExtra(TestShellActivity.TIMEOUT_IN_MILLIS, timeout);
+        intent.putExtra(TestShellActivity.TOTAL_TEST_COUNT, mTestCount);
+        intent.putExtra(TestShellActivity.CURRENT_TEST_NUMBER, testNumber);
+        intent.putExtra(TestShellActivity.STOP_ON_REF_ERROR, true);
         activity.startActivity(intent);
 
         // Wait until done.
@@ -375,8 +379,8 @@
         // Read settings
         mTestPathPrefix = (new File(LAYOUT_TESTS_ROOT + runner.mTestPath)).getAbsolutePath();
         mRebaselineResults = runner.mRebaseline;
-        // JSC is the default JavaScript engine.
-        mJsEngine = runner.mJsEngine == null ? "jsc" : runner.mJsEngine;
+        // V8 is the default JavaScript engine.
+        mJsEngine = runner.mJsEngine == null ? "v8" : runner.mJsEngine;
 
         int timeout = runner.mTimeoutInMillis;
         if (timeout <= 0) {
@@ -393,7 +397,7 @@
             resumeTestList();
 
         TestShellActivity activity = getActivity();
-        activity.setDefaultDumpDataType(DumpDataType.DUMP_AS_TEXT);
+        activity.setDefaultDumpDataType(DumpDataType.EXT_REPR);
 
         // Run tests.
         int addr = -1;
@@ -410,7 +414,9 @@
             boolean ignoreResult = mTestListIgnoreResult.elementAt(i);
             FsUtils.updateTestStatus(TEST_STATUS_FILE, s);
             // Run tests
-            runTestAndWaitUntilDone(activity, s, runner.mTimeoutInMillis, ignoreResult);
+            // i is 0 based, but test count is 1 based so add 1 to i here.
+            runTestAndWaitUntilDone(activity, s, runner.mTimeoutInMillis, ignoreResult,
+                    i + 1 + mResumeIndex);
         }
 
         FsUtils.updateTestStatus(TEST_STATUS_FILE, "#DONE");
@@ -435,7 +441,7 @@
         try {
             File tests_list = new File(LAYOUT_TESTS_LIST_FILE);
             BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(tests_list, false));
-            FsUtils.findLayoutTestsRecursively(bos, getTestPath(), false); // Don't ignore results
+            FsUtils.writeLayoutTestListRecursively(bos, getTestPath(), false); // Don't ignore results
             bos.flush();
             bos.close();
        } catch (Exception e) {
diff --git a/tests/DumpRenderTree/src/com/android/dumprendertree/Menu.java b/tests/DumpRenderTree/src/com/android/dumprendertree/Menu.java
index 9c4b57256..0b00d65 100644
--- a/tests/DumpRenderTree/src/com/android/dumprendertree/Menu.java
+++ b/tests/DumpRenderTree/src/com/android/dumprendertree/Menu.java
@@ -53,29 +53,38 @@
         intent.setClass(this, TestShellActivity.class);
         intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
         intent.putExtra(TestShellActivity.TEST_URL, "file://" + filename);
+        intent.putExtra(TestShellActivity.TOTAL_TEST_COUNT, 1);
+        intent.putExtra(TestShellActivity.CURRENT_TEST_NUMBER, 1);
         startActivity(intent);
     }
 
     @Override
     void processDirectory(String path, boolean selection) {
-        generateTestList(path);
+        int testCount = generateTestList(path);
         Intent intent = new Intent(Intent.ACTION_VIEW);
         intent.setClass(this, TestShellActivity.class);
         intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
         intent.putExtra(TestShellActivity.UI_AUTO_TEST, LAYOUT_TESTS_LIST_FILE);
+        intent.putExtra(TestShellActivity.TOTAL_TEST_COUNT, testCount);
+        // TestShellActivity will process this intent once and increment the test index
+        // before running the first test, so pass 0 here to allow for that.
+        intent.putExtra(TestShellActivity.CURRENT_TEST_NUMBER, 0);
         startActivity(intent);
     }
 
-    private void generateTestList(String path) {
+    private int generateTestList(String path) {
+        int testCount = 0;
         try {
             File tests_list = new File(LAYOUT_TESTS_LIST_FILE);
             BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(tests_list, false));
-            FsUtils.findLayoutTestsRecursively(bos, path, false); // Don't ignore results
+            testCount = FsUtils.writeLayoutTestListRecursively(
+                    bos, path, false); // Don't ignore results
             bos.flush();
             bos.close();
        } catch (Exception e) {
            Log.e(LOGTAG, "Error when creating test list: " + e.getMessage());
        }
+       return testCount;
     }
 
 }
diff --git a/tests/DumpRenderTree/src/com/android/dumprendertree/TestShellActivity.java b/tests/DumpRenderTree/src/com/android/dumprendertree/TestShellActivity.java
index 7475719..f4773b5 100644
--- a/tests/DumpRenderTree/src/com/android/dumprendertree/TestShellActivity.java
+++ b/tests/DumpRenderTree/src/com/android/dumprendertree/TestShellActivity.java
@@ -35,6 +35,8 @@
 import android.os.Message;
 import android.util.Log;
 import android.view.ViewGroup;
+import android.view.Window;
+import android.webkit.ConsoleMessage;
 import android.webkit.GeolocationPermissions;
 import android.webkit.HttpAuthHandler;
 import android.webkit.JsPromptResult;
@@ -99,6 +101,8 @@
         Log.v(LOGTAG, "message sent to WebView to dump text.");
         switch (mDumpDataType) {
             case DUMP_AS_TEXT:
+                callback.arg1 = mDumpTopFrameAsText ? 1 : 0;
+                callback.arg2 = mDumpChildFramesAsText ? 1 : 0;
                 mWebView.documentAsText(callback);
                 break;
             case EXT_REPR:
@@ -117,6 +121,7 @@
     @Override
     protected void onCreate(Bundle icicle) {
         super.onCreate(icicle);
+        requestWindowFeature(Window.FEATURE_PROGRESS);
 
         LinearLayout contentView = new LinearLayout(this);
         contentView.setOrientation(LinearLayout.VERTICAL);
@@ -159,6 +164,9 @@
             return;
         }
 
+        mTotalTestCount = intent.getIntExtra(TOTAL_TEST_COUNT, mTotalTestCount);
+        mCurrentTestNumber = intent.getIntExtra(CURRENT_TEST_NUMBER, mCurrentTestNumber);
+
         mTestUrl = intent.getStringExtra(TEST_URL);
         if (mTestUrl == null) {
             mUiAutoTestPath = intent.getStringExtra(UI_AUTO_TEST);
@@ -172,6 +180,11 @@
         mTimeoutInMillis = intent.getIntExtra(TIMEOUT_IN_MILLIS, 0);
         mGetDrawtime = intent.getBooleanExtra(GET_DRAW_TIME, false);
         mSaveImagePath = intent.getStringExtra(SAVE_IMAGE);
+        mStopOnRefError = intent.getBooleanExtra(STOP_ON_REF_ERROR, false);
+        setTitle("Test " + mCurrentTestNumber + " of " + mTotalTestCount);
+        float ratio = (float)mCurrentTestNumber / mTotalTestCount;
+        int progress = (int)(ratio * Window.PROGRESS_END);
+        getWindow().setFeatureInt(Window.FEATURE_PROGRESS, progress);
 
         Log.v(LOGTAG, "  Loading " + mTestUrl);
         mWebView.loadUrl(mTestUrl);
@@ -237,6 +250,7 @@
         Intent intent = new Intent(Intent.ACTION_VIEW);
         intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
         intent.putExtra(TestShellActivity.TEST_URL, FsUtils.getTestUrl(url));
+        intent.putExtra(TestShellActivity.CURRENT_TEST_NUMBER, ++mCurrentTestNumber);
         intent.putExtra(TIMEOUT_IN_MILLIS, 10000);
         executeIntent(intent);
     }
@@ -329,14 +343,29 @@
 
     // .......................................
     // LayoutTestController Functions
-    public void dumpAsText() {
+    public void dumpAsText(boolean enablePixelTests) {
+        // Added after webkit update to r63859. See trac.webkit.org/changeset/63730.
+        if (enablePixelTests) {
+            Log.v(LOGTAG, "dumpAsText(enablePixelTests == true) not implemented on Android!");
+        }
+
         mDumpDataType = DumpDataType.DUMP_AS_TEXT;
+        mDumpTopFrameAsText = true;
         if (mWebView != null) {
             String url = mWebView.getUrl();
             Log.v(LOGTAG, "dumpAsText called: "+url);
         }
     }
 
+    public void dumpChildFramesAsText() {
+        mDumpDataType = DumpDataType.DUMP_AS_TEXT;
+        mDumpChildFramesAsText = true;
+        if (mWebView != null) {
+            String url = mWebView.getUrl();
+            Log.v(LOGTAG, "dumpChildFramesAsText called: "+url);
+        }
+    }
+
     public void waitUntilDone() {
         mWaitUntilDone = true;
         String url = mWebView.getUrl();
@@ -348,7 +377,9 @@
         Log.v(LOGTAG, "notifyDone called: " + url);
         if (mWaitUntilDone) {
             mWaitUntilDone = false;
-            mChromeClient.onProgressChanged(mWebView, 101);
+            if (!mRequestedWebKitData && !mTimedOut && !finished()) {
+                requestWebKitData();
+            }
         }
     }
 
@@ -473,6 +504,10 @@
         }
     }
 
+    public void setXSSAuditorEnabled (boolean flag) {
+        mWebView.getSettings().setXSSAuditorEnabled(flag);
+    }
+
     private final WebViewClient mViewClient = new WebViewClient(){
         @Override
         public void onPageFinished(WebView view, String url) {
@@ -490,11 +525,30 @@
                     drawPageToFile(mSaveImagePath + "/" + name + ".png", mWebView);
                 }
             }
+
             // Calling finished() will check if we've met all the conditions for completing
-            // this test and move to the next one if we are ready.
+            // this test and move to the next one if we are ready. Otherwise we ask WebCore to
+            // dump the page.
             if (finished()) {
                 return;
             }
+
+            if (!mWaitUntilDone && !mRequestedWebKitData && !mTimedOut) {
+                requestWebKitData();
+            } else {
+                if (mWaitUntilDone) {
+                    Log.v(LOGTAG, "page finished loading but waiting for notifyDone to be called: " + url);
+                }
+
+                if (mRequestedWebKitData) {
+                    Log.v(LOGTAG, "page finished loading but webkit data has already been requested: " + url);
+                }
+
+                if (mTimedOut) {
+                    Log.v(LOGTAG, "page finished loading but already timed out: " + url);
+                }
+            }
+
             super.onPageFinished(view, url);
         }
 
@@ -536,44 +590,8 @@
 
     private final WebChromeClient mChromeClient = new WebChromeClient() {
         @Override
-        public void onProgressChanged(WebView view, int newProgress) {
-
-            // notifyDone calls this with 101%. We only want to update this flag if this
-            // is the real call from WebCore.
-            if (newProgress == 100) {
-                mOneHundredPercentComplete = true;
-            }
-
-            // With the flag updated, we can now proceed as normal whether the progress update came from
-            // WebCore or notifyDone.
-            if (newProgress >= 100) {
-                // finished() will check if we are ready to move to the next test and do so if we are.
-                if (finished()) {
-                    return;
-                }
-
-                if (!mTimedOut && !mWaitUntilDone && !mRequestedWebKitData) {
-                    String url = mWebView.getUrl();
-                    Log.v(LOGTAG, "Finished: "+ url);
-                    requestWebKitData();
-                } else {
-                    String url = mWebView.getUrl();
-                    if (mTimedOut) {
-                        Log.v(LOGTAG, "Timed out before finishing: " + url);
-                    } else if (mWaitUntilDone) {
-                        Log.v(LOGTAG, "Waiting for notifyDone: " + url);
-                    } else if (mRequestedWebKitData) {
-                        Log.v(LOGTAG, "Requested webkit data ready: " + url);
-                    }
-                }
-            }
-        }
-
-        @Override
         public void onReceivedTitle(WebView view, String title) {
-            if (title.length() > 30)
-                title = "..."+title.substring(title.length()-30);
-            setTitle(title);
+            setTitle("Test " + mCurrentTestNumber + " of " + mTotalTestCount + ": "+ title);
             if (mDumpTitleChanges) {
                 mTitleChanges.append("TITLE CHANGED: ");
                 mTitleChanges.append(title);
@@ -676,15 +694,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 && mStopOnRefError) {
+                Log.w(LOGTAG, "Terminating test case on uncaught ReferenceError or TypeError.");
+                mHandler.postDelayed(new Runnable() {
+                    public void run() {
+                        notifyDone();
+                    }
+                }, 500);
+            }
+            return true;
         }
 
         @Override
@@ -730,6 +761,8 @@
     private void resetTestStatus() {
         mWaitUntilDone = false;
         mDumpDataType = mDefaultDumpDataType;
+        mDumpTopFrameAsText = false;
+        mDumpChildFramesAsText = false;
         mTimedOut = false;
         mDumpTitleChanges = false;
         mRequestedWebKitData = false;
@@ -739,10 +772,10 @@
         mEventSender.clearTouchPoints();
         mEventSender.clearTouchMetaState();
         mPageFinished = false;
-        mOneHundredPercentComplete = false;
         mDumpWebKitData = false;
         mGetDrawtime = false;
         mSaveImagePath = null;
+        setDefaultWebSettings(mWebView);
     }
 
     private long[] getDrawWebViewTime(WebView view, int count) {
@@ -779,7 +812,7 @@
     }
 
     private boolean canMoveToNextTest() {
-        return (mDumpWebKitData && mOneHundredPercentComplete && mPageFinished && !mWaitUntilDone) || mTimedOut;
+        return (mDumpWebKitData && mPageFinished && !mWaitUntilDone) || mTimedOut;
     }
 
     private void setupWebViewForLayoutTests(WebView webview, CallbackProxy callbackProxy) {
@@ -787,6 +820,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());
@@ -799,15 +845,7 @@
         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);
+        settings.setXSSAuditorEnabled(false);
     }
 
     private WebView mWebView;
@@ -824,6 +862,9 @@
     private String mSaveImagePath;
     private BufferedReader mTestListReader;
     private boolean mGetDrawtime;
+    private int mTotalTestCount;
+    private int mCurrentTestNumber;
+    private boolean mStopOnRefError;
 
     // States
     private boolean mTimedOut;
@@ -833,6 +874,8 @@
     // Layout test controller variables.
     private DumpDataType mDumpDataType;
     private DumpDataType mDefaultDumpDataType = DumpDataType.EXT_REPR;
+    private boolean mDumpTopFrameAsText;
+    private boolean mDumpChildFramesAsText;
     private boolean mWaitUntilDone;
     private boolean mDumpTitleChanges;
     private StringBuffer mTitleChanges;
@@ -846,7 +889,6 @@
 
     private boolean mPageFinished = false;
     private boolean mDumpWebKitData = false;
-    private boolean mOneHundredPercentComplete = false;
 
     static final String TIMEOUT_STR = "**Test timeout";
 
@@ -861,6 +903,9 @@
     static final String UI_AUTO_TEST = "UiAutoTest";
     static final String GET_DRAW_TIME = "GetDrawTime";
     static final String SAVE_IMAGE = "SaveImage";
+    static final String TOTAL_TEST_COUNT = "TestCount";
+    static final String CURRENT_TEST_NUMBER = "TestNumber";
+    static final String STOP_ON_REF_ERROR = "StopOnReferenceError";
 
     static final int DRAW_RUNS = 5;
     static final String DRAW_TIME_LOG = Environment.getExternalStorageDirectory() +
diff --git a/tests/DumpRenderTree2/Android.mk b/tests/DumpRenderTree2/Android.mk
new file mode 100644
index 0000000..948ad72
--- /dev/null
+++ b/tests/DumpRenderTree2/Android.mk
@@ -0,0 +1,12 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_STATIC_JAVA_LIBRARIES := diff_match_patch
+
+LOCAL_PACKAGE_NAME := DumpRenderTree2
+
+include $(BUILD_PACKAGE)
\ No newline at end of file
diff --git a/tests/DumpRenderTree2/AndroidManifest.xml b/tests/DumpRenderTree2/AndroidManifest.xml
new file mode 100644
index 0000000..9f6097a
--- /dev/null
+++ b/tests/DumpRenderTree2/AndroidManifest.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.dumprendertree2">
+    <application>
+        <activity android:name=".ui.DirListActivity"
+                  android:label="Dump Render Tree 2"
+                  android:configChanges="orientation">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+
+        <activity android:name=".TestsListActivity"
+                  android:label="Tests' list activity">
+        </activity>
+
+        <activity android:name=".LayoutTestsExecutor"
+                  android:label="Layout tests' executor"
+                  android:process=":executor">
+        </activity>
+
+        <service android:name="ManagerService">
+        </service>
+    </application>
+
+    <uses-permission android:name="android.permission.INTERNET" />
+    <uses-permission android:name="android.permission.WRITE_SDCARD" />
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.WAKE_LOCK" />
+</manifest>
\ No newline at end of file
diff --git a/tests/DumpRenderTree2/res/drawable/folder.png b/tests/DumpRenderTree2/res/drawable/folder.png
new file mode 100644
index 0000000..5b3fcec
--- /dev/null
+++ b/tests/DumpRenderTree2/res/drawable/folder.png
Binary files differ
diff --git a/tests/DumpRenderTree2/res/drawable/runtest.png b/tests/DumpRenderTree2/res/drawable/runtest.png
new file mode 100644
index 0000000..910c654
--- /dev/null
+++ b/tests/DumpRenderTree2/res/drawable/runtest.png
Binary files differ
diff --git a/tests/DumpRenderTree2/res/layout/dirlist_row.xml b/tests/DumpRenderTree2/res/layout/dirlist_row.xml
new file mode 100644
index 0000000..e5578a6
--- /dev/null
+++ b/tests/DumpRenderTree2/res/layout/dirlist_row.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+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.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="horizontal"
+    android:gravity="center_vertical"
+    android:layout_width="fill_parent"
+    android:layout_height="wrap_content">
+
+    <ImageView
+        android:id="@+id/icon"
+        android:layout_width="80px"
+        android:adjustViewBounds="true"
+        android:paddingLeft="15px"
+        android:paddingRight="15px"
+        android:paddingTop="15px"
+        android:paddingBottom="15px"
+        android:layout_height="wrap_content"
+    />
+
+    <TextView
+        android:id="@+id/label"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:minHeight="60px"
+        android:gravity="center_vertical"
+        android:textSize="14sp"
+    />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/DumpRenderTree2/res/values/strings.xml b/tests/DumpRenderTree2/res/values/strings.xml
new file mode 100644
index 0000000..5fd1eb9
--- /dev/null
+++ b/tests/DumpRenderTree2/res/values/strings.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+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.
+-->
+<resources>
+    <string name="dialog_run_abort_dir_title_prefix">Directory:</string>
+    <string name="dialog_run_abort_dir_msg">This will run all the tests in this directory and all
+            the subdirectories. It may take a few hours!</string>
+    <string name="dialog_run_abort_dir_ok_button">Run tests!</string>
+    <string name="dialog_run_abort_dir_abort_button">Abort</string>
+
+    <string name="dialog_progress_title">Loading items.</string>
+    <string name="dialog_progress_msg">Please wait...</string>
+
+    <string name="runner_preloading_title">Preloading tests...</string>
+</resources>
\ No newline at end of file
diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/AbstractResult.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/AbstractResult.java
new file mode 100644
index 0000000..d68930c
--- /dev/null
+++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/AbstractResult.java
@@ -0,0 +1,145 @@
+/*
+ * 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.dumprendertree2;
+
+import android.os.Bundle;
+import android.os.Message;
+import android.webkit.WebView;
+
+/**
+ * A class that represent a result of the test. It is responsible for returning the result's
+ * raw data and generating its own diff in HTML format.
+ */
+public abstract class AbstractResult implements Comparable<AbstractResult> {
+
+    private static final String LOG_TAG = "AbstractResult";
+
+    public enum TestType {
+        TEXT {
+            @Override
+            public AbstractResult createResult(Bundle bundle) {
+                return new TextResult(bundle);
+            }
+        },
+        RENDER_TREE {
+            @Override
+            public AbstractResult createResult(Bundle bundle) {
+                /** TODO: RenderTree tests are not yet supported */
+                return null;
+            }
+        };
+
+        public abstract AbstractResult createResult(Bundle bundle);
+    }
+
+    public enum ResultCode {
+        PASS("Passed"),
+        FAIL_RESULT_DIFFERS("Result differs"),
+        FAIL_NO_EXPECTED_RESULT("No expected result"),
+        FAIL_TIMED_OUT("Timed out"),
+        FAIL_CRASHED("Crashed");
+
+        private String mTitle;
+
+        private ResultCode(String title) {
+            mTitle = title;
+        }
+
+        @Override
+        public String toString() {
+            return mTitle;
+        }
+    }
+
+    String mAdditionalTextOutputString;
+
+    public int compareTo(AbstractResult another) {
+        return getRelativePath().compareTo(another.getRelativePath());
+    }
+
+    public void setAdditionalTextOutputString(String additionalTextOutputString) {
+        mAdditionalTextOutputString = additionalTextOutputString;
+    }
+
+    public String getAdditionalTextOutputString() {
+        return mAdditionalTextOutputString;
+    }
+
+    /**
+     * Makes the result object obtain the results of the test from the webview
+     * and store them in the format that suits itself bests. This method is asynchronous.
+     * The message passed as a parameter is a message that should be sent to its target
+     * when the result finishes obtaining the result.
+     *
+     * @param webview
+     * @param resultObtainedMsg
+     */
+    public abstract void obtainActualResults(WebView webview, Message resultObtainedMsg);
+
+    public abstract void setExpectedImageResult(byte[] expectedResult);
+
+    public abstract void setExpectedTextResult(String expectedResult);
+
+    /**
+     * Returns result's image data that can be written to the disk. It can be null
+     * if there is an error of some sort or for example the test times out.
+     *
+     * <p> Some tests will not provide data (like text tests)
+     *
+     * @return
+     *      results image data
+     */
+    public abstract byte[] getActualImageResult();
+
+    /**
+     * Returns result's text data. It can be null
+     * if there is an error of some sort or for example the test times out.
+     *
+     * @return
+     *      results text data
+     */
+    public abstract String getActualTextResult();
+
+    /**
+     * Returns the code of this result.
+     *
+     * @return
+     *      the code of this result
+     */
+    public abstract ResultCode getResultCode();
+
+    /**
+     * Return the type of the result data.
+     *
+     * @return
+     *      the type of the result data.
+     */
+    public abstract TestType getType();
+
+    public abstract String getRelativePath();
+
+    /**
+     * Returns a piece of HTML code that presents a visual diff between a result and
+     * the expected result.
+     *
+     * @return
+     *      a piece of HTML code with a visual diff between the result and the expected result
+     */
+    public abstract String getDiffAsHtml();
+
+    public abstract Bundle getBundle();
+}
\ No newline at end of file
diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/AdditionalTextOutput.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/AdditionalTextOutput.java
new file mode 100644
index 0000000..8fca629
--- /dev/null
+++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/AdditionalTextOutput.java
@@ -0,0 +1,118 @@
+/*
+ * 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.dumprendertree2;
+
+import android.util.Log;
+import android.webkit.ConsoleMessage;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+
+/**
+ * A class that stores consoles messages, database callbacks, alert messages, etc.
+ */
+public class AdditionalTextOutput {
+    private static final String LOG_TAG = "AdditionalTextOutput";
+
+    /**
+     * Ordering of enums is important as it determines ordering of the toString method!
+     * StringBuilders will be printed in the order the corresponding types appear here.
+     */
+    private enum OutputType {
+        JS_DIALOG,
+        EXCEEDED_DB_QUOTA_MESSAGE,
+        CONSOLE_MESSAGE;
+    }
+
+    StringBuilder[] mOutputs = new StringBuilder[OutputType.values().length];
+
+    private StringBuilder getStringBuilderForType(OutputType outputType) {
+        int index = outputType.ordinal();
+        if (mOutputs[index] == null) {
+            mOutputs[index] = new StringBuilder();
+        }
+        return mOutputs[index];
+    }
+
+    public void appendExceededDbQuotaMessage(String urlString, String databaseIdentifier) {
+        StringBuilder output = getStringBuilderForType(OutputType.EXCEEDED_DB_QUOTA_MESSAGE);
+
+        String protocol = "";
+        String host = "";
+        int port = 0;
+
+        try {
+            URL url = new URL(urlString);
+            protocol = url.getProtocol();
+            host = url.getHost();
+            if (url.getPort() > -1) {
+                port = url.getPort();
+            }
+        } catch (MalformedURLException e) {
+            Log.e(LOG_TAG + "::appendDatabaseCallback", e.getMessage());
+        }
+
+        output.append("UI DELEGATE DATABASE CALLBACK: ");
+        output.append("exceededDatabaseQuotaForSecurityOrigin:{");
+        output.append(protocol + ", " + host + ", " + port + "} ");
+        output.append("database:" + databaseIdentifier + "\n");
+    }
+
+    public void appendConsoleMessage(ConsoleMessage consoleMessage) {
+        StringBuilder output = getStringBuilderForType(OutputType.CONSOLE_MESSAGE);
+
+        output.append("CONSOLE MESSAGE: line " + consoleMessage.lineNumber());
+        output.append(": " + consoleMessage.message() + "\n");
+    }
+
+    public void appendJsAlert(String message) {
+        StringBuilder output = getStringBuilderForType(OutputType.JS_DIALOG);
+
+        output.append("ALERT: ");
+        output.append(message);
+        output.append('\n');
+    }
+
+    public void appendJsConfirm(String message) {
+        StringBuilder output = getStringBuilderForType(OutputType.JS_DIALOG);
+
+        output.append("CONFIRM: ");
+        output.append(message);
+        output.append('\n');
+    }
+
+    public void appendJsPrompt(String message, String defaultValue) {
+        StringBuilder output = getStringBuilderForType(OutputType.JS_DIALOG);
+
+        output.append("PROMPT: ");
+        output.append(message);
+        output.append(", default text: ");
+        output.append(defaultValue);
+        output.append('\n');
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder result = new StringBuilder();
+        for (int i = 0; i < mOutputs.length; i++) {
+            if (mOutputs[i] != null) {
+                result.append(mOutputs[i].toString());
+            }
+        }
+        return result.toString();
+    }
+}
\ No newline at end of file
diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/FileFilter.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/FileFilter.java
new file mode 100644
index 0000000..cf82d24
--- /dev/null
+++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/FileFilter.java
@@ -0,0 +1,288 @@
+/*
+ * 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.dumprendertree2;
+
+import android.util.Log;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * A utility to filter out some files/directories from the views and tests that run.
+ */
+public class FileFilter {
+    private static final String LOG_TAG = "FileFilter";
+
+    private static final String TEST_EXPECTATIONS_TXT_PATH =
+            "platform/android/test_expectations.txt";
+
+    private static final String TOKEN_SKIP = "SKIP";
+    private static final String TOKEN_IGNORE_RESULT = "IGNORE_RESULT";
+    private static final String TOKEN_SLOW = "SLOW";
+
+    private final Set<String> mSkipList = new HashSet<String>();
+    private final Set<String> mIgnoreResultList = new HashSet<String>();
+    private final Set<String> mSlowList = new HashSet<String>();
+
+    private final String mRootDirPath;
+
+    public FileFilter(String rootDirPath) {
+        /** It may or may not contain a trailing slash */
+        this.mRootDirPath = rootDirPath;
+
+        reloadConfiguration();
+    }
+
+    private static final String trimTrailingSlashIfPresent(String path) {
+        File file = new File(path);
+        return file.getPath();
+    }
+
+    public void reloadConfiguration() {
+        File txt_exp = new File(mRootDirPath, TEST_EXPECTATIONS_TXT_PATH);
+
+        BufferedReader bufferedReader;
+        try {
+            bufferedReader =
+                    new BufferedReader(new FileReader(txt_exp));
+
+            String line;
+            String entry;
+            String[] parts;
+            String path;
+            Set<String> tokens;
+            Boolean skipped;
+            while (true) {
+                line = bufferedReader.readLine();
+                if (line == null) {
+                    break;
+                }
+
+                /** Remove the comment and trim */
+                entry = line.split("//", 2)[0].trim();
+
+                /** Omit empty lines, advance to next line */
+                if (entry.isEmpty()) {
+                    continue;
+                }
+
+                /** Split on whitespace into path part and the rest */
+                parts = entry.split("\\s", 2);
+
+                /** At this point parts.length >= 1 */
+                if (parts.length == 1) {
+                    Log.w(LOG_TAG + "::reloadConfiguration",
+                            "There are no options specified for the test!");
+                    continue;
+                }
+
+                path = trimTrailingSlashIfPresent(parts[0]);
+
+                /** Split on whitespace */
+                tokens = new HashSet<String>(Arrays.asList(parts[1].split("\\s", 0)));
+
+                /** Chose the right collections to add to */
+                skipped = false;
+                if (tokens.contains(TOKEN_SKIP)) {
+                    mSkipList.add(path);
+                    skipped = true;
+                }
+
+                /** If test is on skip list we ignore any further options */
+                if (skipped) {
+                    continue;
+                }
+
+                if (tokens.contains(TOKEN_IGNORE_RESULT)) {
+                    mIgnoreResultList.add(path);
+                }
+
+                if (tokens.contains(TOKEN_SLOW)) {
+                    mSlowList.add(path);
+                }
+            }
+        } catch (FileNotFoundException e) {
+            Log.w(LOG_TAG + "::reloadConfiguration", "File not found: " + txt_exp.getPath());
+        } catch (IOException e) {
+            Log.e(LOG_TAG + "::reloadConfiguration", "IOException: " + e.getMessage());
+        }
+    }
+
+    /**
+     * Checks if test is supposed to be skipped.
+     *
+     * <p>
+     * Path given should relative within LayoutTests folder, e.g. fast/dom/foo.html
+     *
+     * @param testPath
+     *            - a relative path within LayoutTests folder
+     * @return if the test is supposed to be skipped
+     */
+    public boolean isSkip(String testPath) {
+        for (String prefix : getPrefixes(testPath)) {
+            if (mSkipList.contains(prefix)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Checks if test result is supposed to be ignored.
+     *
+     * <p>
+     * Path given should relative within LayoutTests folder, e.g. fast/dom/foo.html
+     *
+     * @param testPath
+     *            - a relative path within LayoutTests folder
+     * @return if the test result is supposed to be ignored
+     */
+    public boolean isIgnoreRes(String testPath) {
+        for (String prefix : getPrefixes(testPath)) {
+            if (mIgnoreResultList.contains(prefix)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Checks if test is slow and should have timeout increased.
+     *
+     * <p>
+     * Path given should relative within LayoutTests folder, e.g. fast/dom/foo.html
+     *
+     * @param testPath
+     *            - a relative path within LayoutTests folder
+     * @return if the test is slow and should have timeout increased.
+     */
+    public boolean isSlow(String testPath) {
+        for (String prefix : getPrefixes(testPath)) {
+            if (mSlowList.contains(prefix)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Returns the list of all path prefixes of the given path.
+     *
+     * <p>
+     * e.g. this/is/a/path returns the list: this this/is this/is/a this/is/a/path
+     *
+     * @param path
+     * @return the list of all path prefixes of the given path.
+     */
+    private static List<String> getPrefixes(String path) {
+        File file = new File(path);
+        List<String> prefixes = new ArrayList<String>(8);
+
+        do {
+            prefixes.add(file.getPath());
+            file = file.getParentFile();
+        } while (file != null);
+
+        return prefixes;
+    }
+
+    /**
+     * Checks if the directory may contain tests or contains just helper files.
+     *
+     * @param dirName
+     * @return
+     *      if the directory may contain tests
+     */
+    public static boolean isTestDir(String dirName) {
+        return (!dirName.equals("script-tests")
+                && !dirName.equals("resources") && !dirName.startsWith("."));
+    }
+
+    /**
+     * Checks if the file is a test.
+     * Currently we run .html and .xhtml tests.
+     *
+     * @param testName
+     * @return
+     *      if the file is a test
+     */
+    public static boolean isTestFile(String testName) {
+        return testName.endsWith(".html") || testName.endsWith(".xhtml");
+    }
+
+    /**
+     * Return the path to the file relative to the tests root dir
+     *
+     * @param filePath
+     * @return
+     *      the path relative to the tests root dir
+     */
+    public String getRelativePath(String filePath) {
+        File rootDir = new File(mRootDirPath);
+        return filePath.replaceFirst(rootDir.getPath() + File.separator, "");
+    }
+
+    /**
+     * Return the path to the file relative to the tests root dir
+     *
+     * @param filePath
+     * @return
+     *      the path relative to the tests root dir
+     */
+    public String getRelativePath(File file) {
+        return getRelativePath(file.getAbsolutePath());
+    }
+
+    public File getAbsoluteFile(String relativePath) {
+        return new File(mRootDirPath, relativePath);
+    }
+
+    public String getAboslutePath(String relativePath) {
+        return getAbsoluteFile(relativePath).getAbsolutePath();
+    }
+
+    /**
+     * If the path contains extension (e.g .foo at the end of the file) then it changes
+     * this (.foo) into newEnding (so it has to contain the dot if we want to preserve it).
+     *
+     * <p>If the path doesn't contain an extension, it adds the ending to the path.
+     *
+     * @param relativePath
+     * @param newEnding
+     * @return
+     *      a new path, containing the newExtension
+     */
+    public static String setPathEnding(String relativePath, String newEnding) {
+        int dotPos = relativePath.lastIndexOf('.');
+        if (dotPos == -1) {
+            return relativePath + newEnding;
+        }
+
+        return relativePath.substring(0, dotPos) + newEnding;
+    }
+}
\ No newline at end of file
diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/FsUtils.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/FsUtils.java
new file mode 100644
index 0000000..212c187
--- /dev/null
+++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/FsUtils.java
@@ -0,0 +1,78 @@
+/*
+ * 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.dumprendertree2;
+
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ *
+ */
+public class FsUtils {
+    public static final String LOG_TAG = "FsUtils";
+
+    public static void writeDataToStorage(File file, byte[] bytes, boolean append) {
+        Log.d(LOG_TAG + "::writeDataToStorage", file.getAbsolutePath());
+        try {
+            OutputStream outputStream = null;
+            try {
+                file.getParentFile().mkdirs();
+                file.createNewFile();
+                Log.d(LOG_TAG + "::writeDataToStorage", "File created.");
+                outputStream = new FileOutputStream(file, append);
+                outputStream.write(bytes);
+            } finally {
+                if (outputStream != null) {
+                    outputStream.close();
+                }
+            }
+        } catch (IOException e) {
+            Log.e(LOG_TAG + "::writeDataToStorage", e.getMessage());
+        }
+    }
+
+    public static byte[] readDataFromStorage(File file) {
+        if (!file.exists()) {
+            Log.d(LOG_TAG + "::readDataFromStorage", "File does not exist: "
+                    + file.getAbsolutePath());
+            return null;
+        }
+
+        byte[] bytes = null;
+        try {
+            FileInputStream fis = null;
+            try {
+                fis = new FileInputStream(file);
+                bytes = new byte[(int) file.length()];
+                fis.read(bytes);
+            } finally {
+                if (fis != null) {
+                    fis.close();
+                }
+            }
+        } catch (IOException e) {
+            Log.e(LOG_TAG + "::readDataFromStorage", e.getMessage());
+        }
+
+        return bytes;
+    }
+}
\ No newline at end of file
diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/LayoutTestController.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/LayoutTestController.java
new file mode 100644
index 0000000..8ff5e63
--- /dev/null
+++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/LayoutTestController.java
@@ -0,0 +1,92 @@
+/*
+ * 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.dumprendertree2;
+
+import android.net.Uri;
+import android.util.Log;
+import android.webkit.MockGeolocation;
+import android.webkit.WebStorage;
+
+import java.io.File;
+
+/**
+ * A class that is registered as JS interface for webview in LayoutTestExecutor
+ */
+public class LayoutTestController {
+    private static final String LOG_TAG = "LayoutTestController";
+
+    LayoutTestsExecutor mLayoutTestsExecutor;
+
+    public LayoutTestController(LayoutTestsExecutor layoutTestsExecutor) {
+        mLayoutTestsExecutor = layoutTestsExecutor;
+    }
+
+    public void waitUntilDone() {
+        mLayoutTestsExecutor.waitUntilDone();
+    }
+
+    public void notifyDone() {
+        mLayoutTestsExecutor.notifyDone();
+    }
+
+    public void dumpAsText() {
+        dumpAsText(false);
+    }
+
+    public void dumpAsText(boolean enablePixelTest) {
+        mLayoutTestsExecutor.dumpAsText(enablePixelTest);
+    }
+
+    public void dumpChildFramesAsText() {
+        mLayoutTestsExecutor.dumpChildFramesAsText();
+    }
+
+    public void clearAllDatabases() {
+        Log.w(LOG_TAG + "::clearAllDatabases", "called");
+        WebStorage.getInstance().deleteAllData();
+    }
+
+    public void setCanOpenWindows() {
+        mLayoutTestsExecutor.setCanOpenWindows();
+    }
+
+    public void dumpDatabaseCallbacks() {
+        mLayoutTestsExecutor.dumpDatabaseCallbacks();
+    }
+
+    public void setDatabaseQuota(long quota) {
+        /** TODO: Reset this before every test! */
+        Log.w(LOG_TAG + "::setDatabaseQuota", "called with: " + quota);
+        WebStorage.getInstance().setQuotaForOrigin(Uri.fromFile(new File("")).toString(),
+                quota);
+    }
+
+    public void setGeolocationPermission(boolean allow) {
+        mLayoutTestsExecutor.setGeolocationPermission(allow);
+    }
+
+    public void setMockGeolocationPosition(double latitude, double longitude, double accuracy) {
+        Log.w(LOG_TAG + "::setMockGeolocationPosition", "latitude: " + latitude +
+                " longitude: " + longitude + " accuracy: " + accuracy);
+        MockGeolocation.getInstance().setPosition(latitude, longitude, accuracy);
+    }
+
+    public void setMockGeolocationError(int code, String message) {
+        Log.w(LOG_TAG + "::setMockGeolocationError", "code: " + code + " message: " + message);
+        MockGeolocation.getInstance().setError(code, message);
+    }
+}
\ No newline at end of file
diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/LayoutTestsExecutor.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/LayoutTestsExecutor.java
new file mode 100644
index 0000000..063cb73
--- /dev/null
+++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/LayoutTestsExecutor.java
@@ -0,0 +1,541 @@
+/*
+ * 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.dumprendertree2;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.PowerManager.WakeLock;
+import android.util.Log;
+import android.view.Window;
+import android.webkit.ConsoleMessage;
+import android.webkit.JsPromptResult;
+import android.webkit.JsResult;
+import android.webkit.WebChromeClient;
+import android.webkit.WebSettings;
+import android.webkit.WebView;
+import android.webkit.WebViewClient;
+import android.webkit.GeolocationPermissions.Callback;
+import android.webkit.WebStorage.QuotaUpdater;
+
+import java.io.File;
+import java.util.List;
+
+/**
+ * This activity executes the test. It contains WebView and logic of LayoutTestController
+ * functions. It runs in a separate process and sends the results of running the test
+ * to ManagerService. The reason why is to handle crashing (test that crashes brings down
+ * whole process with it).
+ */
+public class LayoutTestsExecutor extends Activity {
+
+    private enum CurrentState {
+        IDLE,
+        RENDERING_PAGE,
+        WAITING_FOR_ASYNCHRONOUS_TEST,
+        OBTAINING_RESULT;
+
+        public boolean isRunningState() {
+            return this == CurrentState.RENDERING_PAGE ||
+                    this == CurrentState.WAITING_FOR_ASYNCHRONOUS_TEST;
+        }
+    }
+
+    /** TODO: make it a setting */
+    static final String TESTS_ROOT_DIR_PATH =
+            Environment.getExternalStorageDirectory() +
+            File.separator + "android" +
+            File.separator + "LayoutTests";
+
+    private static final String LOG_TAG = "LayoutTestExecutor";
+
+    public static final String EXTRA_TESTS_LIST = "TestsList";
+    public static final String EXTRA_TEST_INDEX = "TestIndex";
+
+    private static final int MSG_ACTUAL_RESULT_OBTAINED = 0;
+    private static final int MSG_TEST_TIMED_OUT = 1;
+
+    private static final int DEFAULT_TIME_OUT_MS = 15 * 1000;
+
+    private List<String> mTestsList;
+
+    /**
+     * This is a number of currently running test. It is 0-based and doesn't reset after
+     * the crash. Initial index is passed to LayoutTestsExecuter in the intent that starts
+     * it.
+     */
+    private int mCurrentTestIndex;
+
+    private int mTotalTestCount;
+
+    private WebView mCurrentWebView;
+    private String mCurrentTestRelativePath;
+    private String mCurrentTestUri;
+    private CurrentState mCurrentState = CurrentState.IDLE;
+
+    private boolean mCurrentTestTimedOut;
+    private AbstractResult mCurrentResult;
+    private AdditionalTextOutput mCurrentAdditionalTextOutput;
+
+    private LayoutTestController mLayoutTestController = new LayoutTestController(this);
+    private boolean mCanOpenWindows;
+    private boolean mDumpDatabaseCallbacks;
+    private boolean mSetGeolocationPermissionCalled;
+    private boolean mGeolocationPermission;
+
+    private WakeLock mScreenDimLock;
+
+    /** COMMUNICATION WITH ManagerService */
+
+    private Messenger mManagerServiceMessenger;
+
+    private ServiceConnection mServiceConnection = new ServiceConnection() {
+
+        @Override
+        public void onServiceConnected(ComponentName name, IBinder service) {
+            mManagerServiceMessenger = new Messenger(service);
+            runNextTest();
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName name) {
+            /** TODO */
+        }
+    };
+
+    private final Handler mResultHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_ACTUAL_RESULT_OBTAINED:
+                    onActualResultsObtained();
+                    break;
+
+                case MSG_TEST_TIMED_OUT:
+                    onTestTimedOut();
+                    break;
+
+                default:
+                    break;
+            }
+        }
+    };
+
+    /** WEBVIEW CONFIGURATION */
+
+    private WebViewClient mWebViewClient = new WebViewClient() {
+        @Override
+        public void onPageFinished(WebView view, String url) {
+            /** Some tests fire up many page loads, we don't want to detect them */
+            if (!url.equals(mCurrentTestUri)) {
+                return;
+            }
+
+            if (mCurrentState == CurrentState.RENDERING_PAGE) {
+                onTestFinished();
+            }
+        }
+    };
+
+    private WebChromeClient mWebChromeClient = new WebChromeClient() {
+        @Override
+        public void onExceededDatabaseQuota(String url, String databaseIdentifier,
+                long currentQuota, long estimatedSize, long totalUsedQuota,
+                QuotaUpdater quotaUpdater) {
+            /** TODO: This should be recorded as part of the text result */
+            /** TODO: The quota should also probably be reset somehow for every test? */
+            if (mDumpDatabaseCallbacks) {
+                getCurrentAdditionalTextOutput().appendExceededDbQuotaMessage(url,
+                        databaseIdentifier);
+            }
+            quotaUpdater.updateQuota(currentQuota + 5 * 1024 * 1024);
+        }
+
+        @Override
+        public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
+            getCurrentAdditionalTextOutput().appendJsAlert(message);
+            result.confirm();
+            return true;
+        }
+
+        @Override
+        public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {
+            getCurrentAdditionalTextOutput().appendJsConfirm(message);
+            result.confirm();
+            return true;
+        }
+
+        @Override
+        public boolean onJsPrompt(WebView view, String url, String message, String defaultValue,
+                JsPromptResult result) {
+            getCurrentAdditionalTextOutput().appendJsPrompt(message, defaultValue);
+            result.confirm();
+            return true;
+        }
+
+        @Override
+        public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
+            getCurrentAdditionalTextOutput().appendConsoleMessage(consoleMessage);
+            return true;
+        }
+
+        @Override
+        public boolean onCreateWindow(WebView view, boolean dialog, boolean userGesture,
+                Message resultMsg) {
+            WebView.WebViewTransport transport = (WebView.WebViewTransport)resultMsg.obj;
+            /** By default windows cannot be opened, so just send null back. */
+            WebView newWindowWebView = null;
+
+            if (mCanOpenWindows) {
+                /**
+                 * We never display the new window, just create the view and allow it's content to
+                 * execute and be recorded by the executor.
+                 */
+                newWindowWebView = new WebView(LayoutTestsExecutor.this);
+                setupWebView(newWindowWebView);
+            }
+
+            transport.setWebView(newWindowWebView);
+            resultMsg.sendToTarget();
+            return true;
+        }
+
+        @Override
+        public void onGeolocationPermissionsShowPrompt(String origin, Callback callback) {
+            if (mSetGeolocationPermissionCalled) {
+                callback.invoke(origin, mGeolocationPermission, false);
+            }
+        }
+    };
+
+    /** IMPLEMENTATION */
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        requestWindowFeature(Window.FEATURE_PROGRESS);
+
+        Intent intent = getIntent();
+        mTestsList = intent.getStringArrayListExtra(EXTRA_TESTS_LIST);
+        mCurrentTestIndex = intent.getIntExtra(EXTRA_TEST_INDEX, -1);
+        mTotalTestCount = mCurrentTestIndex + mTestsList.size();
+
+        PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
+        mScreenDimLock = pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK
+                | PowerManager.ON_AFTER_RELEASE, "WakeLock in LayoutTester");
+        mScreenDimLock.acquire();
+
+        bindService(new Intent(this, ManagerService.class), mServiceConnection,
+                Context.BIND_AUTO_CREATE);
+    }
+
+    private void reset() {
+        WebView previousWebView = mCurrentWebView;
+
+        resetLayoutTestController();
+
+        mCurrentTestTimedOut = false;
+        mCurrentResult = null;
+        mCurrentAdditionalTextOutput = null;
+
+        mCurrentWebView = new WebView(this);
+        setupWebView(mCurrentWebView);
+
+        setContentView(mCurrentWebView);
+        if (previousWebView != null) {
+            Log.d(LOG_TAG + "::reset", "previousWebView != null");
+            previousWebView.destroy();
+        }
+    }
+
+    private void setupWebView(WebView webView) {
+        webView.setWebViewClient(mWebViewClient);
+        webView.setWebChromeClient(mWebChromeClient);
+        webView.addJavascriptInterface(mLayoutTestController, "layoutTestController");
+
+        /**
+         * 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);
+
+        WebSettings webViewSettings = webView.getSettings();
+        webViewSettings.setAppCacheEnabled(true);
+        webViewSettings.setAppCachePath(getApplicationContext().getCacheDir().getPath());
+        webViewSettings.setAppCacheMaxSize(Long.MAX_VALUE);
+        webViewSettings.setJavaScriptEnabled(true);
+        webViewSettings.setJavaScriptCanOpenWindowsAutomatically(true);
+        webViewSettings.setSupportMultipleWindows(true);
+        webViewSettings.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.NORMAL);
+        webViewSettings.setDatabaseEnabled(true);
+        webViewSettings.setDatabasePath(getDir("databases", 0).getAbsolutePath());
+        webViewSettings.setDomStorageEnabled(true);
+        webViewSettings.setWorkersEnabled(false);
+        webViewSettings.setXSSAuditorEnabled(false);
+    }
+
+    private void runNextTest() {
+        assert mCurrentState == CurrentState.IDLE : "mCurrentState = " + mCurrentState.name();
+
+        if (mTestsList.isEmpty()) {
+            onAllTestsFinished();
+            return;
+        }
+
+        mCurrentTestRelativePath = mTestsList.remove(0);
+        mCurrentTestUri =
+                Uri.fromFile(new File(TESTS_ROOT_DIR_PATH, mCurrentTestRelativePath)).toString();
+
+        reset();
+
+        /** Start time-out countdown and the test */
+        mCurrentState = CurrentState.RENDERING_PAGE;
+        mResultHandler.sendEmptyMessageDelayed(MSG_TEST_TIMED_OUT, DEFAULT_TIME_OUT_MS);
+        mCurrentWebView.loadUrl(mCurrentTestUri);
+    }
+
+    private void onTestTimedOut() {
+        assert mCurrentState.isRunningState() : "mCurrentState = " + mCurrentState.name();
+
+        mCurrentTestTimedOut = true;
+
+        /**
+         * While it is theoretically possible that the test times out because
+         * of webview becoming unresponsive, it is very unlikely. Therefore it's
+         * assumed that obtaining results (that calls various webview methods)
+         * will not itself hang.
+         */
+        obtainActualResultsFromWebView();
+    }
+
+    private void onTestFinished() {
+        assert mCurrentState.isRunningState() : "mCurrentState = " + mCurrentState.name();
+
+        obtainActualResultsFromWebView();
+    }
+
+    private void obtainActualResultsFromWebView() {
+        /**
+         * If the result has not been set by the time the test finishes we create
+         * a default type of result.
+         */
+        if (mCurrentResult == null) {
+            /** TODO: Default type should be RenderTreeResult. We don't support it now. */
+            mCurrentResult = new TextResult(mCurrentTestRelativePath);
+        }
+
+        mCurrentState = CurrentState.OBTAINING_RESULT;
+
+        mCurrentResult.obtainActualResults(mCurrentWebView,
+                mResultHandler.obtainMessage(MSG_ACTUAL_RESULT_OBTAINED));
+    }
+
+    private void onActualResultsObtained() {
+        assert mCurrentState == CurrentState.OBTAINING_RESULT
+                : "mCurrentState = " + mCurrentState.name();
+
+        mCurrentState = CurrentState.IDLE;
+
+        mResultHandler.removeMessages(MSG_TEST_TIMED_OUT);
+        reportResultToService();
+        mCurrentTestIndex++;
+        updateProgressBar();
+        runNextTest();
+    }
+
+    private void reportResultToService() {
+        if (mCurrentAdditionalTextOutput != null) {
+            mCurrentResult.setAdditionalTextOutputString(mCurrentAdditionalTextOutput.toString());
+        }
+
+        try {
+            Message serviceMsg =
+                    Message.obtain(null, ManagerService.MSG_PROCESS_ACTUAL_RESULTS);
+
+            Bundle bundle = mCurrentResult.getBundle();
+            bundle.putInt("testIndex", mCurrentTestIndex);
+            if (mCurrentTestTimedOut) {
+                bundle.putString("resultCode", AbstractResult.ResultCode.FAIL_TIMED_OUT.name());
+            }
+
+            serviceMsg.setData(bundle);
+            mManagerServiceMessenger.send(serviceMsg);
+        } catch (RemoteException e) {
+            Log.e(LOG_TAG + "::reportResultToService", e.getMessage());
+        }
+    }
+
+    private void updateProgressBar() {
+        getWindow().setFeatureInt(Window.FEATURE_PROGRESS,
+                mCurrentTestIndex * Window.PROGRESS_END / mTotalTestCount);
+        setTitle(mCurrentTestIndex * 100 / mTotalTestCount + "% " +
+                "(" + mCurrentTestIndex + "/" + mTotalTestCount + ")");
+    }
+
+    private void onAllTestsFinished() {
+        mScreenDimLock.release();
+
+        try {
+            Message serviceMsg =
+                    Message.obtain(null, ManagerService.MSG_ALL_TESTS_FINISHED);
+            mManagerServiceMessenger.send(serviceMsg);
+        } catch (RemoteException e) {
+            Log.e(LOG_TAG + "::onAllTestsFinished", e.getMessage());
+        }
+
+        unbindService(mServiceConnection);
+    }
+
+    private AdditionalTextOutput getCurrentAdditionalTextOutput() {
+        if (mCurrentAdditionalTextOutput == null) {
+            mCurrentAdditionalTextOutput = new AdditionalTextOutput();
+        }
+        return mCurrentAdditionalTextOutput;
+    }
+
+    /** LAYOUT TEST CONTROLLER */
+
+    private static final int MSG_WAIT_UNTIL_DONE = 0;
+    private static final int MSG_NOTIFY_DONE = 1;
+    private static final int MSG_DUMP_AS_TEXT = 2;
+    private static final int MSG_DUMP_CHILD_FRAMES_AS_TEXT = 3;
+    private static final int MSG_SET_CAN_OPEN_WINDOWS = 4;
+    private static final int MSG_DUMP_DATABASE_CALLBACKS = 5;
+    private static final int MSG_SET_GEOLOCATION_PREMISSION = 6;
+
+    Handler mLayoutTestControllerHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            assert mCurrentState.isRunningState()
+                    : "mCurrentState = " + mCurrentState.name();
+
+            switch (msg.what) {
+                case MSG_WAIT_UNTIL_DONE:
+                    mCurrentState = CurrentState.WAITING_FOR_ASYNCHRONOUS_TEST;
+                    break;
+
+                case MSG_NOTIFY_DONE:
+                    if (mCurrentState == CurrentState.WAITING_FOR_ASYNCHRONOUS_TEST) {
+                        onTestFinished();
+                    }
+                    break;
+
+                case MSG_DUMP_AS_TEXT:
+                    if (mCurrentResult == null) {
+                        mCurrentResult = new TextResult(mCurrentTestRelativePath);
+                    }
+
+                    assert mCurrentResult instanceof TextResult
+                            : "mCurrentResult instanceof" + mCurrentResult.getClass().getName();
+
+                    break;
+
+                case MSG_DUMP_CHILD_FRAMES_AS_TEXT:
+                    /** If dumpAsText was not called we assume that the result should be text */
+                    if (mCurrentResult == null) {
+                        mCurrentResult = new TextResult(mCurrentTestRelativePath);
+                    }
+
+                    assert mCurrentResult instanceof TextResult
+                            : "mCurrentResult instanceof" + mCurrentResult.getClass().getName();
+
+                    ((TextResult)mCurrentResult).setDumpChildFramesAsText(true);
+                    break;
+
+                case MSG_SET_CAN_OPEN_WINDOWS:
+                    mCanOpenWindows = true;
+                    break;
+
+                case MSG_DUMP_DATABASE_CALLBACKS:
+                    mDumpDatabaseCallbacks = true;
+                    break;
+
+                case MSG_SET_GEOLOCATION_PREMISSION:
+                    mSetGeolocationPermissionCalled = true;
+                    mGeolocationPermission = msg.arg1 == 1;
+                    break;
+
+                default:
+                    Log.w(LOG_TAG + "::handleMessage", "Message code does not exist: " + msg.what);
+                    break;
+            }
+        }
+    };
+
+    private void resetLayoutTestController() {
+        mCanOpenWindows = false;
+        mDumpDatabaseCallbacks = false;
+        mSetGeolocationPermissionCalled = false;
+        mGeolocationPermission = false;
+    }
+
+    public void waitUntilDone() {
+        Log.w(LOG_TAG + "::waitUntilDone", "called");
+        mLayoutTestControllerHandler.sendEmptyMessage(MSG_WAIT_UNTIL_DONE);
+    }
+
+    public void notifyDone() {
+        Log.w(LOG_TAG + "::notifyDone", "called");
+        mLayoutTestControllerHandler.sendEmptyMessage(MSG_NOTIFY_DONE);
+    }
+
+    public void dumpAsText(boolean enablePixelTest) {
+        Log.w(LOG_TAG + "::dumpAsText(" + enablePixelTest + ")", "called");
+        /** TODO: Implement */
+        if (enablePixelTest) {
+            Log.w(LOG_TAG + "::dumpAsText", "enablePixelTest not implemented, switching to false");
+        }
+        mLayoutTestControllerHandler.sendEmptyMessage(MSG_DUMP_AS_TEXT);
+    }
+
+    public void dumpChildFramesAsText() {
+        Log.w(LOG_TAG + "::dumpChildFramesAsText", "called");
+        mLayoutTestControllerHandler.sendEmptyMessage(MSG_DUMP_CHILD_FRAMES_AS_TEXT);
+    }
+
+    public void setCanOpenWindows() {
+        Log.w(LOG_TAG + "::setCanOpenWindows", "called");
+        mLayoutTestControllerHandler.sendEmptyMessage(MSG_SET_CAN_OPEN_WINDOWS);
+    }
+
+    public void dumpDatabaseCallbacks() {
+        Log.w(LOG_TAG + "::dumpDatabaseCallbacks:", "called");
+        mLayoutTestControllerHandler.sendEmptyMessage(MSG_DUMP_DATABASE_CALLBACKS);
+    }
+
+    public void setGeolocationPermission(boolean allow) {
+        Log.w(LOG_TAG + "::setGeolocationPermission", "called");
+        Message msg = mLayoutTestControllerHandler.obtainMessage(MSG_SET_GEOLOCATION_PREMISSION);
+        msg.arg1 = allow ? 1 : 0;
+        msg.sendToTarget();
+    }
+}
\ No newline at end of file
diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/ManagerService.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/ManagerService.java
new file mode 100644
index 0000000..3c77a55
--- /dev/null
+++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/ManagerService.java
@@ -0,0 +1,169 @@
+/*
+ * 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.dumprendertree2;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Messenger;
+import android.util.Log;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A service that handles managing the results of tests, informing of crashes, generating
+ * summaries, etc.
+ */
+public class ManagerService extends Service {
+
+    private static final String LOG_TAG = "ManagerService";
+
+    /** TODO: make it a setting */
+    static final String TESTS_ROOT_DIR_PATH =
+            Environment.getExternalStorageDirectory() +
+            File.separator + "android" +
+            File.separator + "LayoutTests";
+
+    /** TODO: make it a setting */
+    static final String RESULTS_ROOT_DIR_PATH =
+            Environment.getExternalStorageDirectory() +
+            File.separator + "android" +
+            File.separator + "LayoutTests-results";
+
+    /** TODO: Make it a setting */
+    private static final List<String> EXPECTED_RESULT_LOCATION_RELATIVE_DIR_PREFIXES =
+            new ArrayList<String>(3);
+    {
+        EXPECTED_RESULT_LOCATION_RELATIVE_DIR_PREFIXES.add("platform" + File.separator +
+                "android-v8" + File.separator);
+        EXPECTED_RESULT_LOCATION_RELATIVE_DIR_PREFIXES.add("platform" + File.separator +
+                "android" + File.separator);
+        EXPECTED_RESULT_LOCATION_RELATIVE_DIR_PREFIXES.add("");
+    }
+
+    /** TODO: Make these settings */
+    private static final String TEXT_RESULT_EXTENSION = "txt";
+    private static final String IMAGE_RESULT_EXTENSION = "png";
+
+    static final int MSG_PROCESS_ACTUAL_RESULTS = 0;
+    static final int MSG_ALL_TESTS_FINISHED = 1;
+
+    private Handler mIncomingHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_PROCESS_ACTUAL_RESULTS:
+                    Log.d(LOG_TAG + ".mIncomingHandler", msg.getData().getString("relativePath"));
+                    onActualResultsObtained(msg.getData());
+                    break;
+
+                case MSG_ALL_TESTS_FINISHED:
+                    mSummarizer.summarize();
+                    break;
+            }
+        }
+    };
+
+    private Messenger mMessenger = new Messenger(mIncomingHandler);
+
+    private FileFilter mFileFilter;
+    private Summarizer mSummarizer;
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+
+        mFileFilter = new FileFilter(TESTS_ROOT_DIR_PATH);
+        mSummarizer = new Summarizer(mFileFilter, RESULTS_ROOT_DIR_PATH);
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return mMessenger.getBinder();
+    }
+
+    private void onActualResultsObtained(Bundle bundle) {
+        AbstractResult results =
+                AbstractResult.TestType.valueOf(bundle.getString("type")).createResult(bundle);
+        String relativePath = results.getRelativePath();
+        results.setExpectedTextResult(getExpectedTextResult(relativePath));
+        results.setExpectedImageResult(getExpectedImageResult(relativePath));
+
+        dumpActualTextResult(results);
+        dumpActualImageResult(results);
+
+        mSummarizer.appendTest(results);
+    }
+
+    private void dumpActualTextResult(AbstractResult result) {
+        String testPath = result.getRelativePath();
+        String actualTextResult = result.getActualTextResult();
+        if (actualTextResult == null) {
+            return;
+        }
+
+        String resultPath = FileFilter.setPathEnding(testPath, "-actual." + TEXT_RESULT_EXTENSION);
+        FsUtils.writeDataToStorage(new File(RESULTS_ROOT_DIR_PATH, resultPath),
+                actualTextResult.getBytes(), false);
+    }
+
+    private void dumpActualImageResult(AbstractResult result) {
+        String testPath = result.getRelativePath();
+        byte[] actualImageResult = result.getActualImageResult();
+        if (actualImageResult == null) {
+            return;
+        }
+
+        String resultPath = FileFilter.setPathEnding(testPath, "-actual." + IMAGE_RESULT_EXTENSION);
+        FsUtils.writeDataToStorage(new File(RESULTS_ROOT_DIR_PATH, resultPath),
+                actualImageResult, false);
+    }
+
+    public static String getExpectedTextResult(String relativePath) {
+        byte[] result = getExpectedResult(relativePath, TEXT_RESULT_EXTENSION);
+        if (result != null) {
+            return new String(result);
+        }
+        return null;
+    }
+
+    public static byte[] getExpectedImageResult(String relativePath) {
+        return getExpectedResult(relativePath, IMAGE_RESULT_EXTENSION);
+    }
+
+    private static byte[] getExpectedResult(String relativePath, String extension) {
+        String originalRelativePath =
+                FileFilter.setPathEnding(relativePath, "-expected." + extension);
+
+        byte[] bytes = null;
+        List<String> locations = EXPECTED_RESULT_LOCATION_RELATIVE_DIR_PREFIXES;
+
+        int size = EXPECTED_RESULT_LOCATION_RELATIVE_DIR_PREFIXES.size();
+        for (int i = 0; bytes == null && i < size; i++) {
+            relativePath = locations.get(i) + originalRelativePath;
+            bytes = FsUtils.readDataFromStorage(new File(TESTS_ROOT_DIR_PATH, relativePath));
+        }
+
+        return bytes;
+    }
+}
\ No newline at end of file
diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/Summarizer.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/Summarizer.java
new file mode 100644
index 0000000..56822f3
--- /dev/null
+++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/Summarizer.java
@@ -0,0 +1,314 @@
+/*
+ * 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.dumprendertree2;
+
+import java.io.File;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * A class that collects information about tests that ran and can create HTML
+ * files with summaries and easy navigation.
+ */
+public class Summarizer {
+
+    private static final String LOG_TAG = "Summarizer";
+
+    private static final String CSS =
+            "<style type=\"text/css\">" +
+            "* {" +
+            "       font-family: Verdana;" +
+            "       border: 0;" +
+            "       margin: 0;" +
+            "       padding: 0;}" +
+            "body {" +
+            "       margin: 10px;}" +
+            "h1 {" +
+            "       font-size: 24px;" +
+            "       margin: 4px 0 4px 0;}" +
+            "h2 {" +
+            "       font-size:18px;" +
+            "       text-transform: uppercase;" +
+            "       margin: 20px 0 3px 0;}" +
+            "h3, h3 a {" +
+            "       font-size: 14px;" +
+            "       color: black;" +
+            "       text-decoration: none;" +
+            "       margin-bottom: 4px;}" +
+            "h3 a span.path {" +
+            "       text-decoration: underline;}" +
+            "h3 span.tri {" +
+            "       text-decoration: none;" +
+            "       float: left;" +
+            "       width: 20px;}" +
+            "h3 span.sqr {" +
+            "       text-decoration: none;" +
+            "       color: #8ee100;" +
+            "       float: left;" +
+            "       width: 20px;}" +
+            "h3 img {" +
+            "       width: 8px;" +
+            "       margin-right: 4px;}" +
+            "div.diff {" +
+            "       margin-bottom: 25px;}" +
+            "div.diff a {" +
+            "       font-size: 12px;" +
+            "       color: #888;}" +
+            "table.visual_diff {" +
+            "       border-bottom: 0px solid;" +
+            "       border-collapse: collapse;" +
+            "       width: 100%;" +
+            "       margin-bottom: 2px;}" +
+            "table.visual_diff tr.headers td {" +
+            "       border-bottom: 1px solid;" +
+            "       border-top: 0;" +
+            "       padding-bottom: 3px;}" +
+            "table.visual_diff tr.results td {" +
+            "       border-top: 1px dashed;" +
+            "       border-right: 1px solid;" +
+            "       font-size: 15px;" +
+            "       vertical-align: top;}" +
+            "table.visual_diff tr.results td.line_count {" +
+            "       background-color:#aaa;" +
+            "       min-width:20px;" +
+            "       text-align: right;" +
+            "       border-right: 1px solid;" +
+            "       border-left: 1px solid;" +
+            "       padding: 2px 1px 2px 0px;}" +
+            "table.visual_diff tr.results td.line {" +
+            "       padding: 2px 0px 2px 4px;" +
+            "       border-right: 1px solid;" +
+            "       width: 49.8%;}" +
+            "table.visual_diff tr.footers td {" +
+            "       border-top: 1px solid;" +
+            "       border-bottom: 0;}" +
+            "table.visual_diff tr td.space {" +
+            "       border: 0;" +
+            "       width: 0.4%}" +
+            "div.space {" +
+            "       margin-top:4px;}" +
+            "span.eql {" +
+            "       background-color: #f3f3f3;}" +
+            "span.del {" +
+            "       background-color: #ff8888; }" +
+            "span.ins {" +
+            "       background-color: #88ff88; }" +
+            "span.fail {" +
+            "       color: red;}" +
+            "span.pass {" +
+            "       color: green;}" +
+            "span.time_out {" +
+            "       color: orange;}" +
+            "table.summary {" +
+            "       border: 1px solid black;" +
+            "       margin-top: 20px;}" +
+            "table.summary td {" +
+            "       padding: 3px;}" +
+            "span.listItem {" +
+            "       font-size: 11px;" +
+            "       font-weight: normal;" +
+            "       text-transform: uppercase;" +
+            "       padding: 3px;" +
+            "       -webkit-border-radius: 4px;}" +
+            "span." + AbstractResult.ResultCode.PASS.name() + "{" +
+            "       background-color: #8ee100;" +
+            "       color: black;}" +
+            "span." + AbstractResult.ResultCode.FAIL_RESULT_DIFFERS.name() + "{" +
+            "       background-color: #ccc;" +
+            "       color: black;}" +
+            "span." + AbstractResult.ResultCode.FAIL_NO_EXPECTED_RESULT.name() + "{" +
+            "       background-color: #a700e4;" +
+            "       color: #fff;}" +
+            "span." + AbstractResult.ResultCode.FAIL_TIMED_OUT.name() + "{" +
+            "       background-color: #f3cb00;" +
+            "       color: black;}" +
+            "span." + AbstractResult.ResultCode.FAIL_CRASHED.name() + "{" +
+            "       background-color: #c30000;" +
+            "       color: #fff;}" +
+            "span.noLtc {" +
+            "       background-color: #944000;" +
+            "       color: #fff;" +
+            "</style>";
+
+    private static final String SCRIPT =
+            "<script type=\"text/javascript\">" +
+            "    function toggleDisplay(id) {" +
+            "        element = document.getElementById(id);" +
+            "        triangle = document.getElementById('tri.' + id);" +
+            "        if (element.style.display == 'none') {" +
+            "            element.style.display = 'inline';" +
+            "            triangle.innerHTML = '&#x25bc; ';" +
+            "        } else {" +
+            "            element.style.display = 'none';" +
+            "            triangle.innerHTML = '&#x25b6; ';" +
+            "        }" +
+            "    }" +
+            "</script>";
+
+    /** TODO: Make it a setting */
+    private static final String HTML_SUMMARY_RELATIVE_PATH = "summary.html";
+
+    private int mCrashedTestsCount = 0;
+    private List<AbstractResult> mFailedNotIgnoredTests = new ArrayList<AbstractResult>();
+    private List<AbstractResult> mIgnoredTests = new ArrayList<AbstractResult>();
+    private List<String> mPassedNotIgnoredTests = new ArrayList<String>();
+
+    private FileFilter mFileFilter;
+    private String mResultsRootDirPath;
+
+    public Summarizer(FileFilter fileFilter, String resultsRootDirPath) {
+        mFileFilter = fileFilter;
+        mResultsRootDirPath = resultsRootDirPath;
+    }
+
+    public void appendTest(AbstractResult result) {
+        String relativePath = result.getRelativePath();
+
+        if (result.getResultCode() == AbstractResult.ResultCode.FAIL_CRASHED) {
+            mCrashedTestsCount++;
+        }
+
+        if (mFileFilter.isIgnoreRes(relativePath)) {
+            mIgnoredTests.add(result);
+        } else if (result.getResultCode() == AbstractResult.ResultCode.PASS) {
+            mPassedNotIgnoredTests.add(relativePath);
+        } else {
+            mFailedNotIgnoredTests.add(result);
+        }
+    }
+
+    public void summarize() {
+        StringBuilder html = new StringBuilder();
+
+        html.append("<html><head>");
+        html.append(CSS);
+        html.append(SCRIPT);
+        html.append("</head><body>");
+
+        createTopSummaryTable(html);
+
+        createResultsListWithDiff(html, "Failed", mFailedNotIgnoredTests);
+
+        createResultsListWithDiff(html, "Ignored", mIgnoredTests);
+
+        createResultsListNoDiff(html, "Passed", mPassedNotIgnoredTests);
+
+        html.append("</body></html>");
+
+        FsUtils.writeDataToStorage(new File(mResultsRootDirPath, HTML_SUMMARY_RELATIVE_PATH),
+                html.toString().getBytes(), false);
+    }
+
+    private void createTopSummaryTable(StringBuilder html) {
+        int total = mFailedNotIgnoredTests.size() +
+                mPassedNotIgnoredTests.size() +
+                mIgnoredTests.size();
+        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
+        html.append("<h1> - total of " + total + " tests - ");
+        html.append(dateFormat.format(new Date()) + "</h1>");
+
+        html.append("<table class=\"summary\">");
+        createSummaryTableRow(html, "CRASHED", mCrashedTestsCount);
+        createSummaryTableRow(html, "FAILED", mFailedNotIgnoredTests.size());
+        createSummaryTableRow(html, "IGNORED", mIgnoredTests.size());
+        createSummaryTableRow(html, "PASSED", mPassedNotIgnoredTests.size());
+        html.append("</table>");
+    }
+
+    private void createSummaryTableRow(StringBuilder html, String caption, int size) {
+        html.append("<tr>");
+        html.append("    <td>" + caption + "</td>");
+        html.append("    <td>" + size + "</td>");
+        html.append("</tr>");
+    }
+
+    private void createResultsListWithDiff(StringBuilder html, String title,
+            List<AbstractResult> resultsList) {
+        String relativePath;
+        String id = "";
+        AbstractResult.ResultCode resultCode;
+
+        Collections.sort(resultsList);
+        html.append("<h2>" + title + " [" + resultsList.size() + "]</h2>");
+        for (AbstractResult result : resultsList) {
+            relativePath = result.getRelativePath();
+            resultCode = result.getResultCode();
+
+            html.append("<h3>");
+
+            if (resultCode == AbstractResult.ResultCode.PASS) {
+                html.append("<span class=\"sqr\">&#x25a0; </span>");
+                html.append("<span class=\"path\">" + relativePath + "</span>");
+            } else {
+                /**
+                 * Technically, two different paths could end up being the same, because
+                 * ':' is a valid  character in a path. However, it is probably not going
+                 * to cause any problems in this case
+                 */
+                id = relativePath.replace(File.separator, ":");
+                html.append("<a href=\"#\" onClick=\"toggleDisplay('" + id + "');");
+                html.append("return false;\">");
+                html.append("<span class=\"tri\" id=\"tri." + id + "\">&#x25b6; </span>");
+                html.append("<span class=\"path\">" + relativePath + "</span>");
+                html.append("</a>");
+            }
+
+            html.append(" <span class=\"listItem " + resultCode.name() + "\">");
+            html.append(resultCode.toString());
+            html.append("</span>");
+
+            /** Detect missing LTC function */
+            String additionalTextOutputString = result.getAdditionalTextOutputString();
+                if (additionalTextOutputString != null &&
+                        additionalTextOutputString.contains("com.android.dumprendertree") &&
+                        additionalTextOutputString.contains("LayoutTestController") &&
+                        additionalTextOutputString.contains("has no method")) {
+                    html.append(" <span class=\"listItem noLtc\">LTC function missing</span>");
+                }
+
+            html.append("</h3>");
+
+            if (resultCode != AbstractResult.ResultCode.PASS) {
+                html.append("<div class=\"diff\" style=\"display: none;\" id=\"" + id + "\">");
+                html.append(result.getDiffAsHtml());
+                html.append("<a href=\"#\" onClick=\"toggleDisplay('" + id + "');");
+                html.append("return false;\">Hide</a>");
+                html.append("</div>");
+            }
+
+            html.append("<div class=\"space\"></div>");
+        }
+    }
+
+    private void createResultsListNoDiff(StringBuilder html, String title,
+            List<String> resultsList) {
+        Collections.sort(resultsList);
+        html.append("<h2>Passed [" + resultsList.size() + "]</h2>");
+        for (String result : resultsList) {
+            html.append("<h3>");
+            html.append("<span class=\"sqr\">&#x25a0; </span>");
+            html.append("<span class=\"path\">" + result + "</span>");
+            html.append("</h3>");
+            html.append("<div class=\"space\"></div>");
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/TestsListActivity.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/TestsListActivity.java
new file mode 100644
index 0000000..a402ae1
--- /dev/null
+++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/TestsListActivity.java
@@ -0,0 +1,101 @@
+/*
+ * 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.dumprendertree2;
+
+import android.app.Activity;
+import android.app.ProgressDialog;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.view.Window;
+
+import java.util.ArrayList;
+
+/**
+ * An Activity that generates a list of tests and sends the intent to
+ * LayoutTestsExecuter to run them. It also restarts the LayoutTestsExecuter
+ * after it crashes (TODO).
+ */
+public class TestsListActivity extends Activity {
+
+    private static final int MSG_TEST_LIST_PRELOADER_DONE = 0;
+
+    /** Constants for adding extras to an intent */
+    public static final String EXTRA_TEST_PATH = "TestPath";
+
+    private static ProgressDialog sProgressDialog;
+
+    private Handler mHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_TEST_LIST_PRELOADER_DONE:
+                    sProgressDialog.dismiss();
+                    mTestsList = (ArrayList<String>)msg.obj;
+                    mTotalTestCount = mTestsList.size();
+                    restartExecutor(0);
+                    break;
+            }
+        }
+    };
+
+    private ArrayList<String> mTestsList;
+    private int mTotalTestCount;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        /** Prepare the progress dialog */
+        sProgressDialog = new ProgressDialog(TestsListActivity.this);
+        sProgressDialog.setCancelable(false);
+        sProgressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
+        sProgressDialog.setTitle(R.string.dialog_progress_title);
+        sProgressDialog.setMessage(getText(R.string.dialog_progress_msg));
+
+        requestWindowFeature(Window.FEATURE_PROGRESS);
+
+        Intent intent = getIntent();
+        if (!intent.getAction().equals(Intent.ACTION_RUN)) {
+            return;
+        }
+        String path = intent.getStringExtra(EXTRA_TEST_PATH);
+
+        sProgressDialog.show();
+        Message doneMsg = Message.obtain(mHandler, MSG_TEST_LIST_PRELOADER_DONE);
+
+        new TestsListPreloaderThread(path, doneMsg).start();
+    }
+
+    /**
+     * (Re)starts the executer activity from the given test number (inclusive, 0-based).
+     * This number is an index in mTestsList, not the sublist passed in the intent.
+     *
+     * @param startFrom
+     *      test index in mTestsList to start the tests from (inclusive, 0-based)
+     */
+    private void restartExecutor(int startFrom) {
+        Intent intent = new Intent();
+        intent.setClass(this, LayoutTestsExecutor.class);
+        intent.setAction(Intent.ACTION_RUN);
+        intent.putStringArrayListExtra(LayoutTestsExecutor.EXTRA_TESTS_LIST,
+                new ArrayList<String>(mTestsList.subList(startFrom, mTotalTestCount)));
+        intent.putExtra(LayoutTestsExecutor.EXTRA_TEST_INDEX, startFrom);
+        startActivity(intent);
+    }
+}
\ No newline at end of file
diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/TestsListPreloaderThread.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/TestsListPreloaderThread.java
new file mode 100644
index 0000000..f76105d
--- /dev/null
+++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/TestsListPreloaderThread.java
@@ -0,0 +1,121 @@
+/*
+ * 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.dumprendertree2;
+
+import android.os.Environment;
+import android.os.Message;
+import android.util.Log;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.LinkedList;
+
+/**
+ * A Thread that is responsible for generating a lists of tests to run.
+ */
+public class TestsListPreloaderThread extends Thread {
+
+    private static final String LOG_TAG = "TestsListPreloaderThread";
+
+    /** TODO: make it a setting */
+    private static final String TESTS_ROOT_DIR_PATH =
+            Environment.getExternalStorageDirectory() +
+            File.separator + "android" +
+            File.separator + "LayoutTests";
+
+    /** A list containing relative paths of tests to run */
+    private ArrayList<String> mTestsList = new ArrayList<String>();
+
+    private FileFilter mFileFilter;
+
+    /**
+     * A relative path to the folder with the tests we want to run or particular test.
+     * Used up to and including preloadTests().
+     */
+    private String mRelativePath;
+
+    private Message mDoneMsg;
+
+    /**
+     * The given path must be relative to the root dir.
+     *
+     * @param path
+     * @param doneMsg
+     */
+    public TestsListPreloaderThread(String path, Message doneMsg) {
+        mFileFilter = new FileFilter(TESTS_ROOT_DIR_PATH);
+        mRelativePath = path;
+        mDoneMsg = doneMsg;
+    }
+
+    @Override
+    public void run() {
+        /** Check if the path is correct */
+        File file = new File(TESTS_ROOT_DIR_PATH, mRelativePath);
+        if (!file.exists()) {
+            Log.e(LOG_TAG + "::run", "Path does not exist: " + mRelativePath);
+            return;
+        }
+
+        /** Populate the tests' list accordingly */
+        if (file.isDirectory()) {
+            preloadTests(mRelativePath);
+        } else {
+            mTestsList.add(mRelativePath);
+        }
+        mDoneMsg.obj = mTestsList;
+        mDoneMsg.sendToTarget();
+    }
+
+    /**
+     * Loads all the tests from the given folders and all the subfolders
+     * into mTestsList.
+     *
+     * @param dirRelativePath
+     */
+    private void preloadTests(String dirRelativePath) {
+        LinkedList<String> foldersList = new LinkedList<String>();
+        foldersList.add(dirRelativePath);
+
+        String relativePath;
+        String currentDirRelativePath;
+        String itemName;
+        File[] items;
+        while (!foldersList.isEmpty()) {
+            currentDirRelativePath = foldersList.removeFirst();
+            items = new File(TESTS_ROOT_DIR_PATH, currentDirRelativePath).listFiles();
+            for (File item : items) {
+                itemName = item.getName();
+                relativePath = currentDirRelativePath + File.separator + itemName;
+
+                if (item.isDirectory() && FileFilter.isTestDir(itemName)) {
+                    foldersList.add(relativePath);
+                    continue;
+                }
+
+                if (FileFilter.isTestFile(itemName)) {
+                    if (!mFileFilter.isSkip(relativePath)) {
+                        mTestsList.add(relativePath);
+                    } else {
+                        //mSummarizer.addSkippedTest(relativePath);
+                        /** TODO: Summarizer is now in service - figure out how to send the info */
+                    }
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/TextResult.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/TextResult.java
new file mode 100644
index 0000000..0f864e5
--- /dev/null
+++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/TextResult.java
@@ -0,0 +1,229 @@
+/*
+ * 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.dumprendertree2;
+
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.webkit.WebView;
+
+import name.fraser.neil.plaintext.diff_match_patch;
+
+import java.util.LinkedList;
+
+/**
+ * A result object for which the expected output is text. It does not have an image
+ * expected result.
+ *
+ * <p>Created if layoutTestController.dumpAsText() was called.
+ */
+public class TextResult extends AbstractResult {
+
+    private static final int MSG_DOCUMENT_AS_TEXT = 0;
+
+    private String mExpectedResult;
+    private String mActualResult;
+    private String mRelativePath;
+    private ResultCode mResultCode;
+    private Message mResultObtainedMsg;
+
+    private boolean mDumpChildFramesAsText;
+
+    private Handler mHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            if (msg.what == MSG_DOCUMENT_AS_TEXT) {
+                mActualResult = (String)msg.obj;
+                mResultObtainedMsg.sendToTarget();
+            }
+        }
+    };
+
+    public TextResult(String relativePath) {
+        mRelativePath = relativePath;
+    }
+
+    public void setDumpChildFramesAsText(boolean dumpChildFramesAsText) {
+        mDumpChildFramesAsText = dumpChildFramesAsText;
+    }
+
+    /**
+     * Used to recreate the Result when received by the service.
+     *
+     * @param bundle
+     *      bundle with data used to recreate the result
+     */
+    public TextResult(Bundle bundle) {
+        mExpectedResult = bundle.getString("expectedTextualResult");
+        mActualResult = bundle.getString("actualTextualResult");
+        setAdditionalTextOutputString(bundle.getString("additionalTextOutputString"));
+        mRelativePath = bundle.getString("relativePath");
+        String resultCode = bundle.getString("resultCode");
+        if (resultCode != null) {
+            mResultCode = ResultCode.valueOf(resultCode);
+        }
+    }
+
+    @Override
+    public ResultCode getResultCode() {
+        if (mResultCode != null) {
+            return mResultCode;
+        }
+
+        if (mExpectedResult == null) {
+            mResultCode = AbstractResult.ResultCode.FAIL_NO_EXPECTED_RESULT;
+        } else if (!mExpectedResult.equals(mActualResult)) {
+            mResultCode = AbstractResult.ResultCode.FAIL_RESULT_DIFFERS;
+        } else {
+            mResultCode = AbstractResult.ResultCode.PASS;
+        }
+
+        return mResultCode;
+    }
+
+    @Override
+    public byte[] getActualImageResult() {
+        return null;
+    }
+
+    @Override
+    public String getActualTextResult() {
+        String additionalTextResultString = getAdditionalTextOutputString();
+        if (additionalTextResultString != null) {
+            return additionalTextResultString+ mActualResult;
+        }
+
+        return mActualResult;
+    }
+
+    @Override
+    public void setExpectedImageResult(byte[] expectedResult) {
+        /** This method is not applicable to this type of result */
+    }
+
+    @Override
+    public void setExpectedTextResult(String expectedResult) {
+        mExpectedResult = expectedResult;
+    }
+
+    @Override
+    public String getDiffAsHtml() {
+        StringBuilder html = new StringBuilder();
+        html.append("<table class=\"visual_diff\">");
+        html.append("    <tr class=\"headers\">");
+        html.append("        <td colspan=\"2\">Expected result:</td>");
+        html.append("        <td class=\"space\"></td>");
+        html.append("        <td colspan=\"2\">Actual result:</td>");
+        html.append("    </tr>");
+
+        if (mExpectedResult == null || mActualResult == null) {
+            appendNullsHtml(html);
+        } else {
+            appendDiffHtml(html);
+        }
+
+        html.append("    <tr class=\"footers\">");
+        html.append("        <td colspan=\"2\"></td>");
+        html.append("        <td class=\"space\"></td>");
+        html.append("        <td colspan=\"2\"></td>");
+        html.append("    </tr>");
+        html.append("</table>");
+
+        return html.toString();
+    }
+
+    private void appendDiffHtml(StringBuilder html) {
+        LinkedList<diff_match_patch.Diff> diffs =
+                new diff_match_patch().diff_main(mExpectedResult, mActualResult);
+
+        diffs = VisualDiffUtils.splitDiffsOnNewline(diffs);
+
+        LinkedList<String> expectedLines = new LinkedList<String>();
+        LinkedList<Integer> expectedLineNums = new LinkedList<Integer>();
+        LinkedList<String> actualLines = new LinkedList<String>();
+        LinkedList<Integer> actualLineNums = new LinkedList<Integer>();
+
+        VisualDiffUtils.generateExpectedResultLines(diffs, expectedLineNums, expectedLines);
+        VisualDiffUtils.generateActualResultLines(diffs, actualLineNums, actualLines);
+
+        html.append(VisualDiffUtils.getHtml(expectedLineNums, expectedLines,
+                actualLineNums, actualLines));
+    }
+
+    private void appendNullsHtml(StringBuilder html) {
+        /** TODO: Create a separate row for each line of not null result */
+        html.append("    <tr class=\"results\">");
+        html.append("    <td class=\"line_count\">");
+        html.append("    </td>");
+        html.append("    <td class=\"line\">");
+        if (mExpectedResult == null) {
+            html.append("Expected result was NULL");
+        } else {
+            html.append(mExpectedResult.replace("\n", "<br />"));
+        }
+        html.append("        </td>");
+        html.append("        <td class=\"space\"></td>");
+        html.append("    <td class=\"line_count\">");
+        html.append("    </td>");
+        html.append("    <td class=\"line\">");
+        if (mActualResult == null) {
+            html.append("Actual result was NULL");
+        } else {
+            html.append(mActualResult.replace("\n", "<br />"));
+        }
+        html.append("        </td>");
+        html.append("    </tr>");
+    }
+
+    @Override
+    public TestType getType() {
+        return TestType.TEXT;
+    }
+
+    @Override
+    public void obtainActualResults(WebView webview, Message resultObtainedMsg) {
+        mResultObtainedMsg = resultObtainedMsg;
+        Message msg = mHandler.obtainMessage(MSG_DOCUMENT_AS_TEXT);
+
+        /**
+         * arg1 - should dump top frame as text
+         * arg2 - should dump child frames as text
+         */
+        msg.arg1 = 1;
+        msg.arg2 = mDumpChildFramesAsText ? 1 : 0;
+        webview.documentAsText(msg);
+    }
+
+    @Override
+    public Bundle getBundle() {
+        Bundle bundle = new Bundle();
+        bundle.putString("expectedTextualResult", mExpectedResult);
+        bundle.putString("actualTextualResult", getActualTextResult());
+        bundle.putString("additionalTextOutputString", getAdditionalTextOutputString());
+        bundle.putString("relativePath", mRelativePath);
+        if (mResultCode != null) {
+            bundle.putString("resultCode", mResultCode.name());
+        }
+        bundle.putString("type", getType().name());
+        return bundle;
+    }
+
+    @Override
+    public String getRelativePath() {
+        return mRelativePath;
+    }
+}
\ No newline at end of file
diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/VisualDiffUtils.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/VisualDiffUtils.java
new file mode 100644
index 0000000..250b6bc
--- /dev/null
+++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/VisualDiffUtils.java
@@ -0,0 +1,207 @@
+/*
+ * 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.dumprendertree2;
+
+import name.fraser.neil.plaintext.diff_match_patch;
+
+import java.util.LinkedList;
+
+/**
+ * Helper methods fo TextResult.getDiffAsHtml()
+ */
+public class VisualDiffUtils {
+
+    private static final int DONT_PRINT_LINE_NUMBER = -1;
+
+    /**
+     * Preprocesses the list of diffs so that new line characters appear only at the end of
+     * diff.text
+     *
+     * @param diffs
+     * @return
+     *      LinkedList of diffs where new line character appears only on the end of
+     *      diff.text
+     */
+    public static LinkedList<diff_match_patch.Diff> splitDiffsOnNewline(
+            LinkedList<diff_match_patch.Diff> diffs) {
+        LinkedList<diff_match_patch.Diff> newDiffs = new LinkedList<diff_match_patch.Diff>();
+
+        String[] parts;
+        int lengthMinusOne;
+        for (diff_match_patch.Diff diff : diffs) {
+            parts = diff.text.split("\n", -1);
+            if (parts.length == 1) {
+                newDiffs.add(diff);
+                continue;
+            }
+
+            lengthMinusOne = parts.length - 1;
+            for (int i = 0; i < lengthMinusOne; i++) {
+                newDiffs.add(new diff_match_patch.Diff(diff.operation, parts[i] + "\n"));
+            }
+            if (!parts[lengthMinusOne].isEmpty()) {
+                newDiffs.add(new diff_match_patch.Diff(diff.operation, parts[lengthMinusOne]));
+            }
+        }
+
+        return newDiffs;
+    }
+
+    public static void generateExpectedResultLines(LinkedList<diff_match_patch.Diff> diffs,
+            LinkedList<Integer> lineNums, LinkedList<String> lines) {
+        String delSpan = "<span class=\"del\">";
+        String eqlSpan = "<span class=\"eql\">";
+
+        String line = "";
+        int i = 1;
+        for (diff_match_patch.Diff diff : diffs) {
+            switch (diff.operation) {
+                case DELETE:
+                    line = processDiff(diff, lineNums, lines, line, i, delSpan);
+                    if (line.equals("")) {
+                        i++;
+                    }
+                    break;
+
+                case INSERT:
+                    if (diff.text.endsWith("\n")) {
+                        lineNums.add(DONT_PRINT_LINE_NUMBER);
+                        lines.add("");
+                    }
+                    break;
+
+                case EQUAL:
+                    line = processDiff(diff, lineNums, lines, line, i, eqlSpan);
+                    if (line.equals("")) {
+                        i++;
+                    }
+                    break;
+            }
+        }
+
+        if (!line.isEmpty()) {
+            lines.add(line);
+            lineNums.add(i);
+        }
+    }
+
+    public static void generateActualResultLines(LinkedList<diff_match_patch.Diff> diffs,
+            LinkedList<Integer> lineNums, LinkedList<String> lines) {
+        String insSpan = "<span class=\"ins\">";
+        String eqlSpan = "<span class=\"eql\">";
+
+        String line = "";
+        int i = 1;
+        for (diff_match_patch.Diff diff : diffs) {
+            switch (diff.operation) {
+                case INSERT:
+                    line = processDiff(diff, lineNums, lines, line, i, insSpan);
+                    if (line.equals("")) {
+                        i++;
+                    }
+                    break;
+
+                case DELETE:
+                    if (diff.text.endsWith("\n")) {
+                        lineNums.add(DONT_PRINT_LINE_NUMBER);
+                        lines.add("");
+                    }
+                    break;
+
+                case EQUAL:
+                    line = processDiff(diff, lineNums, lines, line, i, eqlSpan);
+                    if (line.equals("")) {
+                        i++;
+                    }
+                    break;
+            }
+        }
+
+        if (!line.isEmpty()) {
+            lines.add(line);
+            lineNums.add(i);
+        }
+    }
+
+    /**
+     * Generate or append a line for a given diff and add it to given collections if necessary.
+     * It puts diffs in HTML spans.
+     *
+     * @param diff
+     * @param lineNums
+     * @param lines
+     * @param line
+     * @param i
+     * @param begSpan
+     * @return
+     */
+    public static String processDiff(diff_match_patch.Diff diff, LinkedList<Integer> lineNums,
+            LinkedList<String> lines, String line, int i, String begSpan) {
+        String endSpan = "</span>";
+        String br = "&nbsp;";
+
+        if (diff.text.endsWith("\n")) {
+            lineNums.add(i++);
+            /** TODO: Think of better way to replace stuff */
+            line += begSpan + diff.text.replace("  ", "&nbsp;&nbsp;")
+                    + endSpan + br;
+            lines.add(line);
+            line = "";
+        } else {
+            line += begSpan + diff.text.replace("  ", "&nbsp;&nbsp;") + endSpan;
+        }
+
+        return line;
+    }
+
+    public static String getHtml(LinkedList<Integer> lineNums1, LinkedList<String> lines1,
+            LinkedList<Integer> lineNums2, LinkedList<String> lines2) {
+        StringBuilder html = new StringBuilder();
+        int lineNum;
+        int size = lines1.size();
+        for (int i = 0; i < size; i++) {
+            html.append("<tr class=\"results\">");
+
+            html.append("    <td class=\"line_count\">");
+            lineNum = lineNums1.removeFirst();
+            if (lineNum > 0) {
+                html.append(lineNum);
+            }
+            html.append("    </td>");
+
+            html.append("    <td class=\"line\">");
+            html.append(lines1.removeFirst());
+            html.append("    </td>");
+
+            html.append("    <td class=\"space\"></td>");
+
+            html.append("    <td class=\"line_count\">");
+            lineNum = lineNums2.removeFirst();
+            if (lineNum > 0) {
+                html.append(lineNum);
+            }
+            html.append("    </td>");
+
+            html.append("    <td class=\"line\">");
+            html.append(lines2.removeFirst());
+            html.append("    </td>");
+
+            html.append("</tr>");
+        }
+        return html.toString();
+    }
+}
diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/ui/DirListActivity.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/ui/DirListActivity.java
new file mode 100644
index 0000000..661a8ec
--- /dev/null
+++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/ui/DirListActivity.java
@@ -0,0 +1,400 @@
+/*
+ * 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.dumprendertree2.ui;
+
+import com.android.dumprendertree2.FileFilter;
+import com.android.dumprendertree2.TestsListActivity;
+import com.android.dumprendertree2.R;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.ListActivity;
+import android.app.ProgressDialog;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.Message;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * An Activity that allows navigating through tests folders and choosing folders or tests to run.
+ */
+public class DirListActivity extends ListActivity {
+
+    private static final String LOG_TAG = "DirListActivity";
+    private static final String ROOT_DIR_PATH =
+            Environment.getExternalStorageDirectory() +
+            File.separator + "android" +
+            File.separator + "LayoutTests";
+
+    /** TODO: This is just a guess - think of a better way to achieve it */
+    private static final int MEAN_TITLE_CHAR_SIZE = 13;
+
+    private static final int PROGRESS_DIALOG_DELAY_MS = 200;
+
+    /** Code for the dialog, used in showDialog and onCreateDialog */
+    private static final int DIALOG_RUN_ABORT_DIR = 0;
+
+    /** Messages codes */
+    private static final int MSG_LOADED_ITEMS = 0;
+    private static final int MSG_SHOW_PROGRESS_DIALOG = 1;
+
+    /** Initialized lazily before first sProgressDialog.show() */
+    private static ProgressDialog sProgressDialog;
+
+    private ListView mListView;
+
+    /** This is a relative path! */
+    private String mCurrentDirPath;
+
+    /**
+     * TODO: This should not be a constant, but rather be configurable from somewhere.
+     */
+    private String mRootDirPath = ROOT_DIR_PATH;
+
+    private FileFilter mFileFilter;
+
+    /**
+     * A thread responsible for loading the contents of the directory from sd card
+     * and sending them via Message to main thread that then loads them into
+     * ListView
+     */
+    private class LoadListItemsThread extends Thread {
+        private Handler mHandler;
+        private String mRelativePath;
+
+        public LoadListItemsThread(String relativePath, Handler handler) {
+            mRelativePath = relativePath;
+            mHandler = handler;
+        }
+
+        @Override
+        public void run() {
+            Message msg = mHandler.obtainMessage(MSG_LOADED_ITEMS);
+            msg.obj = getDirList(mRelativePath);
+            mHandler.sendMessage(msg);
+        }
+    }
+
+    /**
+     * Very simple object to use inside ListView as an item.
+     */
+    private static class ListItem implements Comparable<ListItem> {
+        private String mRelativePath;
+        private String mName;
+        private boolean mIsDirectory;
+
+        public ListItem(String relativePath, boolean isDirectory) {
+            mRelativePath = relativePath;
+            mName = new File(relativePath).getName();
+            mIsDirectory = isDirectory;
+        }
+
+        public boolean isDirectory() {
+            return mIsDirectory;
+        }
+
+        public String getRelativePath() {
+            return mRelativePath;
+        }
+
+        public String getName() {
+            return mName;
+        }
+
+        @Override
+        public int compareTo(ListItem another) {
+            return mRelativePath.compareTo(another.getRelativePath());
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (!(o instanceof ListItem)) {
+                return false;
+            }
+
+            return mRelativePath.equals(((ListItem) o).getRelativePath());
+        }
+
+        @Override
+        public int hashCode() {
+            return mRelativePath.hashCode();
+        }
+
+    }
+
+    /**
+     * A custom adapter that sets the proper icon and label in the list view.
+     */
+    private static class DirListAdapter extends ArrayAdapter<ListItem> {
+        private Activity mContext;
+        private ListItem[] mItems;
+
+        public DirListAdapter(Activity context, ListItem[] items) {
+            super(context, R.layout.dirlist_row, items);
+
+            mContext = context;
+            mItems = items;
+        }
+
+        @Override
+        public View getView(int position, View convertView, ViewGroup parent) {
+            LayoutInflater inflater = mContext.getLayoutInflater();
+            View row = inflater.inflate(R.layout.dirlist_row, null);
+
+            TextView label = (TextView) row.findViewById(R.id.label);
+            label.setText(mItems[position].getName());
+
+            ImageView icon = (ImageView) row.findViewById(R.id.icon);
+            if (mItems[position].isDirectory()) {
+                icon.setImageResource(R.drawable.folder);
+            } else {
+                icon.setImageResource(R.drawable.runtest);
+            }
+
+            return row;
+        }
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        mFileFilter = new FileFilter(ROOT_DIR_PATH);
+        mListView = getListView();
+
+        mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+            @Override
+            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+                ListItem item = (ListItem) parent.getItemAtPosition(position);
+
+                if (item.isDirectory()) {
+                    showDir(item.getRelativePath());
+                } else {
+                    /** Run the test */
+                    Intent intent = new Intent();
+                    intent.setClass(DirListActivity.this, TestsListActivity.class);
+                    intent.setAction(Intent.ACTION_RUN);
+                    intent.putExtra(TestsListActivity.EXTRA_TEST_PATH, item.getRelativePath());
+                    startActivity(intent);
+                }
+            }
+        });
+
+        mListView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
+            @Override
+            public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
+                ListItem item = (ListItem) parent.getItemAtPosition(position);
+
+                if (item.isDirectory()) {
+                    Bundle arguments = new Bundle(1);
+                    arguments.putString("name", item.getName());
+                    arguments.putString("relativePath", item.getRelativePath());
+                    showDialog(DIALOG_RUN_ABORT_DIR, arguments);
+                } else {
+                    /** TODO: Maybe show some info about a test? */
+                }
+
+                return true;
+            }
+        });
+
+        /** All the paths are relative to test root dir where possible */
+        showDir("");
+    }
+
+    @Override
+    /**
+     * Moves to the parent directory if one exists. Does not allow to move above
+     * the test 'root' directory.
+     */
+    public void onBackPressed() {
+        File currentDirParent = new File(mCurrentDirPath).getParentFile();
+        if (currentDirParent != null) {
+            showDir(currentDirParent.getPath());
+        } else {
+            showDir("");
+        }
+    }
+
+    /**
+     * Prevents the activity from recreating on change of orientation. The title needs to
+     * be recalculated.
+     */
+    @Override
+    public void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+
+        setTitle(shortenTitle(mCurrentDirPath));
+    }
+
+    @Override
+    protected Dialog onCreateDialog(int id, final Bundle args) {
+        Dialog dialog = null;
+        AlertDialog.Builder builder = new AlertDialog.Builder(this);
+
+        switch (id) {
+            case DIALOG_RUN_ABORT_DIR:
+                builder.setTitle(getText(R.string.dialog_run_abort_dir_title_prefix) + " " +
+                        args.getString("name"));
+                builder.setMessage(R.string.dialog_run_abort_dir_msg);
+                builder.setCancelable(true);
+
+                builder.setPositiveButton(R.string.dialog_run_abort_dir_ok_button,
+                        new DialogInterface.OnClickListener() {
+                    @Override
+                    public void onClick(DialogInterface dialog, int which) {
+                        removeDialog(DIALOG_RUN_ABORT_DIR);
+                        /** Run the tests */
+                        Intent intent = new Intent();
+                        intent.setClass(DirListActivity.this, TestsListActivity.class);
+                        intent.setAction(Intent.ACTION_RUN);
+                        intent.putExtra(TestsListActivity.EXTRA_TEST_PATH,
+                                args.getString("relativePath"));
+                        startActivity(intent);
+                    }
+                });
+
+                builder.setNegativeButton(R.string.dialog_run_abort_dir_abort_button,
+                        new DialogInterface.OnClickListener() {
+                    @Override
+                    public void onClick(DialogInterface dialog, int which) {
+                        removeDialog(DIALOG_RUN_ABORT_DIR);
+                    }
+                });
+
+                dialog = builder.create();
+                dialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
+                    @Override
+                    public void onCancel(DialogInterface dialog) {
+                        removeDialog(DIALOG_RUN_ABORT_DIR);
+                    }
+                });
+                break;
+        }
+
+        return dialog;
+    }
+
+    /**
+     * Loads the contents of dir into the list view.
+     *
+     * @param dirPath
+     *      directory to load into list view
+     */
+    private void showDir(String dirPath) {
+        mCurrentDirPath = dirPath;
+
+        /** Show progress dialog with a delay */
+        final Handler delayedDialogHandler = new Handler() {
+            @Override
+            public void handleMessage(Message msg) {
+                if (msg.what == MSG_SHOW_PROGRESS_DIALOG) {
+                    if (sProgressDialog == null) {
+                        sProgressDialog = new ProgressDialog(DirListActivity.this);
+                        sProgressDialog.setCancelable(false);
+                        sProgressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
+                        sProgressDialog.setTitle(R.string.dialog_progress_title);
+                        sProgressDialog.setMessage(getText(R.string.dialog_progress_msg));
+                    }
+                    sProgressDialog.show();
+                }
+            }
+        };
+        Message msgShowDialog = delayedDialogHandler.obtainMessage(MSG_SHOW_PROGRESS_DIALOG);
+        delayedDialogHandler.sendMessageDelayed(msgShowDialog, PROGRESS_DIALOG_DELAY_MS);
+
+        /** Delegate loading contents from SD card to a new thread */
+        new LoadListItemsThread(mCurrentDirPath, new Handler() {
+            @Override
+            public void handleMessage(Message msg) {
+                if (msg.what == MSG_LOADED_ITEMS) {
+                    setListAdapter(new DirListAdapter(DirListActivity.this,
+                            (ListItem[])msg.obj));
+                    delayedDialogHandler.removeMessages(MSG_SHOW_PROGRESS_DIALOG);
+                    setTitle(shortenTitle(mCurrentDirPath));
+                    if (sProgressDialog != null) {
+                        sProgressDialog.dismiss();
+                    }
+                }
+            }
+        }).start();
+    }
+
+    /**
+     * TODO: find a neat way to determine number of characters that fit in the title
+     * bar.
+     * */
+    private String shortenTitle(String title) {
+        if (title.equals("")) {
+            return "Tests' root dir:";
+        }
+        int charCount = mListView.getWidth() / MEAN_TITLE_CHAR_SIZE;
+
+        if (title.length() > charCount) {
+            return "..." + title.substring(title.length() - charCount);
+        } else {
+            return title;
+        }
+    }
+
+    /**
+     * Return the array with contents of the given directory.
+     * First it contains the subfolders, then the files. Both sorted
+     * alphabetically.
+     *
+     * The dirPath is relative.
+     */
+    private ListItem[] getDirList(String dirPath) {
+        File dir = new File(mRootDirPath, dirPath);
+
+        List<ListItem> subDirs = new ArrayList<ListItem>();
+        List<ListItem> subFiles = new ArrayList<ListItem>();
+
+        for (File item : dir.listFiles()) {
+            if (item.isDirectory() && FileFilter.isTestDir(item.getName())) {
+                subDirs.add(new ListItem(mFileFilter.getRelativePath(item), true));
+            } else if (FileFilter.isTestFile(item.getName())) {
+                subFiles.add(new ListItem(mFileFilter.getRelativePath(item), false));
+            }
+        }
+
+        Collections.sort(subDirs);
+        Collections.sort(subFiles);
+
+        /** Concatenate the two lists */
+        subDirs.addAll(subFiles);
+
+        return subDirs.toArray(new ListItem[subDirs.size()]);
+    }
+}
\ No newline at end of file
diff --git a/tests/HwAccelerationTest/Android.mk b/tests/HwAccelerationTest/Android.mk
new file mode 100644
index 0000000..d4743f9
--- /dev/null
+++ b/tests/HwAccelerationTest/Android.mk
@@ -0,0 +1,26 @@
+#
+# 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.
+#
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_PACKAGE_NAME := HwAccelerationTest
+
+LOCAL_MODULE_TAGS := tests
+
+include $(BUILD_PACKAGE)
diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml
new file mode 100644
index 0000000..ef18787
--- /dev/null
+++ b/tests/HwAccelerationTest/AndroidManifest.xml
@@ -0,0 +1,157 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.google.android.test.hwui">
+
+    <application
+        android:label="HwUi"
+        android:hardwareAccelerated="true">
+
+        <activity
+                android:name="AlphaLayersActivity"
+                android:label="_αLayers">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+        
+        <activity
+                android:name="LayersActivity"
+                android:label="_Layers"
+                android:theme="@android:style/Theme.Translucent.NoTitleBar">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+
+        <activity
+                android:name="XfermodeActivity"
+                android:label="_Xfermodes"
+                android:theme="@android:style/Theme.Translucent.NoTitleBar">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+        
+        <activity
+                android:name="BitmapsActivity"
+                android:label="_Bitmaps"
+                android:theme="@android:style/Theme.Translucent.NoTitleBar">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+        
+        <activity
+                android:name="BitmapsRectActivity"
+                android:label="_BitmapsRect"
+                android:theme="@android:style/Theme.Translucent.NoTitleBar">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+        
+        <activity
+                android:name="NinePatchesActivity"
+                android:label="_9patch">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+        
+        <activity
+                android:name="QuickRejectActivity"
+                android:label="_QuickReject">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+        
+        <activity
+                android:name="RotationActivity"
+                android:label="_Rotation">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+
+        <activity
+                android:name="ShadersActivity"
+                android:label="_Shaders">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+        
+        <activity
+                android:name="TextActivity"
+                android:label="_Text"
+                android:theme="@android:style/Theme.NoTitleBar">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+
+        <activity
+                android:name="ListActivity"
+                android:label="_List">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+        
+        <activity
+                android:name="MoreShadersActivity"
+                android:label="_Shaders2">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+
+        <activity
+                android:name="ColorFiltersActivity"
+                android:label="_ColorFilters">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+
+        <activity
+                android:name="LinesActivity"
+                android:label="_Lines"
+                android:hardwareAccelerated="false">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+                
+    </application>
+</manifest>
diff --git a/tests/HwAccelerationTest/res/drawable-hdpi/icon.png b/tests/HwAccelerationTest/res/drawable-hdpi/icon.png
new file mode 100644
index 0000000..60fbdf5
--- /dev/null
+++ b/tests/HwAccelerationTest/res/drawable-hdpi/icon.png
Binary files differ
diff --git a/tests/HwAccelerationTest/res/drawable-hdpi/sunset1.jpg b/tests/HwAccelerationTest/res/drawable-hdpi/sunset1.jpg
new file mode 100644
index 0000000..92851f3
--- /dev/null
+++ b/tests/HwAccelerationTest/res/drawable-hdpi/sunset1.jpg
Binary files differ
diff --git a/tests/HwAccelerationTest/res/drawable-hdpi/sunset2.png b/tests/HwAccelerationTest/res/drawable-hdpi/sunset2.png
new file mode 100644
index 0000000..3258ee7
--- /dev/null
+++ b/tests/HwAccelerationTest/res/drawable-hdpi/sunset2.png
Binary files differ
diff --git a/tests/HwAccelerationTest/res/drawable/icon.png b/tests/HwAccelerationTest/res/drawable/icon.png
new file mode 100644
index 0000000..cb40a19
--- /dev/null
+++ b/tests/HwAccelerationTest/res/drawable/icon.png
Binary files differ
diff --git a/tests/HwAccelerationTest/res/drawable/sunset1.jpg b/tests/HwAccelerationTest/res/drawable/sunset1.jpg
new file mode 100644
index 0000000..92851f3
--- /dev/null
+++ b/tests/HwAccelerationTest/res/drawable/sunset1.jpg
Binary files differ
diff --git a/tests/HwAccelerationTest/res/drawable/sunset2.png b/tests/HwAccelerationTest/res/drawable/sunset2.png
new file mode 100644
index 0000000..3258ee7
--- /dev/null
+++ b/tests/HwAccelerationTest/res/drawable/sunset2.png
Binary files differ
diff --git a/tests/HwAccelerationTest/res/layout/list_activity.xml b/tests/HwAccelerationTest/res/layout/list_activity.xml
new file mode 100644
index 0000000..f548f53
--- /dev/null
+++ b/tests/HwAccelerationTest/res/layout/list_activity.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <ListView
+        android:id="@+id/list"
+        android:layout_width="match_parent"
+        android:layout_height="0dip"
+        android:layout_weight="1.0" />
+
+    <LinearLayout
+        android:orientation="horizontal"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+        
+        <Button
+            android:layout_width="0dip"
+            android:layout_weight="1.0"
+            android:layout_height="wrap_content"
+            android:layout_marginLeft="10dip"
+            android:layout_marginRight="3dip"
+
+            android:text="Add" />
+        
+        <Button
+            android:layout_width="0dip"
+            android:layout_weight="1.0"
+            android:layout_height="wrap_content"
+            android:layout_marginLeft="3dip"
+            android:layout_marginRight="10dip"
+
+            android:text="Remove" />
+        
+    </LinearLayout>
+
+</LinearLayout>
diff --git a/tests/HwAccelerationTest/src/com/google/android/test/hwui/AlphaLayersActivity.java b/tests/HwAccelerationTest/src/com/google/android/test/hwui/AlphaLayersActivity.java
new file mode 100644
index 0000000..0217a05
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/google/android/test/hwui/AlphaLayersActivity.java
@@ -0,0 +1,141 @@
+/*
+ * 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.google.android.test.hwui;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.View;
+import android.view.animation.AlphaAnimation;
+import android.view.animation.Animation;
+import android.widget.FrameLayout;
+
+@SuppressWarnings({"UnusedDeclaration"})
+public class AlphaLayersActivity extends Activity {
+    private static final String LOG_TAG = "HwUi";
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        DirtyBitmapView container = new DirtyBitmapView(this);
+
+        ColorView color = new ColorView(this);
+        container.addView(color, new DirtyBitmapView.LayoutParams(
+                dipToPx(this, 100), dipToPx(this, 100), Gravity.CENTER));
+
+        AlphaAnimation a = new AlphaAnimation(1.0f, 0.0f);
+        a.setDuration(2000);
+        a.setRepeatCount(Animation.INFINITE);
+        a.setRepeatMode(Animation.REVERSE);
+        color.startAnimation(a);
+
+        setContentView(container);
+    }
+    
+    @SuppressWarnings({"UnusedDeclaration"})
+    static int dipToPx(Context c, int dip) {
+        return (int) (c.getResources().getDisplayMetrics().density * dip + 0.5f);
+    }
+
+    static class ColorView extends View {
+        ColorView(Context c) {
+            super(c);
+        }
+
+        @Override
+        protected void onDraw(Canvas canvas) {
+            super.onDraw(canvas);
+            canvas.drawRGB(0, 255, 0);
+        }
+    }
+
+    static class DirtyBitmapView extends FrameLayout {
+        private final Paint mPaint;
+
+        DirtyBitmapView(Context c) {
+            super(c);
+            mPaint = new Paint();
+        }
+
+        @Override
+        public void dispatchDraw(Canvas canvas) {
+            canvas.drawRGB(255, 255, 255);
+
+            mPaint.setColor(0xffff0000);
+            canvas.drawRect(200.0f, 0.0f, 220.0f, 20.0f, mPaint);
+
+            canvas.save();
+            canvas.clipRect(20.0f, 0.0f, 40.0f, 20.0f);
+            Log.d(LOG_TAG, "clipRect = " + canvas.getClipBounds());
+            Log.d(LOG_TAG, "rejected = " + canvas.quickReject(100.0f, 100.0f, 110.0f, 110.0f,
+                    Canvas.EdgeType.BW));
+            Log.d(LOG_TAG, "rejected = " + canvas.quickReject(25.0f, 5.0f, 30.0f, 10.0f,
+                    Canvas.EdgeType.BW));
+            canvas.restore();
+            
+            canvas.save();
+            canvas.scale(2.0f, 2.0f);
+            canvas.clipRect(20.0f, 0.0f, 40.0f, 20.0f);
+            Log.d(LOG_TAG, "clipRect = " + canvas.getClipBounds());
+            Log.d(LOG_TAG, "rejected = " + canvas.quickReject(50.0f, 50.0f, 60.0f, 60.0f,
+                    Canvas.EdgeType.BW));
+            Log.d(LOG_TAG, "rejected = " + canvas.quickReject(25.0f, 5.0f, 30.0f, 10.0f,
+                    Canvas.EdgeType.BW));
+            canvas.restore();
+
+            canvas.save();
+            canvas.translate(20.0f, 20.0f);
+            canvas.clipRect(20.0f, 0.0f, 40.0f, 20.0f);
+            Log.d(LOG_TAG, "clipRect = " + canvas.getClipBounds());
+            Log.d(LOG_TAG, "rejected = " + canvas.quickReject(80.0f, 80.0f, 90.0f, 90.0f,
+                    Canvas.EdgeType.BW));
+            Log.d(LOG_TAG, "rejected = " + canvas.quickReject(25.0f, 5.0f, 30.0f, 10.0f,
+                    Canvas.EdgeType.BW));
+            canvas.restore();
+
+            canvas.save();
+            canvas.scale(2.0f, 2.0f);            
+            canvas.clipRect(20.0f, 0.0f, 40.0f, 20.0f);
+
+            mPaint.setColor(0xff00ff00);
+            canvas.drawRect(0.0f, 0.0f, 20.0f, 20.0f, mPaint);
+            
+            mPaint.setColor(0xff0000ff);
+            canvas.drawRect(20.0f, 0.0f, 40.0f, 20.0f, mPaint);
+            
+            canvas.restore();
+
+            final int restoreTo = canvas.save();
+            canvas.saveLayerAlpha(0.0f, 100.0f, getWidth(), 150.0f, 127,
+                    Canvas.HAS_ALPHA_LAYER_SAVE_FLAG | Canvas.CLIP_TO_LAYER_SAVE_FLAG);
+            mPaint.setColor(0xff0000ff);
+            canvas.drawRect(0.0f, 100.0f, 40.0f, 150.0f, mPaint);
+            mPaint.setColor(0xff00ffff);
+            canvas.drawRect(40.0f, 100.0f, 140.0f, 150.0f, mPaint);
+            mPaint.setColor(0xffff00ff);
+            canvas.drawRect(140.0f, 100.0f, 240.0f, 150.0f, mPaint);
+            canvas.restoreToCount(restoreTo);
+
+            super.dispatchDraw(canvas);
+        }
+    }
+}
diff --git a/tests/HwAccelerationTest/src/com/google/android/test/hwui/BitmapsActivity.java b/tests/HwAccelerationTest/src/com/google/android/test/hwui/BitmapsActivity.java
new file mode 100644
index 0000000..cfa8d3c
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/google/android/test/hwui/BitmapsActivity.java
@@ -0,0 +1,96 @@
+/*
+ * 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.google.android.test.hwui;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
+import android.os.Bundle;
+import android.view.Gravity;
+import android.view.View;
+import android.view.animation.Animation;
+import android.view.animation.ScaleAnimation;
+import android.widget.FrameLayout;
+
+@SuppressWarnings({"UnusedDeclaration"})
+public class BitmapsActivity extends Activity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        final BitmapsView view = new BitmapsView(this);
+        final FrameLayout layout = new FrameLayout(this);
+        layout.addView(view, new FrameLayout.LayoutParams(480, 800, Gravity.CENTER));
+        setContentView(layout);
+        
+        ScaleAnimation a = new ScaleAnimation(1.0f, 2.0f, 1.0f, 2.0f,
+                ScaleAnimation.RELATIVE_TO_SELF, 0.5f,
+                ScaleAnimation.RELATIVE_TO_SELF,0.5f);
+        a.setDuration(2000);
+        a.setRepeatCount(Animation.INFINITE);
+        a.setRepeatMode(Animation.REVERSE);
+        view.startAnimation(a);
+    }
+
+    static class BitmapsView extends View {
+        private Paint mBitmapPaint;
+        private final Bitmap mBitmap1;
+        private final Bitmap mBitmap2;
+        private final PorterDuffXfermode mDstIn;
+
+        BitmapsView(Context c) {
+            super(c);
+
+            mBitmap1 = BitmapFactory.decodeResource(c.getResources(), R.drawable.sunset1);
+            mBitmap2 = BitmapFactory.decodeResource(c.getResources(), R.drawable.sunset2);
+
+            mBitmapPaint = new Paint();
+            mDstIn = new PorterDuffXfermode(PorterDuff.Mode.DST_IN);
+        }
+
+        @Override
+        protected void onDraw(Canvas canvas) {
+            super.onDraw(canvas);
+
+            canvas.translate(120.0f, 50.0f);
+            canvas.drawBitmap(mBitmap1, 0.0f, 0.0f, mBitmapPaint);
+
+            canvas.translate(0.0f, mBitmap1.getHeight());
+            canvas.translate(0.0f, 25.0f);
+            canvas.drawBitmap(mBitmap2, 0.0f, 0.0f, null);
+            
+            mBitmapPaint.setAlpha(127);
+            canvas.translate(0.0f, mBitmap2.getHeight());
+            canvas.translate(0.0f, 25.0f);
+            canvas.drawBitmap(mBitmap1, 0.0f, 0.0f, mBitmapPaint);
+            
+            mBitmapPaint.setAlpha(255);
+            canvas.translate(0.0f, mBitmap1.getHeight());
+            canvas.translate(0.0f, 25.0f);
+            mBitmapPaint.setColor(0xffff0000);
+            canvas.drawRect(0.0f, 0.0f, mBitmap2.getWidth(), mBitmap2.getHeight(), mBitmapPaint);
+            mBitmapPaint.setXfermode(mDstIn);
+            canvas.drawBitmap(mBitmap2, 0.0f, 0.0f, mBitmapPaint);
+
+            mBitmapPaint.reset();
+        }
+    }
+}
diff --git a/tests/HwAccelerationTest/src/com/google/android/test/hwui/BitmapsRectActivity.java b/tests/HwAccelerationTest/src/com/google/android/test/hwui/BitmapsRectActivity.java
new file mode 100644
index 0000000..f8726c2
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/google/android/test/hwui/BitmapsRectActivity.java
@@ -0,0 +1,77 @@
+/*
+ * 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.google.android.test.hwui;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.os.Bundle;
+import android.view.View;
+
+@SuppressWarnings({"UnusedDeclaration"})
+public class BitmapsRectActivity extends Activity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        final BitmapsView view = new BitmapsView(this);
+        setContentView(view);
+    }
+
+    static class BitmapsView extends View {
+        private Paint mBitmapPaint;
+        private final Bitmap mBitmap1;
+        private final Bitmap mBitmap2;
+        private final Rect mSrcRect;
+        private final RectF mDstRect;
+        private final RectF mDstRect2;
+
+        BitmapsView(Context c) {
+            super(c);
+
+            mBitmap1 = BitmapFactory.decodeResource(c.getResources(), R.drawable.sunset1);
+            mBitmap2 = BitmapFactory.decodeResource(c.getResources(), R.drawable.sunset2);
+
+            mBitmapPaint = new Paint();
+            mBitmapPaint.setFilterBitmap(true);
+
+            final float fourth = mBitmap1.getWidth() / 4.0f;
+            final float half = mBitmap1.getHeight() / 2.0f;
+            mSrcRect = new Rect((int) fourth, (int) (half - half / 2.0f),
+                    (int) (fourth + fourth), (int) (half + half / 2.0f));
+            mDstRect = new RectF(fourth, half - half / 2.0f, fourth + fourth, half + half / 2.0f);
+            mDstRect2 = new RectF(fourth, half - half / 2.0f,
+                    (fourth + fourth) * 3.0f, (half + half / 2.0f) * 3.0f);
+        }
+
+        @Override
+        protected void onDraw(Canvas canvas) {
+            super.onDraw(canvas);
+
+            canvas.translate(120.0f, 50.0f);
+            canvas.drawBitmap(mBitmap1, mSrcRect, mDstRect, mBitmapPaint);
+
+            canvas.translate(0.0f, mBitmap1.getHeight());
+            canvas.translate(-100.0f, 25.0f);
+            canvas.drawBitmap(mBitmap1, mSrcRect, mDstRect2, mBitmapPaint);
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/HwAccelerationTest/src/com/google/android/test/hwui/ColorFiltersActivity.java b/tests/HwAccelerationTest/src/com/google/android/test/hwui/ColorFiltersActivity.java
new file mode 100644
index 0000000..49e1eaa
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/google/android/test/hwui/ColorFiltersActivity.java
@@ -0,0 +1,97 @@
+/*
+ * 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.google.android.test.hwui;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.ColorMatrix;
+import android.graphics.ColorMatrixColorFilter;
+import android.graphics.LightingColorFilter;
+import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffColorFilter;
+import android.os.Bundle;
+import android.view.View;
+
+@SuppressWarnings({"UnusedDeclaration"})
+public class ColorFiltersActivity extends Activity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        final BitmapsView view = new BitmapsView(this);
+        setContentView(view);
+    }
+
+    static class BitmapsView extends View {
+        private final Bitmap mBitmap1;
+        private final Bitmap mBitmap2;
+        private final Paint mColorMatrixPaint;
+        private final Paint mLightingPaint;
+        private final Paint mBlendPaint;
+
+        BitmapsView(Context c) {
+            super(c);
+
+            mBitmap1 = BitmapFactory.decodeResource(c.getResources(), R.drawable.sunset1);
+            mBitmap2 = BitmapFactory.decodeResource(c.getResources(), R.drawable.sunset2);
+
+            mColorMatrixPaint = new Paint();
+            final ColorMatrix colorMatrix = new ColorMatrix();
+            colorMatrix.setSaturation(0);
+            mColorMatrixPaint.setColorFilter(new ColorMatrixColorFilter(colorMatrix));
+
+            mLightingPaint = new Paint();
+            mLightingPaint.setColorFilter(new LightingColorFilter(0x0060ffff, 0x00101030));
+
+            mBlendPaint = new Paint();
+            mBlendPaint.setColorFilter(new PorterDuffColorFilter(0x7f990040,
+                    PorterDuff.Mode.SRC_OVER));
+        }
+
+        @Override
+        protected void onDraw(Canvas canvas) {
+            super.onDraw(canvas);
+
+            canvas.drawARGB(255, 255, 255, 255);
+            
+            canvas.save();
+            canvas.translate(120.0f, 50.0f);
+            canvas.drawBitmap(mBitmap1, 0.0f, 0.0f, mColorMatrixPaint);
+
+            canvas.translate(0.0f, 50.0f + mBitmap1.getHeight());
+            canvas.drawBitmap(mBitmap1, 0.0f, 0.0f, mLightingPaint);
+            
+            canvas.translate(0.0f, 50.0f + mBitmap1.getHeight());
+            canvas.drawBitmap(mBitmap1, 0.0f, 0.0f, mBlendPaint);
+            canvas.restore();
+
+            canvas.save();
+            canvas.translate(120.0f + mBitmap1.getWidth() + 120.0f, 50.0f);
+            canvas.drawBitmap(mBitmap2, 0.0f, 0.0f, mColorMatrixPaint);
+
+            canvas.translate(0.0f, 50.0f + mBitmap2.getHeight());
+            canvas.drawBitmap(mBitmap2, 0.0f, 0.0f, mLightingPaint);
+            
+            canvas.translate(0.0f, 50.0f + mBitmap2.getHeight());
+            canvas.drawBitmap(mBitmap2, 0.0f, 0.0f, mBlendPaint);
+            canvas.restore();            
+        }
+    }
+}
diff --git a/tests/HwAccelerationTest/src/com/google/android/test/hwui/LayersActivity.java b/tests/HwAccelerationTest/src/com/google/android/test/hwui/LayersActivity.java
new file mode 100644
index 0000000..437cd1c
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/google/android/test/hwui/LayersActivity.java
@@ -0,0 +1,110 @@
+/*
+ * 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.google.android.test.hwui;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
+import android.os.Bundle;
+import android.view.View;
+
+@SuppressWarnings({"UnusedDeclaration"})
+public class LayersActivity extends Activity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(new LayersView(this));
+    }
+
+    static class LayersView extends View {
+        private Paint mLayerPaint;
+        private final Paint mRectPaint;
+
+        LayersView(Context c) {
+            super(c);
+
+            mLayerPaint = new Paint();
+            mRectPaint = new Paint();
+        }
+
+        @Override
+        protected void onDraw(Canvas canvas) {
+            super.onDraw(canvas);
+            
+            canvas.translate(140.0f, 100.0f);
+
+            //canvas.drawRGB(255, 255, 255);
+            
+            int count = canvas.saveLayer(0.0f, 0.0f, 200.0f, 100.0f, mLayerPaint,
+                    Canvas.ALL_SAVE_FLAG);
+
+            mRectPaint.setColor(0xffff0000);
+            canvas.drawRect(0.0f, 0.0f, 200.0f, 100.0f, mRectPaint);
+
+            canvas.restoreToCount(count);
+            
+            canvas.translate(0.0f, 125.0f);
+            
+            count = canvas.saveLayer(0.0f, 0.0f, 200.0f, 100.0f, mLayerPaint,
+                    Canvas.ALL_SAVE_FLAG);
+
+            mRectPaint.setColor(0xff00ff00);
+            mRectPaint.setAlpha(50);
+            canvas.drawRect(0.0f, 0.0f, 200.0f, 100.0f, mRectPaint);
+
+            canvas.restoreToCount(count);
+
+            canvas.translate(25.0f, 125.0f);
+
+            mRectPaint.setColor(0xff0000ff);
+            mRectPaint.setAlpha(255);
+            canvas.drawRect(0.0f, 0.0f, 100.0f, 50.0f, mRectPaint);            
+            
+            mLayerPaint.setAlpha(127);
+            mLayerPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OUT));
+            count = canvas.saveLayer(50.0f, 25.0f, 150.0f, 75.0f, mLayerPaint,
+                    Canvas.ALL_SAVE_FLAG);
+
+            mRectPaint.setColor(0xffff0000);
+            canvas.drawRect(50.0f, 25.0f, 150.0f, 75.0f, mRectPaint);
+
+            canvas.restoreToCount(count);
+            
+            canvas.translate(0.0f, 125.0f);
+
+            mRectPaint.setColor(0xff0000ff);
+            mRectPaint.setAlpha(255);
+            canvas.drawRect(0.0f, 0.0f, 100.0f, 50.0f, mRectPaint);            
+            
+            mLayerPaint.setColor(0xffff0000);
+            mLayerPaint.setAlpha(127);
+            mLayerPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_ATOP));
+            count = canvas.saveLayer(50.0f, 25.0f, 150.0f, 75.0f, mLayerPaint,
+                    Canvas.ALL_SAVE_FLAG);
+
+            mRectPaint.setColor(0xffff0000);
+            canvas.drawRect(50.0f, 25.0f, 150.0f, 75.0f, mRectPaint);
+
+            canvas.restoreToCount(count);
+            
+            mLayerPaint = new Paint();
+        }
+    }
+}
diff --git a/tests/HwAccelerationTest/src/com/google/android/test/hwui/LinesActivity.java b/tests/HwAccelerationTest/src/com/google/android/test/hwui/LinesActivity.java
new file mode 100644
index 0000000..c800d42
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/google/android/test/hwui/LinesActivity.java
@@ -0,0 +1,109 @@
+/*
+ * 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.google.android.test.hwui;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.BitmapShader;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.os.Bundle;
+import android.view.View;
+
+@SuppressWarnings({"UnusedDeclaration"})
+public class LinesActivity extends Activity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        final LinesView view = new LinesView(this);
+        setContentView(view);
+    }
+
+    static class LinesView extends View {
+        private final Bitmap mBitmap1;
+        private final Paint mSmallPaint;
+        private final Paint mMediumPaint;
+        private final Paint mLargePaint;
+        private final BitmapShader mShader;
+        private final float[] mPoints;
+        private final Paint mAlphaPaint;
+
+        LinesView(Context c) {
+            super(c);
+
+            mBitmap1 = BitmapFactory.decodeResource(c.getResources(), R.drawable.sunset1);
+
+            mSmallPaint = new Paint();
+            mSmallPaint.setAntiAlias(true);
+            mSmallPaint.setColor(0xffff0000);
+            mSmallPaint.setStrokeWidth(1.0f);
+
+            mMediumPaint = new Paint();
+            mMediumPaint.setAntiAlias(true);
+            mMediumPaint.setColor(0xff0000ff);
+            mMediumPaint.setStrokeWidth(4.0f);
+
+            mLargePaint = new Paint();
+            mLargePaint.setAntiAlias(true);
+            mLargePaint.setColor(0xff00ff00);
+            mLargePaint.setStrokeWidth(15.0f);
+
+            mAlphaPaint = new Paint();
+            mAlphaPaint.setAntiAlias(true);
+            mAlphaPaint.setColor(0x7fff0050);
+            mAlphaPaint.setStrokeWidth(10.0f);
+            
+            mShader = new BitmapShader(mBitmap1, BitmapShader.TileMode.MIRROR,
+                    BitmapShader.TileMode.MIRROR);
+
+            mPoints = new float[] {
+                    62.0f, 0.0f, 302.0f, 400.0f,
+                    302.0f, 400.0f, 352.0f, 400.0f,
+                    352.0f, 400.0f, 352.0f, 500.0f
+            };
+        }
+
+        @Override
+        protected void onDraw(Canvas canvas) {
+            super.onDraw(canvas);
+
+            canvas.drawARGB(255, 255, 255, 255);
+
+            canvas.save();
+            canvas.translate(100.0f, 20.0f);
+
+            canvas.drawLine(0.0f, 0.0f, 40.0f, 400.0f, mSmallPaint);
+            canvas.drawLine(5.0f, 0.0f, 95.0f, 400.0f, mMediumPaint);
+            canvas.drawLine(22.0f, 0.0f, 162.0f, 400.0f, mLargePaint);
+            
+            mLargePaint.setShader(mShader);
+            canvas.drawLine(42.0f, 0.0f, 222.0f, 400.0f, mLargePaint);
+            mLargePaint.setShader(null);
+            
+            canvas.drawLines(mPoints, mAlphaPaint);
+            
+            canvas.translate(120.0f, 0.0f);
+            mAlphaPaint.setShader(mShader);            
+            canvas.drawLines(mPoints, mAlphaPaint);
+            mAlphaPaint.setShader(null);
+
+            canvas.restore();
+        }
+    }
+}
diff --git a/tests/HwAccelerationTest/src/com/google/android/test/hwui/ListActivity.java b/tests/HwAccelerationTest/src/com/google/android/test/hwui/ListActivity.java
new file mode 100644
index 0000000..c638958
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/google/android/test/hwui/ListActivity.java
@@ -0,0 +1,104 @@
+/*
+ * 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.google.android.test.hwui;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.res.Resources;
+import android.os.Bundle;
+import android.util.DisplayMetrics;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.ListAdapter;
+import android.widget.ListView;
+import android.widget.TextView;
+
+@SuppressWarnings({"UnusedDeclaration"})
+public class ListActivity extends Activity {
+    private static final String[] DATA_LIST = {
+            "Afghanistan", "Albania", "Algeria", "American Samoa", "Andorra",
+            "Angola", "Anguilla", "Antarctica", "Antigua and Barbuda", "Argentina",
+            "Armenia", "Aruba", "Australia", "Austria", "Azerbaijan",
+            "Bahrain", "Bangladesh", "Barbados", "Belarus", "Belgium",
+            "Belize", "Benin", "Bermuda", "Bhutan", "Bolivia",
+            "Bosnia and Herzegovina", "Botswana", "Bouvet Island", "Brazil",
+            "British Indian Ocean Territory", "British Virgin Islands", "Brunei", "Bulgaria",
+            "Burkina Faso", "Burundi", "Cote d'Ivoire", "Cambodia", "Cameroon", "Canada", "Cape Verde",
+            "Cayman Islands", "Central African Republic", "Chad", "Chile", "China",
+            "Christmas Island", "Cocos (Keeling) Islands", "Colombia", "Comoros", "Congo",
+            "Cook Islands", "Costa Rica", "Croatia", "Cuba", "Cyprus", "Czech Republic",
+            "Democratic Republic of the Congo", "Denmark", "Djibouti", "Dominica", "Dominican Republic",
+            "East Timor", "Ecuador", "Egypt", "El Salvador", "Equatorial Guinea", "Eritrea",
+            "Estonia", "Ethiopia", "Faeroe Islands", "Falkland Islands", "Fiji", "Finland",
+            "Former Yugoslav Republic of Macedonia", "France", "French Guiana", "French Polynesia",
+            "French Southern Territories", "Gabon", "Georgia", "Germany", "Ghana", "Gibraltar",
+            "Greece", "Greenland", "Grenada", "Guadeloupe", "Guam", "Guatemala", "Guinea", "Guinea-Bissau",
+            "Guyana", "Haiti", "Heard Island and McDonald Islands", "Honduras", "Hong Kong", "Hungary",
+            "Iceland", "India", "Indonesia", "Iran", "Iraq", "Ireland", "Israel", "Italy", "Jamaica",
+            "Japan", "Jordan", "Kazakhstan", "Kenya", "Kiribati", "Kuwait", "Kyrgyzstan", "Laos",
+            "Latvia", "Lebanon", "Lesotho", "Liberia", "Libya", "Liechtenstein", "Lithuania", "Luxembourg",
+            "Macau", "Madagascar", "Malawi", "Malaysia", "Maldives", "Mali", "Malta", "Marshall Islands",
+            "Martinique", "Mauritania", "Mauritius", "Mayotte", "Mexico", "Micronesia", "Moldova",
+            "Monaco", "Mongolia", "Montserrat", "Morocco", "Mozambique", "Myanmar", "Namibia",
+            "Nauru", "Nepal", "Netherlands", "Netherlands Antilles", "New Caledonia", "New Zealand",
+            "Nicaragua", "Niger", "Nigeria", "Niue", "Norfolk Island", "North Korea", "Northern Marianas",
+            "Norway", "Oman", "Pakistan", "Palau", "Panama", "Papua New Guinea", "Paraguay", "Peru",
+            "Philippines", "Pitcairn Islands", "Poland", "Portugal", "Puerto Rico", "Qatar",
+            "Reunion", "Romania", "Russia", "Rwanda", "Sqo Tome and Principe", "Saint Helena",
+            "Saint Kitts and Nevis", "Saint Lucia", "Saint Pierre and Miquelon",
+            "Saint Vincent and the Grenadines", "Samoa", "San Marino", "Saudi Arabia", "Senegal",
+            "Seychelles", "Sierra Leone", "Singapore", "Slovakia", "Slovenia", "Solomon Islands",
+            "Somalia", "South Africa", "South Georgia and the South Sandwich Islands", "South Korea",
+            "Spain", "Sri Lanka", "Sudan", "Suriname", "Svalbard and Jan Mayen", "Swaziland", "Sweden",
+            "Switzerland", "Syria", "Taiwan", "Tajikistan", "Tanzania", "Thailand", "The Bahamas",
+            "The Gambia", "Togo", "Tokelau", "Tonga", "Trinidad and Tobago", "Tunisia", "Turkey",
+            "Turkmenistan", "Turks and Caicos Islands", "Tuvalu", "Virgin Islands", "Uganda",
+            "Ukraine", "United Arab Emirates", "United Kingdom",
+            "United States", "United States Minor Outlying Islands", "Uruguay", "Uzbekistan",
+            "Vanuatu", "Vatican City", "Venezuela", "Vietnam", "Wallis and Futuna", "Western Sahara",
+            "Yemen", "Yugoslavia", "Zambia", "Zimbabwe"
+    };
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.list_activity);
+
+        ListAdapter adapter = new SimpleListAdapter(this);
+
+        ListView list = (ListView) findViewById(R.id.list);
+        list.setAdapter(adapter);
+    }
+
+    private static class SimpleListAdapter extends ArrayAdapter<String> {
+        public SimpleListAdapter(Context context) {
+            super(context, android.R.layout.simple_list_item_1, DATA_LIST);
+        }
+
+        @Override
+        public View getView(int position, View convertView, ViewGroup parent) {
+            TextView v = (TextView) super.getView(position, convertView, parent);
+            final Resources r = getContext().getResources();
+            final DisplayMetrics metrics = r.getDisplayMetrics();
+            v.setCompoundDrawablePadding((int) (6 * metrics.density + 0.5f));
+            v.setCompoundDrawablesWithIntrinsicBounds(r.getDrawable(R.drawable.icon),
+                    null, null, null);
+            return v;
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/HwAccelerationTest/src/com/google/android/test/hwui/MoreShadersActivity.java b/tests/HwAccelerationTest/src/com/google/android/test/hwui/MoreShadersActivity.java
new file mode 100644
index 0000000..f43eeba
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/google/android/test/hwui/MoreShadersActivity.java
@@ -0,0 +1,141 @@
+/*
+ * 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.google.android.test.hwui;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.BitmapShader;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.ColorFilter;
+import android.graphics.ComposeShader;
+import android.graphics.LightingColorFilter;
+import android.graphics.LinearGradient;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.Shader;
+import android.os.Bundle;
+import android.view.View;
+
+@SuppressWarnings({"UnusedDeclaration"})
+public class MoreShadersActivity extends Activity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(new ShadersView(this));
+    }
+
+    static class ShadersView extends View {
+        private BitmapShader mScaledShader;
+        private int mTexWidth;
+        private int mTexHeight;
+        private Paint mPaint;
+        private float mDrawWidth;
+        private float mDrawHeight;
+        private LinearGradient mHorGradient;
+        private LinearGradient mVertGradient;
+        private ComposeShader mComposeShader;
+        private ComposeShader mCompose2Shader;
+        private Paint mLargePaint;
+        private BitmapShader mScaled2Shader;
+        private ColorFilter mColorFilter;
+
+        ShadersView(Context c) {
+            super(c);
+
+            Bitmap texture = BitmapFactory.decodeResource(c.getResources(), R.drawable.sunset1);
+            mTexWidth = texture.getWidth();
+            mTexHeight = texture.getHeight();
+            mDrawWidth = mTexWidth * 2.2f;
+            mDrawHeight = mTexHeight * 1.2f;
+
+            mScaledShader = new BitmapShader(texture, Shader.TileMode.MIRROR,
+                    Shader.TileMode.MIRROR);
+            Matrix m2 = new Matrix();
+            m2.setScale(0.5f, 0.5f);
+            mScaledShader.setLocalMatrix(m2);
+            
+            mScaled2Shader = new BitmapShader(texture, Shader.TileMode.MIRROR,
+                    Shader.TileMode.MIRROR);
+            Matrix m3 = new Matrix();
+            m3.setScale(0.1f, 0.1f);
+            mScaled2Shader.setLocalMatrix(m3);
+
+            mHorGradient = new LinearGradient(0.0f, 0.0f, mDrawWidth, 0.0f,
+                    Color.RED, 0x7f00ff00, Shader.TileMode.CLAMP);
+            
+            mVertGradient = new LinearGradient(0.0f, 0.0f, 0.0f, mDrawHeight / 2.0f,
+                    Color.YELLOW, Color.MAGENTA, Shader.TileMode.MIRROR);
+
+            mComposeShader = new ComposeShader(mScaledShader, mHorGradient,
+                    PorterDuff.Mode.SRC_OVER);
+            mCompose2Shader = new ComposeShader(mHorGradient, mScaledShader,
+                    PorterDuff.Mode.SRC_OUT);
+
+            mColorFilter = new LightingColorFilter(0x0060ffff, 0x00101030);
+ 
+            mLargePaint = new Paint();
+            mLargePaint.setAntiAlias(true);
+            mLargePaint.setTextSize(36.0f);
+            mLargePaint.setColor(0xff000000);
+            
+            mPaint = new Paint();
+        }
+
+        @Override
+        protected void onDraw(Canvas canvas) {
+            super.onDraw(canvas);
+            canvas.drawRGB(255, 255, 255);
+
+            canvas.save();
+            canvas.translate(40.0f, 40.0f);
+
+            mPaint.setShader(mComposeShader);
+            canvas.drawRect(0.0f, 0.0f, mDrawWidth, mDrawHeight, mPaint);
+            
+            canvas.translate(0.0f, 40.0f + mDrawHeight);
+            mPaint.setShader(mCompose2Shader);
+            canvas.drawRect(0.0f, 0.0f, mDrawWidth, mDrawHeight, mPaint);
+
+            canvas.restore();
+
+            canvas.save();
+            canvas.translate(40.0f + mDrawWidth + 40.0f, 40.0f);
+
+            mLargePaint.setShader(mHorGradient);
+            canvas.drawText("OpenGL rendering", 0.0f, 20.0f, mLargePaint);
+            
+            mLargePaint.setShader(mScaled2Shader);
+            canvas.drawText("OpenGL rendering", 0.0f, 60.0f, mLargePaint);
+            
+            mLargePaint.setShader(mCompose2Shader);
+            mLargePaint.setColorFilter(mColorFilter);
+            canvas.drawText("OpenGL rendering", 0.0f, 100.0f, mLargePaint);
+            mLargePaint.setColorFilter(null);
+            
+            canvas.translate(0.0f, 40.0f + mDrawHeight);
+            mLargePaint.setShader(mVertGradient);
+            canvas.drawText("OpenGL rendering", 0.0f, 20.0f, mLargePaint);
+            
+            canvas.restore();
+        }
+    }
+}
diff --git a/tests/HwAccelerationTest/src/com/google/android/test/hwui/NinePatchesActivity.java b/tests/HwAccelerationTest/src/com/google/android/test/hwui/NinePatchesActivity.java
new file mode 100644
index 0000000..3268fbf
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/google/android/test/hwui/NinePatchesActivity.java
@@ -0,0 +1,41 @@
+/*
+ * 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.google.android.test.hwui;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.Gravity;
+import android.widget.Button;
+import android.widget.FrameLayout;
+
+@SuppressWarnings({"UnusedDeclaration"})
+public class NinePatchesActivity extends Activity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        
+        FrameLayout layout = new FrameLayout(this);
+        Button b = new Button(this);
+        b.setLayoutParams(new FrameLayout.LayoutParams(FrameLayout.LayoutParams.WRAP_CONTENT,
+                FrameLayout.LayoutParams.WRAP_CONTENT, Gravity.CENTER));
+        b.setText("9 patches");
+        layout.addView(b);
+        layout.setBackgroundColor(0xffffffff);
+        
+        setContentView(layout);
+    }
+}
diff --git a/tests/HwAccelerationTest/src/com/google/android/test/hwui/QuickRejectActivity.java b/tests/HwAccelerationTest/src/com/google/android/test/hwui/QuickRejectActivity.java
new file mode 100644
index 0000000..fd7a1e6
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/google/android/test/hwui/QuickRejectActivity.java
@@ -0,0 +1,62 @@
+/*
+ * 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.google.android.test.hwui;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.os.Bundle;
+import android.view.View;
+
+@SuppressWarnings({"UnusedDeclaration"})
+public class QuickRejectActivity extends Activity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        final QuickRejectView view = new QuickRejectView(this);
+        setContentView(view);
+    }
+
+    static class QuickRejectView extends View {
+        private Paint mBitmapPaint;
+        private final Bitmap mBitmap1;
+
+        QuickRejectView(Context c) {
+            super(c);
+
+            mBitmap1 = BitmapFactory.decodeResource(c.getResources(), R.drawable.sunset1);
+
+            mBitmapPaint = new Paint();
+            mBitmapPaint.setFilterBitmap(true);
+        }
+
+        @Override
+        protected void onDraw(Canvas canvas) {
+            super.onDraw(canvas);
+
+            canvas.save();
+            canvas.clipRect(0.0f, 0.0f, 40.0f, 40.0f);
+            canvas.drawBitmap(mBitmap1, 0.0f, 0.0f, mBitmapPaint);
+            canvas.drawBitmap(mBitmap1, -mBitmap1.getWidth(), 0.0f, mBitmapPaint);
+            canvas.drawBitmap(mBitmap1, 50.0f, 0.0f, mBitmapPaint);
+            canvas.restore();
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/HwAccelerationTest/src/com/google/android/test/hwui/RotationActivity.java b/tests/HwAccelerationTest/src/com/google/android/test/hwui/RotationActivity.java
new file mode 100644
index 0000000..e629cb8
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/google/android/test/hwui/RotationActivity.java
@@ -0,0 +1,63 @@
+/*
+ * 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.google.android.test.hwui;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.os.Bundle;
+import android.view.View;
+
+@SuppressWarnings({"UnusedDeclaration"})
+public class RotationActivity extends Activity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        DrawingView container = new DrawingView(this);
+
+        setContentView(container);
+    }
+    
+    @SuppressWarnings({"UnusedDeclaration"})
+    static int dipToPx(Context c, int dip) {
+        return (int) (c.getResources().getDisplayMetrics().density * dip + 0.5f);
+    }
+
+    static class DrawingView extends View {
+        private final Paint mPaint;
+
+        DrawingView(Context c) {
+            super(c);
+            mPaint = new Paint();
+            mPaint.setAntiAlias(true);
+        }
+
+        @Override
+        protected void onDraw(Canvas canvas) {
+            canvas.save();
+            canvas.translate(dipToPx(getContext(), 400), dipToPx(getContext(), 200));
+            canvas.rotate(45.0f);
+            canvas.drawRGB(255, 255, 255);
+            mPaint.setColor(0xffff0000);
+            canvas.drawRect(-80.0f, -80.0f, 80.0f, 80.0f, mPaint);
+            canvas.drawRect(0.0f, 0.0f, 220.0f, 220.0f, mPaint);            
+            canvas.restore();
+        }
+    }
+}
diff --git a/tests/HwAccelerationTest/src/com/google/android/test/hwui/ShadersActivity.java b/tests/HwAccelerationTest/src/com/google/android/test/hwui/ShadersActivity.java
new file mode 100644
index 0000000..0cd1426
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/google/android/test/hwui/ShadersActivity.java
@@ -0,0 +1,132 @@
+/*
+ * 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.google.android.test.hwui;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.BitmapShader;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.LinearGradient;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Shader;
+import android.os.Bundle;
+import android.view.View;
+
+@SuppressWarnings({"UnusedDeclaration"})
+public class ShadersActivity extends Activity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(new ShadersView(this));
+    }
+
+    static class ShadersView extends View {
+        private BitmapShader mRepeatShader;
+        private BitmapShader mTranslatedShader;
+        private BitmapShader mScaledShader;
+        private int mTexWidth;
+        private int mTexHeight;
+        private Paint mPaint;
+        private float mDrawWidth;
+        private float mDrawHeight;
+        private LinearGradient mHorGradient;
+        private LinearGradient mDiagGradient;
+        private LinearGradient mVertGradient;
+
+        ShadersView(Context c) {
+            super(c);
+
+            Bitmap texture = BitmapFactory.decodeResource(c.getResources(), R.drawable.sunset1);
+            mTexWidth = texture.getWidth();
+            mTexHeight = texture.getHeight();
+            mDrawWidth = mTexWidth * 2.2f;
+            mDrawHeight = mTexHeight * 1.2f;
+
+            mRepeatShader = new BitmapShader(texture, Shader.TileMode.REPEAT,
+                    Shader.TileMode.REPEAT);
+
+            mTranslatedShader = new BitmapShader(texture, Shader.TileMode.REPEAT,
+                    Shader.TileMode.REPEAT);
+            Matrix m1 = new Matrix();
+            m1.setTranslate(mTexWidth / 2.0f, mTexHeight / 2.0f);
+            m1.postRotate(45, 0, 0);
+            mTranslatedShader.setLocalMatrix(m1);
+            
+            mScaledShader = new BitmapShader(texture, Shader.TileMode.MIRROR,
+                    Shader.TileMode.MIRROR);
+            Matrix m2 = new Matrix();
+            m2.setScale(0.5f, 0.5f);
+            mScaledShader.setLocalMatrix(m2);
+
+            mHorGradient = new LinearGradient(0.0f, 0.0f, mDrawWidth, 0.0f,
+                    Color.RED, Color.GREEN, Shader.TileMode.CLAMP);
+            
+            mDiagGradient = new LinearGradient(0.0f, 0.0f, mDrawWidth / 1.5f, mDrawHeight,
+                    Color.BLUE, Color.MAGENTA, Shader.TileMode.CLAMP);
+            
+            mVertGradient = new LinearGradient(0.0f, 0.0f, 0.0f, mDrawHeight / 2.0f,
+                    Color.YELLOW, Color.MAGENTA, Shader.TileMode.MIRROR);
+
+            mPaint = new Paint();
+        }
+
+        @Override
+        protected void onDraw(Canvas canvas) {
+            super.onDraw(canvas);
+            //canvas.drawRGB(255, 255, 255);
+
+            // Bitmap shaders
+            canvas.save();
+            canvas.translate(40.0f, 40.0f);
+
+            mPaint.setShader(mRepeatShader);
+            canvas.drawRect(0.0f, 0.0f, mDrawWidth, mDrawHeight, mPaint);
+            
+            canvas.translate(0.0f, 40.0f + mDrawHeight);
+            mPaint.setShader(mTranslatedShader);
+            canvas.drawRect(0.0f, 0.0f, mDrawWidth, mDrawHeight, mPaint);
+
+            canvas.translate(0.0f, 40.0f + mDrawHeight);
+            mPaint.setShader(mScaledShader);
+            canvas.drawRect(0.0f, 0.0f, mDrawWidth, mDrawHeight, mPaint);
+
+            canvas.restore();
+
+            // Gradients
+            canvas.save();
+            canvas.translate(40.0f + mDrawWidth + 40.0f, 40.0f);
+
+            mPaint.setShader(mHorGradient);
+            canvas.drawRect(0.0f, 0.0f, mDrawWidth, mDrawHeight, mPaint);
+            
+            canvas.translate(0.0f, 40.0f + mDrawHeight);
+            mPaint.setShader(mDiagGradient);
+            canvas.drawRect(0.0f, 0.0f, mDrawWidth, mDrawHeight, mPaint);
+
+            canvas.translate(0.0f, 40.0f + mDrawHeight);
+            mPaint.setShader(mVertGradient);
+            canvas.drawRect(0.0f, 0.0f, mDrawWidth, mDrawHeight, mPaint);
+            
+            canvas.restore();
+        }
+    }
+}
diff --git a/tests/HwAccelerationTest/src/com/google/android/test/hwui/TextActivity.java b/tests/HwAccelerationTest/src/com/google/android/test/hwui/TextActivity.java
new file mode 100644
index 0000000..3b5cf43
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/google/android/test/hwui/TextActivity.java
@@ -0,0 +1,70 @@
+/*
+ * 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.google.android.test.hwui;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.os.Bundle;
+import android.view.View;
+
+@SuppressWarnings({"UnusedDeclaration"})
+public class TextActivity extends Activity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(new CustomTextView(this));
+    }
+
+    static class CustomTextView extends View {
+        private final Paint mMediumPaint;
+        private final Paint mLargePaint;
+
+        CustomTextView(Context c) {
+            super(c);
+
+            mMediumPaint = new Paint();
+            mMediumPaint.setAntiAlias(true);
+            mMediumPaint.setColor(0xffff0000);
+            mLargePaint = new Paint();
+            mLargePaint.setAntiAlias(true);
+            mLargePaint.setTextSize(36.0f);
+        }
+
+        @Override
+        protected void onDraw(Canvas canvas) {
+            super.onDraw(canvas);
+            canvas.drawRGB(255, 255, 255);
+
+            canvas.drawText("Hello OpenGL renderer!", 100, 20, mMediumPaint);
+            mMediumPaint.setTextAlign(Paint.Align.CENTER);
+            canvas.drawText("Hello OpenGL renderer!", 100, 40, mMediumPaint);
+            mMediumPaint.setTextAlign(Paint.Align.RIGHT);
+            canvas.drawText("Hello OpenGL renderer!", 100, 60, mMediumPaint);
+            mMediumPaint.setTextAlign(Paint.Align.LEFT);
+            canvas.drawText("Hello OpenGL renderer!", 100, 100, mMediumPaint);
+            canvas.drawText("Hello OpenGL renderer!", 100, 200, mLargePaint);
+            
+            canvas.save();
+            canvas.clipRect(150.0f, 220.0f, 450.0f, 320.0f);
+            canvas.drawText("Hello OpenGL renderer!", 100, 300, mLargePaint);
+            canvas.restore();
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/HwAccelerationTest/src/com/google/android/test/hwui/XfermodeActivity.java b/tests/HwAccelerationTest/src/com/google/android/test/hwui/XfermodeActivity.java
new file mode 100644
index 0000000..8c81f02
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/google/android/test/hwui/XfermodeActivity.java
@@ -0,0 +1,137 @@
+/*
+ * 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.google.android.test.hwui;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
+import android.os.Bundle;
+import android.view.View;
+
+import static android.graphics.PorterDuff.Mode;
+
+@SuppressWarnings({"UnusedDeclaration"})
+public class XfermodeActivity extends Activity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        
+        setContentView(new XfermodesView(this));
+    }
+
+    static class XfermodesView extends View {
+        private final Paint mBluePaint;
+        private final Paint mRedPaint;
+
+        XfermodesView(Context c) {
+            super(c);
+
+            mBluePaint = new Paint();
+            mBluePaint.setColor(0xff0000ff);
+            
+            mRedPaint = new Paint();
+            mRedPaint.setColor(0x7fff0000);
+        }
+
+        @Override
+        protected void onDraw(Canvas canvas) {
+            super.onDraw(canvas);
+            //canvas.drawRGB(255, 255, 255);
+
+            canvas.translate(100.0f, 100.0f);
+            
+            // SRC modes
+            canvas.save();
+
+            drawRects(canvas, Mode.SRC_OVER);
+            canvas.translate(0.0f, 100.0f);
+
+            drawRects(canvas, Mode.SRC_IN);
+            canvas.translate(0.0f, 100.0f);            
+
+            drawRects(canvas, Mode.SRC_OUT);
+            canvas.translate(0.0f, 100.0f);
+
+            drawRects(canvas, Mode.SRC_ATOP);
+            canvas.translate(0.0f, 100.0f);
+            
+            drawRects(canvas, Mode.SRC);
+
+            canvas.restore();
+            
+            canvas.translate(100.0f, 0.0f);
+            
+            // DST modes
+            canvas.save();
+
+            drawRects(canvas, Mode.DST_OVER);
+            canvas.translate(0.0f, 100.0f);
+
+            drawRects(canvas, Mode.DST_IN);
+            canvas.translate(0.0f, 100.0f);            
+
+            drawRects(canvas, Mode.DST_OUT);
+            canvas.translate(0.0f, 100.0f);
+
+            drawRects(canvas, Mode.DST_ATOP);
+            canvas.translate(0.0f, 100.0f);
+            
+            drawRects(canvas, Mode.DST);
+
+            canvas.restore();
+            
+            canvas.translate(100.0f, 0.0f);
+            
+            // Other modes
+            canvas.save();
+
+            drawRects(canvas, Mode.CLEAR);
+            canvas.translate(0.0f, 100.0f);
+
+            drawRects(canvas, Mode.XOR);
+            
+            canvas.translate(0.0f, 100.0f);
+            
+            mBluePaint.setAlpha(127);
+            canvas.drawRect(0.0f, 0.0f, 50.0f, 50.0f, mBluePaint);
+            
+            canvas.translate(0.0f, 100.0f);
+            
+            mBluePaint.setAlpha(10);
+            mBluePaint.setColor(0x7f0000ff);
+            canvas.drawRect(0.0f, 0.0f, 50.0f, 50.0f, mBluePaint);
+            
+            mBluePaint.setColor(0xff0000ff);
+            mBluePaint.setAlpha(255);
+
+            canvas.restore();
+        }
+
+        private void drawRects(Canvas canvas, PorterDuff.Mode mode) {
+            canvas.drawRect(0.0f, 0.0f, 50.0f, 50.0f, mBluePaint);
+
+            canvas.save();
+            canvas.translate(25.0f, 25.0f);
+            mRedPaint.setXfermode(new PorterDuffXfermode(mode));
+            canvas.drawRect(0.0f, 0.0f, 50.0f, 50.0f, mRedPaint);
+            canvas.restore();
+        }
+    }
+}
diff --git a/tests/StatusBar/res/drawable-hdpi/app_gmail.png b/tests/StatusBar/res/drawable-hdpi/app_gmail.png
new file mode 100644
index 0000000..23d33e5
--- /dev/null
+++ b/tests/StatusBar/res/drawable-hdpi/app_gmail.png
Binary files differ
diff --git a/tests/StatusBar/res/drawable-hdpi/app_phone.png b/tests/StatusBar/res/drawable-hdpi/app_phone.png
new file mode 100644
index 0000000..772e7d3
--- /dev/null
+++ b/tests/StatusBar/res/drawable-hdpi/app_phone.png
Binary files differ
diff --git a/tests/StatusBar/res/drawable-hdpi/ic_statusbar_chat.png b/tests/StatusBar/res/drawable-hdpi/ic_statusbar_chat.png
new file mode 100644
index 0000000..e3b4e2b
--- /dev/null
+++ b/tests/StatusBar/res/drawable-hdpi/ic_statusbar_chat.png
Binary files differ
diff --git a/tests/StatusBar/res/drawable-hdpi/ic_statusbar_email.png b/tests/StatusBar/res/drawable-hdpi/ic_statusbar_email.png
new file mode 100644
index 0000000..8cefd36
--- /dev/null
+++ b/tests/StatusBar/res/drawable-hdpi/ic_statusbar_email.png
Binary files differ
diff --git a/tests/StatusBar/res/drawable-hdpi/ic_statusbar_missedcall.png b/tests/StatusBar/res/drawable-hdpi/ic_statusbar_missedcall.png
new file mode 100644
index 0000000..4feec28
--- /dev/null
+++ b/tests/StatusBar/res/drawable-hdpi/ic_statusbar_missedcall.png
Binary files differ
diff --git a/tests/StatusBar/res/drawable-hdpi/icon1.png b/tests/StatusBar/res/drawable-hdpi/icon1.png
new file mode 100644
index 0000000..0485257
--- /dev/null
+++ b/tests/StatusBar/res/drawable-hdpi/icon1.png
Binary files differ
diff --git a/tests/StatusBar/res/drawable-hdpi/icon2.png b/tests/StatusBar/res/drawable-hdpi/icon2.png
new file mode 100644
index 0000000..a630986
--- /dev/null
+++ b/tests/StatusBar/res/drawable-hdpi/icon2.png
Binary files differ
diff --git a/tests/StatusBar/res/drawable-hdpi/icon3.png b/tests/StatusBar/res/drawable-hdpi/icon3.png
new file mode 100644
index 0000000..9d72b66
--- /dev/null
+++ b/tests/StatusBar/res/drawable-hdpi/icon3.png
Binary files differ
diff --git a/tests/StatusBar/res/drawable-hdpi/icon4.png b/tests/StatusBar/res/drawable-hdpi/icon4.png
new file mode 100644
index 0000000..a4a40f2
--- /dev/null
+++ b/tests/StatusBar/res/drawable-hdpi/icon4.png
Binary files differ
diff --git a/tests/StatusBar/res/drawable/app_gmail.png b/tests/StatusBar/res/drawable-mdpi/app_gmail.png
similarity index 100%
rename from tests/StatusBar/res/drawable/app_gmail.png
rename to tests/StatusBar/res/drawable-mdpi/app_gmail.png
Binary files differ
diff --git a/tests/StatusBar/res/drawable/app_phone.png b/tests/StatusBar/res/drawable-mdpi/app_phone.png
similarity index 100%
rename from tests/StatusBar/res/drawable/app_phone.png
rename to tests/StatusBar/res/drawable-mdpi/app_phone.png
Binary files differ
diff --git a/tests/StatusBar/res/drawable/ic_statusbar_chat.png b/tests/StatusBar/res/drawable-mdpi/ic_statusbar_chat.png
similarity index 100%
rename from tests/StatusBar/res/drawable/ic_statusbar_chat.png
rename to tests/StatusBar/res/drawable-mdpi/ic_statusbar_chat.png
Binary files differ
diff --git a/tests/StatusBar/res/drawable/ic_statusbar_email.png b/tests/StatusBar/res/drawable-mdpi/ic_statusbar_email.png
similarity index 100%
rename from tests/StatusBar/res/drawable/ic_statusbar_email.png
rename to tests/StatusBar/res/drawable-mdpi/ic_statusbar_email.png
Binary files differ
diff --git a/tests/StatusBar/res/drawable/ic_statusbar_missedcall.png b/tests/StatusBar/res/drawable-mdpi/ic_statusbar_missedcall.png
similarity index 100%
rename from tests/StatusBar/res/drawable/ic_statusbar_missedcall.png
rename to tests/StatusBar/res/drawable-mdpi/ic_statusbar_missedcall.png
Binary files differ
diff --git a/tests/StatusBar/res/drawable/icon1.png b/tests/StatusBar/res/drawable-mdpi/icon1.png
similarity index 100%
rename from tests/StatusBar/res/drawable/icon1.png
rename to tests/StatusBar/res/drawable-mdpi/icon1.png
Binary files differ
diff --git a/tests/StatusBar/res/drawable/icon2.png b/tests/StatusBar/res/drawable-mdpi/icon2.png
similarity index 100%
rename from tests/StatusBar/res/drawable/icon2.png
rename to tests/StatusBar/res/drawable-mdpi/icon2.png
Binary files differ
diff --git a/tests/StatusBar/res/drawable/icon3.png b/tests/StatusBar/res/drawable-mdpi/icon3.png
similarity index 100%
rename from tests/StatusBar/res/drawable/icon3.png
rename to tests/StatusBar/res/drawable-mdpi/icon3.png
Binary files differ
diff --git a/tests/StatusBar/res/drawable/icon4.png b/tests/StatusBar/res/drawable-mdpi/icon4.png
similarity index 100%
rename from tests/StatusBar/res/drawable/icon4.png
rename to tests/StatusBar/res/drawable-mdpi/icon4.png
Binary files differ
diff --git a/tools/aapt/Command.cpp b/tools/aapt/Command.cpp
index 83057b8..c40af80 100644
--- a/tools/aapt/Command.cpp
+++ b/tools/aapt/Command.cpp
@@ -197,8 +197,10 @@
         if (&res == NULL) {
             printf("\nNo resource table found.\n");
         } else {
+#ifndef HAVE_ANDROID_OS
             printf("\nResource table:\n");
             res.print(false);
+#endif
         }
 
         Asset* manifestAsset = assets.openNonAsset("AndroidManifest.xml",
@@ -388,8 +390,9 @@
     }
 
     if (strcmp("resources", option) == 0) {
+#ifndef HAVE_ANDROID_OS
         res.print(bundle->getValues());
-
+#endif
     } else if (strcmp("xmltree", option) == 0) {
         if (bundle->getFileSpecCount() < 3) {
             fprintf(stderr, "ERROR: no dump xmltree resource file specified\n");
diff --git a/tools/aapt/Package.cpp b/tools/aapt/Package.cpp
index 999a5cf..3cb614f 100644
--- a/tools/aapt/Package.cpp
+++ b/tools/aapt/Package.cpp
@@ -441,7 +441,7 @@
 
 ssize_t processJarFiles(Bundle* bundle, ZipFile* zip)
 {
-    ssize_t err;
+    status_t err;
     ssize_t count = 0;
     const android::Vector<const char*>& jars = bundle->getJarFiles();
 
diff --git a/tools/aapt/Resource.cpp b/tools/aapt/Resource.cpp
index cafd635..5855b56 100644
--- a/tools/aapt/Resource.cpp
+++ b/tools/aapt/Resource.cpp
@@ -542,11 +542,11 @@
                         DefaultKeyedVector<AaptGroupEntry, sp<AaptFile> > baseFiles =
                                 baseGroup->getFiles();
                         for (size_t i=0; i < baseFiles.size(); i++) {
-                            printf("baseFile %ld has flavor %s\n", i,
+                            printf("baseFile %d has flavor %s\n", i,
                                     baseFiles.keyAt(i).toString().string());
                         }
                         for (size_t i=0; i < overlayFiles.size(); i++) {
-                            printf("overlayFile %ld has flavor %s\n", i,
+                            printf("overlayFile %d has flavor %s\n", i,
                                     overlayFiles.keyAt(i).toString().string());
                         }
                     }
@@ -560,7 +560,7 @@
                                 keyAt(overlayGroupIndex));
                         if(baseFileIndex < UNKNOWN_ERROR) {
                             if (bundle->getVerbose()) {
-                                printf("found a match (%ld) for overlay file %s, for flavor %s\n",
+                                printf("found a match (%d) for overlay file %s, for flavor %s\n",
                                         baseFileIndex,
                                         overlayGroup->getLeaf().string(),
                                         overlayFiles.keyAt(overlayGroupIndex).toString().string());
diff --git a/tools/aapt/ResourceTable.cpp b/tools/aapt/ResourceTable.cpp
index 755b93b..f40a877 100644
--- a/tools/aapt/ResourceTable.cpp
+++ b/tools/aapt/ResourceTable.cpp
@@ -2366,7 +2366,7 @@
         if (configSet.count(defaultLocale) == 0) {
             fprintf(stdout, "aapt: warning: string '%s' has no default translation in %s; found:",
                     String8(nameIter->first).string(), mBundle->getResourceSourceDirs()[0]);
-            for (set<String8>::iterator locales = configSet.begin();
+            for (set<String8>::const_iterator locales = configSet.begin();
                  locales != configSet.end();
                  locales++) {
                 fprintf(stdout, " %s", (*locales).string());
diff --git a/tools/aapt/StringPool.cpp b/tools/aapt/StringPool.cpp
index a09cec0..e28bdff 100644
--- a/tools/aapt/StringPool.cpp
+++ b/tools/aapt/StringPool.cpp
@@ -30,7 +30,7 @@
             str = String8(pool->stringAt(s, &len)).string();
         }
 
-        printf("String #%ld: %s\n", s, str);
+        printf("String #%d: %s\n", s, str);
     }
 }
 
diff --git a/tools/aapt/XMLNode.cpp b/tools/aapt/XMLNode.cpp
index 57ff47a..452549b 100644
--- a/tools/aapt/XMLNode.cpp
+++ b/tools/aapt/XMLNode.cpp
@@ -203,9 +203,13 @@
                 }
             }
             if (xliffDepth == 0 && pseudolocalize) {
+#ifdef ENABLE_PSEUDOLOCALIZE
                 std::string orig(String8(text).string());
                 std::string pseudo = pseudolocalize_string(orig);
                 curString.append(String16(String8(pseudo.c_str())));
+#else
+                assert(false);
+#endif
             } else {
                 if (isFormatted && hasSubstitutionErrors(fileName, inXml, text) != NO_ERROR) {
                     return UNKNOWN_ERROR;
diff --git a/tools/aapt/ZipFile.h b/tools/aapt/ZipFile.h
index dbbd072..7877550 100644
--- a/tools/aapt/ZipFile.h
+++ b/tools/aapt/ZipFile.h
@@ -57,7 +57,7 @@
     /*
      * Open a new or existing archive.
      */
-    typedef enum {
+    enum {
         kOpenReadOnly   = 0x01,
         kOpenReadWrite  = 0x02,
         kOpenCreate     = 0x04,     // create if it doesn't exist
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
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeContext.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeContext.java
index 744bfbe..58b1b6c 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeContext.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeContext.java
@@ -37,6 +37,7 @@
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.content.res.Resources.Theme;
+import android.database.DatabaseErrorHandler;
 import android.database.sqlite.SQLiteDatabase;
 import android.database.sqlite.SQLiteDatabase.CursorFactory;
 import android.graphics.Bitmap;
@@ -1059,6 +1060,13 @@
     }
 
     @Override
+    public SQLiteDatabase openOrCreateDatabase(String arg0, int arg1,
+            CursorFactory arg2, DatabaseErrorHandler arg3) {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    @Override
     public Drawable peekWallpaper() {
         // TODO Auto-generated method stub
         return null;
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index 4a22b68..6fac902 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -165,7 +165,7 @@
      *
      * @hide
      */
-    public static final int WIFI_AP_STATE_DISABLING = 0;
+    public static final int WIFI_AP_STATE_DISABLING = 10;
     /**
      * Wi-Fi AP is disabled.
      *
@@ -174,7 +174,7 @@
      *
      * @hide
      */
-    public static final int WIFI_AP_STATE_DISABLED = 1;
+    public static final int WIFI_AP_STATE_DISABLED = 11;
     /**
      * Wi-Fi AP is currently being enabled. The state will change to
      * {@link #WIFI_AP_STATE_ENABLED} if it finishes successfully.
@@ -184,7 +184,7 @@
      *
      * @hide
      */
-    public static final int WIFI_AP_STATE_ENABLING = 2;
+    public static final int WIFI_AP_STATE_ENABLING = 12;
     /**
      * Wi-Fi AP is enabled.
      *
@@ -193,7 +193,7 @@
      *
      * @hide
      */
-    public static final int WIFI_AP_STATE_ENABLED = 3;
+    public static final int WIFI_AP_STATE_ENABLED = 13;
     /**
      * Wi-Fi AP is in a failed state. This state will occur when an error occurs during
      * enabling or disabling
@@ -203,7 +203,7 @@
      *
      * @hide
      */
-    public static final int WIFI_AP_STATE_FAILED = 4;
+    public static final int WIFI_AP_STATE_FAILED = 14;
 
     /**
      * Broadcast intent action indicating that a connection to the supplicant has
diff --git a/wifi/java/android/net/wifi/WifiMonitor.java b/wifi/java/android/net/wifi/WifiMonitor.java
index 266d801..f2f8343 100644
--- a/wifi/java/android/net/wifi/WifiMonitor.java
+++ b/wifi/java/android/net/wifi/WifiMonitor.java
@@ -155,7 +155,7 @@
         public MonitorThread() {
             super("WifiMonitor");
         }
-        
+
         public void run() {
 
             if (connectToSupplicant()) {
@@ -272,7 +272,7 @@
             int connectTries = 0;
 
             while (true) {
-                if (mWifiStateTracker.connectToSupplicant()) {
+                if (WifiNative.connectToSupplicant()) {
                     return true;
                 }
                 if (connectTries++ < 3) {
@@ -375,7 +375,7 @@
             if (newSupplicantState == SupplicantState.INVALID) {
                 Log.w(TAG, "Invalid supplicant state: " + newState);
             }
-            mWifiStateTracker.notifyStateChange(networkId, BSSID, newSupplicantState);
+            mWifiStateTracker.notifySupplicantStateChange(networkId, BSSID, newSupplicantState);
         }
     }
 
@@ -395,7 +395,7 @@
                 }
             }
         }
-        mWifiStateTracker.notifyStateChange(newState, BSSID, networkId);
+        mWifiStateTracker.notifyNetworkStateChange(newState, BSSID, networkId);
     }
 
     /**
diff --git a/wifi/java/android/net/wifi/WifiNative.java b/wifi/java/android/net/wifi/WifiNative.java
index e08f857..47f8813 100644
--- a/wifi/java/android/net/wifi/WifiNative.java
+++ b/wifi/java/android/net/wifi/WifiNative.java
@@ -16,6 +16,8 @@
 
 package android.net.wifi;
 
+import android.net.DhcpInfo;
+
 /**
  * Native calls for sending requests to the supplicant daemon, and for
  * receiving asynchronous events. All methods of the form "xxxxCommand()"
@@ -39,6 +41,8 @@
     public native static String getErrorString(int errorCode);
 
     public native static boolean loadDriver();
+
+    public native static boolean isDriverLoaded();
     
     public native static boolean unloadDriver();
 
@@ -143,6 +147,10 @@
 
     public native static boolean clearBlacklistCommand();
 
+    public native static boolean doDhcpRequest(DhcpInfo results);
+
+    public native static String getDhcpError();
+
     /**
      * Wait for the supplicant to send an event, returning the event string.
      * @return the event string sent by the supplicant.
diff --git a/wifi/java/android/net/wifi/WifiStateTracker.java b/wifi/java/android/net/wifi/WifiStateTracker.java
index 99f3d06..388beea 100644
--- a/wifi/java/android/net/wifi/WifiStateTracker.java
+++ b/wifi/java/android/net/wifi/WifiStateTracker.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2008 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.
@@ -22,6 +22,21 @@
 import static android.net.wifi.WifiManager.WIFI_STATE_ENABLING;
 import static android.net.wifi.WifiManager.WIFI_STATE_UNKNOWN;
 
+/**
+ * TODO: Add soft AP states as part of WIFI_STATE_XXX
+ * Retain WIFI_STATE_ENABLING that indicates driver is loading
+ * Add WIFI_STATE_AP_ENABLED to indicate soft AP has started
+ * and WIFI_STATE_FAILED for failure
+ * Deprecate WIFI_STATE_UNKNOWN
+ *
+ * Doing this will simplify the logic for sending broadcasts
+ */
+import static android.net.wifi.WifiManager.WIFI_AP_STATE_DISABLED;
+import static android.net.wifi.WifiManager.WIFI_AP_STATE_DISABLING;
+import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLED;
+import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLING;
+import static android.net.wifi.WifiManager.WIFI_AP_STATE_FAILED;
+
 import android.app.ActivityManagerNative;
 import android.net.NetworkInfo;
 import android.net.NetworkStateTracker;
@@ -30,21 +45,26 @@
 import android.net.ConnectivityManager;
 import android.net.NetworkInfo.DetailedState;
 import android.net.NetworkInfo.State;
+import android.net.NetworkProperties;
+import android.os.Binder;
 import android.os.Message;
 import android.os.Parcelable;
 import android.os.Handler;
-import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.INetworkManagementService;
+import android.os.PowerManager;
 import android.os.SystemProperties;
-import android.os.Looper;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.Process;
 import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.EventLog;
 import android.util.Log;
-import android.util.Config;
+import android.util.Slog;
 import android.app.Notification;
 import android.app.PendingIntent;
+import android.app.backup.IBackupManager;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothHeadset;
 import android.bluetooth.BluetoothA2dp;
@@ -53,12 +73,22 @@
 import android.content.Context;
 import android.database.ContentObserver;
 import com.android.internal.app.IBatteryStats;
+import com.android.internal.util.HierarchicalState;
+import com.android.internal.util.HierarchicalStateMachine;
 
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
 import java.net.UnknownHostException;
 import java.util.ArrayList;
+import java.util.BitSet;
+import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.regex.Pattern;
 
 /**
  * Track the state of Wifi connectivity. All event handling is done here,
@@ -66,162 +96,61 @@
  *
  * @hide
  */
-public class WifiStateTracker extends NetworkStateTracker {
+//TODO: we still need frequent scanning for the case when
+// we issue disconnect but need scan results for open network notification
+public class WifiStateTracker extends HierarchicalStateMachine implements NetworkStateTracker {
 
-    private static final boolean LOCAL_LOGD = Config.LOGD || false;
-    
     private static final String TAG = "WifiStateTracker";
+    private static final String NETWORKTYPE = "WIFI";
+    private static final boolean DBG = false;
 
-    // Event log tags (must be in sync with event-log-tags)
-    private static final int EVENTLOG_NETWORK_STATE_CHANGED = 50021;
-    private static final int EVENTLOG_SUPPLICANT_STATE_CHANGED = 50022;
-    private static final int EVENTLOG_DRIVER_STATE_CHANGED = 50023;
-    private static final int EVENTLOG_INTERFACE_CONFIGURATION_STATE_CHANGED = 50024;
-    private static final int EVENTLOG_SUPPLICANT_CONNECTION_STATE_CHANGED = 50025;
-
-    // Event codes
-    private static final int EVENT_SUPPLICANT_CONNECTION             = 1;
-    private static final int EVENT_SUPPLICANT_DISCONNECT             = 2;
-    private static final int EVENT_SUPPLICANT_STATE_CHANGED          = 3;
-    private static final int EVENT_NETWORK_STATE_CHANGED             = 4;
-    private static final int EVENT_SCAN_RESULTS_AVAILABLE            = 5;
-    private static final int EVENT_INTERFACE_CONFIGURATION_SUCCEEDED = 6;
-    private static final int EVENT_INTERFACE_CONFIGURATION_FAILED    = 7;
-    private static final int EVENT_POLL_INTERVAL                     = 8;
-    private static final int EVENT_DHCP_START                        = 9;
-    private static final int EVENT_DEFERRED_DISCONNECT               = 10;
-    private static final int EVENT_DEFERRED_RECONNECT                = 11;
-    /**
-     * The driver is started or stopped. The object will be the state: true for
-     * started, false for stopped.
-     */
-    private static final int EVENT_DRIVER_STATE_CHANGED              = 12;
-    private static final int EVENT_PASSWORD_KEY_MAY_BE_INCORRECT     = 13;
-    private static final int EVENT_MAYBE_START_SCAN_POST_DISCONNECT  = 14;
-
-    /**
-     * The driver state indication.
-     */
-    private static final int DRIVER_STARTED                          = 0;
-    private static final int DRIVER_STOPPED                          = 1;
-    private static final int DRIVER_HUNG                             = 2;
-
-    /**
-     * Interval in milliseconds between polling for connection
-     * status items that are not sent via asynchronous events.
-     * An example is RSSI (signal strength).
-     */
-    private static final int POLL_STATUS_INTERVAL_MSECS = 3000;
-
-    /**
-     * The max number of the WPA supplicant loop iterations before we
-     * decide that the loop should be terminated:
-     */
-    private static final int MAX_SUPPLICANT_LOOP_ITERATIONS = 4;
-
-    /**
-     * When a DISCONNECT event is received, we defer handling it to
-     * allow for the possibility that the DISCONNECT is about to
-     * be followed shortly by a CONNECT to the same network we were
-     * just connected to. In such a case, we don't want to report
-     * the network as down, nor do we want to reconfigure the network
-     * interface, etc. If we get a CONNECT event for another network
-     * within the delay window, we immediately handle the pending
-     * disconnect before processing the CONNECT.<p/>
-     * The five second delay is chosen somewhat arbitrarily, but is
-     * meant to cover most of the cases where a DISCONNECT/CONNECT
-     * happens to a network.
-     */
-    private static final int DISCONNECT_DELAY_MSECS = 5000;
-    /**
-     * When the supplicant goes idle after we do an explicit disconnect
-     * following a DHCP failure, we need to kick the supplicant into
-     * trying to associate with access points.
-     */
-    private static final int RECONNECT_DELAY_MSECS = 2000;
-
-    /**
-     * When the supplicant disconnects from an AP it sometimes forgets
-     * to restart scanning.  Wait this delay before asking it to start
-     * scanning (in case it forgot).  15 sec is the standard delay between
-     * scans.
-     */
-    private static final int KICKSTART_SCANNING_DELAY_MSECS = 15000;
-
-    /**
-     * The maximum number of times we will retry a connection to an access point
-     * for which we have failed in acquiring an IP address from DHCP. A value of
-     * N means that we will make N+1 connection attempts in all.
-     * <p>
-     * See {@link Settings.Secure#WIFI_MAX_DHCP_RETRY_COUNT}. This is the default
-     * value if a Settings value is not present.
-     */
-    private static final int DEFAULT_MAX_DHCP_RETRIES = 9;
-
-    private static final int DRIVER_POWER_MODE_AUTO = 0;
-    private static final int DRIVER_POWER_MODE_ACTIVE = 1;
-
-    /**
-     * The current WPA supplicant loop state (used to detect looping behavior):
-     */
-    private SupplicantState mSupplicantLoopState = SupplicantState.DISCONNECTED;
-
-    /**
-     * The current number of WPA supplicant loop iterations:
-     */
-    private int mNumSupplicantLoopIterations = 0;
-
-    /**
-     * The current number of supplicant state changes.  This is used to determine
-     * if we've received any new info since we found out it was DISCONNECTED or
-     * INACTIVE.  If we haven't for X ms, we then request a scan - it should have
-     * done that automatically, but sometimes some firmware does not.
-     */
-    private int mNumSupplicantStateChanges = 0;
-
-    /**
-     * True if we received an event that that a password-key may be incorrect.
-     * If the next incoming supplicant state change event is DISCONNECT,
-     * broadcast a message that we have a possible password error and disable
-     * the network.
-     */
-    private boolean mPasswordKeyMayBeIncorrect = false;
-
-    public static final int SUPPL_SCAN_HANDLING_NORMAL = 1;
-    public static final int SUPPL_SCAN_HANDLING_LIST_ONLY = 2;
+    /* TODO: fetch a configurable interface */
+    private static final String SOFTAP_IFACE = "wl0.1";
 
     private WifiMonitor mWifiMonitor;
-    private WifiInfo mWifiInfo;
+    private INetworkManagementService nwService;
+    private ConnectivityManager mCm;
+
+    /* Scan results handling */
     private List<ScanResult> mScanResults;
-    private WifiManager mWM;
-    private boolean mHaveIpAddress;
-    private boolean mObtainingIpAddress;
-    private boolean mTornDownByConnMgr;
-    /**
-     * A DISCONNECT event has been received, but processing it
-     * is being deferred.
-     */
-    private boolean mDisconnectPending;
-    /**
-     * An operation has been performed as a result of which we expect the next event
-     * will be a DISCONNECT.
-     */
-    private boolean mDisconnectExpected;
-    private DhcpHandler mDhcpTarget;
-    private DhcpInfo mDhcpInfo;
+    private static final Pattern scanResultPattern = Pattern.compile("\t+");
+    private static final int SCAN_RESULT_CACHE_SIZE = 80;
+    private final LinkedHashMap<String, ScanResult> mScanResultCache;
+
+    private String mInterfaceName;
+    private AtomicBoolean mTeardownRequested = new AtomicBoolean(false);
+    private AtomicBoolean mPrivateDnsRouteSet = new AtomicBoolean(false);
+    private AtomicInteger mDefaultGatewayAddr = new AtomicInteger(0);
+    private AtomicBoolean mDefaultRouteSet = new AtomicBoolean(false);
+
+    private int mNumAllowedChannels = 0;
     private int mLastSignalLevel = -1;
     private String mLastBssid;
-    private String mLastSsid;
-    private int mLastNetworkId = -1;
+    private int mLastNetworkId;
+    private boolean mEnableRssiPolling = false;
+    private boolean mPasswordKeyMayBeIncorrect = false;
     private boolean mUseStaticIp = false;
-    private int mReconnectCount;
+    private int mReconnectCount = 0;
+    private boolean mIsScanMode = false;
 
-    // used to store the (non-persisted) num determined during device boot 
-    // (from mcc or other phone info) before the driver is started.
-    private int mNumAllowedChannels = 0;
+    /**
+     * Instance of the bluetooth headset helper. This needs to be created
+     * early because there is a delay before it actually 'connects', as
+     * noted by its javadoc. If we check before it is connected, it will be
+     * in an error state and we will not disable coexistence.
+     */
+    private BluetoothHeadset mBluetoothHeadset;
+
+    private BluetoothA2dp mBluetoothA2dp;
+
+    /**
+     * Observes the static IP address settings.
+     */
+    private SettingsObserver mSettingsObserver;
+    private NetworkProperties mNetworkProperties;
+
 
     // Variables relating to the 'available networks' notification
-    
     /**
      * The icon to show in the 'available networks' notification. This will also
      * be the ID of the Notification given to the NotificationManager.
@@ -269,13 +198,230 @@
      * something other than scanning, we reset this to 0.
      */
     private int mNumScansSinceNetworkStateChange;
-    /**
-     * Observes the static IP address settings.
+
+    // Held during driver load and unload
+    private static PowerManager.WakeLock sWakeLock;
+
+    /* For sending events to connectivity service handler */
+    private Handler mCsHandler;
+    private Context mContext;
+
+    private DhcpInfo mDhcpInfo;
+    private WifiInfo mWifiInfo;
+    private NetworkInfo mNetworkInfo;
+    private SupplicantStateTracker mSupplicantStateTracker;
+
+    // Event log tags (must be in sync with event-log-tags)
+    private static final int EVENTLOG_WIFI_STATE_CHANGED        = 50021;
+    private static final int EVENTLOG_WIFI_EVENT_HANDLED        = 50022;
+    private static final int EVENTLOG_SUPPLICANT_STATE_CHANGED  = 50023;
+
+    /* Load the driver */
+    private static final int CMD_LOAD_DRIVER                      = 1;
+    /* Unload the driver */
+    private static final int CMD_UNLOAD_DRIVER                    = 2;
+    /* Indicates driver load succeeded */
+    private static final int CMD_LOAD_DRIVER_SUCCESS              = 3;
+    /* Indicates driver load failed */
+    private static final int CMD_LOAD_DRIVER_FAILURE              = 4;
+    /* Indicates driver unload succeeded */
+    private static final int CMD_UNLOAD_DRIVER_SUCCESS            = 5;
+    /* Indicates driver unload failed */
+    private static final int CMD_UNLOAD_DRIVER_FAILURE            = 6;
+
+    /* Start the supplicant */
+    private static final int CMD_START_SUPPLICANT                 = 11;
+    /* Stop the supplicant */
+    private static final int CMD_STOP_SUPPLICANT                  = 12;
+    /* Start the driver */
+    private static final int CMD_START_DRIVER                     = 13;
+    /* Start the driver */
+    private static final int CMD_STOP_DRIVER                      = 14;
+    /* Indicates DHCP succeded */
+    private static final int CMD_IP_CONFIG_SUCCESS                = 15;
+    /* Indicates DHCP failed */
+    private static final int CMD_IP_CONFIG_FAILURE                = 16;
+    /* Re-configure interface */
+    private static final int CMD_RECONFIGURE_IP                   = 17;
+
+
+    /* Start the soft access point */
+    private static final int CMD_START_AP                         = 21;
+    /* Stop the soft access point */
+    private static final int CMD_STOP_AP                          = 22;
+
+
+    /* Supplicant events */
+    /* Connection to supplicant established */
+    private static final int SUP_CONNECTION_EVENT                 = 31;
+    /* Connection to supplicant lost */
+    private static final int SUP_DISCONNECTION_EVENT              = 32;
+    /* Driver start completed */
+    private static final int DRIVER_START_EVENT                   = 33;
+    /* Driver stop completed */
+    private static final int DRIVER_STOP_EVENT                    = 34;
+    /* Network connection completed */
+    private static final int NETWORK_CONNECTION_EVENT             = 36;
+    /* Network disconnection completed */
+    private static final int NETWORK_DISCONNECTION_EVENT          = 37;
+    /* Scan results are available */
+    private static final int SCAN_RESULTS_EVENT                   = 38;
+    /* Supplicate state changed */
+    private static final int SUPPLICANT_STATE_CHANGE_EVENT        = 39;
+    /* Password may be incorrect */
+    private static final int PASSWORD_MAY_BE_INCORRECT_EVENT      = 40;
+
+    /* Supplicant commands */
+    /* Is supplicant alive ? */
+    private static final int CMD_PING_SUPPLICANT                  = 51;
+    /* Add/update a network configuration */
+    private static final int CMD_ADD_OR_UPDATE_NETWORK            = 52;
+    /* Delete a network */
+    private static final int CMD_REMOVE_NETWORK                   = 53;
+    /* Enable a network. The device will attempt a connection to the given network. */
+    private static final int CMD_ENABLE_NETWORK                   = 54;
+    /* Disable a network. The device does not attempt a connection to the given network. */
+    private static final int CMD_DISABLE_NETWORK                  = 55;
+    /* Blacklist network. De-prioritizes the given BSSID for connection. */
+    private static final int CMD_BLACKLIST_NETWORK                = 56;
+    /* Clear the blacklist network list */
+    private static final int CMD_CLEAR_BLACKLIST                  = 57;
+    /* Get the configured networks */
+    private static final int CMD_GET_NETWORK_CONFIG               = 58;
+    /* Save configuration */
+    private static final int CMD_SAVE_CONFIG                      = 59;
+    /* Connection status */
+    private static final int CMD_CONNECTION_STATUS                = 60;
+
+    /* Supplicant commands after driver start*/
+    /* Initiate a scan */
+    private static final int CMD_START_SCAN                       = 71;
+    /* Set scan mode. CONNECT_MODE or SCAN_ONLY_MODE */
+    private static final int CMD_SET_SCAN_MODE                    = 72;
+    /* Set scan type. SCAN_ACTIVE or SCAN_PASSIVE */
+    private static final int CMD_SET_SCAN_TYPE                    = 73;
+    /* Disconnect from a network */
+    private static final int CMD_DISCONNECT                       = 74;
+    /* Reconnect to a network */
+    private static final int CMD_RECONNECT                        = 75;
+    /* Reassociate to a network */
+    private static final int CMD_REASSOCIATE                      = 76;
+    /* Set power mode
+     * POWER_MODE_ACTIVE
+     * POWER_MODE_AUTO
      */
-    private SettingsObserver mSettingsObserver;
-    
-    private boolean mIsScanModeActive;
-    private boolean mEnableRssiPolling;
+    private static final int CMD_SET_POWER_MODE                   = 77;
+    /* Set bluetooth co-existence
+     * BLUETOOTH_COEXISTENCE_MODE_ENABLED
+     * BLUETOOTH_COEXISTENCE_MODE_DISABLED
+     * BLUETOOTH_COEXISTENCE_MODE_SENSE
+     */
+    private static final int CMD_SET_BLUETOOTH_COEXISTENCE        = 78;
+    /* Enable/disable bluetooth scan mode
+     * true(1)
+     * false(0)
+     */
+    private static final int CMD_SET_BLUETOOTH_SCAN_MODE          = 79;
+    /* Set number of allowed channels */
+    private static final int CMD_SET_NUM_ALLOWED_CHANNELS         = 80;
+    /* Request connectivity manager wake lock before driver stop */
+    private static final int CMD_REQUEST_CM_WAKELOCK              = 81;
+    /* Enables RSSI poll */
+    private static final int CMD_ENABLE_RSSI_POLL                 = 82;
+    /* RSSI poll */
+    private static final int CMD_RSSI_POLL                        = 83;
+    /* Get current RSSI */
+    private static final int CMD_GET_RSSI                         = 84;
+    /* Get approx current RSSI */
+    private static final int CMD_GET_RSSI_APPROX                  = 85;
+    /* Get link speed on connection */
+    private static final int CMD_GET_LINK_SPEED                   = 86;
+    /* Radio mac address */
+    private static final int CMD_GET_MAC_ADDR                     = 87;
+    /* Set up packet filtering */
+    private static final int CMD_START_PACKET_FILTERING           = 88;
+    /* Clear packet filter */
+    private static final int CMD_STOP_PACKET_FILTERING            = 89;
+
+    /* Connectivity service commands */
+    /* Bring down wifi connection */
+    private static final int CM_CMD_TEARDOWN                      = 110;
+    /* Reconnect to wifi */
+    private static final int CM_CMD_RECONNECT                     = 111;
+
+    /**
+     * Interval in milliseconds between polling for connection
+     * status items that are not sent via asynchronous events.
+     * An example is RSSI (signal strength).
+     */
+    private static final int POLL_RSSI_INTERVAL_MSECS = 3000;
+
+    private static final int CONNECT_MODE   = 1;
+    private static final int SCAN_ONLY_MODE = 2;
+
+    private static final int SCAN_ACTIVE = 1;
+    private static final int SCAN_PASSIVE = 2;
+
+    /**
+     * The maximum number of times we will retry a connection to an access point
+     * for which we have failed in acquiring an IP address from DHCP. A value of
+     * N means that we will make N+1 connection attempts in all.
+     * <p>
+     * See {@link Settings.Secure#WIFI_MAX_DHCP_RETRY_COUNT}. This is the default
+     * value if a Settings value is not present.
+     */
+    private static final int DEFAULT_MAX_DHCP_RETRIES = 9;
+
+    private static final int DRIVER_POWER_MODE_ACTIVE = 1;
+    private static final int DRIVER_POWER_MODE_AUTO = 0;
+
+    /* Default parent state */
+    private HierarchicalState mDefaultState = new DefaultState();
+    /* Temporary initial state */
+    private HierarchicalState mInitialState = new InitialState();
+    /* Unloading the driver */
+    private HierarchicalState mDriverUnloadingState = new DriverUnloadingState();
+    /* Loading the driver */
+    private HierarchicalState mDriverUnloadedState = new DriverUnloadedState();
+    /* Driver load/unload failed */
+    private HierarchicalState mDriverFailedState = new DriverFailedState();
+    /* Driver loading */
+    private HierarchicalState mDriverLoadingState = new DriverLoadingState();
+    /* Driver loaded */
+    private HierarchicalState mDriverLoadedState = new DriverLoadedState();
+    /* Driver loaded, waiting for supplicant to start */
+    private HierarchicalState mWaitForSupState = new WaitForSupState();
+
+    /* Driver loaded and supplicant ready */
+    private HierarchicalState mDriverSupReadyState = new DriverSupReadyState();
+    /* Driver start issued, waiting for completed event */
+    private HierarchicalState mDriverStartingState = new DriverStartingState();
+    /* Driver started */
+    private HierarchicalState mDriverStartedState = new DriverStartedState();
+    /* Driver stopping */
+    private HierarchicalState mDriverStoppingState = new DriverStoppingState();
+    /* Driver stopped */
+    private HierarchicalState mDriverStoppedState = new DriverStoppedState();
+    /* Scan for networks, no connection will be established */
+    private HierarchicalState mScanModeState = new ScanModeState();
+    /* Connecting to an access point */
+    private HierarchicalState mConnectModeState = new ConnectModeState();
+    /* Fetching IP after network connection (assoc+auth complete) */
+    private HierarchicalState mConnectingState = new ConnectingState();
+    /* Connected with IP addr */
+    private HierarchicalState mConnectedState = new ConnectedState();
+    /* disconnect issued, waiting for network disconnect confirmation */
+    private HierarchicalState mDisconnectingState = new DisconnectingState();
+    /* Network is not connected, supplicant assoc+auth is not complete */
+    private HierarchicalState mDisconnectedState = new DisconnectedState();
+
+    /* Soft Ap is running */
+    private HierarchicalState mSoftApStartedState = new SoftApStartedState();
+
+    /* Argument for Message object to indicate a synchronous call */
+    private static final int SYNCHRONOUS_CALL = 1;
+    private static final int ASYNCHRONOUS_CALL = 0;
+
 
     /**
      * One of  {@link WifiManager#WIFI_STATE_DISABLED},
@@ -284,166 +430,153 @@
      *         {@link WifiManager#WIFI_STATE_ENABLING},
      *         {@link WifiManager#WIFI_STATE_UNKNOWN}
      *
-     * getWifiState() is not synchronized to make sure it's always fast,
-     * even when the instance lock is held on other slow operations.
-     * Use a atomic variable for state.
      */
-    private final AtomicInteger mWifiState = new AtomicInteger(WIFI_STATE_UNKNOWN);
+    private final AtomicInteger mWifiState = new AtomicInteger(WIFI_STATE_DISABLED);
 
-    // Wi-Fi run states:
-    private static final int RUN_STATE_STARTING = 1;
-    private static final int RUN_STATE_RUNNING  = 2;
-    private static final int RUN_STATE_STOPPING = 3;
-    private static final int RUN_STATE_STOPPED  = 4;
+    /**
+     * One of  {@link WifiManager#WIFI_AP_STATE_DISABLED},
+     *         {@link WifiManager#WIFI_AP_STATE_DISABLING},
+     *         {@link WifiManager#WIFI_AP_STATE_ENABLED},
+     *         {@link WifiManager#WIFI_AP_STATE_ENABLING},
+     *         {@link WifiManager#WIFI_AP_STATE_FAILED}
+     *
+     */
+    private final AtomicInteger mWifiApState = new AtomicInteger(WIFI_AP_STATE_DISABLED);
 
-    private static final String mRunStateNames[] = {
-            "Starting",
-            "Running",
-            "Stopping",
-            "Stopped"
-    };
-    private int mRunState;
+    private final AtomicInteger mLastEnableUid = new AtomicInteger(Process.myUid());
+    private final AtomicInteger mLastApEnableUid = new AtomicInteger(Process.myUid());
 
     private final IBatteryStats mBatteryStats;
 
-    private boolean mIsScanOnly;
-
-    private BluetoothA2dp mBluetoothA2dp;
-
-    private String mInterfaceName;
-    private static String LS = System.getProperty("line.separator");
-
-    private static String[] sDnsPropNames;
-    private Runnable mReleaseWakeLockCallback;
-
-    /**
-     * A structure for supplying information about a supplicant state
-     * change in the STATE_CHANGE event message that comes from the
-     * WifiMonitor
-     * thread.
-     */
-    private static class SupplicantStateChangeResult {
-        SupplicantStateChangeResult(int networkId, String BSSID, SupplicantState state) {
-            this.state = state;
-            this.BSSID = BSSID;
-            this.networkId = networkId;
-        }
-        int networkId;
-        String BSSID;
-        SupplicantState state;
-    }
-
-    /**
-     * A structure for supplying information about a connection in
-     * the CONNECTED event message that comes from the WifiMonitor
-     * thread.
-     */
-    private static class NetworkStateChangeResult {
-        NetworkStateChangeResult(DetailedState state, String BSSID, int networkId) {
-            this.state = state;
-            this.BSSID = BSSID;
-            this.networkId = networkId;
-        }
-        DetailedState state;
-        String BSSID;
-        int networkId;
-    }
-
     public WifiStateTracker(Context context, Handler target) {
-        super(context, target, ConnectivityManager.TYPE_WIFI, 0, "WIFI", "");
-        
-        mWifiInfo = new WifiInfo();
+        super(NETWORKTYPE);
+
+        mCsHandler = target;
+        mContext = context;
+
+        mNetworkInfo = new NetworkInfo(ConnectivityManager.TYPE_WIFI, 0, NETWORKTYPE, "");
+        mBatteryStats = IBatteryStats.Stub.asInterface(ServiceManager.getService("batteryinfo"));
+
+        IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
+        nwService = INetworkManagementService.Stub.asInterface(b);
+
         mWifiMonitor = new WifiMonitor(this);
-        mHaveIpAddress = false;
-        mObtainingIpAddress = false;
-        setTornDownByConnMgr(false);
-        mDisconnectPending = false;
-        mScanResults = new ArrayList<ScanResult>();
-        // Allocate DHCP info object once, and fill it in on each request
         mDhcpInfo = new DhcpInfo();
-        mRunState = RUN_STATE_STARTING;
+        mWifiInfo = new WifiInfo();
+        mInterfaceName = SystemProperties.get("wifi.interface", "tiwlan0");
+        mSupplicantStateTracker = new SupplicantStateTracker(context, getHandler());
+
+        mBluetoothHeadset = new BluetoothHeadset(mContext, null);
+        mNetworkProperties = new NetworkProperties();
+
+        mNetworkInfo.setIsAvailable(false);
+        mNetworkProperties.clear();
+        resetNotificationTimer();
+        setTeardownRequested(false);
+        mLastBssid = null;
+        mLastNetworkId = -1;
+        mLastSignalLevel = -1;
+
+        mScanResultCache = new LinkedHashMap<String, ScanResult>(
+            SCAN_RESULT_CACHE_SIZE, 0.75f, true) {
+                /*
+                 * Limit the cache size by SCAN_RESULT_CACHE_SIZE
+                 * elements
+                 */
+                @Override
+                public boolean removeEldestEntry(Map.Entry eldest) {
+                    return SCAN_RESULT_CACHE_SIZE < this.size();
+                }
+        };
 
         // Setting is in seconds
-        NOTIFICATION_REPEAT_DELAY_MS = Settings.Secure.getInt(context.getContentResolver(), 
+        NOTIFICATION_REPEAT_DELAY_MS = Settings.Secure.getInt(context.getContentResolver(),
                 Settings.Secure.WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY, 900) * 1000l;
-        mNotificationEnabledSettingObserver = new NotificationEnabledSettingObserver(new Handler());
+        mNotificationEnabledSettingObserver = new NotificationEnabledSettingObserver(target);
         mNotificationEnabledSettingObserver.register();
 
         mSettingsObserver = new SettingsObserver(new Handler());
 
-        mInterfaceName = SystemProperties.get("wifi.interface", "tiwlan0");
-        mDnsPropNames = new String[] {
-            "net." + mInterfaceName + ".dns1",
-            "net." + mInterfaceName + ".dns2"
-        };
-        mBatteryStats = IBatteryStats.Stub.asInterface(ServiceManager.getService("batteryinfo"));
+        PowerManager powerManager = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE);
+        sWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
 
+        addState(mDefaultState);
+            addState(mInitialState, mDefaultState);
+            addState(mDriverUnloadingState, mDefaultState);
+            addState(mDriverUnloadedState, mDefaultState);
+                addState(mDriverFailedState, mDriverUnloadedState);
+            addState(mDriverLoadingState, mDefaultState);
+            addState(mDriverLoadedState, mDefaultState);
+                addState(mWaitForSupState, mDriverLoadedState);
+            addState(mDriverSupReadyState, mDefaultState);
+                addState(mDriverStartingState, mDriverSupReadyState);
+                addState(mDriverStartedState, mDriverSupReadyState);
+                    addState(mScanModeState, mDriverStartedState);
+                    addState(mConnectModeState, mDriverStartedState);
+                        addState(mConnectingState, mConnectModeState);
+                        addState(mConnectedState, mConnectModeState);
+                        addState(mDisconnectingState, mConnectModeState);
+                        addState(mDisconnectedState, mConnectModeState);
+                addState(mDriverStoppingState, mDriverSupReadyState);
+                addState(mDriverStoppedState, mDriverSupReadyState);
+            addState(mSoftApStartedState, mDefaultState);
+
+        setInitialState(mInitialState);
+
+        if (DBG) setDbg(true);
+
+        //start the state machine
+        start();
+    }
+
+
+    /*********************************************************
+     * NetworkStateTracker interface implementation
+     ********************************************************/
+
+    public void setTeardownRequested(boolean isRequested) {
+        mTeardownRequested.set(isRequested);
+    }
+
+    public boolean isTeardownRequested() {
+        return mTeardownRequested.get();
     }
 
     /**
-     * Helper method: sets the supplicant state and keeps the network
-     * info updated.
-     * @param state the new state
+     * Begin monitoring wifi connectivity
+     * @deprecated
+     *
+     * TODO: remove this from callers
      */
-    private void setSupplicantState(SupplicantState state) {
-        mWifiInfo.setSupplicantState(state);
-        updateNetworkInfo();
-        checkPollTimer();
-    }
-
-    public SupplicantState getSupplicantState() {
-        return mWifiInfo.getSupplicantState();
-    }
-
-    /**
-     * Helper method: sets the supplicant state and keeps the network
-     * info updated (string version).
-     * @param stateName the string name of the new state
-     */
-    private void setSupplicantState(String stateName) {
-        mWifiInfo.setSupplicantState(stateName);
-        updateNetworkInfo();
-        checkPollTimer();
-    }
-
-    /**
-     * Helper method: sets the boolean indicating that the connection
-     * manager asked the network to be torn down (and so only the connection
-     * manager can set it up again).
-     * network info updated.
-     * @param flag {@code true} if explicitly disabled.
-     */
-    private void setTornDownByConnMgr(boolean flag) {
-        mTornDownByConnMgr = flag;
-        updateNetworkInfo();
-    }
-
-    /**
-     * Return the name of our WLAN network interface.
-     * @return the name of our interface.
-     */
-    public String getInterfaceName() {
-        return mInterfaceName;
-    }
-
-    /**
-     * Return the system properties name associated with the tcp buffer sizes
-     * for this network.
-     */
-    public String getTcpBufferSizesPropName() {
-        return "net.tcp.buffersize.wifi";
-    }
-
     public void startMonitoring() {
-        /*
-         * Get a handle on the WifiManager. This cannot be done in our
-         * constructor, because the Wifi service is not yet registered.
-         */
-        mWM = (WifiManager)mContext.getSystemService(Context.WIFI_SERVICE);
     }
 
-    public void startEventLoop() {
-        mWifiMonitor.startMonitoring();
+    /**
+     * Disable connectivity to a network
+     * TODO: do away with return value after making MobileDataStateTracker async
+     */
+    public boolean teardown() {
+        sendMessage(CM_CMD_TEARDOWN);
+        return true;
+    }
+
+    /**
+     * Re-enable connectivity to a network after a {@link #teardown()}.
+     * TODO: do away with return value after making MobileDataStateTracker async
+     */
+    public boolean reconnect() {
+        sendMessage(CM_CMD_RECONNECT);
+        return true;
+    }
+
+    /**
+     * Turn the wireless radio off for a network.
+     * @param turnOn {@code true} to turn the radio on, {@code false}
+     * TODO: do away with return value after making MobileDataStateTracker async
+     */
+    public boolean setRadio(boolean turnOn) {
+        setWifiEnabled(turnOn);
+        return true;
     }
 
     /**
@@ -455,1249 +588,334 @@
      * unavailable.
      * @return {@code true} if Wi-Fi connections are possible
      */
-    public synchronized boolean isAvailable() {
-        /*
-         * TODO: Need to also look at scan results to see whether we're
-         * in range of any access points. If we have scan results that
-         * are no more than N seconds old, use those, otherwise, initiate
-         * a scan and wait for the results. This only matters if we
-         * allow mobile to be the preferred network.
-         */
-        SupplicantState suppState = mWifiInfo.getSupplicantState();
-        return suppState != SupplicantState.UNINITIALIZED &&
-                suppState != SupplicantState.INACTIVE &&
-                (mTornDownByConnMgr || !isDriverStopped());
+    public boolean isAvailable() {
+        return mNetworkInfo.isAvailable();
     }
 
     /**
-     * {@inheritDoc}
-     * There are currently no defined Wi-Fi subtypes.
+     * Tells the underlying networking system that the caller wants to
+     * begin using the named feature. The interpretation of {@code feature}
+     * is completely up to each networking implementation.
+     * @param feature the name of the feature to be used
+     * @param callingPid the process ID of the process that is issuing this request
+     * @param callingUid the user ID of the process that is issuing this request
+     * @return an integer value representing the outcome of the request.
+     * The interpretation of this value is specific to each networking
+     * implementation+feature combination, except that the value {@code -1}
+     * always indicates failure.
+     * TODO: needs to go away
      */
-    public int getNetworkSubtype() {
-        return 0;
+    public int startUsingNetworkFeature(String feature, int callingPid, int callingUid) {
+        return -1;
     }
 
     /**
-     * Helper method: updates the network info object to keep it in sync with
-     * the Wi-Fi state tracker.
+     * Tells the underlying networking system that the caller is finished
+     * using the named feature. The interpretation of {@code feature}
+     * is completely up to each networking implementation.
+     * @param feature the name of the feature that is no longer needed.
+     * @param callingPid the process ID of the process that is issuing this request
+     * @param callingUid the user ID of the process that is issuing this request
+     * @return an integer value representing the outcome of the request.
+     * The interpretation of this value is specific to each networking
+     * implementation+feature combination, except that the value {@code -1}
+     * always indicates failure.
+     * TODO: needs to go away
      */
-    private void updateNetworkInfo() {
-        mNetworkInfo.setIsAvailable(isAvailable());
+    public int stopUsingNetworkFeature(String feature, int callingPid, int callingUid) {
+        return -1;
+    }
+
+    /* TODO: will go away.
+     * Notifications are directly handled in WifiStateTracker at checkAndSetNotification()
+     */
+    public void interpretScanResultsAvailable() {
+
     }
 
     /**
-     * Report whether the Wi-Fi connection is fully configured for data.
-     * @return {@code true} if the {@link SupplicantState} is
-     * {@link android.net.wifi.SupplicantState#COMPLETED COMPLETED}.
+     * Return the name of our WLAN network interface.
+     * @return the name of our interface.
      */
-    public boolean isConnectionCompleted() {
-        return mWifiInfo.getSupplicantState() == SupplicantState.COMPLETED;
+    public String getInterfaceName() {
+        return mInterfaceName;
     }
 
     /**
-     * Report whether the Wi-Fi connection has successfully acquired an IP address.
-     * @return {@code true} if the Wi-Fi connection has been assigned an IP address.
+     * Check if private DNS route is set for the network
      */
-    public boolean hasIpAddress() {
-        return mHaveIpAddress;
+    public boolean isPrivateDnsRouteSet() {
+        return mPrivateDnsRouteSet.get();
     }
 
     /**
-     * Send the tracker a notification that a user-entered password key
-     * may be incorrect (i.e., caused authentication to fail).
+     * Set a flag indicating private DNS route is set
      */
-    void notifyPasswordKeyMayBeIncorrect() {
-        sendEmptyMessage(EVENT_PASSWORD_KEY_MAY_BE_INCORRECT);
+    public void privateDnsRouteSet(boolean enabled) {
+        mPrivateDnsRouteSet.set(enabled);
     }
 
     /**
-     * Send the tracker a notification that a connection to the supplicant
-     * daemon has been established.
+     * Fetch NetworkInfo for the network
      */
-    void notifySupplicantConnection() {
-        sendEmptyMessage(EVENT_SUPPLICANT_CONNECTION);
+    public NetworkInfo getNetworkInfo() {
+        return mNetworkInfo;
     }
 
     /**
-     * Send the tracker a notification that the state of the supplicant
-     * has changed.
-     * @param networkId the configured network on which the state change occurred
-     * @param newState the new {@code SupplicantState}
+     * Fetch NetworkProperties for the network
      */
-    void notifyStateChange(int networkId, String BSSID, SupplicantState newState) {
-        Message msg = Message.obtain(
-            this, EVENT_SUPPLICANT_STATE_CHANGED,
-            new SupplicantStateChangeResult(networkId, BSSID, newState));
-        msg.sendToTarget();
+    public NetworkProperties getNetworkProperties() {
+        return mNetworkProperties;
     }
 
     /**
-     * Send the tracker a notification that the state of Wifi connectivity
-     * has changed.
-     * @param networkId the configured network on which the state change occurred
-     * @param newState the new network state
-     * @param BSSID when the new state is {@link DetailedState#CONNECTED
-     * NetworkInfo.DetailedState.CONNECTED},
-     * this is the MAC address of the access point. Otherwise, it
-     * is {@code null}.
+     * Fetch default gateway address for the network
      */
-    void notifyStateChange(DetailedState newState, String BSSID, int networkId) {
-        Message msg = Message.obtain(
-            this, EVENT_NETWORK_STATE_CHANGED,
-            new NetworkStateChangeResult(newState, BSSID, networkId));
-        msg.sendToTarget();
+    public int getDefaultGatewayAddr() {
+        return mDefaultGatewayAddr.get();
     }
 
     /**
-     * Send the tracker a notification that a scan has completed, and results
-     * are available.
+     * Check if default route is set
      */
-    void notifyScanResultsAvailable() {
-        // reset the supplicant's handling of scan results to "normal" mode
-        setScanResultHandling(SUPPL_SCAN_HANDLING_NORMAL);
-        sendEmptyMessage(EVENT_SCAN_RESULTS_AVAILABLE);
+    public boolean isDefaultRouteSet() {
+        return mDefaultRouteSet.get();
     }
 
     /**
-     * Send the tracker a notification that we can no longer communicate with
-     * the supplicant daemon.
+     * Set a flag indicating default route is set for the network
      */
-    void notifySupplicantLost() {
-        sendEmptyMessage(EVENT_SUPPLICANT_DISCONNECT);
+    public void defaultRouteSet(boolean enabled) {
+        mDefaultRouteSet.set(enabled);
     }
 
     /**
-     * Send the tracker a notification that the Wi-Fi driver has been stopped.
+     * Return the system properties name associated with the tcp buffer sizes
+     * for this network.
      */
-    void notifyDriverStopped() {
-        mRunState = RUN_STATE_STOPPED;
+    public String getTcpBufferSizesPropName() {
+        return "net.tcp.buffersize.wifi";
+    }
 
-        // Send a driver stopped message to our handler
-        Message.obtain(this, EVENT_DRIVER_STATE_CHANGED, DRIVER_STOPPED, 0).sendToTarget();
+
+    /*********************************************************
+     * Methods exposed for public use
+     ********************************************************/
+
+    /**
+     * TODO: doc
+     */
+    public boolean pingSupplicant() {
+        return sendSyncMessage(CMD_PING_SUPPLICANT).boolValue;
     }
 
     /**
-     * Send the tracker a notification that the Wi-Fi driver has been restarted after
-     * having been stopped.
+     * TODO: doc
      */
-    void notifyDriverStarted() {
-        // Send a driver started message to our handler
-        Message.obtain(this, EVENT_DRIVER_STATE_CHANGED, DRIVER_STARTED, 0).sendToTarget();
+    public boolean startScan(boolean forceActive) {
+        return sendSyncMessage(obtainMessage(CMD_START_SCAN, forceActive ?
+                SCAN_ACTIVE : SCAN_PASSIVE, 0)).boolValue;
     }
 
     /**
-     * Send the tracker a notification that the Wi-Fi driver has hung and needs restarting.
+     * TODO: doc
      */
-    void notifyDriverHung() {
-        // Send a driver hanged message to our handler
-        Message.obtain(this, EVENT_DRIVER_STATE_CHANGED, DRIVER_HUNG, 0).sendToTarget();
-    }
-
-    /**
-     * Set the interval timer for polling connection information
-     * that is not delivered asynchronously.
-     */
-    private synchronized void checkPollTimer() {
-        if (mEnableRssiPolling &&
-                mWifiInfo.getSupplicantState() == SupplicantState.COMPLETED &&
-                !hasMessages(EVENT_POLL_INTERVAL)) {
-            sendEmptyMessageDelayed(EVENT_POLL_INTERVAL, POLL_STATUS_INTERVAL_MSECS);
-        }
-    }
-
-    /**
-     * TODO: mRunState is not synchronized in some places
-     * address this as part of re-architect.
-     *
-     * TODO: We are exposing an additional public synchronized call
-     * for a wakelock optimization in WifiService. Remove it
-     * when we handle the wakelock in ConnectivityService.
-     */
-    public synchronized boolean isDriverStopped() {
-        return mRunState == RUN_STATE_STOPPED || mRunState == RUN_STATE_STOPPING;
-    }
-
-    private void noteRunState() {
-        try {
-            if (mRunState == RUN_STATE_RUNNING) {
-                mBatteryStats.noteWifiRunning();
-            } else if (mRunState == RUN_STATE_STOPPED) {
-                mBatteryStats.noteWifiStopped();
-            }
-        } catch (RemoteException ignore) {
-        }
-    }
-
-    /**
-     * Set the run state to either "normal" or "scan-only".
-     * @param scanOnlyMode true if the new mode should be scan-only.
-     */
-    public synchronized void setScanOnlyMode(boolean scanOnlyMode) {
-        // do nothing unless scan-only mode is changing
-        if (mIsScanOnly != scanOnlyMode) {
-            int scanType = (scanOnlyMode ?
-                    SUPPL_SCAN_HANDLING_LIST_ONLY : SUPPL_SCAN_HANDLING_NORMAL);
-            if (LOCAL_LOGD) Log.v(TAG, "Scan-only mode changing to " + scanOnlyMode + " scanType=" + scanType);
-            if (setScanResultHandling(scanType)) {
-                mIsScanOnly = scanOnlyMode;
-                if (!isDriverStopped()) {
-                    if (scanOnlyMode) {
-                        disconnect();
-                    } else {
-                        reconnectCommand();
-                    }
-                }
-            }
-        }
-    }
-
-
-    private void checkIsBluetoothPlaying() {
-        boolean isBluetoothPlaying = false;
-        Set<BluetoothDevice> connected = mBluetoothA2dp.getConnectedSinks();
-
-        for (BluetoothDevice device : connected) {
-            if (mBluetoothA2dp.getSinkState(device) == BluetoothA2dp.STATE_PLAYING) {
-                isBluetoothPlaying = true;
-                break;
-            }
-        }
-        setBluetoothScanMode(isBluetoothPlaying);
-    }
-
-    public void enableRssiPolling(boolean enable) {
-        if (mEnableRssiPolling != enable) {
-            mEnableRssiPolling = enable;
-            checkPollTimer();
-        }
-    }
-
-    /**
-     * We release the wakelock in WifiService
-     * using a timer.
-     *
-     * TODO:
-     * Releasing wakelock using both timer and
-     * a call from ConnectivityService requires
-     * a rethink. We had problems where WifiService
-     * could keep a wakelock forever if we delete
-     * messages in the asynchronous call
-     * from ConnectivityService
-     */
-    @Override
-    public void releaseWakeLock() {
-    }
-
-    /**
-     * Tracks the WPA supplicant states to detect "loop" situations.
-     * @param newSupplicantState The new WPA supplicant state.
-     * @return {@code true} if the supplicant loop should be stopped
-     * and {@code false} if it should continue.
-     */
-    private boolean isSupplicantLooping(SupplicantState newSupplicantState) {
-        if (SupplicantState.ASSOCIATING.ordinal() <= newSupplicantState.ordinal()
-            && newSupplicantState.ordinal() < SupplicantState.COMPLETED.ordinal()) {
-            if (mSupplicantLoopState != newSupplicantState) {
-                if (newSupplicantState.ordinal() < mSupplicantLoopState.ordinal()) {
-                    ++mNumSupplicantLoopIterations;
-                }
-
-                mSupplicantLoopState = newSupplicantState;
-            }
-        } else if (newSupplicantState == SupplicantState.COMPLETED) {
-            resetSupplicantLoopState();
-        }
-
-        return mNumSupplicantLoopIterations >= MAX_SUPPLICANT_LOOP_ITERATIONS;
-    }
-
-    /**
-     * Resets the WPA supplicant loop state.
-     */
-    private void resetSupplicantLoopState() {
-        mNumSupplicantLoopIterations = 0;
-    }
-
-    @Override
-    public void handleMessage(Message msg) {
-        Intent intent;
-
-        switch (msg.what) {
-            case EVENT_SUPPLICANT_CONNECTION:
-                mRunState = RUN_STATE_RUNNING;
-                noteRunState();
-                checkUseStaticIp();
-                /* Reset notification state on new connection */
-                resetNotificationTimer();
-                /*
-                 * DHCP requests are blocking, so run them in a separate thread.
-                 */
-                HandlerThread dhcpThread = new HandlerThread("DHCP Handler Thread");
-                dhcpThread.start();
-                mDhcpTarget = new DhcpHandler(dhcpThread.getLooper(), this);
-                mIsScanModeActive = true;
-                mTornDownByConnMgr = false;
-                mLastBssid = null;
-                mLastSsid = null;
-                requestConnectionInfo();
-                SupplicantState supplState = mWifiInfo.getSupplicantState();
-                /**
-                 * The MAC address isn't going to change, so just request it
-                 * once here.
-                 */
-                String macaddr = getMacAddress();
-
-                if (macaddr != null) {
-                    mWifiInfo.setMacAddress(macaddr);
-                }
-                if (LOCAL_LOGD) Log.v(TAG, "Connection to supplicant established, state=" +
-                    supplState);
-                // Wi-Fi supplicant connection state changed:
-                // [31- 2] Reserved for future use
-                // [ 1- 0] Connected to supplicant (1), disconnected from supplicant (0) ,
-                //         or supplicant died (2)
-                EventLog.writeEvent(EVENTLOG_SUPPLICANT_CONNECTION_STATE_CHANGED, 1);
-                /*
-                 * The COMPLETED state change from the supplicant may have occurred
-                 * in between polling for supplicant availability, in which case
-                 * we didn't perform a DHCP request to get an IP address.
-                 */
-                if (supplState == SupplicantState.COMPLETED) {
-                    mLastBssid = mWifiInfo.getBSSID();
-                    mLastSsid = mWifiInfo.getSSID();
-                    configureInterface();
-                }
-                if (ActivityManagerNative.isSystemReady()) {
-                    intent = new Intent(WifiManager.SUPPLICANT_CONNECTION_CHANGE_ACTION);
-                    intent.putExtra(WifiManager.EXTRA_SUPPLICANT_CONNECTED, true);
-                    mContext.sendBroadcast(intent);
-                }
-                if (supplState == SupplicantState.COMPLETED && mHaveIpAddress) {
-                    setDetailedState(DetailedState.CONNECTED);
-                } else {
-                    setDetailedState(WifiInfo.getDetailedStateOf(supplState));
-                }
-                /*
-                 * Filter out multicast packets. This saves battery power, since
-                 * the CPU doesn't have to spend time processing packets that
-                 * are going to end up being thrown away.
-                 */
-                mWM.initializeMulticastFiltering();
-
-                if (mBluetoothA2dp == null) {
-                    mBluetoothA2dp = new BluetoothA2dp(mContext);
-                }
-                checkIsBluetoothPlaying();
-
-                // initialize this after the supplicant is alive
-                setNumAllowedChannels();
-                break;
-
-            case EVENT_SUPPLICANT_DISCONNECT:
-                mRunState = RUN_STATE_STOPPED;
-                noteRunState();
-                boolean died = mWifiState.get() != WIFI_STATE_DISABLED &&
-                               mWifiState.get() != WIFI_STATE_DISABLING;
-                if (died) {
-                    if (LOCAL_LOGD) Log.v(TAG, "Supplicant died unexpectedly");
-                } else {
-                    if (LOCAL_LOGD) Log.v(TAG, "Connection to supplicant lost");
-                }
-                // Wi-Fi supplicant connection state changed:
-                // [31- 2] Reserved for future use
-                // [ 1- 0] Connected to supplicant (1), disconnected from supplicant (0) ,
-                //         or supplicant died (2)
-                EventLog.writeEvent(EVENTLOG_SUPPLICANT_CONNECTION_STATE_CHANGED, died ? 2 : 0);
-                closeSupplicantConnection();
-
-                if (died) {
-                    resetConnections(true);
-                }
-                // When supplicant dies, kill the DHCP thread
-                if (mDhcpTarget != null) {
-                    mDhcpTarget.getLooper().quit();
-                    mDhcpTarget = null;
-                }
-                mContext.removeStickyBroadcast(new Intent(WifiManager.NETWORK_STATE_CHANGED_ACTION));
-                if (ActivityManagerNative.isSystemReady()) {
-                    intent = new Intent(WifiManager.SUPPLICANT_CONNECTION_CHANGE_ACTION);
-                    intent.putExtra(WifiManager.EXTRA_SUPPLICANT_CONNECTED, false);
-                    mContext.sendBroadcast(intent);
-                }
-                setDetailedState(DetailedState.DISCONNECTED);
-                setSupplicantState(SupplicantState.UNINITIALIZED);
-                mHaveIpAddress = false;
-                mObtainingIpAddress = false;
-                if (died) {
-                    mWM.setWifiEnabled(false);
-                }
-                break;
-
-            case EVENT_MAYBE_START_SCAN_POST_DISCONNECT:
-                // Only do this if we haven't gotten a new supplicant status since the timer
-                // started
-                if (mNumSupplicantStateChanges == msg.arg1) {
-                    scan(false); // do a passive scan
-                }
-                break;
-
-            case EVENT_SUPPLICANT_STATE_CHANGED:
-                mNumSupplicantStateChanges++;
-                SupplicantStateChangeResult supplicantStateResult =
-                    (SupplicantStateChangeResult) msg.obj;
-                SupplicantState newState = supplicantStateResult.state;
-                SupplicantState currentState = mWifiInfo.getSupplicantState();
-
-                // Wi-Fi supplicant state changed:
-                // [31- 6] Reserved for future use
-                // [ 5- 0] Supplicant state ordinal (as defined by SupplicantState)
-                int eventLogParam = (newState.ordinal() & 0x3f);
-                EventLog.writeEvent(EVENTLOG_SUPPLICANT_STATE_CHANGED, eventLogParam);
-
-                if (LOCAL_LOGD) Log.v(TAG, "Changing supplicant state: "
-                                      + currentState +
-                                      " ==> " + newState);
-
-                int networkId = supplicantStateResult.networkId;
-
-                /**
-                 * The SupplicantState BSSID value is valid in ASSOCIATING state only.
-                 * The NetworkState BSSID value comes upon a successful connection.
-                 */
-                if (supplicantStateResult.state == SupplicantState.ASSOCIATING) {
-                    mLastBssid = supplicantStateResult.BSSID;
-                }
-                /*
-                 * If we get disconnect or inactive we need to start our
-                 * watchdog timer to start a scan
-                 */
-                if (newState == SupplicantState.DISCONNECTED ||
-                        newState == SupplicantState.INACTIVE) {
-                    sendMessageDelayed(obtainMessage(EVENT_MAYBE_START_SCAN_POST_DISCONNECT,
-                            mNumSupplicantStateChanges, 0), KICKSTART_SCANNING_DELAY_MSECS);
-                }
-
-
-                /*
-                 * Did we get to DISCONNECTED state due to an
-                 * authentication (password) failure?
-                 */
-                boolean failedToAuthenticate = false;
-                if (newState == SupplicantState.DISCONNECTED) {
-                    failedToAuthenticate = mPasswordKeyMayBeIncorrect;
-                }
-                mPasswordKeyMayBeIncorrect = false;
-
-                /*
-                 * Keep track of the supplicant state and check if we should
-                 * disable the network
-                 */
-                boolean disabledNetwork = false;
-                if (isSupplicantLooping(newState)) {
-                    if (LOCAL_LOGD) {
-                        Log.v(TAG,
-                              "Stop WPA supplicant loop and disable network");
-                    }
-                    disabledNetwork = wifiManagerDisableNetwork(networkId);
-                }
-
-                if (disabledNetwork) {
-                    /*
-                     * Reset the loop state if we disabled the network
-                     */
-                    resetSupplicantLoopState();
-                } else if (newState != currentState ||
-                        (newState == SupplicantState.DISCONNECTED && isDriverStopped())) {
-                    setSupplicantState(newState);
-                    if (newState == SupplicantState.DORMANT) {
-                        DetailedState newDetailedState;
-                        Message reconnectMsg = obtainMessage(EVENT_DEFERRED_RECONNECT, mLastBssid);
-                        if (mIsScanOnly || mRunState == RUN_STATE_STOPPING) {
-                            newDetailedState = DetailedState.IDLE;
-                        } else {
-                            newDetailedState = DetailedState.FAILED;
-                        }
-                        handleDisconnectedState(newDetailedState, true);
-                        /**
-                         * If we were associated with a network (networkId != -1),
-                         * assume we reached this state because of a failed attempt
-                         * to acquire an IP address, and attempt another connection
-                         * and IP address acquisition in RECONNECT_DELAY_MSECS
-                         * milliseconds.
-                         */
-                        if (mRunState == RUN_STATE_RUNNING && !mIsScanOnly && networkId != -1) {
-                            sendMessageDelayed(reconnectMsg, RECONNECT_DELAY_MSECS);
-                        } else if (mRunState == RUN_STATE_STOPPING) {
-                            stopDriver();
-                        } else if (mRunState == RUN_STATE_STARTING && !mIsScanOnly) {
-                            reconnectCommand();
-                        }
-                    } else if (newState == SupplicantState.DISCONNECTED) {
-                        mHaveIpAddress = false;
-                        if (isDriverStopped() || mDisconnectExpected) {
-                            handleDisconnectedState(DetailedState.DISCONNECTED, true);
-                        } else {
-                            scheduleDisconnect();
-                        }
-                    } else if (newState != SupplicantState.COMPLETED && !mDisconnectPending) {
-                        /**
-                         * Ignore events that don't change the connectivity state,
-                         * such as WPA rekeying operations.
-                         */
-                        if (!(currentState == SupplicantState.COMPLETED &&
-                               (newState == SupplicantState.ASSOCIATING ||
-                                newState == SupplicantState.ASSOCIATED ||
-                                newState == SupplicantState.FOUR_WAY_HANDSHAKE ||
-                                newState == SupplicantState.GROUP_HANDSHAKE))) {
-                            setDetailedState(WifiInfo.getDetailedStateOf(newState));
-                        }
-                    }
-
-                    mDisconnectExpected = false;
-                    intent = new Intent(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION);
-                    intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
-                            | Intent.FLAG_RECEIVER_REPLACE_PENDING);
-                    intent.putExtra(WifiManager.EXTRA_NEW_STATE, (Parcelable)newState);
-                    if (failedToAuthenticate) {
-                        if (LOCAL_LOGD) Log.d(TAG, "Failed to authenticate, disabling network " + networkId);
-                        wifiManagerDisableNetwork(networkId);
-                        intent.putExtra(
-                            WifiManager.EXTRA_SUPPLICANT_ERROR,
-                            WifiManager.ERROR_AUTHENTICATING);
-                    }
-                    mContext.sendStickyBroadcast(intent);
-                }
-                break;
-
-            case EVENT_NETWORK_STATE_CHANGED:
-                /*
-                 * Each CONNECT or DISCONNECT generates a pair of events.
-                 * One is a supplicant state change event, and the other
-                 * is a network state change event. For connects, the
-                 * supplicant event always arrives first, followed by
-                 * the network state change event. Only the latter event
-                 * has the BSSID, which we are interested in capturing.
-                 * For disconnects, the order is the opposite -- the
-                 * network state change event comes first, followed by
-                 * the supplicant state change event.
-                 */
-                NetworkStateChangeResult result =
-                    (NetworkStateChangeResult) msg.obj;
-
-                // Wi-Fi network state changed:
-                // [31- 6] Reserved for future use
-                // [ 5- 0] Detailed state ordinal (as defined by NetworkInfo.DetailedState)   
-                eventLogParam = (result.state.ordinal() & 0x3f);
-                EventLog.writeEvent(EVENTLOG_NETWORK_STATE_CHANGED, eventLogParam);
-                
-                if (LOCAL_LOGD) Log.v(TAG, "New network state is " + result.state);
-                /*
-                 * If we're in scan-only mode, don't advance the state machine, and
-                 * don't report the state change to clients.
-                 */
-                if (mIsScanOnly) {
-                    if (LOCAL_LOGD) Log.v(TAG, "Dropping event in scan-only mode");
-                    break;
-                }
-                if (result.state != DetailedState.SCANNING) {
-                    /*
-                     * Reset the scan count since there was a network state
-                     * change. This could be from supplicant trying to associate
-                     * with a network.
-                     */
-                    mNumScansSinceNetworkStateChange = 0;
-                }
-                /*
-                 * If the supplicant sent us a CONNECTED event, we don't
-                 * want to send out an indication of overall network
-                 * connectivity until we have our IP address. If the
-                 * supplicant sent us a DISCONNECTED event, we delay
-                 * sending a notification in case a reconnection to
-                 * the same access point occurs within a short time.
-                 */
-                if (result.state == DetailedState.DISCONNECTED) {
-                    if (mWifiInfo.getSupplicantState() != SupplicantState.DORMANT) {
-                        scheduleDisconnect();
-                    }
-                    break;
-                }
-                requestConnectionStatus(mWifiInfo);
-                if (!(result.state == DetailedState.CONNECTED &&
-                        (!mHaveIpAddress || mDisconnectPending))) {
-                    setDetailedState(result.state);
-                }
-
-                if (result.state == DetailedState.CONNECTED) {
-                    /*
-                     * Remove the 'available networks' notification when we
-                     * successfully connect to a network.
-                     */
-                    setNotificationVisible(false, 0, false, 0);
-                    boolean wasDisconnectPending = mDisconnectPending;
-                    cancelDisconnect();
-                    /*
-                     * The connection is fully configured as far as link-level
-                     * connectivity is concerned, but we may still need to obtain
-                     * an IP address.
-                     */
-                    if (wasDisconnectPending) {
-                        DetailedState saveState = getNetworkInfo().getDetailedState();
-                        handleDisconnectedState(DetailedState.DISCONNECTED, false);
-                        setDetailedStateInternal(saveState);
-                    }
-
-                    configureInterface();
-                    mLastBssid = result.BSSID;
-                    mLastSsid = mWifiInfo.getSSID();
-                    mLastNetworkId = result.networkId;
-                    if (mHaveIpAddress) {
-                        setDetailedState(DetailedState.CONNECTED);
-                    } else {
-                        setDetailedState(DetailedState.OBTAINING_IPADDR);
-                    }
-                }
-                sendNetworkStateChangeBroadcast(mWifiInfo.getBSSID());
-                break;
-
-            case EVENT_SCAN_RESULTS_AVAILABLE:
-                if (ActivityManagerNative.isSystemReady()) {
-                    mContext.sendBroadcast(new Intent(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION));
-                }
-                sendScanResultsAvailable();
-                /**
-                 * On receiving the first scan results after connecting to
-                 * the supplicant, switch scan mode over to passive.
-                 */
-                setScanMode(false);
-                break;
-
-            case EVENT_POLL_INTERVAL:
-                if (mWifiInfo.getSupplicantState() != SupplicantState.UNINITIALIZED) {
-                    requestPolledInfo(mWifiInfo, true);
-                    checkPollTimer();
-                }
-                break;
-            
-            case EVENT_DEFERRED_DISCONNECT:
-                if (mWifiInfo.getSupplicantState() != SupplicantState.UNINITIALIZED) {
-                    handleDisconnectedState(DetailedState.DISCONNECTED, true);
-                }
-                break;
-
-            case EVENT_DEFERRED_RECONNECT:
-                /**
-                 * mLastBssid can be null when there is a reconnect
-                 * request on the first BSSID we connect to
-                 */
-                String BSSID = (msg.obj != null) ? msg.obj.toString() : null;
-                /**
-                 * If we've exceeded the maximum number of retries for reconnecting
-                 * to a given network, disable the network
-                 */
-                if (mWifiInfo.getSupplicantState() != SupplicantState.UNINITIALIZED) {
-                    if (++mReconnectCount > getMaxDhcpRetries()) {
-                        if (LOCAL_LOGD) {
-                            Log.d(TAG, "Failed reconnect count: " +
-                                    mReconnectCount + " Disabling " + BSSID);
-                        }
-                        mWM.disableNetwork(mLastNetworkId);
-                    }
-                    reconnectCommand();
-                }
-                break;
-
-            case EVENT_INTERFACE_CONFIGURATION_SUCCEEDED:
-                /**
-                 * Since this event is sent from another thread, it might have been
-                 * sent after we closed our connection to the supplicant in the course
-                 * of disabling Wi-Fi. In that case, we should just ignore the event.
-                 */
-                if (mWifiInfo.getSupplicantState() == SupplicantState.UNINITIALIZED) {
-                    break;
-                }
-                mReconnectCount = 0;
-                mHaveIpAddress = true;
-                mObtainingIpAddress = false;
-                mWifiInfo.setIpAddress(mDhcpInfo.ipAddress);
-                mLastSignalLevel = -1; // force update of signal strength
-                if (mNetworkInfo.getDetailedState() != DetailedState.CONNECTED) {
-                    setDetailedState(DetailedState.CONNECTED);
-                    sendNetworkStateChangeBroadcast(mWifiInfo.getBSSID());
-                } else {
-                    mTarget.sendEmptyMessage(EVENT_CONFIGURATION_CHANGED);
-                }
-                if (LOCAL_LOGD) Log.v(TAG, "IP configuration: " + mDhcpInfo);
-                // Wi-Fi interface configuration state changed:
-                // [31- 1] Reserved for future use
-                // [ 0- 0] Interface configuration succeeded (1) or failed (0)   
-                EventLog.writeEvent(EVENTLOG_INTERFACE_CONFIGURATION_STATE_CHANGED, 1);
-
-                // We've connected successfully, so allow the notification again in the future
-                resetNotificationTimer();
-                break;
-
-            case EVENT_INTERFACE_CONFIGURATION_FAILED:
-                if (mWifiInfo.getSupplicantState() != SupplicantState.UNINITIALIZED) {
-                    // Wi-Fi interface configuration state changed:
-                    // [31- 1] Reserved for future use
-                    // [ 0- 0] Interface configuration succeeded (1) or failed (0)
-                    EventLog.writeEvent(EVENTLOG_INTERFACE_CONFIGURATION_STATE_CHANGED, 0);
-                    mHaveIpAddress = false;
-                    mWifiInfo.setIpAddress(0);
-                    mObtainingIpAddress = false;
-                    disconnect();
-                }
-                break;
-
-            case EVENT_DRIVER_STATE_CHANGED:
-                // Wi-Fi driver state changed:
-                // 0 STARTED
-                // 1 STOPPED
-                // 2 HUNG
-                EventLog.writeEvent(EVENTLOG_DRIVER_STATE_CHANGED, msg.arg1);
-
-                switch (msg.arg1) {
-                case DRIVER_STARTED:
-                    /**
-                     * Set the number of allowed radio channels according
-                     * to the system setting, since it gets reset by the
-                     * driver upon changing to the STARTED state.
-                     */
-                    setNumAllowedChannels();
-                    synchronized (this) {
-                        if (mRunState == RUN_STATE_STARTING) {
-                            mRunState = RUN_STATE_RUNNING;
-                            if (!mIsScanOnly) {
-                                reconnectCommand();
-                            } else {
-                                // In some situations, supplicant needs to be kickstarted to
-                                // start the background scanning
-                                scan(true);
-                            }
-                        }
-                    }
-                    break;
-                case DRIVER_HUNG:
-                    Log.e(TAG, "Wifi Driver reports HUNG - reloading.");
-                    /**
-                     * restart the driver - toggle off and on
-                     */
-                    mWM.setWifiEnabled(false);
-                    mWM.setWifiEnabled(true);
-                    break;
-                }
-                noteRunState();
-                break;
-
-            case EVENT_PASSWORD_KEY_MAY_BE_INCORRECT:
-                mPasswordKeyMayBeIncorrect = true;
-                break;
-        }
-    }
-
-    private boolean wifiManagerDisableNetwork(int networkId) {
-        boolean disabledNetwork = false;
-        if (0 <= networkId) {
-            disabledNetwork = mWM.disableNetwork(networkId);
-            if (LOCAL_LOGD) {
-                if (disabledNetwork) {
-                    Log.v(TAG, "Disabled network: " + networkId);
-                }
-            }
-        }
-        if (LOCAL_LOGD) {
-            if (!disabledNetwork) {
-                Log.e(TAG, "Failed to disable network:" +
-                      " invalid network id: " + networkId);
-            }
-        }
-        return disabledNetwork;
-    }
-
-    private void configureInterface() {
-        checkPollTimer();
-        mLastSignalLevel = -1;
-        if (!mUseStaticIp) {
-            if (!mHaveIpAddress && !mObtainingIpAddress) {
-                mObtainingIpAddress = true;
-                mDhcpTarget.sendEmptyMessage(EVENT_DHCP_START);
-            }
+    public void setWifiEnabled(boolean enable) {
+        mLastEnableUid.set(Binder.getCallingUid());
+        if (enable) {
+            /* Argument is the state that is entered prior to load */
+            sendMessage(obtainMessage(CMD_LOAD_DRIVER, WIFI_STATE_ENABLING, 0));
+            sendMessage(CMD_START_SUPPLICANT);
         } else {
-            int event;
-            if (NetworkUtils.configureInterface(mInterfaceName, mDhcpInfo)) {
-                mHaveIpAddress = true;
-                event = EVENT_INTERFACE_CONFIGURATION_SUCCEEDED;
-                if (LOCAL_LOGD) Log.v(TAG, "Static IP configuration succeeded");
-            } else {
-                mHaveIpAddress = false;
-                event = EVENT_INTERFACE_CONFIGURATION_FAILED;
-                if (LOCAL_LOGD) Log.v(TAG, "Static IP configuration failed");
-            }
-            sendEmptyMessage(event);
+            sendMessage(CMD_STOP_SUPPLICANT);
+            /* Argument is the state that is entered upon success */
+            sendMessage(obtainMessage(CMD_UNLOAD_DRIVER, WIFI_STATE_DISABLED, 0));
         }
     }
 
     /**
-     * Reset our IP state and send out broadcasts following a disconnect.
-     * @param newState the {@code DetailedState} to set. Should be either
-     * {@code DISCONNECTED} or {@code FAILED}.
-     * @param disableInterface indicates whether the interface should
-     * be disabled
+     * TODO: doc
      */
-    private void handleDisconnectedState(DetailedState newState, boolean disableInterface) {
-        if (mDisconnectPending) {
-            cancelDisconnect();
-        }
-        mDisconnectExpected = false;
-        resetConnections(disableInterface);
-        setDetailedState(newState);
-        sendNetworkStateChangeBroadcast(mLastBssid);
-        mWifiInfo.setBSSID(null);
-        mLastBssid = null;
-        mLastSsid = null;
-        mDisconnectPending = false;
-    }
-
-    /**
-     * Resets the Wi-Fi Connections by clearing any state, resetting any sockets
-     * using the interface, stopping DHCP, and disabling the interface.
-     */
-    public void resetConnections(boolean disableInterface) {
-        if (LOCAL_LOGD) Log.d(TAG, "Reset connections and stopping DHCP");
-        mHaveIpAddress = false;
-        mObtainingIpAddress = false;
-        mWifiInfo.setIpAddress(0);
-
-        /*
-         * Reset connection depends on both the interface and the IP assigned,
-         * so it should be done before any chance of the IP being lost.
-         */
-        NetworkUtils.resetConnections(mInterfaceName);
-
-        // Stop DHCP
-        if (mDhcpTarget != null) {
-            mDhcpTarget.setCancelCallback(true);
-            mDhcpTarget.removeMessages(EVENT_DHCP_START);
-        }
-        if (!NetworkUtils.stopDhcp(mInterfaceName)) {
-            Log.e(TAG, "Could not stop DHCP");
-        }
-
-        /**
-         * Interface is re-enabled in the supplicant
-         * when moving out of ASSOCIATING state
-         */
-        if(disableInterface) {
-            if (LOCAL_LOGD) Log.d(TAG, "Disabling interface");
-            NetworkUtils.disableInterface(mInterfaceName);
+    public void setWifiApEnabled(WifiConfiguration wifiConfig, boolean enable) {
+        mLastApEnableUid.set(Binder.getCallingUid());
+        if (enable) {
+            /* Argument is the state that is entered prior to load */
+            sendMessage(obtainMessage(CMD_LOAD_DRIVER, WIFI_AP_STATE_ENABLING, 0));
+            sendMessage(obtainMessage(CMD_START_AP, wifiConfig));
+        } else {
+            sendMessage(CMD_STOP_AP);
+            /* Argument is the state that is entered upon success */
+            sendMessage(obtainMessage(CMD_UNLOAD_DRIVER, WIFI_AP_STATE_DISABLED, 0));
         }
     }
 
     /**
-     * The supplicant is reporting that we are disconnected from the current
-     * access point. Often, however, a disconnect will be followed very shortly
-     * by a reconnect to the same access point. Therefore, we delay resetting
-     * the connection's IP state for a bit.
+     * TODO: doc
      */
-    private void scheduleDisconnect() {
-        mDisconnectPending = true;
-        if (!hasMessages(EVENT_DEFERRED_DISCONNECT)) {
-            sendEmptyMessageDelayed(EVENT_DEFERRED_DISCONNECT, DISCONNECT_DELAY_MSECS);
+    public int getWifiState() {
+        return mWifiState.get();
+    }
+
+    /**
+     * TODO: doc
+     */
+    public String getWifiStateByName() {
+        switch (mWifiState.get()) {
+            case WIFI_STATE_DISABLING:
+                return "disabling";
+            case WIFI_STATE_DISABLED:
+                return "disabled";
+            case WIFI_STATE_ENABLING:
+                return "enabling";
+            case WIFI_STATE_ENABLED:
+                return "enabled";
+            case WIFI_STATE_UNKNOWN:
+                return "unknown state";
+            default:
+                return "[invalid state]";
         }
     }
 
-    private void cancelDisconnect() {
-        mDisconnectPending = false;
-        removeMessages(EVENT_DEFERRED_DISCONNECT);
+    /**
+     * TODO: doc
+     */
+    public int getWifiApState() {
+        return mWifiApState.get();
+    }
+
+    /**
+     * TODO: doc
+     */
+    public String getWifiApStateByName() {
+        switch (mWifiApState.get()) {
+            case WIFI_AP_STATE_DISABLING:
+                return "disabling";
+            case WIFI_AP_STATE_DISABLED:
+                return "disabled";
+            case WIFI_AP_STATE_ENABLING:
+                return "enabling";
+            case WIFI_AP_STATE_ENABLED:
+                return "enabled";
+            case WIFI_AP_STATE_FAILED:
+                return "failed";
+            default:
+                return "[invalid state]";
+        }
+    }
+
+    /**
+     * Get status information for the current connection, if any.
+     * @return a {@link WifiInfo} object containing information about the current connection
+     *
+     */
+    public WifiInfo requestConnectionInfo() {
+        return mWifiInfo;
     }
 
     public DhcpInfo getDhcpInfo() {
         return mDhcpInfo;
     }
 
-    public synchronized List<ScanResult> getScanResultsList() {
+    /**
+     * TODO: doc
+     */
+    public void startWifi(boolean enable) {
+      if (enable) {
+          sendMessage(CMD_START_DRIVER);
+      } else {
+          sendMessage(CMD_STOP_DRIVER);
+      }
+    }
+
+    /**
+     * TODO: doc
+     */
+    public void disconnectAndStop() {
+        sendMessage(CMD_DISCONNECT);
+        sendMessage(CMD_STOP_DRIVER);
+    }
+
+    /**
+     * TODO: doc
+     */
+    public void setScanOnlyMode(boolean enable) {
+      if (enable) {
+          sendMessage(obtainMessage(CMD_SET_SCAN_MODE, SCAN_ONLY_MODE, 0));
+      } else {
+          sendMessage(obtainMessage(CMD_SET_SCAN_MODE, CONNECT_MODE, 0));
+      }
+    }
+
+    /**
+     * TODO: doc
+     */
+    public void setScanType(boolean active) {
+      if (active) {
+          sendMessage(obtainMessage(CMD_SET_SCAN_TYPE, SCAN_ACTIVE, 0));
+      } else {
+          sendMessage(obtainMessage(CMD_SET_SCAN_TYPE, SCAN_PASSIVE, 0));
+      }
+    }
+
+    /**
+     * TODO: doc
+     */
+    public List<ScanResult> getScanResultsList() {
         return mScanResults;
     }
 
-    public synchronized void setScanResultsList(List<ScanResult> scanList) {
-        mScanResults = scanList;
-    }
-
-    /**
-     * Get status information for the current connection, if any.
-     * @return a {@link WifiInfo} object containing information about the current connection
-     */
-    public WifiInfo requestConnectionInfo() {
-        requestConnectionStatus(mWifiInfo);
-        requestPolledInfo(mWifiInfo, false);
-        return mWifiInfo;
-    }
-
-    private void requestConnectionStatus(WifiInfo info) {
-        String reply = status();
-        if (reply == null) {
-            return;
-        }
-        /*
-         * Parse the reply from the supplicant to the status command, and update
-         * local state accordingly. The reply is a series of lines of the form
-         * "name=value".
-         */
-        String SSID = null;
-        String BSSID = null;
-        String suppState = null;
-        int netId = -1;
-        String[] lines = reply.split("\n");
-        for (String line : lines) {
-            String[] prop = line.split(" *= *", 2);
-            if (prop.length < 2)
-                continue;
-            String name = prop[0];
-            String value = prop[1];
-            if (name.equalsIgnoreCase("id"))
-                netId = Integer.parseInt(value);
-            else if (name.equalsIgnoreCase("ssid"))
-                SSID = value;
-            else if (name.equalsIgnoreCase("bssid"))
-                BSSID = value;
-            else if (name.equalsIgnoreCase("wpa_state"))
-                suppState = value;
-        }
-        info.setNetworkId(netId);
-        info.setSSID(SSID);
-        info.setBSSID(BSSID);
-        /*
-         * We only set the supplicant state if the previous state was
-         * UNINITIALIZED. This should only happen when we first connect to
-         * the supplicant. Once we're connected, we should always receive
-         * an event upon any state change, but in this case, we want to
-         * make sure any listeners are made aware of the state change.
-         */
-        if (mWifiInfo.getSupplicantState() == SupplicantState.UNINITIALIZED && suppState != null)
-            setSupplicantState(suppState);
-    }
-
-    /**
-     * Get the dynamic information that is not reported via events.
-     * @param info the object into which the information should be captured.
-     */
-    private synchronized void requestPolledInfo(WifiInfo info, boolean polling)
-    {
-        int newRssi = (polling ? getRssiApprox() : getRssi());
-        if (newRssi != -1 && -200 < newRssi && newRssi < 256) { // screen out invalid values
-            /* some implementations avoid negative values by adding 256
-             * so we need to adjust for that here.
-             */
-            if (newRssi > 0) newRssi -= 256;
-            info.setRssi(newRssi);
-            /*
-             * Rather then sending the raw RSSI out every time it
-             * changes, we precalculate the signal level that would
-             * be displayed in the status bar, and only send the
-             * broadcast if that much more coarse-grained number
-             * changes. This cuts down greatly on the number of
-             * broadcasts, at the cost of not informing others
-             * interested in RSSI of all the changes in signal
-             * level.
-             */
-            // TODO: The second arg to the call below needs to be a symbol somewhere, but
-            // it's actually the size of an array of icons that's private
-            // to StatusBar Policy.
-            int newSignalLevel = WifiManager.calculateSignalLevel(newRssi, 4);
-            if (newSignalLevel != mLastSignalLevel) {
-                sendRssiChangeBroadcast(newRssi);
-            }
-            mLastSignalLevel = newSignalLevel;
-        } else {
-            info.setRssi(-200);
-        }
-        int newLinkSpeed = getLinkSpeed();
-        if (newLinkSpeed != -1) {
-            info.setLinkSpeed(newLinkSpeed);
-        }
-    }
-
-    private void sendRssiChangeBroadcast(final int newRssi) {
-        if (ActivityManagerNative.isSystemReady()) {
-            Intent intent = new Intent(WifiManager.RSSI_CHANGED_ACTION);
-            intent.putExtra(WifiManager.EXTRA_NEW_RSSI, newRssi);
-            mContext.sendBroadcast(intent);
-        }
-    }
-
-    private void sendNetworkStateChangeBroadcast(String bssid) {
-        Intent intent = new Intent(WifiManager.NETWORK_STATE_CHANGED_ACTION);
-        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
-                | Intent.FLAG_RECEIVER_REPLACE_PENDING);
-        intent.putExtra(WifiManager.EXTRA_NETWORK_INFO, mNetworkInfo);
-        if (bssid != null)
-            intent.putExtra(WifiManager.EXTRA_BSSID, bssid);
-        mContext.sendStickyBroadcast(intent);
-    }
-
-    /**
-     * Disable Wi-Fi connectivity by stopping the driver.
-     */
-    public boolean teardown() {
-        if (!mTornDownByConnMgr) {
-            if (disconnectAndStop()) {
-                setTornDownByConnMgr(true);
-                return true;
-            } else {
-                return false;
-            }
-        } else {
-            return true;
-        }
-    }
-
-    /**
-     * Reenable Wi-Fi connectivity by restarting the driver.
-     */
-    public boolean reconnect() {
-        if (mTornDownByConnMgr) {
-            if (restart()) {
-                setTornDownByConnMgr(false);
-                return true;
-            } else {
-                return false;
-            }
-        } else {
-            return true;
-        }
-    }
-
-    /**
-     * We want to stop the driver, but if we're connected to a network,
-     * we first want to disconnect, so that the supplicant is always in
-     * a known state (DISCONNECTED) when the driver is stopped.
-     * @return {@code true} if the operation succeeds, which means that the
-     * disconnect or stop command was initiated.
-     */
-    public synchronized boolean disconnectAndStop() {
-        boolean ret = true;;
-        if (mRunState != RUN_STATE_STOPPING && mRunState != RUN_STATE_STOPPED) {
-            // Take down any open network notifications
-            setNotificationVisible(false, 0, false, 0);
-
-            if (mWifiInfo.getSupplicantState() == SupplicantState.DORMANT) {
-                ret = stopDriver();
-            } else {
-                ret = disconnect();
-            }
-            mRunState = RUN_STATE_STOPPING;
-        }
-        return ret;
-    }
-
-    public synchronized boolean restart() {
-        if (mRunState == RUN_STATE_STOPPED) {
-            mRunState = RUN_STATE_STARTING;
-            resetConnections(true);
-            return startDriver();
-        } else if (mRunState == RUN_STATE_STOPPING) {
-            mRunState = RUN_STATE_STARTING;
-        }
-        return true;
-    }
-
-    public int getWifiState() {
-        return mWifiState.get();
-    }
-
-    public void setWifiState(int wifiState) {
-        mWifiState.set(wifiState);
-    }
-
-   /**
-     * The WifiNative interface functions are listed below.
-     * The only native call that is not synchronized on
-     * WifiStateTracker is waitForEvent() which waits on a
-     * seperate monitor channel.
-     *
-     * All supplicant commands need the wifi to be in an
-     * enabled state. This can be done by checking the
-     * mWifiState to be WIFI_STATE_ENABLED.
-     *
-     * All commands that can cause commands to driver
-     * initiated need the driver state to be started.
-     * This is done by checking isDriverStopped() to
-     * be false.
-     */
-
-    /**
-     * Load the driver and firmware
-     *
-     * @return {@code true} if the operation succeeds, {@code false} otherwise
-     */
-    public synchronized boolean loadDriver() {
-        return WifiNative.loadDriver();
-    }
-
-    /**
-     * Unload the driver and firmware
-     *
-     * @return {@code true} if the operation succeeds, {@code false} otherwise
-     */
-    public synchronized boolean unloadDriver() {
-        return WifiNative.unloadDriver();
-    }
-
-    /**
-     * Check the supplicant config and
-     * start the supplicant daemon
-     *
-     * @return {@code true} if the operation succeeds, {@code false} otherwise
-     */
-    public synchronized boolean startSupplicant() {
-        return WifiNative.startSupplicant();
-    }
-
-    /**
-     * Stop the supplicant daemon
-     *
-     * @return {@code true} if the operation succeeds, {@code false} otherwise
-     */
-    public synchronized boolean stopSupplicant() {
-        return WifiNative.stopSupplicant();
-    }
-
-    /**
-     * Establishes two channels - control channel for commands
-     * and monitor channel for notifying WifiMonitor
-     *
-     * @return {@code true} if the operation succeeds, {@code false} otherwise
-     */
-    public synchronized boolean connectToSupplicant() {
-        return WifiNative.connectToSupplicant();
-    }
-
-    /**
-     * Close the control/monitor channels to supplicant
-     */
-    public synchronized void closeSupplicantConnection() {
-        WifiNative.closeSupplicantConnection();
-    }
-
-    /**
-     * Check if the supplicant is alive
-     *
-     * @return {@code true} if the operation succeeds, {@code false} otherwise
-     */
-    public synchronized boolean ping() {
-        if (mWifiState.get() != WIFI_STATE_ENABLED) {
-            return false;
-        }
-        return WifiNative.pingCommand();
-    }
-
-    /**
-     * initiate an active or passive scan
-     *
-     * @param forceActive true if it is a active scan
-     * @return {@code true} if the operation succeeds, {@code false} otherwise
-     */
-    public synchronized boolean scan(boolean forceActive) {
-        if (mWifiState.get() != WIFI_STATE_ENABLED || isDriverStopped()) {
-            return false;
-        }
-        return WifiNative.scanCommand(forceActive);
-    }
-
-    /**
-     * Specifies whether the supplicant or driver
-     * take care of initiating scan and doing AP selection
-     *
-     * @param mode
-     *    SUPPL_SCAN_HANDLING_NORMAL
-     *    SUPPL_SCAN_HANDLING_LIST_ONLY
-     * @return {@code true} if the operation succeeds, {@code false} otherwise
-     */
-    public synchronized boolean setScanResultHandling(int mode) {
-        if (mWifiState.get() != WIFI_STATE_ENABLED) {
-            return false;
-        }
-        return WifiNative.setScanResultHandlingCommand(mode);
-    }
-
-    /**
-     * Fetch the scan results from the supplicant
-     *
-     * @return example result string
-     * 00:bb:cc:dd:cc:ee       2427    166     [WPA-EAP-TKIP][WPA2-EAP-CCMP]   Net1
-     * 00:bb:cc:dd:cc:ff       2412    165     [WPA-EAP-TKIP][WPA2-EAP-CCMP]   Net2
-     */
-    public synchronized String scanResults() {
-        if (mWifiState.get() != WIFI_STATE_ENABLED || isDriverStopped()) {
-            return null;
-        }
-        return WifiNative.scanResultsCommand();
-    }
-
-    /**
-     * Set the scan mode - active or passive
-     *
-     * @return {@code true} if the operation succeeds, {@code false} otherwise
-     */
-    public synchronized boolean setScanMode(boolean isScanModeActive) {
-        if (mWifiState.get() != WIFI_STATE_ENABLED || isDriverStopped()) {
-            return false;
-        }
-        if (mIsScanModeActive != isScanModeActive) {
-            return WifiNative.setScanModeCommand(mIsScanModeActive = isScanModeActive);
-        }
-        return true;
-    }
-
     /**
      * Disconnect from Access Point
-     *
-     * @return {@code true} if the operation succeeds, {@code false} otherwise
      */
-    public synchronized boolean disconnect() {
-        if (mWifiState.get() != WIFI_STATE_ENABLED || isDriverStopped()) {
-            return false;
-        }
-        return WifiNative.disconnectCommand();
+    public boolean disconnectCommand() {
+        return sendSyncMessage(CMD_DISCONNECT).boolValue;
     }
 
     /**
      * Initiate a reconnection to AP
-     *
-     * @return {@code true} if the operation succeeds, {@code false} otherwise
      */
-    public synchronized boolean reconnectCommand() {
-        if (mWifiState.get() != WIFI_STATE_ENABLED || isDriverStopped()) {
-            return false;
-        }
-        return WifiNative.reconnectCommand();
+    public boolean reconnectCommand() {
+        return sendSyncMessage(CMD_RECONNECT).boolValue;
     }
 
     /**
-     * Add a network
+     * Initiate a re-association to AP
+     */
+    public boolean reassociateCommand() {
+        return sendSyncMessage(CMD_REASSOCIATE).boolValue;
+    }
+
+    /**
+     * Add a network synchronously
      *
      * @return network id of the new network
      */
-    public synchronized int addNetwork() {
-        if (mWifiState.get() != WIFI_STATE_ENABLED) {
-            return -1;
-        }
-        return WifiNative.addNetworkCommand();
+    public int addOrUpdateNetwork(WifiConfiguration config) {
+        return sendSyncMessage(CMD_ADD_OR_UPDATE_NETWORK, config).intValue;
+    }
+
+    public List<WifiConfiguration> getConfiguredNetworks() {
+        return sendSyncMessage(CMD_GET_NETWORK_CONFIG).configList;
     }
 
     /**
      * Delete a network
      *
      * @param networkId id of the network to be removed
-     * @return {@code true} if the operation succeeds, {@code false} otherwise
      */
-    public synchronized boolean removeNetwork(int networkId) {
-        if (mWifiState.get() != WIFI_STATE_ENABLED) {
-            return false;
-        }
-        return mDisconnectExpected = WifiNative.removeNetworkCommand(networkId);
+    public boolean removeNetwork(int networkId) {
+        return sendSyncMessage(obtainMessage(CMD_REMOVE_NETWORK, networkId, 0)).boolValue;
     }
 
+    private class EnableNetParams {
+        private int netId;
+        private boolean disableOthers;
+        EnableNetParams(int n, boolean b) {
+            netId = n;
+            disableOthers = b;
+        }
+    }
     /**
      * Enable a network
      *
@@ -1705,11 +923,9 @@
      * @param disableOthers true, if all other networks have to be disabled
      * @return {@code true} if the operation succeeds, {@code false} otherwise
      */
-    public synchronized boolean enableNetwork(int netId, boolean disableOthers) {
-        if (mWifiState.get() != WIFI_STATE_ENABLED) {
-            return false;
-        }
-        return WifiNative.enableNetworkCommand(netId, disableOthers);
+    public boolean enableNetwork(int netId, boolean disableOthers) {
+        return sendSyncMessage(CMD_ENABLE_NETWORK,
+                new EnableNetParams(netId, disableOthers)).boolValue;
     }
 
     /**
@@ -1718,23 +934,8 @@
      * @param netId network id of the network
      * @return {@code true} if the operation succeeds, {@code false} otherwise
      */
-    public synchronized boolean disableNetwork(int netId) {
-        if (mWifiState.get() != WIFI_STATE_ENABLED) {
-            return false;
-        }
-        return WifiNative.disableNetworkCommand(netId);
-    }
-
-    /**
-     * Initiate a re-association in supplicant
-     *
-     * @return {@code true} if the operation succeeds, {@code false} otherwise
-     */
-    public synchronized boolean reassociate() {
-        if (mWifiState.get() != WIFI_STATE_ENABLED || isDriverStopped()) {
-            return false;
-        }
-        return WifiNative.reassociateCommand();
+    public boolean disableNetwork(int netId) {
+        return sendSyncMessage(obtainMessage(CMD_DISABLE_NETWORK, netId, 0)).boolValue;
     }
 
     /**
@@ -1742,66 +943,17 @@
      * alternate APs to connect
      *
      * @param bssid BSSID of the network
-     * @return {@code true} if the operation succeeds, {@code false} otherwise
      */
-    public synchronized boolean addToBlacklist(String bssid) {
-        if (mWifiState.get() != WIFI_STATE_ENABLED) {
-            return false;
-        }
-        return WifiNative.addToBlacklistCommand(bssid);
+    public void addToBlacklist(String bssid) {
+        sendMessage(obtainMessage(CMD_BLACKLIST_NETWORK, bssid));
     }
 
     /**
      * Clear the blacklist list
      *
-     * @return {@code true} if the operation succeeds, {@code false} otherwise
      */
-    public synchronized boolean clearBlacklist() {
-        if (mWifiState.get() != WIFI_STATE_ENABLED) {
-            return false;
-        }
-        return WifiNative.clearBlacklistCommand();
-    }
-
-    /**
-     * List all configured networks
-     *
-     * @return list of networks or null on failure
-     */
-    public synchronized String listNetworks() {
-        if (mWifiState.get() != WIFI_STATE_ENABLED) {
-            return null;
-        }
-        return WifiNative.listNetworksCommand();
-    }
-
-    /**
-     * Get network setting by name
-     *
-     * @param netId network id of the network
-     * @param name network variable key
-     * @return value corresponding to key
-     */
-    public synchronized String getNetworkVariable(int netId, String name) {
-        if (mWifiState.get() != WIFI_STATE_ENABLED) {
-            return null;
-        }
-        return WifiNative.getNetworkVariableCommand(netId, name);
-    }
-
-    /**
-     * Set network setting by name
-     *
-     * @param netId network id of the network
-     * @param name network variable key
-     * @param value network variable value
-     * @return {@code true} if the operation succeeds, {@code false} otherwise
-     */
-    public synchronized boolean setNetworkVariable(int netId, String name, String value) {
-        if (mWifiState.get() != WIFI_STATE_ENABLED) {
-            return false;
-        }
-        return WifiNative.setNetworkVariableCommand(netId, name, value);
+    public void clearBlacklist() {
+        sendMessage(obtainMessage(CMD_CLEAR_BLACKLIST));
     }
 
     /**
@@ -1817,23 +969,20 @@
      *  wpa_state=COMPLETED
      *  ip_address=X.X.X.X
      */
-    public synchronized String status() {
-        if (mWifiState.get() != WIFI_STATE_ENABLED) {
-            return null;
-        }
-        return WifiNative.statusCommand();
+    public String status() {
+        return sendSyncMessage(CMD_CONNECTION_STATUS).stringValue;
     }
 
+    public void enableRssiPolling(boolean enabled) {
+       sendMessage(obtainMessage(CMD_ENABLE_RSSI_POLL, enabled ? 1 : 0, 0));
+    }
     /**
      * Get RSSI to currently connected network
      *
      * @return RSSI value, -1 on failure
      */
-    public synchronized int getRssi() {
-        if (mWifiState.get() != WIFI_STATE_ENABLED || isDriverStopped()) {
-            return -1;
-        }
-        return WifiNative.getRssiApproxCommand();
+    public int getRssi() {
+        return sendSyncMessage(CMD_GET_RSSI).intValue;
     }
 
     /**
@@ -1841,11 +990,8 @@
      *
      * @return RSSI value, -1 on failure
      */
-    public synchronized int getRssiApprox() {
-        if (mWifiState.get() != WIFI_STATE_ENABLED || isDriverStopped()) {
-            return -1;
-        }
-        return WifiNative.getRssiApproxCommand();
+    public int getRssiApprox() {
+        return sendSyncMessage(CMD_GET_RSSI_APPROX).intValue;
     }
 
     /**
@@ -1853,11 +999,8 @@
      *
      * @return link speed, -1 on failure
      */
-    public synchronized int getLinkSpeed() {
-        if (mWifiState.get() != WIFI_STATE_ENABLED || isDriverStopped()) {
-            return -1;
-        }
-        return WifiNative.getLinkSpeedCommand();
+    public int getLinkSpeed() {
+        return sendSyncMessage(CMD_GET_LINK_SPEED).intValue;
     }
 
     /**
@@ -1865,72 +1008,22 @@
      *
      * @return MAC address, null on failure
      */
-    public synchronized String getMacAddress() {
-        if (mWifiState.get() != WIFI_STATE_ENABLED || isDriverStopped()) {
-            return null;
-        }
-        return WifiNative.getMacAddressCommand();
-    }
-
-    /**
-     * Start driver
-     *
-     * @return {@code true} if the operation succeeds, {@code false} otherwise
-     */
-    public synchronized boolean startDriver() {
-        if (mWifiState.get() != WIFI_STATE_ENABLED) {
-            return false;
-        }
-        return WifiNative.startDriverCommand();
-    }
-
-    /**
-     * Stop driver
-     *
-     * @return {@code true} if the operation succeeds, {@code false} otherwise
-     */
-    public synchronized boolean stopDriver() {
-        /* Driver stop should not happen only when supplicant event
-         * DRIVER_STOPPED has already been handled */
-        if (mWifiState.get() != WIFI_STATE_ENABLED || mRunState == RUN_STATE_STOPPED) {
-            return false;
-        }
-        return WifiNative.stopDriverCommand();
+    public String getMacAddress() {
+        return sendSyncMessage(CMD_GET_MAC_ADDR).stringValue;
     }
 
     /**
      * Start packet filtering
-     *
-     * @return {@code true} if the operation succeeds, {@code false} otherwise
      */
-    public synchronized boolean startPacketFiltering() {
-        if (mWifiState.get() != WIFI_STATE_ENABLED || isDriverStopped()) {
-            return false;
-        }
-        return WifiNative.startPacketFiltering();
+    public void startPacketFiltering() {
+        sendMessage(CMD_START_PACKET_FILTERING);
     }
 
     /**
      * Stop packet filtering
-     *
-     * @return {@code true} if the operation succeeds, {@code false} otherwise
      */
-    public synchronized boolean stopPacketFiltering() {
-        if (mWifiState.get() != WIFI_STATE_ENABLED || isDriverStopped()) {
-            return false;
-        }
-        return WifiNative.stopPacketFiltering();
-    }
-
-    /**
-     * Get power mode
-     * @return power mode
-     */
-    public synchronized int getPowerMode() {
-        if (mWifiState.get() != WIFI_STATE_ENABLED && !isDriverStopped()) {
-            return -1;
-        }
-        return WifiNative.getPowerModeCommand();
+    public void stopPacketFiltering() {
+        sendMessage(CMD_STOP_PACKET_FILTERING);
     }
 
     /**
@@ -1938,36 +1031,26 @@
      * @param mode
      *     DRIVER_POWER_MODE_AUTO
      *     DRIVER_POWER_MODE_ACTIVE
-     * @return {@code true} if the operation succeeds, {@code false} otherwise
      */
-    public synchronized boolean setPowerMode(int mode) {
-        if (mWifiState.get() != WIFI_STATE_ENABLED || isDriverStopped()) {
-            return false;
-        }
-        return WifiNative.setPowerModeCommand(mode);
+    public void setPowerMode(int mode) {
+        sendMessage(obtainMessage(CMD_SET_POWER_MODE, mode, 0));
     }
 
     /**
      * Set the number of allowed radio frequency channels from the system
      * setting value, if any.
-     * @return {@code true} if the operation succeeds, {@code false} otherwise, e.g.,
-     * the number of channels is invalid.
      */
-    public synchronized boolean setNumAllowedChannels() {
-        if (mWifiState.get() != WIFI_STATE_ENABLED || isDriverStopped()) {
-            return false;
-        }
+    public void setNumAllowedChannels() {
         try {
-            return setNumAllowedChannels(
+            setNumAllowedChannels(
                     Settings.Secure.getInt(mContext.getContentResolver(),
                     Settings.Secure.WIFI_NUM_ALLOWED_CHANNELS));
         } catch (Settings.SettingNotFoundException e) {
             if (mNumAllowedChannels != 0) {
-                WifiNative.setNumAllowedChannelsCommand(mNumAllowedChannels);
+                setNumAllowedChannels(mNumAllowedChannels);
             }
             // otherwise, use the driver default
         }
-        return true;
     }
 
     /**
@@ -1975,27 +1058,21 @@
      * in the current regulatory domain.
      * @param numChannels the number of allowed channels. Must be greater than 0
      * and less than or equal to 16.
-     * @return {@code true} if the operation succeeds, {@code false} otherwise, e.g.,
-     * {@code numChannels} is outside the valid range.
      */
-    public synchronized boolean setNumAllowedChannels(int numChannels) {
-        if (mWifiState.get() != WIFI_STATE_ENABLED || isDriverStopped()) {
-            return false;
-        }
-        mNumAllowedChannels = numChannels;
-        return WifiNative.setNumAllowedChannelsCommand(numChannels);
+    public void setNumAllowedChannels(int numChannels) {
+        sendMessage(obtainMessage(CMD_SET_NUM_ALLOWED_CHANNELS, numChannels, 0));
     }
 
     /**
      * Get number of allowed channels
      *
      * @return channel count, -1 on failure
+     *
+     * TODO: this is not a public API and needs to be removed in favor
+     * of asynchronous reporting. unused for now.
      */
-    public synchronized int getNumAllowedChannels() {
-        if (mWifiState.get() != WIFI_STATE_ENABLED || isDriverStopped()) {
-            return -1;
-        }
-        return WifiNative.getNumAllowedChannelsCommand();
+    public int getNumAllowedChannels() {
+        return -1;
     }
 
     /**
@@ -2005,13 +1082,9 @@
      *  BLUETOOTH_COEXISTENCE_MODE_ENABLED
      *  BLUETOOTH_COEXISTENCE_MODE_DISABLED
      *  BLUETOOTH_COEXISTENCE_MODE_SENSE
-     * @return {@code true} if the operation succeeds, {@code false} otherwise
      */
-    public synchronized boolean setBluetoothCoexistenceMode(int mode) {
-        if (mWifiState.get() != WIFI_STATE_ENABLED || isDriverStopped()) {
-            return false;
-        }
-        return WifiNative.setBluetoothCoexistenceModeCommand(mode);
+    public void setBluetoothCoexistenceMode(int mode) {
+        sendMessage(obtainMessage(CMD_SET_BLUETOOTH_COEXISTENCE, mode, 0));
     }
 
     /**
@@ -2021,76 +1094,289 @@
      *
      * @param isBluetoothPlaying whether to enable or disable this mode
      */
-    public synchronized void setBluetoothScanMode(boolean isBluetoothPlaying) {
-        if (mWifiState.get() != WIFI_STATE_ENABLED || isDriverStopped()) {
-            return;
-        }
-        WifiNative.setBluetoothCoexistenceScanModeCommand(isBluetoothPlaying);
+    public void setBluetoothScanMode(boolean isBluetoothPlaying) {
+        sendMessage(obtainMessage(CMD_SET_BLUETOOTH_SCAN_MODE, isBluetoothPlaying ? 1 : 0, 0));
     }
 
     /**
      * Save configuration on supplicant
      *
      * @return {@code true} if the operation succeeds, {@code false} otherwise
-     */
-    public synchronized boolean saveConfig() {
-        if (mWifiState.get() != WIFI_STATE_ENABLED) {
-            return false;
-        }
-        return WifiNative.saveConfigCommand();
-    }
-
-    /**
-     * Reload the configuration from file
      *
-     * @return {@code true} if the operation succeeds, {@code false} otherwise
+     * TODO: deprecate this
      */
-    public synchronized boolean reloadConfig() {
-        if (mWifiState.get() != WIFI_STATE_ENABLED) {
-            return false;
+    public boolean saveConfig() {
+        return sendSyncMessage(CMD_SAVE_CONFIG).boolValue;
+    }
+
+    /**
+     * TODO: doc
+     */
+    public void requestCmWakeLock() {
+        sendMessage(CMD_REQUEST_CM_WAKELOCK);
+    }
+
+    /*********************************************************
+     * Internal private functions
+     ********************************************************/
+
+    class SyncReturn {
+        boolean boolValue;
+        int intValue;
+        String stringValue;
+        Object objValue;
+        List<WifiConfiguration> configList;
+    }
+
+    class SyncParams {
+        Object mParameter;
+        SyncReturn mSyncReturn;
+        SyncParams() {
+            mSyncReturn = new SyncReturn();
         }
-        return WifiNative.reloadConfigCommand();
-    }
-
-    public boolean setRadio(boolean turnOn) {
-        return mWM.setWifiEnabled(turnOn);
+        SyncParams(Object p) {
+            mParameter = p;
+            mSyncReturn = new SyncReturn();
+        }
     }
 
     /**
-     * {@inheritDoc}
-     * There are currently no Wi-Fi-specific features supported.
-     * @param feature the name of the feature
-     * @return {@code -1} indicating failure, always
+     * message.arg2 is reserved to indicate synchronized
+     * message.obj is used to store SyncParams
      */
-    public int startUsingNetworkFeature(String feature, int callingPid, int callingUid) {
-        return -1;
+    private SyncReturn syncedSend(Message msg) {
+        SyncParams syncParams = (SyncParams) msg.obj;
+        msg.arg2 = SYNCHRONOUS_CALL;
+        synchronized(syncParams) {
+            if (DBG) Log.d(TAG, "syncedSend " + msg);
+            sendMessage(msg);
+            try {
+                syncParams.wait();
+            } catch (InterruptedException e) {
+                Log.e(TAG, "sendSyncMessage: unexpected interruption of wait()");
+                return null;
+            }
+        }
+        return syncParams.mSyncReturn;
+    }
+
+    private SyncReturn sendSyncMessage(Message msg) {
+        SyncParams syncParams = new SyncParams();
+        msg.obj = syncParams;
+        return syncedSend(msg);
+    }
+
+    private SyncReturn sendSyncMessage(int what, Object param) {
+        SyncParams syncParams = new SyncParams(param);
+        Message msg = obtainMessage(what, syncParams);
+        return syncedSend(msg);
+    }
+
+
+    private SyncReturn sendSyncMessage(int what) {
+        return sendSyncMessage(obtainMessage(what));
+    }
+
+    private void notifyOnMsgObject(Message msg) {
+        SyncParams syncParams = (SyncParams) msg.obj;
+        if (syncParams != null) {
+            synchronized(syncParams) {
+                if (DBG) Log.d(TAG, "notifyOnMsgObject " + msg);
+                syncParams.notify();
+            }
+        }
+        else {
+            Log.e(TAG, "Error! syncParams in notifyOnMsgObject is null");
+        }
+    }
+
+    private void setWifiState(int wifiState) {
+        final int previousWifiState = mWifiState.get();
+
+        try {
+            if (wifiState == WIFI_STATE_ENABLED) {
+                mBatteryStats.noteWifiOn(mLastEnableUid.get());
+            } else if (wifiState == WIFI_STATE_DISABLED) {
+                mBatteryStats.noteWifiOff(mLastEnableUid.get());
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to note battery stats in wifi");
+        }
+
+        mWifiState.set(wifiState);
+
+        if (DBG) Log.d(TAG, "setWifiState: " + getWifiStateByName());
+
+        final Intent intent = new Intent(WifiManager.WIFI_STATE_CHANGED_ACTION);
+        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+        intent.putExtra(WifiManager.EXTRA_WIFI_STATE, wifiState);
+        intent.putExtra(WifiManager.EXTRA_PREVIOUS_WIFI_STATE, previousWifiState);
+        mContext.sendStickyBroadcast(intent);
+    }
+
+    private void setWifiApState(int wifiApState) {
+        final int previousWifiApState = mWifiApState.get();
+
+        try {
+            if (wifiApState == WIFI_AP_STATE_ENABLED) {
+                mBatteryStats.noteWifiOn(mLastApEnableUid.get());
+            } else if (wifiApState == WIFI_AP_STATE_DISABLED) {
+                mBatteryStats.noteWifiOff(mLastApEnableUid.get());
+            }
+        } catch (RemoteException e) {
+            Log.d(TAG, "Failed to note battery stats in wifi");
+        }
+
+        // Update state
+        mWifiApState.set(wifiApState);
+
+        if (DBG) Log.d(TAG, "setWifiApState: " + getWifiApStateByName());
+
+        // Broadcast
+        final Intent intent = new Intent(WifiManager.WIFI_AP_STATE_CHANGED_ACTION);
+        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+        intent.putExtra(WifiManager.EXTRA_WIFI_AP_STATE, wifiApState);
+        intent.putExtra(WifiManager.EXTRA_PREVIOUS_WIFI_AP_STATE, previousWifiApState);
+        mContext.sendStickyBroadcast(intent);
     }
 
     /**
-     * {@inheritDoc}
-     * There are currently no Wi-Fi-specific features supported.
-     * @param feature the name of the feature
-     * @return {@code -1} indicating failure, always
+     * Parse the scan result line passed to us by wpa_supplicant (helper).
+     * @param line the line to parse
+     * @return the {@link ScanResult} object
      */
-    public int stopUsingNetworkFeature(String feature, int callingPid, int callingUid) {
-        return -1;
+    private ScanResult parseScanResult(String line) {
+        ScanResult scanResult = null;
+        if (line != null) {
+            /*
+             * Cache implementation (LinkedHashMap) is not synchronized, thus,
+             * must synchronized here!
+             */
+            synchronized (mScanResultCache) {
+                String[] result = scanResultPattern.split(line);
+                if (3 <= result.length && result.length <= 5) {
+                    String bssid = result[0];
+                    // bssid | frequency | level | flags | ssid
+                    int frequency;
+                    int level;
+                    try {
+                        frequency = Integer.parseInt(result[1]);
+                        level = Integer.parseInt(result[2]);
+                        /* some implementations avoid negative values by adding 256
+                         * so we need to adjust for that here.
+                         */
+                        if (level > 0) level -= 256;
+                    } catch (NumberFormatException e) {
+                        frequency = 0;
+                        level = 0;
+                    }
+
+                    /*
+                     * The formatting of the results returned by
+                     * wpa_supplicant is intended to make the fields
+                     * line up nicely when printed,
+                     * not to make them easy to parse. So we have to
+                     * apply some heuristics to figure out which field
+                     * is the SSID and which field is the flags.
+                     */
+                    String ssid;
+                    String flags;
+                    if (result.length == 4) {
+                        if (result[3].charAt(0) == '[') {
+                            flags = result[3];
+                            ssid = "";
+                        } else {
+                            flags = "";
+                            ssid = result[3];
+                        }
+                    } else if (result.length == 5) {
+                        flags = result[3];
+                        ssid = result[4];
+                    } else {
+                        // Here, we must have 3 fields: no flags and ssid
+                        // set
+                        flags = "";
+                        ssid = "";
+                    }
+
+                    // bssid + ssid is the hash key
+                    String key = bssid + ssid;
+                    scanResult = mScanResultCache.get(key);
+                    if (scanResult != null) {
+                        scanResult.level = level;
+                        scanResult.SSID = ssid;
+                        scanResult.capabilities = flags;
+                        scanResult.frequency = frequency;
+                    } else {
+                        // Do not add scan results that have no SSID set
+                        if (0 < ssid.trim().length()) {
+                            scanResult =
+                                new ScanResult(
+                                    ssid, bssid, flags, level, frequency);
+                            mScanResultCache.put(key, scanResult);
+                        }
+                    }
+                } else {
+                    Log.w(TAG, "Misformatted scan result text with " +
+                          result.length + " fields: " + line);
+                }
+            }
+        }
+
+        return scanResult;
     }
 
-    @Override
-    public void interpretScanResultsAvailable() {
+    /**
+     * scanResults input format
+     * 00:bb:cc:dd:cc:ee       2427    166     [WPA-EAP-TKIP][WPA2-EAP-CCMP]   Net1
+     * 00:bb:cc:dd:cc:ff       2412    165     [WPA-EAP-TKIP][WPA2-EAP-CCMP]   Net2
+     */
+    private void setScanResults(String scanResults) {
+        if (scanResults == null) {
+            return;
+        }
 
+        List<ScanResult> scanList = new ArrayList<ScanResult>();
+
+        int lineCount = 0;
+
+        int scanResultsLen = scanResults.length();
+        // Parse the result string, keeping in mind that the last line does
+        // not end with a newline.
+        for (int lineBeg = 0, lineEnd = 0; lineEnd <= scanResultsLen; ++lineEnd) {
+            if (lineEnd == scanResultsLen || scanResults.charAt(lineEnd) == '\n') {
+                ++lineCount;
+
+                if (lineCount == 1) {
+                    lineBeg = lineEnd + 1;
+                    continue;
+                }
+                if (lineEnd > lineBeg) {
+                    String line = scanResults.substring(lineBeg, lineEnd);
+                    ScanResult scanResult = parseScanResult(line);
+                    if (scanResult != null) {
+                        scanList.add(scanResult);
+                    } else {
+                        Log.w(TAG, "misformatted scan result for: " + line);
+                    }
+                }
+                lineBeg = lineEnd + 1;
+            }
+        }
+
+        mScanResults = scanList;
+    }
+
+    private void checkAndSetNotification() {
         // If we shouldn't place a notification on available networks, then
         // don't bother doing any of the following
         if (!mNotificationEnabled) return;
 
-        NetworkInfo networkInfo = getNetworkInfo();
-
-        State state = networkInfo.getState();
+        State state = mNetworkInfo.getState();
         if ((state == NetworkInfo.State.DISCONNECTED)
                 || (state == NetworkInfo.State.UNKNOWN)) {
-
             // Look for an open network
-            List<ScanResult> scanResults = getScanResultsList();
+            List<ScanResult> scanResults = mScanResults;
             if (scanResults != null) {
                 int numOpenNetworks = 0;
                 for (int i = scanResults.size() - 1; i >= 0; i--) {
@@ -2100,7 +1386,7 @@
                         numOpenNetworks++;
                     }
                 }
-            
+
                 if (numOpenNetworks > 0) {
                     if (++mNumScansSinceNetworkStateChange >= NUM_SCANS_BEFORE_ACTUALLY_SCANNING) {
                         /*
@@ -2116,7 +1402,7 @@
                 }
             }
         }
-        
+
         // No open networks in range, remove the notification
         setNotificationVisible(false, 0, false, 0);
     }
@@ -2130,14 +1416,15 @@
      * @param delay time in milliseconds after which the notification should be made
      * visible or invisible.
      */
-    public void setNotificationVisible(boolean visible, int numNetworks, boolean force, int delay) {
-        
+    private void setNotificationVisible(boolean visible, int numNetworks, boolean force,
+            int delay) {
+
         // Since we use auto cancel on the notification, when the
         // mNetworksAvailableNotificationShown is true, the notification may
         // have actually been canceled.  However, when it is false we know
         // for sure that it is not being shown (it will not be shown any other
         // place than here)
-        
+
         // If it should be hidden and it is already hidden, then noop
         if (!visible && !mNotificationShown && !force) {
             return;
@@ -2145,12 +1432,12 @@
 
         Message message;
         if (visible) {
-            
+
             // Not enough time has passed to show the notification again
             if (System.currentTimeMillis() < mNotificationRepeatTime) {
                 return;
             }
-            
+
             if (mNotification == null) {
                 // Cache the Notification mainly so we can remove the
                 // EVENT_NOTIFICATION_CHANGED message with this Notification from
@@ -2169,183 +1456,69 @@
                     com.android.internal.R.plurals.wifi_available_detailed, numNetworks);
             mNotification.tickerText = title;
             mNotification.setLatestEventInfo(mContext, title, details, mNotification.contentIntent);
-            
+
             mNotificationRepeatTime = System.currentTimeMillis() + NOTIFICATION_REPEAT_DELAY_MS;
 
-            message = mTarget.obtainMessage(EVENT_NOTIFICATION_CHANGED, 1,
+            message = mCsHandler.obtainMessage(EVENT_NOTIFICATION_CHANGED, 1,
                     ICON_NETWORKS_AVAILABLE, mNotification);
-            
+
         } else {
 
             // Remove any pending messages to show the notification
-            mTarget.removeMessages(EVENT_NOTIFICATION_CHANGED, mNotification);
-            
-            message = mTarget.obtainMessage(EVENT_NOTIFICATION_CHANGED, 0, ICON_NETWORKS_AVAILABLE);
+            mCsHandler.removeMessages(EVENT_NOTIFICATION_CHANGED, mNotification);
+
+            message = mCsHandler.obtainMessage(EVENT_NOTIFICATION_CHANGED, 0,
+                    ICON_NETWORKS_AVAILABLE);
         }
 
-        mTarget.sendMessageDelayed(message, delay);
-        
+        mCsHandler.sendMessageDelayed(message, delay);
+
         mNotificationShown = visible;
     }
 
-    /**
-     * Clears variables related to tracking whether a notification has been
-     * shown recently.
-     * <p>
-     * After calling this method, the timer that prevents notifications from
-     * being shown too often will be cleared.
-     */
-    private void resetNotificationTimer() {
-        mNotificationRepeatTime = 0;
-        mNumScansSinceNetworkStateChange = 0;
-    }
-    
-    @Override
-    public String toString() {
-        StringBuffer sb = new StringBuffer();
-        sb.append("interface ").append(mInterfaceName);
-        sb.append(" runState=");
-        if (mRunState >= 1 && mRunState <= mRunStateNames.length) {
-            sb.append(mRunStateNames[mRunState-1]);
-        } else {
-            sb.append(mRunState);
+    private void configureNetworkProperties() {
+        try {
+            mNetworkProperties.setInterface(NetworkInterface.getByName(mInterfaceName));
+        } catch (SocketException e) {
+            Log.e(TAG, "SocketException creating NetworkInterface from " + mInterfaceName +
+                    ". e=" + e);
+            return;
+        } catch (NullPointerException e) {
+            Log.e(TAG, "NPE creating NetworkInterface. e=" + e);
+            return;
         }
-        sb.append(LS).append(mWifiInfo).append(LS);
-        sb.append(mDhcpInfo).append(LS);
-        sb.append("haveIpAddress=").append(mHaveIpAddress).
-                append(", obtainingIpAddress=").append(mObtainingIpAddress).
-                append(", scanModeActive=").append(mIsScanModeActive).append(LS).
-                append("lastSignalLevel=").append(mLastSignalLevel).
-                append(", explicitlyDisabled=").append(mTornDownByConnMgr);
-        return sb.toString();
+        // TODO - fix this for v6
+        try {
+            mNetworkProperties.addAddress(InetAddress.getByAddress(
+                    NetworkUtils.v4IntToArray(mDhcpInfo.ipAddress)));
+        } catch (UnknownHostException e) {
+            Log.e(TAG, "Exception setting IpAddress using " + mDhcpInfo + ", e=" + e);
+        }
+
+        try {
+            mNetworkProperties.setGateway(InetAddress.getByAddress(NetworkUtils.v4IntToArray(
+                    mDhcpInfo.gateway)));
+        } catch (UnknownHostException e) {
+            Log.e(TAG, "Exception setting Gateway using " + mDhcpInfo + ", e=" + e);
+        }
+
+        try {
+            mNetworkProperties.addDns(InetAddress.getByAddress(
+                    NetworkUtils.v4IntToArray(mDhcpInfo.dns1)));
+        } catch (UnknownHostException e) {
+            Log.e(TAG, "Exception setting Dns1 using " + mDhcpInfo + ", e=" + e);
+        }
+        try {
+            mNetworkProperties.addDns(InetAddress.getByAddress(
+                    NetworkUtils.v4IntToArray(mDhcpInfo.dns2)));
+
+        } catch (UnknownHostException e) {
+            Log.e(TAG, "Exception setting Dns2 using " + mDhcpInfo + ", e=" + e);
+        }
+        // TODO - add proxy info
     }
 
-    private class DhcpHandler extends Handler {
 
-        private Handler mTarget;
-        
-        /**
-         * Whether to skip the DHCP result callback to the target. For example,
-         * this could be set if the network we were requesting an IP for has
-         * since been disconnected.
-         * <p>
-         * Note: There is still a chance where the client's intended DHCP
-         * request not being canceled. For example, we are request for IP on
-         * A, and he queues request for IP on B, and then cancels the request on
-         * B while we're still requesting from A.
-         */
-        private boolean mCancelCallback;
-
-        /**
-         * Instance of the bluetooth headset helper. This needs to be created
-         * early because there is a delay before it actually 'connects', as
-         * noted by its javadoc. If we check before it is connected, it will be
-         * in an error state and we will not disable coexistence.
-         */
-        private BluetoothHeadset mBluetoothHeadset;
-        
-        public DhcpHandler(Looper looper, Handler target) {
-            super(looper);
-            mTarget = target;
-            
-            mBluetoothHeadset = new BluetoothHeadset(mContext, null);
-        }
-
-        public void handleMessage(Message msg) {
-            int event;
-
-            switch (msg.what) {
-                case EVENT_DHCP_START:
-                    
-                    boolean modifiedBluetoothCoexistenceMode = false;
-                    int powerMode = DRIVER_POWER_MODE_AUTO;
-
-                    if (shouldDisableCoexistenceMode()) {
-                        /*
-                         * There are problems setting the Wi-Fi driver's power
-                         * mode to active when bluetooth coexistence mode is
-                         * enabled or sense.
-                         * <p>
-                         * We set Wi-Fi to active mode when
-                         * obtaining an IP address because we've found
-                         * compatibility issues with some routers with low power
-                         * mode.
-                         * <p>
-                         * In order for this active power mode to properly be set,
-                         * we disable coexistence mode until we're done with
-                         * obtaining an IP address.  One exception is if we
-                         * are currently connected to a headset, since disabling
-                         * coexistence would interrupt that connection.
-                         */
-                        modifiedBluetoothCoexistenceMode = true;
-
-                        // Disable the coexistence mode
-                        setBluetoothCoexistenceMode(
-                                WifiNative.BLUETOOTH_COEXISTENCE_MODE_DISABLED);
-                    }
-                    
-                    powerMode = getPowerMode();
-                    if (powerMode < 0) {
-                      // Handle the case where supplicant driver does not support
-                      // getPowerModeCommand.
-                        powerMode = DRIVER_POWER_MODE_AUTO;
-                    }
-                    if (powerMode != DRIVER_POWER_MODE_ACTIVE) {
-                        setPowerMode(DRIVER_POWER_MODE_ACTIVE);
-                    }
-
-                    synchronized (this) {
-                        // A new request is being made, so assume we will callback
-                        mCancelCallback = false;
-                    }
-                    Log.d(TAG, "DhcpHandler: DHCP request started");
-                    if (NetworkUtils.runDhcp(mInterfaceName, mDhcpInfo)) {
-                        event = EVENT_INTERFACE_CONFIGURATION_SUCCEEDED;
-                        if (LOCAL_LOGD) Log.v(TAG, "DhcpHandler: DHCP request succeeded");
-                    } else {
-                        event = EVENT_INTERFACE_CONFIGURATION_FAILED;
-                        Log.i(TAG, "DhcpHandler: DHCP request failed: " +
-                            NetworkUtils.getDhcpError());
-                    }
-
-                    if (powerMode != DRIVER_POWER_MODE_ACTIVE) {
-                        setPowerMode(powerMode);
-                    }
-
-                    if (modifiedBluetoothCoexistenceMode) {
-                        // Set the coexistence mode back to its default value
-                        setBluetoothCoexistenceMode(
-                                WifiNative.BLUETOOTH_COEXISTENCE_MODE_SENSE);
-                    }
-
-                    synchronized (this) {
-                        if (!mCancelCallback) {
-                            mTarget.sendEmptyMessage(event);
-                        }
-                    }
-                    break;
-            }
-        }
-
-        public synchronized void setCancelCallback(boolean cancelCallback) {
-            mCancelCallback = cancelCallback;
-        }
-
-        /**
-         * Whether to disable coexistence mode while obtaining IP address. This
-         * logic will return true only if the current bluetooth
-         * headset/handsfree state is disconnected. This means if it is in an
-         * error state, we will NOT disable coexistence mode to err on the side
-         * of safety.
-         * 
-         * @return Whether to disable coexistence mode.
-         */
-        private boolean shouldDisableCoexistenceMode() {
-            int state = mBluetoothHeadset.getState(mBluetoothHeadset.getCurrentHeadset());
-            return state == BluetoothHeadset.STATE_DISCONNECTED;
-        }
-    }
-    
     private void checkUseStaticIp() {
         mUseStaticIp = false;
         final ContentResolver cr = mContext.getContentResolver();
@@ -2436,6 +1609,7 @@
                 Settings.System.WIFI_STATIC_DNS2), false, this);
         }
 
+        @Override
         public void onChange(boolean selfChange) {
             super.onChange(selfChange);
 
@@ -2465,15 +1639,2309 @@
                         oDns2 != mDhcpInfo.dns2));
 
             if (changed) {
-                resetConnections(true);
-                configureInterface();
+                sendMessage(CMD_RECONFIGURE_IP);
                 if (mUseStaticIp) {
-                    mTarget.sendEmptyMessage(EVENT_CONFIGURATION_CHANGED);
+                    mCsHandler.sendEmptyMessage(EVENT_CONFIGURATION_CHANGED);
                 }
             }
         }
     }
 
+
+    /**
+     * Clears variables related to tracking whether a notification has been
+     * shown recently.
+     * <p>
+     * After calling this method, the timer that prevents notifications from
+     * being shown too often will be cleared.
+     */
+    private void resetNotificationTimer() {
+        mNotificationRepeatTime = 0;
+        mNumScansSinceNetworkStateChange = 0;
+    }
+
+
+    /**
+     * Whether to disable coexistence mode while obtaining IP address. This
+     * logic will return true only if the current bluetooth
+     * headset/handsfree state is disconnected. This means if it is in an
+     * error state, we will NOT disable coexistence mode to err on the side
+     * of safety.
+     *
+     * @return Whether to disable coexistence mode.
+     */
+    private boolean shouldDisableCoexistenceMode() {
+        int state = mBluetoothHeadset.getState(mBluetoothHeadset.getCurrentHeadset());
+        return state == BluetoothHeadset.STATE_DISCONNECTED;
+    }
+
+    private void checkIsBluetoothPlaying() {
+        boolean isBluetoothPlaying = false;
+        Set<BluetoothDevice> connected = mBluetoothA2dp.getConnectedSinks();
+
+        for (BluetoothDevice device : connected) {
+            if (mBluetoothA2dp.getSinkState(device) == BluetoothA2dp.STATE_PLAYING) {
+                isBluetoothPlaying = true;
+                break;
+            }
+        }
+        setBluetoothScanMode(isBluetoothPlaying);
+    }
+
+    private void sendScanResultsAvailableBroadcast() {
+        if (!ActivityManagerNative.isSystemReady()) return;
+
+        mContext.sendBroadcast(new Intent(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION));
+    }
+
+    private void sendRssiChangeBroadcast(final int newRssi) {
+        if (!ActivityManagerNative.isSystemReady()) return;
+
+        Intent intent = new Intent(WifiManager.RSSI_CHANGED_ACTION);
+        intent.putExtra(WifiManager.EXTRA_NEW_RSSI, newRssi);
+        mContext.sendBroadcast(intent);
+    }
+
+    private void sendNetworkStateChangeBroadcast(String bssid) {
+        Intent intent = new Intent(WifiManager.NETWORK_STATE_CHANGED_ACTION);
+        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
+                | Intent.FLAG_RECEIVER_REPLACE_PENDING);
+        intent.putExtra(WifiManager.EXTRA_NETWORK_INFO, mNetworkInfo);
+        if (bssid != null)
+            intent.putExtra(WifiManager.EXTRA_BSSID, bssid);
+        mContext.sendStickyBroadcast(intent);
+    }
+
+    private void sendSupplicantStateChangedBroadcast(StateChangeResult sc, boolean failedAuth) {
+        Intent intent = new Intent(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION);
+        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
+                | Intent.FLAG_RECEIVER_REPLACE_PENDING);
+        intent.putExtra(WifiManager.EXTRA_NEW_STATE, (Parcelable)sc.state);
+        if (failedAuth) {
+            intent.putExtra(
+                WifiManager.EXTRA_SUPPLICANT_ERROR,
+                WifiManager.ERROR_AUTHENTICATING);
+        }
+        mContext.sendStickyBroadcast(intent);
+    }
+
+    private void sendSupplicantConnectionChangedBroadcast(boolean connected) {
+        if (!ActivityManagerNative.isSystemReady()) return;
+
+        Intent intent = new Intent(WifiManager.SUPPLICANT_CONNECTION_CHANGE_ACTION);
+        intent.putExtra(WifiManager.EXTRA_SUPPLICANT_CONNECTED, connected);
+        mContext.sendBroadcast(intent);
+    }
+
+    /**
+     * Record the detailed state of a network, and if it is a
+     * change from the previous state, send a notification to
+     * any listeners.
+     * @param state the new @{code DetailedState}
+     */
+    private void setDetailedState(NetworkInfo.DetailedState state) {
+        Log.d(TAG, "setDetailed state, old ="
+                + mNetworkInfo.getDetailedState() + " and new state=" + state);
+        if (state != mNetworkInfo.getDetailedState()) {
+            mNetworkInfo.setDetailedState(state, null, null);
+            Message msg = mCsHandler.obtainMessage(EVENT_STATE_CHANGED, mNetworkInfo);
+            msg.sendToTarget();
+        }
+    }
+
+    private static String removeDoubleQuotes(String string) {
+      if (string.length() <= 2) return "";
+      return string.substring(1, string.length() - 1);
+    }
+
+    private static String convertToQuotedString(String string) {
+        return "\"" + string + "\"";
+    }
+
+    private static String makeString(BitSet set, String[] strings) {
+        StringBuffer buf = new StringBuffer();
+        int nextSetBit = -1;
+
+        /* Make sure all set bits are in [0, strings.length) to avoid
+         * going out of bounds on strings.  (Shouldn't happen, but...) */
+        set = set.get(0, strings.length);
+
+        while ((nextSetBit = set.nextSetBit(nextSetBit + 1)) != -1) {
+            buf.append(strings[nextSetBit].replace('_', '-')).append(' ');
+        }
+
+        // remove trailing space
+        if (set.cardinality() > 0) {
+            buf.setLength(buf.length() - 1);
+        }
+
+        return buf.toString();
+    }
+
+    private static int lookupString(String string, String[] strings) {
+        int size = strings.length;
+
+        string = string.replace('-', '_');
+
+        for (int i = 0; i < size; i++)
+            if (string.equals(strings[i]))
+                return i;
+
+        // if we ever get here, we should probably add the
+        // value to WifiConfiguration to reflect that it's
+        // supported by the WPA supplicant
+        Log.w(TAG, "Failed to look-up a string: " + string);
+
+        return -1;
+    }
+
+    private int addOrUpdateNetworkNative(WifiConfiguration config) {
+        /*
+         * If the supplied networkId is -1, we create a new empty
+         * network configuration. Otherwise, the networkId should
+         * refer to an existing configuration.
+         */
+        int netId = config.networkId;
+        boolean newNetwork = netId == -1;
+        // networkId of -1 means we want to create a new network
+
+        if (newNetwork) {
+            netId = WifiNative.addNetworkCommand();
+            if (netId < 0) {
+                Log.e(TAG, "Failed to add a network!");
+                return -1;
+          }
+        }
+
+        setVariables: {
+
+            if (config.SSID != null &&
+                    !WifiNative.setNetworkVariableCommand(
+                        netId,
+                        WifiConfiguration.ssidVarName,
+                        config.SSID)) {
+                Log.d(TAG, "failed to set SSID: "+config.SSID);
+                break setVariables;
+            }
+
+            if (config.BSSID != null &&
+                    !WifiNative.setNetworkVariableCommand(
+                        netId,
+                        WifiConfiguration.bssidVarName,
+                        config.BSSID)) {
+                Log.d(TAG, "failed to set BSSID: "+config.BSSID);
+                break setVariables;
+            }
+
+            String allowedKeyManagementString =
+                makeString(config.allowedKeyManagement, WifiConfiguration.KeyMgmt.strings);
+            if (config.allowedKeyManagement.cardinality() != 0 &&
+                    !WifiNative.setNetworkVariableCommand(
+                        netId,
+                        WifiConfiguration.KeyMgmt.varName,
+                        allowedKeyManagementString)) {
+                Log.d(TAG, "failed to set key_mgmt: "+
+                        allowedKeyManagementString);
+                break setVariables;
+            }
+
+            String allowedProtocolsString =
+                makeString(config.allowedProtocols, WifiConfiguration.Protocol.strings);
+            if (config.allowedProtocols.cardinality() != 0 &&
+                    !WifiNative.setNetworkVariableCommand(
+                        netId,
+                        WifiConfiguration.Protocol.varName,
+                        allowedProtocolsString)) {
+                Log.d(TAG, "failed to set proto: "+
+                        allowedProtocolsString);
+                break setVariables;
+            }
+
+            String allowedAuthAlgorithmsString =
+                makeString(config.allowedAuthAlgorithms, WifiConfiguration.AuthAlgorithm.strings);
+            if (config.allowedAuthAlgorithms.cardinality() != 0 &&
+                    !WifiNative.setNetworkVariableCommand(
+                        netId,
+                        WifiConfiguration.AuthAlgorithm.varName,
+                        allowedAuthAlgorithmsString)) {
+                Log.d(TAG, "failed to set auth_alg: "+
+                        allowedAuthAlgorithmsString);
+                break setVariables;
+            }
+
+            String allowedPairwiseCiphersString =
+                    makeString(config.allowedPairwiseCiphers,
+                    WifiConfiguration.PairwiseCipher.strings);
+            if (config.allowedPairwiseCiphers.cardinality() != 0 &&
+                    !WifiNative.setNetworkVariableCommand(
+                        netId,
+                        WifiConfiguration.PairwiseCipher.varName,
+                        allowedPairwiseCiphersString)) {
+                Log.d(TAG, "failed to set pairwise: "+
+                        allowedPairwiseCiphersString);
+                break setVariables;
+            }
+
+            String allowedGroupCiphersString =
+                makeString(config.allowedGroupCiphers, WifiConfiguration.GroupCipher.strings);
+            if (config.allowedGroupCiphers.cardinality() != 0 &&
+                    !WifiNative.setNetworkVariableCommand(
+                        netId,
+                        WifiConfiguration.GroupCipher.varName,
+                        allowedGroupCiphersString)) {
+                Log.d(TAG, "failed to set group: "+
+                        allowedGroupCiphersString);
+                break setVariables;
+            }
+
+            // Prevent client screw-up by passing in a WifiConfiguration we gave it
+            // by preventing "*" as a key.
+            if (config.preSharedKey != null && !config.preSharedKey.equals("*") &&
+                    !WifiNative.setNetworkVariableCommand(
+                        netId,
+                        WifiConfiguration.pskVarName,
+                        config.preSharedKey)) {
+                Log.d(TAG, "failed to set psk: "+config.preSharedKey);
+                break setVariables;
+            }
+
+            boolean hasSetKey = false;
+            if (config.wepKeys != null) {
+                for (int i = 0; i < config.wepKeys.length; i++) {
+                    // Prevent client screw-up by passing in a WifiConfiguration we gave it
+                    // by preventing "*" as a key.
+                    if (config.wepKeys[i] != null && !config.wepKeys[i].equals("*")) {
+                        if (!WifiNative.setNetworkVariableCommand(
+                                    netId,
+                                    WifiConfiguration.wepKeyVarNames[i],
+                                    config.wepKeys[i])) {
+                            Log.d(TAG,
+                                    "failed to set wep_key"+i+": " +
+                                    config.wepKeys[i]);
+                            break setVariables;
+                        }
+                        hasSetKey = true;
+                    }
+                }
+            }
+
+            if (hasSetKey) {
+                if (!WifiNative.setNetworkVariableCommand(
+                            netId,
+                            WifiConfiguration.wepTxKeyIdxVarName,
+                            Integer.toString(config.wepTxKeyIndex))) {
+                    Log.d(TAG,
+                            "failed to set wep_tx_keyidx: "+
+                            config.wepTxKeyIndex);
+                    break setVariables;
+                }
+            }
+
+            if (!WifiNative.setNetworkVariableCommand(
+                        netId,
+                        WifiConfiguration.priorityVarName,
+                        Integer.toString(config.priority))) {
+                Log.d(TAG, config.SSID + ": failed to set priority: "
+                        +config.priority);
+                break setVariables;
+            }
+
+            if (config.hiddenSSID && !WifiNative.setNetworkVariableCommand(
+                        netId,
+                        WifiConfiguration.hiddenSSIDVarName,
+                        Integer.toString(config.hiddenSSID ? 1 : 0))) {
+                Log.d(TAG, config.SSID + ": failed to set hiddenSSID: "+
+                        config.hiddenSSID);
+                break setVariables;
+            }
+
+            for (WifiConfiguration.EnterpriseField field
+                    : config.enterpriseFields) {
+                String varName = field.varName();
+                String value = field.value();
+                if (value != null) {
+                    if (field != config.eap) {
+                        value = (value.length() == 0) ? "NULL" : convertToQuotedString(value);
+                    }
+                    if (!WifiNative.setNetworkVariableCommand(
+                                netId,
+                                varName,
+                                value)) {
+                        Log.d(TAG, config.SSID + ": failed to set " + varName +
+                                ": " + value);
+                        break setVariables;
+                    }
+                }
+            }
+            return netId;
+        }
+
+        if (newNetwork) {
+            WifiNative.removeNetworkCommand(netId);
+            Log.d(TAG,
+                    "Failed to set a network variable, removed network: "
+                    + netId);
+        }
+
+        return -1;
+    }
+
+    private List<WifiConfiguration> getConfiguredNetworksNative() {
+        String listStr = WifiNative.listNetworksCommand();
+
+        List<WifiConfiguration> networks =
+            new ArrayList<WifiConfiguration>();
+        if (listStr == null)
+            return networks;
+
+        String[] lines = listStr.split("\n");
+        // Skip the first line, which is a header
+        for (int i = 1; i < lines.length; i++) {
+            String[] result = lines[i].split("\t");
+            // network-id | ssid | bssid | flags
+            WifiConfiguration config = new WifiConfiguration();
+            try {
+                config.networkId = Integer.parseInt(result[0]);
+            } catch(NumberFormatException e) {
+                continue;
+            }
+            if (result.length > 3) {
+                if (result[3].indexOf("[CURRENT]") != -1)
+                    config.status = WifiConfiguration.Status.CURRENT;
+                else if (result[3].indexOf("[DISABLED]") != -1)
+                    config.status = WifiConfiguration.Status.DISABLED;
+                else
+                    config.status = WifiConfiguration.Status.ENABLED;
+            } else {
+                config.status = WifiConfiguration.Status.ENABLED;
+            }
+            readNetworkVariables(config);
+            networks.add(config);
+        }
+        return networks;
+    }
+
+    /**
+     * Read the variables from the supplicant daemon that are needed to
+     * fill in the WifiConfiguration object.
+     *
+     * @param config the {@link WifiConfiguration} object to be filled in.
+     */
+    private void readNetworkVariables(WifiConfiguration config) {
+
+        int netId = config.networkId;
+        if (netId < 0)
+            return;
+
+        /*
+         * TODO: maybe should have a native method that takes an array of
+         * variable names and returns an array of values. But we'd still
+         * be doing a round trip to the supplicant daemon for each variable.
+         */
+        String value;
+
+        value = WifiNative.getNetworkVariableCommand(netId, WifiConfiguration.ssidVarName);
+        if (!TextUtils.isEmpty(value)) {
+            config.SSID = removeDoubleQuotes(value);
+        } else {
+            config.SSID = null;
+        }
+
+        value = WifiNative.getNetworkVariableCommand(netId, WifiConfiguration.bssidVarName);
+        if (!TextUtils.isEmpty(value)) {
+            config.BSSID = value;
+        } else {
+            config.BSSID = null;
+        }
+
+        value = WifiNative.getNetworkVariableCommand(netId, WifiConfiguration.priorityVarName);
+        config.priority = -1;
+        if (!TextUtils.isEmpty(value)) {
+            try {
+                config.priority = Integer.parseInt(value);
+            } catch (NumberFormatException ignore) {
+            }
+        }
+
+        value = WifiNative.getNetworkVariableCommand(netId, WifiConfiguration.hiddenSSIDVarName);
+        config.hiddenSSID = false;
+        if (!TextUtils.isEmpty(value)) {
+            try {
+                config.hiddenSSID = Integer.parseInt(value) != 0;
+            } catch (NumberFormatException ignore) {
+            }
+        }
+
+        value = WifiNative.getNetworkVariableCommand(netId, WifiConfiguration.wepTxKeyIdxVarName);
+        config.wepTxKeyIndex = -1;
+        if (!TextUtils.isEmpty(value)) {
+            try {
+                config.wepTxKeyIndex = Integer.parseInt(value);
+            } catch (NumberFormatException ignore) {
+            }
+        }
+
+        for (int i = 0; i < 4; i++) {
+            value = WifiNative.getNetworkVariableCommand(netId,
+                    WifiConfiguration.wepKeyVarNames[i]);
+            if (!TextUtils.isEmpty(value)) {
+                config.wepKeys[i] = value;
+            } else {
+                config.wepKeys[i] = null;
+            }
+        }
+
+        value = WifiNative.getNetworkVariableCommand(netId, WifiConfiguration.pskVarName);
+        if (!TextUtils.isEmpty(value)) {
+            config.preSharedKey = value;
+        } else {
+            config.preSharedKey = null;
+        }
+
+        value = WifiNative.getNetworkVariableCommand(config.networkId,
+                WifiConfiguration.Protocol.varName);
+        if (!TextUtils.isEmpty(value)) {
+            String vals[] = value.split(" ");
+            for (String val : vals) {
+                int index =
+                    lookupString(val, WifiConfiguration.Protocol.strings);
+                if (0 <= index) {
+                    config.allowedProtocols.set(index);
+                }
+            }
+        }
+
+        value = WifiNative.getNetworkVariableCommand(config.networkId,
+                WifiConfiguration.KeyMgmt.varName);
+        if (!TextUtils.isEmpty(value)) {
+            String vals[] = value.split(" ");
+            for (String val : vals) {
+                int index =
+                    lookupString(val, WifiConfiguration.KeyMgmt.strings);
+                if (0 <= index) {
+                    config.allowedKeyManagement.set(index);
+                }
+            }
+        }
+
+        value = WifiNative.getNetworkVariableCommand(config.networkId,
+                WifiConfiguration.AuthAlgorithm.varName);
+        if (!TextUtils.isEmpty(value)) {
+            String vals[] = value.split(" ");
+            for (String val : vals) {
+                int index =
+                    lookupString(val, WifiConfiguration.AuthAlgorithm.strings);
+                if (0 <= index) {
+                    config.allowedAuthAlgorithms.set(index);
+                }
+            }
+        }
+
+        value = WifiNative.getNetworkVariableCommand(config.networkId,
+                WifiConfiguration.PairwiseCipher.varName);
+        if (!TextUtils.isEmpty(value)) {
+            String vals[] = value.split(" ");
+            for (String val : vals) {
+                int index =
+                    lookupString(val, WifiConfiguration.PairwiseCipher.strings);
+                if (0 <= index) {
+                    config.allowedPairwiseCiphers.set(index);
+                }
+            }
+        }
+
+        value = WifiNative.getNetworkVariableCommand(config.networkId,
+                WifiConfiguration.GroupCipher.varName);
+        if (!TextUtils.isEmpty(value)) {
+            String vals[] = value.split(" ");
+            for (String val : vals) {
+                int index =
+                    lookupString(val, WifiConfiguration.GroupCipher.strings);
+                if (0 <= index) {
+                    config.allowedGroupCiphers.set(index);
+                }
+            }
+        }
+
+        for (WifiConfiguration.EnterpriseField field :
+                config.enterpriseFields) {
+            value = WifiNative.getNetworkVariableCommand(netId,
+                    field.varName());
+            if (!TextUtils.isEmpty(value)) {
+                if (field != config.eap) value = removeDoubleQuotes(value);
+                field.setValue(value);
+            }
+        }
+
+    }
+
+    /**
+     * Poll for info not reported via events
+     * RSSI & Linkspeed
+     */
+    private void requestPolledInfo() {
+        int newRssi = WifiNative.getRssiCommand();
+        if (newRssi != -1 && -200 < newRssi && newRssi < 256) { // screen out invalid values
+            /* some implementations avoid negative values by adding 256
+             * so we need to adjust for that here.
+             */
+            if (newRssi > 0) newRssi -= 256;
+            mWifiInfo.setRssi(newRssi);
+            /*
+             * Rather then sending the raw RSSI out every time it
+             * changes, we precalculate the signal level that would
+             * be displayed in the status bar, and only send the
+             * broadcast if that much more coarse-grained number
+             * changes. This cuts down greatly on the number of
+             * broadcasts, at the cost of not mWifiInforming others
+             * interested in RSSI of all the changes in signal
+             * level.
+             */
+            // TODO: The second arg to the call below needs to be a symbol somewhere, but
+            // it's actually the size of an array of icons that's private
+            // to StatusBar Policy.
+            int newSignalLevel = WifiManager.calculateSignalLevel(newRssi, 4);
+            if (newSignalLevel != mLastSignalLevel) {
+                sendRssiChangeBroadcast(newRssi);
+            }
+            mLastSignalLevel = newSignalLevel;
+        } else {
+            mWifiInfo.setRssi(-200);
+        }
+        int newLinkSpeed = WifiNative.getLinkSpeedCommand();
+        if (newLinkSpeed != -1) {
+            mWifiInfo.setLinkSpeed(newLinkSpeed);
+        }
+    }
+
+    /**
+     * Resets the Wi-Fi Connections by clearing any state, resetting any sockets
+     * using the interface, stopping DHCP & disabling interface
+     */
+    private void handleNetworkDisconnect() {
+        Log.d(TAG, "Reset connections and stopping DHCP");
+
+        /*
+         * Reset connections & stop DHCP
+         */
+        NetworkUtils.resetConnections(mInterfaceName);
+
+        if (!NetworkUtils.stopDhcp(mInterfaceName)) {
+            Log.e(TAG, "Could not stop DHCP");
+        }
+
+        /* Disable interface */
+        NetworkUtils.disableInterface(mInterfaceName);
+
+        /* send event to CM & network change broadcast */
+        setDetailedState(DetailedState.DISCONNECTED);
+        sendNetworkStateChangeBroadcast(mLastBssid);
+
+        /* Reset data structures */
+        mWifiInfo.setIpAddress(0);
+        mWifiInfo.setBSSID(null);
+        mWifiInfo.setSSID(null);
+        mWifiInfo.setNetworkId(-1);
+
+        /* Clear network properties */
+        mNetworkProperties.clear();
+
+        mLastBssid= null;
+        mLastNetworkId = -1;
+
+    }
+
+
+    /*********************************************************
+     * Notifications from WifiMonitor
+     ********************************************************/
+
+    /**
+     * A structure for supplying information about a supplicant state
+     * change in the STATE_CHANGE event message that comes from the
+     * WifiMonitor
+     * thread.
+     */
+    private static class StateChangeResult {
+        StateChangeResult(int networkId, String BSSID, Object state) {
+            this.state = state;
+            this.BSSID = BSSID;
+            this.networkId = networkId;
+        }
+        int networkId;
+        String BSSID;
+        Object state;
+    }
+
+    /**
+     * Send the tracker a notification that a user-entered password key
+     * may be incorrect (i.e., caused authentication to fail).
+     */
+    void notifyPasswordKeyMayBeIncorrect() {
+        sendMessage(PASSWORD_MAY_BE_INCORRECT_EVENT);
+    }
+
+    /**
+     * Send the tracker a notification that a connection to the supplicant
+     * daemon has been established.
+     */
+    void notifySupplicantConnection() {
+        sendMessage(SUP_CONNECTION_EVENT);
+    }
+
+    /**
+     * Send the tracker a notification that a connection to the supplicant
+     * daemon has been established.
+     */
+    void notifySupplicantLost() {
+        sendMessage(SUP_DISCONNECTION_EVENT);
+    }
+
+    /**
+     * Send the tracker a notification that the state of Wifi connectivity
+     * has changed.
+     * @param networkId the configured network on which the state change occurred
+     * @param newState the new network state
+     * @param BSSID when the new state is {@link DetailedState#CONNECTED
+     * NetworkInfo.DetailedState.CONNECTED},
+     * this is the MAC address of the access point. Otherwise, it
+     * is {@code null}.
+     */
+    void notifyNetworkStateChange(DetailedState newState, String BSSID, int networkId) {
+        if (newState == NetworkInfo.DetailedState.CONNECTED) {
+            sendMessage(obtainMessage(NETWORK_CONNECTION_EVENT,
+                    new StateChangeResult(networkId, BSSID, newState)));
+        } else {
+            sendMessage(obtainMessage(NETWORK_DISCONNECTION_EVENT,
+                    new StateChangeResult(networkId, BSSID, newState)));
+        }
+    }
+
+    /**
+     * Send the tracker a notification that the state of the supplicant
+     * has changed.
+     * @param networkId the configured network on which the state change occurred
+     * @param newState the new {@code SupplicantState}
+     */
+    void notifySupplicantStateChange(int networkId, String BSSID, SupplicantState newState) {
+        sendMessage(obtainMessage(SUPPLICANT_STATE_CHANGE_EVENT,
+                new StateChangeResult(networkId, BSSID, newState)));
+    }
+
+    /**
+     * Send the tracker a notification that a scan has completed, and results
+     * are available.
+     */
+    void notifyScanResultsAvailable() {
+        /**
+         * Switch scan mode over to passive.
+         * Turning off scan-only mode happens only in "Connect" mode
+         */
+        setScanType(false);
+        sendMessage(SCAN_RESULTS_EVENT);
+    }
+
+    void notifyDriverStarted() {
+        sendMessage(DRIVER_START_EVENT);
+    }
+
+    void notifyDriverStopped() {
+        sendMessage(DRIVER_STOP_EVENT);
+    }
+
+    void notifyDriverHung() {
+        setWifiEnabled(false);
+        setWifiEnabled(true);
+    }
+
+
+    /********************************************************
+     * HSM states
+     *******************************************************/
+
+    class DefaultState extends HierarchicalState {
+        @Override
+        public boolean processMessage(Message message) {
+            if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
+            SyncParams syncParams;
+            switch (message.what) {
+                    /* Synchronous call returns */
+                case CMD_PING_SUPPLICANT:
+                case CMD_START_SCAN:
+                case CMD_DISCONNECT:
+                case CMD_RECONNECT:
+                case CMD_REASSOCIATE:
+                case CMD_REMOVE_NETWORK:
+                case CMD_ENABLE_NETWORK:
+                case CMD_DISABLE_NETWORK:
+                case CMD_ADD_OR_UPDATE_NETWORK:
+                case CMD_GET_RSSI:
+                case CMD_GET_RSSI_APPROX:
+                case CMD_GET_LINK_SPEED:
+                case CMD_GET_MAC_ADDR:
+                case CMD_SAVE_CONFIG:
+                case CMD_CONNECTION_STATUS:
+                case CMD_GET_NETWORK_CONFIG:
+                    if (message.arg2 == SYNCHRONOUS_CALL) {
+                        syncParams = (SyncParams) message.obj;
+                        syncParams.mSyncReturn.boolValue = false;
+                        syncParams.mSyncReturn.intValue = -1;
+                        syncParams.mSyncReturn.stringValue = null;
+                        syncParams.mSyncReturn.configList = null;
+                        notifyOnMsgObject(message);
+                    }
+                    break;
+                case CM_CMD_TEARDOWN:
+                    EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what);
+                    mTeardownRequested.set(true);
+                    sendMessage(CMD_DISCONNECT);
+                    sendMessage(CMD_STOP_DRIVER);
+                    /* Mark wifi available when CM tears down */
+                    mNetworkInfo.setIsAvailable(true);
+                    break;
+                case CM_CMD_RECONNECT:
+                    EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what);
+                    mTeardownRequested.set(false);
+                    sendMessage(CMD_START_DRIVER);
+                    sendMessage(CMD_RECONNECT);
+                    break;
+                case CMD_ENABLE_RSSI_POLL:
+                    mEnableRssiPolling = (message.arg1 == 1);
+                    mSupplicantStateTracker.sendMessage(CMD_ENABLE_RSSI_POLL);
+                    break;
+                default:
+                    if (DBG) Log.w(TAG, "Unhandled " + message);
+                    break;
+            }
+            return HANDLED;
+        }
+    }
+
+    class InitialState extends HierarchicalState {
+        @Override
+        //TODO: could move logging into a common class
+        public void enter() {
+            if (DBG) Log.d(TAG, getName() + "\n");
+            // [31-8] Reserved for future use
+            // [7 - 0] HSM state change
+            // 50021 wifi_state_changed (custom|1|5)
+            EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName());
+
+            if (WifiNative.isDriverLoaded()) {
+                transitionTo(mDriverLoadedState);
+            }
+            else {
+                transitionTo(mDriverUnloadedState);
+            }
+        }
+    }
+
+    class DriverLoadingState extends HierarchicalState {
+        @Override
+        public void enter() {
+            if (DBG) Log.d(TAG, getName() + "\n");
+            EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName());
+
+            final Message message = new Message();
+            message.copyFrom(getCurrentMessage());
+            new Thread(new Runnable() {
+                public void run() {
+                    sWakeLock.acquire();
+                    //enabling state
+                    switch(message.arg1) {
+                        case WIFI_STATE_ENABLING:
+                            setWifiState(WIFI_STATE_ENABLING);
+                            break;
+                        case WIFI_AP_STATE_ENABLING:
+                            setWifiApState(WIFI_AP_STATE_ENABLING);
+                            break;
+                    }
+
+                    if(WifiNative.loadDriver()) {
+                        Log.d(TAG, "Driver load successful");
+                        sendMessage(CMD_LOAD_DRIVER_SUCCESS);
+                    } else {
+                        Log.e(TAG, "Failed to load driver!");
+                        switch(message.arg1) {
+                            case WIFI_STATE_ENABLING:
+                                setWifiState(WIFI_STATE_UNKNOWN);
+                                break;
+                            case WIFI_AP_STATE_ENABLING:
+                                setWifiApState(WIFI_AP_STATE_FAILED);
+                                break;
+                        }
+                        sendMessage(CMD_LOAD_DRIVER_FAILURE);
+                    }
+                    sWakeLock.release();
+                }
+            }).start();
+        }
+
+        @Override
+        public boolean processMessage(Message message) {
+            if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
+            switch (message.what) {
+                case CMD_LOAD_DRIVER_SUCCESS:
+                    transitionTo(mDriverLoadedState);
+                    break;
+                case CMD_LOAD_DRIVER_FAILURE:
+                    transitionTo(mDriverFailedState);
+                    break;
+                case CMD_LOAD_DRIVER:
+                case CMD_UNLOAD_DRIVER:
+                case CMD_START_SUPPLICANT:
+                case CMD_STOP_SUPPLICANT:
+                case CMD_START_AP:
+                case CMD_STOP_AP:
+                case CMD_START_DRIVER:
+                case CMD_STOP_DRIVER:
+                case CMD_SET_SCAN_MODE:
+                case CMD_SET_SCAN_TYPE:
+                case CMD_SET_POWER_MODE:
+                case CMD_SET_BLUETOOTH_COEXISTENCE:
+                case CMD_SET_BLUETOOTH_SCAN_MODE:
+                case CMD_SET_NUM_ALLOWED_CHANNELS:
+                case CMD_START_PACKET_FILTERING:
+                case CMD_STOP_PACKET_FILTERING:
+                    deferMessage(message);
+                    break;
+                default:
+                    return NOT_HANDLED;
+            }
+            EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what);
+            return HANDLED;
+        }
+    }
+
+    class DriverLoadedState extends HierarchicalState {
+        @Override
+        public void enter() {
+            if (DBG) Log.d(TAG, getName() + "\n");
+            EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName());
+        }
+        @Override
+        public boolean processMessage(Message message) {
+            if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
+            switch(message.what) {
+                case CMD_UNLOAD_DRIVER:
+                    transitionTo(mDriverUnloadingState);
+                    break;
+                case CMD_START_SUPPLICANT:
+                    if(WifiNative.startSupplicant()) {
+                        Log.d(TAG, "Supplicant start successful");
+                        mWifiMonitor.startMonitoring();
+                        setWifiState(WIFI_STATE_ENABLED);
+                        transitionTo(mWaitForSupState);
+                    } else {
+                        Log.e(TAG, "Failed to start supplicant!");
+                        sendMessage(obtainMessage(CMD_UNLOAD_DRIVER, WIFI_STATE_UNKNOWN, 0));
+                    }
+                    break;
+                case CMD_START_AP:
+                    try {
+                        nwService.startAccessPoint((WifiConfiguration) message.obj,
+                                    mInterfaceName,
+                                    SOFTAP_IFACE);
+                    } catch(Exception e) {
+                        Log.e(TAG, "Exception in startAccessPoint()");
+                        sendMessage(obtainMessage(CMD_UNLOAD_DRIVER, WIFI_AP_STATE_FAILED, 0));
+                        break;
+                    }
+                    Log.d(TAG, "Soft AP start successful");
+                    setWifiApState(WIFI_AP_STATE_ENABLED);
+                    transitionTo(mSoftApStartedState);
+                    break;
+                default:
+                    return NOT_HANDLED;
+            }
+            EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what);
+            return HANDLED;
+        }
+    }
+
+    class DriverUnloadingState extends HierarchicalState {
+        @Override
+        public void enter() {
+            if (DBG) Log.d(TAG, getName() + "\n");
+            EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName());
+
+            final Message message = new Message();
+            message.copyFrom(getCurrentMessage());
+            new Thread(new Runnable() {
+                public void run() {
+                    if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
+                    sWakeLock.acquire();
+                    if(WifiNative.unloadDriver()) {
+                        Log.d(TAG, "Driver unload successful");
+                        sendMessage(CMD_UNLOAD_DRIVER_SUCCESS);
+
+                        switch(message.arg1) {
+                            case WIFI_STATE_DISABLED:
+                            case WIFI_STATE_UNKNOWN:
+                                setWifiState(message.arg1);
+                                break;
+                            case WIFI_AP_STATE_DISABLED:
+                            case WIFI_AP_STATE_FAILED:
+                                setWifiApState(message.arg1);
+                                break;
+                        }
+                    } else {
+                        Log.e(TAG, "Failed to unload driver!");
+                        sendMessage(CMD_UNLOAD_DRIVER_FAILURE);
+
+                        switch(message.arg1) {
+                            case WIFI_STATE_DISABLED:
+                            case WIFI_STATE_UNKNOWN:
+                                setWifiState(WIFI_STATE_UNKNOWN);
+                                break;
+                            case WIFI_AP_STATE_DISABLED:
+                            case WIFI_AP_STATE_FAILED:
+                                setWifiApState(WIFI_AP_STATE_FAILED);
+                                break;
+                        }
+                    }
+                    sWakeLock.release();
+                }
+            }).start();
+        }
+
+        @Override
+        public boolean processMessage(Message message) {
+            if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
+            switch (message.what) {
+                case CMD_UNLOAD_DRIVER_SUCCESS:
+                    transitionTo(mDriverUnloadedState);
+                    break;
+                case CMD_UNLOAD_DRIVER_FAILURE:
+                    transitionTo(mDriverFailedState);
+                    break;
+                case CMD_LOAD_DRIVER:
+                case CMD_UNLOAD_DRIVER:
+                case CMD_START_SUPPLICANT:
+                case CMD_STOP_SUPPLICANT:
+                case CMD_START_AP:
+                case CMD_STOP_AP:
+                case CMD_START_DRIVER:
+                case CMD_STOP_DRIVER:
+                case CMD_SET_SCAN_MODE:
+                case CMD_SET_SCAN_TYPE:
+                case CMD_SET_POWER_MODE:
+                case CMD_SET_BLUETOOTH_COEXISTENCE:
+                case CMD_SET_BLUETOOTH_SCAN_MODE:
+                case CMD_SET_NUM_ALLOWED_CHANNELS:
+                case CMD_START_PACKET_FILTERING:
+                case CMD_STOP_PACKET_FILTERING:
+                    deferMessage(message);
+                    break;
+                default:
+                    return NOT_HANDLED;
+            }
+            EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what);
+            return HANDLED;
+        }
+    }
+
+    class DriverUnloadedState extends HierarchicalState {
+        @Override
+        public void enter() {
+            if (DBG) Log.d(TAG, getName() + "\n");
+            EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName());
+        }
+        @Override
+        public boolean processMessage(Message message) {
+            if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
+            switch (message.what) {
+                case CMD_LOAD_DRIVER:
+                    transitionTo(mDriverLoadingState);
+                    break;
+                default:
+                    return NOT_HANDLED;
+            }
+            EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what);
+            return HANDLED;
+        }
+    }
+
+    class DriverFailedState extends HierarchicalState {
+        @Override
+        public void enter() {
+            Log.e(TAG, getName() + "\n");
+            EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName());
+        }
+        @Override
+        public boolean processMessage(Message message) {
+            if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
+            return NOT_HANDLED;
+        }
+    }
+
+
+    class WaitForSupState extends HierarchicalState {
+        @Override
+        public void enter() {
+            if (DBG) Log.d(TAG, getName() + "\n");
+            EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName());
+        }
+        @Override
+        public boolean processMessage(Message message) {
+            if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
+            switch(message.what) {
+                case SUP_CONNECTION_EVENT:
+                    Log.d(TAG, "Supplicant connection established");
+                    mSupplicantStateTracker.resetSupplicantState();
+                    /* Initialize data structures */
+                    resetNotificationTimer();
+                    setTeardownRequested(false);
+                    mLastBssid = null;
+                    mLastNetworkId = -1;
+                    mLastSignalLevel = -1;
+
+                    mWifiInfo.setMacAddress(WifiNative.getMacAddressCommand());
+
+                    //TODO: initialize and fix multicast filtering
+                    //mWM.initializeMulticastFiltering();
+
+                    if (mBluetoothA2dp == null) {
+                        mBluetoothA2dp = new BluetoothA2dp(mContext);
+                    }
+                    checkIsBluetoothPlaying();
+
+                    checkUseStaticIp();
+                    sendSupplicantConnectionChangedBroadcast(true);
+                    transitionTo(mDriverSupReadyState);
+                    break;
+                case CMD_STOP_SUPPLICANT:
+                    Log.d(TAG, "Stop supplicant received");
+                    WifiNative.stopSupplicant();
+                    transitionTo(mDriverLoadedState);
+                    break;
+                    /* Fail soft ap when waiting for supplicant start */
+                case CMD_START_AP:
+                    Log.d(TAG, "Failed to start soft AP with a running supplicant");
+                    setWifiApState(WIFI_AP_STATE_FAILED);
+                    break;
+                case CMD_START_DRIVER:
+                case CMD_STOP_DRIVER:
+                case CMD_SET_SCAN_MODE:
+                case CMD_SET_SCAN_TYPE:
+                case CMD_SET_POWER_MODE:
+                case CMD_SET_BLUETOOTH_COEXISTENCE:
+                case CMD_SET_BLUETOOTH_SCAN_MODE:
+                case CMD_SET_NUM_ALLOWED_CHANNELS:
+                case CMD_START_PACKET_FILTERING:
+                case CMD_STOP_PACKET_FILTERING:
+                    deferMessage(message);
+                    break;
+                case CMD_STOP_AP:
+                case CMD_START_SUPPLICANT:
+                case CMD_UNLOAD_DRIVER:
+                    break;
+                default:
+                    return NOT_HANDLED;
+            }
+            EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what);
+            return HANDLED;
+        }
+    }
+
+    class DriverSupReadyState extends HierarchicalState {
+        @Override
+        public void enter() {
+            if (DBG) Log.d(TAG, getName() + "\n");
+            EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName());
+            /* Initialize for connect mode operation at start */
+            mIsScanMode = false;
+        }
+        @Override
+        public boolean processMessage(Message message) {
+            if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
+            SyncParams syncParams;
+            switch(message.what) {
+                case CMD_STOP_SUPPLICANT:   /* Supplicant stopped by user */
+                    Log.d(TAG, "Stop supplicant received");
+                    WifiNative.stopSupplicant();
+                    //$FALL-THROUGH$
+                case SUP_DISCONNECTION_EVENT:  /* Supplicant died */
+                    EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what);
+                    //Remove any notifications on disconnection
+                    setNotificationVisible(false, 0, false, 0);
+                    WifiNative.closeSupplicantConnection();
+                    handleNetworkDisconnect();
+                    sendSupplicantConnectionChangedBroadcast(false);
+                    mSupplicantStateTracker.resetSupplicantState();
+                    transitionTo(mDriverLoadedState);
+
+                    /* When supplicant dies, unload driver and enter failed state */
+                    //TODO: consider bringing up supplicant again
+                    if (message.what == SUP_DISCONNECTION_EVENT) {
+                        Log.d(TAG, "Supplicant died, unloading driver");
+                        sendMessage(obtainMessage(CMD_UNLOAD_DRIVER, WIFI_STATE_UNKNOWN, 0));
+                    }
+                    break;
+                case CMD_START_DRIVER:
+                    EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what);
+                    WifiNative.startDriverCommand();
+                    transitionTo(mDriverStartingState);
+                    break;
+                case SCAN_RESULTS_EVENT:
+                    setScanResults(WifiNative.scanResultsCommand());
+                    sendScanResultsAvailableBroadcast();
+                    checkAndSetNotification();
+                    break;
+                case CMD_PING_SUPPLICANT:
+                    syncParams = (SyncParams) message.obj;
+                    syncParams.mSyncReturn.boolValue = WifiNative.pingCommand();
+                    notifyOnMsgObject(message);
+                    break;
+                case CMD_ADD_OR_UPDATE_NETWORK:
+                    EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what);
+                    syncParams = (SyncParams) message.obj;
+                    WifiConfiguration config = (WifiConfiguration) syncParams.mParameter;
+                    syncParams.mSyncReturn.intValue = addOrUpdateNetworkNative(config);
+                    notifyOnMsgObject(message);
+                    break;
+                case CMD_REMOVE_NETWORK:
+                    EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what);
+                    if (message.arg2 == SYNCHRONOUS_CALL) {
+                        syncParams = (SyncParams) message.obj;
+                        syncParams.mSyncReturn.boolValue = WifiNative.removeNetworkCommand(
+                                message.arg1);
+                        notifyOnMsgObject(message);
+                    } else {
+                        /* asynchronous handling */
+                        WifiNative.removeNetworkCommand(message.arg1);
+                    }
+                    break;
+                case CMD_ENABLE_NETWORK:
+                    EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what);
+                    if (message.arg2 == SYNCHRONOUS_CALL) {
+                        syncParams = (SyncParams) message.obj;
+                        EnableNetParams enableNetParams = (EnableNetParams) syncParams.mParameter;
+                        syncParams.mSyncReturn.boolValue = WifiNative.enableNetworkCommand(
+                                enableNetParams.netId, enableNetParams.disableOthers);
+                        notifyOnMsgObject(message);
+                    } else {
+                        /* asynchronous handling */
+                        WifiNative.enableNetworkCommand(message.arg1, message.arg2 == 1);
+                    }
+                    break;
+                case CMD_DISABLE_NETWORK:
+                    EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what);
+                    if (message.arg2 == SYNCHRONOUS_CALL) {
+                        syncParams = (SyncParams) message.obj;
+                        syncParams.mSyncReturn.boolValue = WifiNative.disableNetworkCommand(
+                                message.arg1);
+                        notifyOnMsgObject(message);
+                    } else {
+                        /* asynchronous handling */
+                        WifiNative.disableNetworkCommand(message.arg1);
+                    }
+                    break;
+                case CMD_BLACKLIST_NETWORK:
+                    EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what);
+                    WifiNative.addToBlacklistCommand((String)message.obj);
+                    break;
+                case CMD_CLEAR_BLACKLIST:
+                    WifiNative.clearBlacklistCommand();
+                    break;
+                case CMD_GET_NETWORK_CONFIG:
+                    syncParams = (SyncParams) message.obj;
+                    syncParams.mSyncReturn.configList = getConfiguredNetworksNative();
+                    notifyOnMsgObject(message);
+                    break;
+                case CMD_SAVE_CONFIG:
+                    if (message.arg2 == SYNCHRONOUS_CALL) {
+                        syncParams = (SyncParams) message.obj;
+                        syncParams.mSyncReturn.boolValue = WifiNative.saveConfigCommand();
+                        notifyOnMsgObject(message);
+                    } else {
+                        /* asynchronous handling */
+                        WifiNative.saveConfigCommand();
+                    }
+                    // Inform the backup manager about a data change
+                    IBackupManager ibm = IBackupManager.Stub.asInterface(
+                            ServiceManager.getService(Context.BACKUP_SERVICE));
+                    if (ibm != null) {
+                        try {
+                            ibm.dataChanged("com.android.providers.settings");
+                        } catch (Exception e) {
+                            // Try again later
+                        }
+                    }
+                    break;
+                case CMD_CONNECTION_STATUS:
+                    syncParams = (SyncParams) message.obj;
+                    syncParams.mSyncReturn.stringValue = WifiNative.statusCommand();
+                    notifyOnMsgObject(message);
+                    break;
+                case CMD_GET_MAC_ADDR:
+                    syncParams = (SyncParams) message.obj;
+                    syncParams.mSyncReturn.stringValue = WifiNative.getMacAddressCommand();
+                    notifyOnMsgObject(message);
+                    break;
+                    /* Cannot start soft AP while in client mode */
+                case CMD_START_AP:
+                    Log.d(TAG, "Failed to start soft AP with a running supplicant");
+                    EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what);
+                    setWifiApState(WIFI_AP_STATE_FAILED);
+                    break;
+                case CMD_SET_SCAN_MODE:
+                    mIsScanMode = (message.arg1 == SCAN_ONLY_MODE);
+                    break;
+                default:
+                    return NOT_HANDLED;
+            }
+            return HANDLED;
+        }
+    }
+
+    class DriverStartingState extends HierarchicalState {
+        @Override
+        public void enter() {
+            if (DBG) Log.d(TAG, getName() + "\n");
+            EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName());
+        }
+        @Override
+        public boolean processMessage(Message message) {
+            if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
+            switch(message.what) {
+                case DRIVER_START_EVENT:
+                    transitionTo(mDriverStartedState);
+                    break;
+                    /* Queue driver commands & connection events */
+                case CMD_START_DRIVER:
+                case CMD_STOP_DRIVER:
+                case SUPPLICANT_STATE_CHANGE_EVENT:
+                case NETWORK_CONNECTION_EVENT:
+                case NETWORK_DISCONNECTION_EVENT:
+                case PASSWORD_MAY_BE_INCORRECT_EVENT:
+                case CMD_SET_SCAN_TYPE:
+                case CMD_SET_POWER_MODE:
+                case CMD_SET_BLUETOOTH_COEXISTENCE:
+                case CMD_SET_BLUETOOTH_SCAN_MODE:
+                case CMD_SET_NUM_ALLOWED_CHANNELS:
+                case CMD_START_PACKET_FILTERING:
+                case CMD_STOP_PACKET_FILTERING:
+                    deferMessage(message);
+                    break;
+                    /* Queue the asynchronous version of these commands */
+                case CMD_START_SCAN:
+                case CMD_DISCONNECT:
+                case CMD_REASSOCIATE:
+                case CMD_RECONNECT:
+                    if (message.arg2 != SYNCHRONOUS_CALL) {
+                        deferMessage(message);
+                    }
+                    break;
+                default:
+                    return NOT_HANDLED;
+            }
+            EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what);
+            return HANDLED;
+        }
+    }
+
+    class DriverStartedState extends HierarchicalState {
+        @Override
+        public void enter() {
+            if (DBG) Log.d(TAG, getName() + "\n");
+            EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName());
+
+            try {
+                mBatteryStats.noteWifiRunning();
+            } catch (RemoteException ignore) {}
+
+            /* Initialize channel count */
+            setNumAllowedChannels();
+
+            if (mIsScanMode) {
+                WifiNative.setScanResultHandlingCommand(SCAN_ONLY_MODE);
+                WifiNative.disconnectCommand();
+                transitionTo(mScanModeState);
+            } else {
+                WifiNative.setScanResultHandlingCommand(CONNECT_MODE);
+                /* If supplicant has already connected, before we could finish establishing
+                 * the control channel connection, we miss all the supplicant events.
+                 * Disconnect and reconnect when driver has started to ensure we receive
+                 * all supplicant events.
+                 *
+                 * TODO: This is a bit unclean, ideally the supplicant should never
+                 * connect until told to do so by the framework
+                 */
+                WifiNative.disconnectCommand();
+                WifiNative.reconnectCommand();
+                transitionTo(mConnectModeState);
+            }
+        }
+        @Override
+        public boolean processMessage(Message message) {
+            if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
+            SyncParams syncParams;
+            switch(message.what) {
+                case CMD_SET_SCAN_TYPE:
+                    if (message.arg1 == SCAN_ACTIVE) {
+                        WifiNative.setScanModeCommand(true);
+                    } else {
+                        WifiNative.setScanModeCommand(false);
+                    }
+                    break;
+                case CMD_SET_POWER_MODE:
+                    WifiNative.setPowerModeCommand(message.arg1);
+                    break;
+                case CMD_SET_BLUETOOTH_COEXISTENCE:
+                    WifiNative.setBluetoothCoexistenceModeCommand(message.arg1);
+                    break;
+                case CMD_SET_BLUETOOTH_SCAN_MODE:
+                    WifiNative.setBluetoothCoexistenceScanModeCommand(message.arg1 == 1);
+                    break;
+                case CMD_SET_NUM_ALLOWED_CHANNELS:
+                    mNumAllowedChannels = message.arg1;
+                    WifiNative.setNumAllowedChannelsCommand(message.arg1);
+                    break;
+                case CMD_START_DRIVER:
+                    /* Ignore another driver start */
+                    break;
+                case CMD_STOP_DRIVER:
+                    WifiNative.stopDriverCommand();
+                    transitionTo(mDriverStoppingState);
+                    break;
+                case CMD_REQUEST_CM_WAKELOCK:
+                    if (mCm == null) {
+                        mCm = (ConnectivityManager)mContext.getSystemService(
+                                Context.CONNECTIVITY_SERVICE);
+                    }
+                    mCm.requestNetworkTransitionWakelock(TAG);
+                    break;
+                case CMD_START_PACKET_FILTERING:
+                    WifiNative.startPacketFiltering();
+                    break;
+                case CMD_STOP_PACKET_FILTERING:
+                    WifiNative.stopPacketFiltering();
+                    break;
+                default:
+                    return NOT_HANDLED;
+            }
+            EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what);
+            return HANDLED;
+        }
+        @Override
+        public void exit() {
+            if (DBG) Log.d(TAG, getName() + "\n");
+            try {
+                mBatteryStats.noteWifiStopped();
+            } catch (RemoteException ignore) { }
+        }
+    }
+
+    class DriverStoppingState extends HierarchicalState {
+        @Override
+        public void enter() {
+            if (DBG) Log.d(TAG, getName() + "\n");
+            EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName());
+        }
+        @Override
+        public boolean processMessage(Message message) {
+            if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
+            switch(message.what) {
+                case DRIVER_STOP_EVENT:
+                    transitionTo(mDriverStoppedState);
+                    break;
+                    /* Queue driver commands */
+                case CMD_START_DRIVER:
+                case CMD_STOP_DRIVER:
+                case CMD_SET_SCAN_TYPE:
+                case CMD_SET_POWER_MODE:
+                case CMD_SET_BLUETOOTH_COEXISTENCE:
+                case CMD_SET_BLUETOOTH_SCAN_MODE:
+                case CMD_SET_NUM_ALLOWED_CHANNELS:
+                case CMD_START_PACKET_FILTERING:
+                case CMD_STOP_PACKET_FILTERING:
+                    deferMessage(message);
+                    break;
+                    /* Queue the asynchronous version of these commands */
+                case CMD_START_SCAN:
+                case CMD_DISCONNECT:
+                case CMD_REASSOCIATE:
+                case CMD_RECONNECT:
+                    if (message.arg2 != SYNCHRONOUS_CALL) {
+                        deferMessage(message);
+                    }
+                    break;
+                default:
+                    return NOT_HANDLED;
+            }
+            EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what);
+            return HANDLED;
+        }
+    }
+
+    class DriverStoppedState extends HierarchicalState {
+        @Override
+        public void enter() {
+            if (DBG) Log.d(TAG, getName() + "\n");
+            EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName());
+
+            // Take down any open network notifications on driver stop
+            setNotificationVisible(false, 0, false, 0);
+        }
+        @Override
+        public boolean processMessage(Message message) {
+            if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
+            return NOT_HANDLED;
+        }
+    }
+
+    class ScanModeState extends HierarchicalState {
+        @Override
+        public void enter() {
+            if (DBG) Log.d(TAG, getName() + "\n");
+            EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName());
+        }
+        @Override
+        public boolean processMessage(Message message) {
+            if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
+            SyncParams syncParams;
+            switch(message.what) {
+                case CMD_SET_SCAN_MODE:
+                    if (message.arg1 == SCAN_ONLY_MODE) {
+                        /* Ignore */
+                        return HANDLED;
+                    } else {
+                        WifiNative.setScanResultHandlingCommand(message.arg1);
+                        WifiNative.reconnectCommand();
+                        mIsScanMode = false;
+                        transitionTo(mDisconnectedState);
+                    }
+                    break;
+                case CMD_START_SCAN:
+                    if (message.arg2 == SYNCHRONOUS_CALL) {
+                        syncParams = (SyncParams) message.obj;
+                        syncParams.mSyncReturn.boolValue = WifiNative.scanCommand(
+                                message.arg1 == SCAN_ACTIVE);
+                        notifyOnMsgObject(message);
+                    } else {
+                        /* asynchronous handling */
+                        WifiNative.scanCommand(message.arg1 == SCAN_ACTIVE);
+                    }
+                    break;
+                    /* Ignore */
+                case CMD_DISCONNECT:
+                case CMD_RECONNECT:
+                case CMD_REASSOCIATE:
+                case SUPPLICANT_STATE_CHANGE_EVENT:
+                case NETWORK_CONNECTION_EVENT:
+                case NETWORK_DISCONNECTION_EVENT:
+                    break;
+                default:
+                    return NOT_HANDLED;
+            }
+            EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what);
+            return HANDLED;
+        }
+    }
+
+    class ConnectModeState extends HierarchicalState {
+        @Override
+        public void enter() {
+            if (DBG) Log.d(TAG, getName() + "\n");
+            EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName());
+        }
+        @Override
+        public boolean processMessage(Message message) {
+            if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
+            SyncParams syncParams;
+            StateChangeResult stateChangeResult;
+            switch(message.what) {
+                case PASSWORD_MAY_BE_INCORRECT_EVENT:
+                    mPasswordKeyMayBeIncorrect = true;
+                    break;
+                case SUPPLICANT_STATE_CHANGE_EVENT:
+                    stateChangeResult = (StateChangeResult) message.obj;
+                    mSupplicantStateTracker.handleEvent(stateChangeResult);
+                    break;
+                case CMD_START_SCAN:
+                    if (message.arg2 == SYNCHRONOUS_CALL) {
+                        syncParams = (SyncParams) message.obj;
+                        syncParams.mSyncReturn.boolValue = true;
+                        notifyOnMsgObject(message);
+                    }
+                    /* We need to set scan type in completed state */
+                    Message newMsg = obtainMessage();
+                    newMsg.copyFrom(message);
+                    mSupplicantStateTracker.sendMessage(newMsg);
+                    break;
+                    /* Do a redundant disconnect without transition */
+                case CMD_DISCONNECT:
+                    if (message.arg2 == SYNCHRONOUS_CALL) {
+                        syncParams = (SyncParams) message.obj;
+                        syncParams.mSyncReturn.boolValue = WifiNative.disconnectCommand();
+                        notifyOnMsgObject(message);
+                    } else {
+                        /* asynchronous handling */
+                        WifiNative.disconnectCommand();
+                    }
+                    break;
+                case CMD_RECONNECT:
+                    if (message.arg2 == SYNCHRONOUS_CALL) {
+                        syncParams = (SyncParams) message.obj;
+                        syncParams.mSyncReturn.boolValue = WifiNative.reconnectCommand();
+                        notifyOnMsgObject(message);
+                    } else {
+                        /* asynchronous handling */
+                        WifiNative.reconnectCommand();
+                    }
+                    break;
+                case CMD_REASSOCIATE:
+                    if (message.arg2 == SYNCHRONOUS_CALL) {
+                        syncParams = (SyncParams) message.obj;
+                        syncParams.mSyncReturn.boolValue = WifiNative.reassociateCommand();
+                        notifyOnMsgObject(message);
+                    } else {
+                        /* asynchronous handling */
+                        WifiNative.reassociateCommand();
+                    }
+                    break;
+                case SCAN_RESULTS_EVENT:
+                    /* Set the scan setting back to "connect" mode */
+                    WifiNative.setScanResultHandlingCommand(CONNECT_MODE);
+                    /* Handle scan results */
+                    return NOT_HANDLED;
+                case NETWORK_CONNECTION_EVENT:
+                    Log.d(TAG,"Network connection established");
+                    stateChangeResult = (StateChangeResult) message.obj;
+
+                    /* Remove any notifications */
+                    setNotificationVisible(false, 0, false, 0);
+                    resetNotificationTimer();
+
+                    mWifiInfo.setBSSID(mLastBssid = stateChangeResult.BSSID);
+                    mWifiInfo.setNetworkId(stateChangeResult.networkId);
+                    mLastNetworkId = stateChangeResult.networkId;
+
+                    /* send event to CM & network change broadcast */
+                    setDetailedState(DetailedState.OBTAINING_IPADDR);
+                    sendNetworkStateChangeBroadcast(mLastBssid);
+
+                    transitionTo(mConnectingState);
+                    break;
+                case NETWORK_DISCONNECTION_EVENT:
+                    Log.d(TAG,"Network connection lost");
+                    handleNetworkDisconnect();
+                    transitionTo(mDisconnectedState);
+                    break;
+                case CMD_GET_RSSI:
+                    syncParams = (SyncParams) message.obj;
+                    syncParams.mSyncReturn.intValue = WifiNative.getRssiCommand();
+                    notifyOnMsgObject(message);
+                    break;
+                case CMD_GET_RSSI_APPROX:
+                    syncParams = (SyncParams) message.obj;
+                    syncParams.mSyncReturn.intValue = WifiNative.getRssiApproxCommand();
+                    notifyOnMsgObject(message);
+                    break;
+                case CMD_GET_LINK_SPEED:
+                    syncParams = (SyncParams) message.obj;
+                    syncParams.mSyncReturn.intValue = WifiNative.getLinkSpeedCommand();
+                    notifyOnMsgObject(message);
+                    break;
+                default:
+                    return NOT_HANDLED;
+            }
+            EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what);
+            return HANDLED;
+        }
+    }
+
+    class ConnectingState extends HierarchicalState {
+        boolean modifiedBluetoothCoexistenceMode;
+        int powerMode;
+        Thread mDhcpThread;
+
+        @Override
+        public void enter() {
+            if (DBG) Log.d(TAG, getName() + "\n");
+            EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName());
+
+            if (!mUseStaticIp) {
+
+                mDhcpThread = null;
+                modifiedBluetoothCoexistenceMode = false;
+                powerMode = DRIVER_POWER_MODE_AUTO;
+
+                if (shouldDisableCoexistenceMode()) {
+                    /*
+                     * There are problems setting the Wi-Fi driver's power
+                     * mode to active when bluetooth coexistence mode is
+                     * enabled or sense.
+                     * <p>
+                     * We set Wi-Fi to active mode when
+                     * obtaining an IP address because we've found
+                     * compatibility issues with some routers with low power
+                     * mode.
+                     * <p>
+                     * In order for this active power mode to properly be set,
+                     * we disable coexistence mode until we're done with
+                     * obtaining an IP address.  One exception is if we
+                     * are currently connected to a headset, since disabling
+                     * coexistence would interrupt that connection.
+                     */
+                    modifiedBluetoothCoexistenceMode = true;
+
+                    // Disable the coexistence mode
+                    WifiNative.setBluetoothCoexistenceModeCommand(
+                            WifiNative.BLUETOOTH_COEXISTENCE_MODE_DISABLED);
+                }
+
+                powerMode =  WifiNative.getPowerModeCommand();
+                if (powerMode < 0) {
+                  // Handle the case where supplicant driver does not support
+                  // getPowerModeCommand.
+                    powerMode = DRIVER_POWER_MODE_AUTO;
+                }
+                if (powerMode != DRIVER_POWER_MODE_ACTIVE) {
+                    WifiNative.setPowerModeCommand(DRIVER_POWER_MODE_ACTIVE);
+                }
+
+                Log.d(TAG, "DHCP request started");
+                mDhcpThread = new Thread(new Runnable() {
+                    public void run() {
+                        if (NetworkUtils.runDhcp(mInterfaceName, mDhcpInfo)) {
+                            Log.d(TAG, "DHCP request succeeded");
+                            sendMessage(CMD_IP_CONFIG_SUCCESS);
+                        } else {
+                            Log.d(TAG, "DHCP request failed: " +
+                                    NetworkUtils.getDhcpError());
+                            sendMessage(CMD_IP_CONFIG_FAILURE);
+                        }
+                    }
+                });
+                mDhcpThread.start();
+            } else {
+                if (NetworkUtils.configureInterface(mInterfaceName, mDhcpInfo)) {
+                    Log.v(TAG, "Static IP configuration succeeded");
+                    sendMessage(CMD_IP_CONFIG_SUCCESS);
+                } else {
+                    Log.v(TAG, "Static IP configuration failed");
+                    sendMessage(CMD_IP_CONFIG_FAILURE);
+                }
+            }
+         }
+      @Override
+      public boolean processMessage(Message message) {
+          if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
+
+          switch(message.what) {
+              case CMD_IP_CONFIG_SUCCESS:
+                  mReconnectCount = 0;
+                  mLastSignalLevel = -1; // force update of signal strength
+                  mWifiInfo.setIpAddress(mDhcpInfo.ipAddress);
+                  Log.d(TAG, "IP configuration: " + mDhcpInfo);
+                  configureNetworkProperties();
+                  setDetailedState(DetailedState.CONNECTED);
+                  sendNetworkStateChangeBroadcast(mLastBssid);
+                  transitionTo(mConnectedState);
+                  break;
+              case CMD_IP_CONFIG_FAILURE:
+                  mWifiInfo.setIpAddress(0);
+
+                  Log.e(TAG, "IP configuration failed");
+                  /**
+                   * If we've exceeded the maximum number of retries for DHCP
+                   * to a given network, disable the network
+                   */
+                  if (++mReconnectCount > getMaxDhcpRetries()) {
+                          Log.e(TAG, "Failed " +
+                                  mReconnectCount + " times, Disabling " + mLastNetworkId);
+                      WifiNative.disableNetworkCommand(mLastNetworkId);
+                  }
+
+                  /* DHCP times out after about 30 seconds, we do a
+                   * disconnect and an immediate reconnect to try again
+                   */
+                  WifiNative.disconnectCommand();
+                  WifiNative.reconnectCommand();
+                  transitionTo(mDisconnectingState);
+                  break;
+              case CMD_DISCONNECT:
+                  if (message.arg2 == SYNCHRONOUS_CALL) {
+                      SyncParams syncParams = (SyncParams) message.obj;
+                      syncParams.mSyncReturn.boolValue = WifiNative.disconnectCommand();
+                      notifyOnMsgObject(message);
+                  } else {
+                      /* asynchronous handling */
+                      WifiNative.disconnectCommand();
+                  }
+                  transitionTo(mDisconnectingState);
+                  break;
+                  /* Ignore */
+              case NETWORK_CONNECTION_EVENT:
+                  break;
+              case CMD_STOP_DRIVER:
+                  sendMessage(CMD_DISCONNECT);
+                  deferMessage(message);
+                  break;
+              case CMD_SET_SCAN_MODE:
+                  if (message.arg1 == SCAN_ONLY_MODE) {
+                      sendMessage(CMD_DISCONNECT);
+                      deferMessage(message);
+                  }
+                  break;
+              case CMD_RECONFIGURE_IP:
+                  deferMessage(message);
+                  break;
+              default:
+                return NOT_HANDLED;
+          }
+          EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what);
+          return HANDLED;
+      }
+
+      @Override
+      public void exit() {
+          /* reset power state & bluetooth coexistence if on DHCP */
+          if (!mUseStaticIp) {
+              if (powerMode != DRIVER_POWER_MODE_ACTIVE) {
+                  WifiNative.setPowerModeCommand(powerMode);
+              }
+
+              if (modifiedBluetoothCoexistenceMode) {
+                  // Set the coexistence mode back to its default value
+                  WifiNative.setBluetoothCoexistenceModeCommand(
+                          WifiNative.BLUETOOTH_COEXISTENCE_MODE_SENSE);
+              }
+          }
+
+      }
+    }
+
+    class ConnectedState extends HierarchicalState {
+        @Override
+        public void enter() {
+            if (DBG) Log.d(TAG, getName() + "\n");
+            EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName());
+        }
+        @Override
+        public boolean processMessage(Message message) {
+            if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
+            switch (message.what) {
+                case CMD_DISCONNECT:
+                    if (message.arg2 == SYNCHRONOUS_CALL) {
+                        SyncParams syncParams = (SyncParams) message.obj;
+                        syncParams.mSyncReturn.boolValue = WifiNative.disconnectCommand();
+                        notifyOnMsgObject(message);
+                    } else {
+                        /* asynchronous handling */
+                        WifiNative.disconnectCommand();
+                    }
+                    transitionTo(mDisconnectingState);
+                    break;
+                case CMD_RECONFIGURE_IP:
+                    Log.d(TAG,"Reconfiguring IP on connection");
+                    NetworkUtils.resetConnections(mInterfaceName);
+                    transitionTo(mConnectingState);
+                    break;
+                case CMD_STOP_DRIVER:
+                    sendMessage(CMD_DISCONNECT);
+                    deferMessage(message);
+                    break;
+                case CMD_SET_SCAN_MODE:
+                    if (message.arg1 == SCAN_ONLY_MODE) {
+                        sendMessage(CMD_DISCONNECT);
+                        deferMessage(message);
+                    }
+                    break;
+                    /* Ignore */
+                case NETWORK_CONNECTION_EVENT:
+                    break;
+                default:
+                    return NOT_HANDLED;
+            }
+            EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what);
+            return HANDLED;
+        }
+    }
+
+    class DisconnectingState extends HierarchicalState {
+        @Override
+        public void enter() {
+            if (DBG) Log.d(TAG, getName() + "\n");
+            EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName());
+        }
+        @Override
+        public boolean processMessage(Message message) {
+            if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
+            switch (message.what) {
+                case CMD_STOP_DRIVER: /* Stop driver only after disconnect handled */
+                    deferMessage(message);
+                    break;
+                case CMD_SET_SCAN_MODE:
+                    if (message.arg1 == SCAN_ONLY_MODE) {
+                        deferMessage(message);
+                    }
+                    break;
+                default:
+                    return NOT_HANDLED;
+            }
+            EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what);
+            return HANDLED;
+        }
+    }
+
+    class DisconnectedState extends HierarchicalState {
+        @Override
+        public void enter() {
+            if (DBG) Log.d(TAG, getName() + "\n");
+            EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName());
+        }
+        @Override
+        public boolean processMessage(Message message) {
+            if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
+            switch (message.what) {
+                case CMD_SET_SCAN_MODE:
+                    if (message.arg1 == SCAN_ONLY_MODE) {
+                        WifiNative.setScanResultHandlingCommand(message.arg1);
+                        //Supplicant disconnect to prevent further connects
+                        WifiNative.disconnectCommand();
+                        mIsScanMode = true;
+                        transitionTo(mScanModeState);
+                    }
+                    break;
+                    /* Ignore network disconnect */
+                case NETWORK_DISCONNECTION_EVENT:
+                    break;
+                default:
+                    return NOT_HANDLED;
+            }
+            EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what);
+            return HANDLED;
+        }
+    }
+
+    class SoftApStartedState extends HierarchicalState {
+        @Override
+        public void enter() {
+            if (DBG) Log.d(TAG, getName() + "\n");
+            EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName());
+        }
+        @Override
+        public boolean processMessage(Message message) {
+            if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
+            switch(message.what) {
+                case CMD_STOP_AP:
+                    Log.d(TAG,"Stopping Soft AP");
+                    setWifiApState(WIFI_AP_STATE_DISABLING);
+                    try {
+                        nwService.stopAccessPoint();
+                    } catch(Exception e) {
+                        Log.e(TAG, "Exception in stopAccessPoint()");
+                    }
+                    transitionTo(mDriverLoadedState);
+                    break;
+                case CMD_START_AP:
+                    Log.d(TAG,"SoftAP set on a running access point");
+                    try {
+                        nwService.setAccessPoint((WifiConfiguration) message.obj,
+                                    mInterfaceName,
+                                    SOFTAP_IFACE);
+                    } catch(Exception e) {
+                        Log.e(TAG, "Exception in nwService during soft AP set");
+                        try {
+                            nwService.stopAccessPoint();
+                        } catch (Exception ee) {
+                            Slog.e(TAG, "Could not stop AP, :" + ee);
+                        }
+                        sendMessage(obtainMessage(CMD_UNLOAD_DRIVER, WIFI_AP_STATE_FAILED, 0));
+                    }
+                    break;
+                /* Fail client mode operation when soft AP is enabled */
+                case CMD_START_SUPPLICANT:
+                    Log.e(TAG,"Cannot start supplicant with a running soft AP");
+                    setWifiState(WIFI_STATE_UNKNOWN);
+                    break;
+                default:
+                    return NOT_HANDLED;
+            }
+            EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what);
+            return HANDLED;
+        }
+    }
+
+
+    class SupplicantStateTracker extends HierarchicalStateMachine {
+
+        private int mRssiPollToken = 0;
+
+        /**
+         * The max number of the WPA supplicant loop iterations before we
+         * decide that the loop should be terminated:
+         */
+        private static final int MAX_SUPPLICANT_LOOP_ITERATIONS = 4;
+        private int mLoopDetectIndex = 0;
+        private int mLoopDetectCount = 0;
+
+        /**
+         *  Supplicant state change commands follow
+         *  the ordinal values defined in SupplicantState.java
+         */
+        private static final int DISCONNECTED           = 0;
+        private static final int INACTIVE               = 1;
+        private static final int SCANNING               = 2;
+        private static final int ASSOCIATING            = 3;
+        private static final int ASSOCIATED             = 4;
+        private static final int FOUR_WAY_HANDSHAKE     = 5;
+        private static final int GROUP_HANDSHAKE        = 6;
+        private static final int COMPLETED              = 7;
+        private static final int DORMANT                = 8;
+        private static final int UNINITIALIZED          = 9;
+        private static final int INVALID                = 10;
+
+        private HierarchicalState mUninitializedState;
+        private HierarchicalState mInitializedState;
+        private HierarchicalState mInactiveState;
+        private HierarchicalState mDisconnectState;
+        private HierarchicalState mScanState;
+        private HierarchicalState mConnectState;
+        private HierarchicalState mHandshakeState;
+        private HierarchicalState mCompletedState;
+        private HierarchicalState mDormantState;
+
+
+        public SupplicantStateTracker(Context context, Handler target) {
+            super(TAG, target.getLooper());
+
+            mUninitializedState = new UninitializedState();
+            mInitializedState = new InitializedState();
+            mInactiveState = new InactiveState();
+            mDisconnectState = new DisconnectedState();
+            mScanState = new ScanState();
+            mConnectState = new ConnectState();
+            mHandshakeState = new HandshakeState();
+            mCompletedState = new CompletedState();
+            mDormantState = new DormantState();
+
+
+            addState(mUninitializedState);
+            addState(mInitializedState);
+                addState(mInactiveState, mInitializedState);
+                addState(mDisconnectState, mInitializedState);
+                addState(mScanState, mInitializedState);
+                addState(mConnectState, mInitializedState);
+                    addState(mHandshakeState, mConnectState);
+                    addState(mCompletedState, mConnectState);
+                addState(mDormantState, mInitializedState);
+
+            setInitialState(mUninitializedState);
+
+            //start the state machine
+            start();
+        }
+
+        public void handleEvent(StateChangeResult stateChangeResult) {
+            SupplicantState newState = (SupplicantState) stateChangeResult.state;
+
+            // Supplicant state change
+            // [31-13] Reserved for future use
+            // [8 - 0] Supplicant state (as defined in SupplicantState.java)
+            // 50023 supplicant_state_changed (custom|1|5)
+            EventLog.writeEvent(EVENTLOG_SUPPLICANT_STATE_CHANGED, newState.ordinal());
+
+            sendMessage(obtainMessage(newState.ordinal(), stateChangeResult));
+        }
+
+        public void resetSupplicantState() {
+            transitionTo(mUninitializedState);
+        }
+
+        private void resetLoopDetection() {
+            mLoopDetectCount = 0;
+            mLoopDetectIndex = 0;
+        }
+
+        private boolean handleTransition(Message msg) {
+            if (DBG) Log.d(TAG, getName() + msg.toString() + "\n");
+            switch (msg.what) {
+                case DISCONNECTED:
+                    transitionTo(mDisconnectState);
+                    break;
+                case SCANNING:
+                    transitionTo(mScanState);
+                    break;
+                case ASSOCIATING:
+                    StateChangeResult stateChangeResult = (StateChangeResult) msg.obj;
+                    /* BSSID is valid only in ASSOCIATING state */
+                    mWifiInfo.setBSSID(stateChangeResult.BSSID);
+                    //$FALL-THROUGH$
+                case ASSOCIATED:
+                case FOUR_WAY_HANDSHAKE:
+                case GROUP_HANDSHAKE:
+                    transitionTo(mHandshakeState);
+                    break;
+                case COMPLETED:
+                    transitionTo(mCompletedState);
+                    break;
+                case DORMANT:
+                    transitionTo(mDormantState);
+                    break;
+                case INACTIVE:
+                    transitionTo(mInactiveState);
+                    break;
+                case UNINITIALIZED:
+                case INVALID:
+                    transitionTo(mUninitializedState);
+                    break;
+                default:
+                    return NOT_HANDLED;
+            }
+            StateChangeResult stateChangeResult = (StateChangeResult) msg.obj;
+            SupplicantState supState = (SupplicantState) stateChangeResult.state;
+            setDetailedState(WifiInfo.getDetailedStateOf(supState));
+            mWifiInfo.setSupplicantState(supState);
+            mWifiInfo.setNetworkId(stateChangeResult.networkId);
+            //TODO: Modify WifiMonitor to report SSID on events
+            //mWifiInfo.setSSID()
+            return HANDLED;
+        }
+
+        /********************************************************
+         * HSM states
+         *******************************************************/
+
+        class InitializedState extends HierarchicalState {
+            @Override
+             public void enter() {
+                 if (DBG) Log.d(TAG, getName() + "\n");
+             }
+            @Override
+            public boolean processMessage(Message message) {
+                if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
+                switch (message.what) {
+                    case CMD_START_SCAN:
+                        WifiNative.scanCommand(message.arg1 == SCAN_ACTIVE);
+                        break;
+                    default:
+                        if (DBG) Log.w(TAG, "Ignoring " + message);
+                        break;
+                }
+                return HANDLED;
+            }
+        }
+
+        class UninitializedState extends HierarchicalState {
+            @Override
+             public void enter() {
+                 if (DBG) Log.d(TAG, getName() + "\n");
+                 mNetworkInfo.setIsAvailable(false);
+                 resetLoopDetection();
+                 mPasswordKeyMayBeIncorrect = false;
+             }
+            @Override
+            public boolean processMessage(Message message) {
+                switch(message.what) {
+                    default:
+                        if (!handleTransition(message)) {
+                            if (DBG) Log.w(TAG, "Ignoring " + message);
+                        }
+                        break;
+                }
+                return HANDLED;
+            }
+        }
+
+        class InactiveState extends HierarchicalState {
+            @Override
+             public void enter() {
+                 if (DBG) Log.d(TAG, getName() + "\n");
+                 Message message = getCurrentMessage();
+                 StateChangeResult stateChangeResult = (StateChangeResult) message.obj;
+
+                 mNetworkInfo.setIsAvailable(false);
+                 resetLoopDetection();
+                 mPasswordKeyMayBeIncorrect = false;
+
+                 sendSupplicantStateChangedBroadcast(stateChangeResult, false);
+             }
+            @Override
+            public boolean processMessage(Message message) {
+                return handleTransition(message);
+            }
+        }
+
+
+        class DisconnectedState extends HierarchicalState {
+            @Override
+             public void enter() {
+                 if (DBG) Log.d(TAG, getName() + "\n");
+                 Message message = getCurrentMessage();
+                 StateChangeResult stateChangeResult = (StateChangeResult) message.obj;
+
+                 mNetworkInfo.setIsAvailable(true);
+                 resetLoopDetection();
+
+                 /* If a disconnect event happens after a password key failure
+                  * event, disable the network
+                  */
+                 if (mPasswordKeyMayBeIncorrect) {
+                     Log.d(TAG, "Failed to authenticate, disabling network " +
+                             mWifiInfo.getNetworkId());
+                     WifiNative.disableNetworkCommand(mWifiInfo.getNetworkId());
+                     mPasswordKeyMayBeIncorrect = false;
+                     sendSupplicantStateChangedBroadcast(stateChangeResult, true);
+                 }
+                 else {
+                     sendSupplicantStateChangedBroadcast(stateChangeResult, false);
+                 }
+             }
+            @Override
+            public boolean processMessage(Message message) {
+                return handleTransition(message);
+            }
+        }
+
+        class ScanState extends HierarchicalState {
+            @Override
+             public void enter() {
+                 if (DBG) Log.d(TAG, getName() + "\n");
+                 Message message = getCurrentMessage();
+                 StateChangeResult stateChangeResult = (StateChangeResult) message.obj;
+
+                 mNetworkInfo.setIsAvailable(true);
+                 mPasswordKeyMayBeIncorrect = false;
+                 resetLoopDetection();
+                 sendSupplicantStateChangedBroadcast(stateChangeResult, false);
+             }
+            @Override
+            public boolean processMessage(Message message) {
+                return handleTransition(message);
+            }
+        }
+
+        class ConnectState extends HierarchicalState {
+            @Override
+             public void enter() {
+                 if (DBG) Log.d(TAG, getName() + "\n");
+             }
+            @Override
+            public boolean processMessage(Message message) {
+                switch (message.what) {
+                    case CMD_START_SCAN:
+                        WifiNative.setScanResultHandlingCommand(SCAN_ONLY_MODE);
+                        WifiNative.scanCommand(message.arg1 == SCAN_ACTIVE);
+                        break;
+                    default:
+                        return NOT_HANDLED;
+                }
+                return HANDLED;
+            }
+        }
+
+        class HandshakeState extends HierarchicalState {
+            @Override
+             public void enter() {
+                 if (DBG) Log.d(TAG, getName() + "\n");
+                 final Message message = getCurrentMessage();
+                 StateChangeResult stateChangeResult = (StateChangeResult) message.obj;
+
+                 mNetworkInfo.setIsAvailable(true);
+
+                 if (mLoopDetectIndex > message.what) {
+                     mLoopDetectCount++;
+                 }
+                 if (mLoopDetectCount > MAX_SUPPLICANT_LOOP_ITERATIONS) {
+                     WifiNative.disableNetworkCommand(stateChangeResult.networkId);
+                     mLoopDetectCount = 0;
+                 }
+
+                 mLoopDetectIndex = message.what;
+
+                 mPasswordKeyMayBeIncorrect = false;
+                 sendSupplicantStateChangedBroadcast(stateChangeResult, false);
+             }
+            @Override
+            public boolean processMessage(Message message) {
+                return handleTransition(message);
+            }
+        }
+
+        class CompletedState extends HierarchicalState {
+            @Override
+             public void enter() {
+                 if (DBG) Log.d(TAG, getName() + "\n");
+                 Message message = getCurrentMessage();
+                 StateChangeResult stateChangeResult = (StateChangeResult) message.obj;
+
+                 mNetworkInfo.setIsAvailable(true);
+
+                 mRssiPollToken++;
+                 if (mEnableRssiPolling) {
+                     sendMessageDelayed(obtainMessage(CMD_RSSI_POLL, mRssiPollToken, 0),
+                             POLL_RSSI_INTERVAL_MSECS);
+                 }
+
+                 resetLoopDetection();
+
+                 mPasswordKeyMayBeIncorrect = false;
+                 sendSupplicantStateChangedBroadcast(stateChangeResult, false);
+             }
+            @Override
+            public boolean processMessage(Message message) {
+                switch(message.what) {
+                    case ASSOCIATING:
+                    case ASSOCIATED:
+                    case FOUR_WAY_HANDSHAKE:
+                    case GROUP_HANDSHAKE:
+                    case COMPLETED:
+                        break;
+                    case CMD_RSSI_POLL:
+                        if (message.arg1 == mRssiPollToken) {
+                            // Get Info and continue polling
+                            requestPolledInfo();
+                            sendMessageDelayed(obtainMessage(CMD_RSSI_POLL, mRssiPollToken, 0),
+                                    POLL_RSSI_INTERVAL_MSECS);
+                        } else {
+                            // Polling has completed
+                        }
+                        break;
+                    case CMD_ENABLE_RSSI_POLL:
+                        mRssiPollToken++;
+                        if (mEnableRssiPolling) {
+                            // first poll
+                            requestPolledInfo();
+                            sendMessageDelayed(obtainMessage(CMD_RSSI_POLL, mRssiPollToken, 0),
+                                    POLL_RSSI_INTERVAL_MSECS);
+                        }
+                        break;
+                    default:
+                        return handleTransition(message);
+                }
+                return HANDLED;
+            }
+        }
+
+        class DormantState extends HierarchicalState {
+            @Override
+             public void enter() {
+                 if (DBG) Log.d(TAG, getName() + "\n");
+                 Message message = getCurrentMessage();
+                 StateChangeResult stateChangeResult = (StateChangeResult) message.obj;
+
+                 mNetworkInfo.setIsAvailable(true);
+                 resetLoopDetection();
+                 mPasswordKeyMayBeIncorrect = false;
+
+                 sendSupplicantStateChangedBroadcast(stateChangeResult, false);
+
+                 /* TODO: reconnect is now being handled at DHCP failure handling
+                  * If we run into issues with staying in Dormant state, might
+                  * need a reconnect here
+                  */
+             }
+            @Override
+            public boolean processMessage(Message message) {
+                return handleTransition(message);
+            }
+        }
+    }
+
     private class NotificationEnabledSettingObserver extends ContentObserver {
 
         public NotificationEnabledSettingObserver(Handler handler) {